Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5-internal/pr-2704
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `POST /password-reset/complete` endpoint of the account API is now migrated to servant
11 changes: 10 additions & 1 deletion libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import Wire.API.User.Activation
import Wire.API.User.Client
import Wire.API.User.Client.Prekey
import Wire.API.User.Handle
import Wire.API.User.Password (NewPasswordReset)
import Wire.API.User.Password (CompletePasswordReset, NewPasswordReset)
import Wire.API.User.RichInfo (RichInfoAssocList)
import Wire.API.User.Search (Contact, RoleFilter, SearchResult, TeamContact, TeamUserSearchSortBy, TeamUserSearchSortOrder)
import Wire.API.UserMap
Expand Down Expand Up @@ -467,6 +467,15 @@ type AccountAPI =
:> ReqBody '[JSON] NewPasswordReset
:> MultiVerb 'POST '[JSON] '[RespondEmpty 201 "Password reset code created and sent by email."] ()
)
:<|> Named
"post-password-reset-complete"
( Summary "Complete a password reset."
:> CanThrow 'InvalidPasswordResetCode
:> "password-reset"
:> "complete"
:> ReqBody '[JSON] CompletePasswordReset
:> MultiVerb 'POST '[JSON] '[RespondEmpty 200 "Password reset successful."] ()
)

data ActivationRespWithStatus
= ActivationResp ActivationResponse
Expand Down
2 changes: 0 additions & 2 deletions libs/wire-api/src/Wire/API/Swagger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import qualified Wire.API.User.Auth as User.Auth
import qualified Wire.API.User.Client as User.Client
import qualified Wire.API.User.Client.Prekey as User.Client.Prekey
import qualified Wire.API.User.Handle as User.Handle
import qualified Wire.API.User.Password as User.Password
import qualified Wire.API.User.Profile as User.Profile
import qualified Wire.API.User.RichInfo as User.RichInfo
import qualified Wire.API.User.Search as User.Search
Expand Down Expand Up @@ -118,7 +117,6 @@ models =
User.Client.Prekey.modelPrekey,
User.Handle.modelUserHandleInfo,
User.Handle.modelCheckHandles,
User.Password.modelCompletePasswordReset,
User.Profile.modelAsset,
User.RichInfo.modelRichInfo,
User.RichInfo.modelRichField,
Expand Down
84 changes: 46 additions & 38 deletions libs/wire-api/src/Wire/API/User/Password.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ module Wire.API.User.Password

-- * deprecated
PasswordReset (..),

-- * Swagger
modelCompletePasswordReset,
)
where

Expand All @@ -42,8 +39,8 @@ import Data.Misc (PlainTextPassword (..))
import Data.Range (Ranged (..))
import qualified Data.Schema as Schema
import qualified Data.Swagger as S
import qualified Data.Swagger.Build.Api as Doc
import Data.Text.Ascii
import Data.Tuple.Extra (fst3, snd3, thd3)
import Imports
import Wire.API.User.Identity
import Wire.Arbitrary (Arbitrary, GenericUniform (..))
Expand Down Expand Up @@ -107,41 +104,52 @@ data CompletePasswordReset = CompletePasswordReset
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform CompletePasswordReset)
deriving (ToJSON, FromJSON, S.ToSchema) via Schema.Schema CompletePasswordReset

modelCompletePasswordReset :: Doc.Model
modelCompletePasswordReset = Doc.defineModel "CompletePasswordReset" $ do
Doc.description "Data to complete a password reset."
Doc.property "key" Doc.string' $ do
Doc.description "An opaque key for a pending password reset."
Doc.optional
Doc.property "email" Doc.string' $ do
Doc.description "A known email with a pending password reset."
Doc.optional
Doc.property "phone" Doc.string' $ do
Doc.description "A known phone number with a pending password reset."
Doc.optional
Doc.property "code" Doc.string' $
Doc.description "Password reset code"
Doc.property "password" Doc.string' $
Doc.description "New password (6 - 1024 characters)"

instance ToJSON CompletePasswordReset where
toJSON (CompletePasswordReset i c pw) =
object
[ident i, "code" .= c, "password" .= pw]
instance Schema.ToSchema CompletePasswordReset where
schema =
Schema.objectWithDocModifier "CompletePasswordReset" objectDocs $
CompletePasswordReset
<$> (maybePasswordResetIdentityToTuple . cpwrIdent) Schema..= maybePasswordResetIdentityObjectSchema
<*> cpwrCode Schema..= Schema.fieldWithDocModifier "code" codeDocs Schema.schema
<*> cpwrPassword Schema..= Schema.fieldWithDocModifier "password" pwDocs Schema.schema
where
ident (PasswordResetIdentityKey k) = "key" .= k
ident (PasswordResetEmailIdentity e) = "email" .= e
ident (PasswordResetPhoneIdentity p) = "phone" .= p
objectDocs :: Schema.NamedSwaggerDoc -> Schema.NamedSwaggerDoc
objectDocs = Schema.description ?~ "Data to complete a password reset"

instance FromJSON CompletePasswordReset where
parseJSON = withObject "CompletePasswordReset" $ \o ->
CompletePasswordReset <$> ident o <*> o .: "code" <*> o .: "password"
where
ident o =
(PasswordResetIdentityKey <$> o .: "key")
<|> (PasswordResetEmailIdentity <$> o .: "email")
<|> (PasswordResetPhoneIdentity <$> o .: "phone")
codeDocs :: Schema.NamedSwaggerDoc -> Schema.NamedSwaggerDoc
codeDocs = Schema.description ?~ "Password reset code"

pwDocs :: Schema.NamedSwaggerDoc -> Schema.NamedSwaggerDoc
pwDocs = Schema.description ?~ "New password (6 - 1024 characters)"

maybePasswordResetIdentityObjectSchema :: Schema.ObjectSchemaP Schema.SwaggerDoc (Maybe PasswordResetKey, Maybe Email, Maybe Phone) PasswordResetIdentity
maybePasswordResetIdentityObjectSchema =
Schema.withParser passwordResetIdentityTupleObjectSchema maybePasswordResetIdentityTargetFromTuple
where
passwordResetIdentityTupleObjectSchema :: Schema.ObjectSchema Schema.SwaggerDoc (Maybe PasswordResetKey, Maybe Email, Maybe Phone)
passwordResetIdentityTupleObjectSchema =
(,,)
<$> fst3 Schema..= Schema.maybe_ (Schema.optFieldWithDocModifier "key" keyDocs Schema.schema)
<*> snd3 Schema..= Schema.maybe_ (Schema.optFieldWithDocModifier "email" emailDocs Schema.schema)
<*> thd3 Schema..= Schema.maybe_ (Schema.optFieldWithDocModifier "phone" phoneDocs Schema.schema)
where
keyDocs = Schema.description ?~ "An opaque key for a pending password reset."
emailDocs = Schema.description ?~ "A known email with a pending password reset."
phoneDocs = Schema.description ?~ "A known phone number with a pending password reset."

maybePasswordResetIdentityTargetFromTuple :: (Maybe PasswordResetKey, Maybe Email, Maybe Phone) -> Parser PasswordResetIdentity
maybePasswordResetIdentityTargetFromTuple = \case
(Just key, _, _) -> pure $ PasswordResetIdentityKey key
(_, Just email, _) -> pure $ PasswordResetEmailIdentity email
(_, _, Just phone) -> pure $ PasswordResetPhoneIdentity phone
_ -> fail "key, email or phone must be present"

maybePasswordResetIdentityToTuple :: PasswordResetIdentity -> (Maybe PasswordResetKey, Maybe Email, Maybe Phone)
maybePasswordResetIdentityToTuple = \case
PasswordResetIdentityKey key -> (Just key, Nothing, Nothing)
PasswordResetEmailIdentity email -> (Nothing, Just email, Nothing)
PasswordResetPhoneIdentity phone -> (Nothing, Nothing, Just phone)

--------------------------------------------------------------------------------
-- PasswordResetIdentity
Expand All @@ -161,7 +169,7 @@ data PasswordResetIdentity
newtype PasswordResetKey = PasswordResetKey
{fromPasswordResetKey :: AsciiBase64Url}
deriving stock (Eq, Show)
deriving newtype (FromByteString, ToByteString, FromJSON, ToJSON, Arbitrary)
deriving newtype (Schema.ToSchema, FromByteString, ToByteString, FromJSON, ToJSON, Arbitrary)

--------------------------------------------------------------------------------
-- PasswordResetCode
Expand All @@ -170,7 +178,7 @@ newtype PasswordResetKey = PasswordResetKey
newtype PasswordResetCode = PasswordResetCode
{fromPasswordResetCode :: AsciiBase64Url}
deriving stock (Eq, Show, Generic)
deriving newtype (FromByteString, ToByteString, FromJSON, ToJSON)
deriving newtype (Schema.ToSchema, FromByteString, ToByteString, FromJSON, ToJSON)
deriving (Arbitrary) via (Ranged 6 1024 AsciiBase64Url)

--------------------------------------------------------------------------------
Expand Down
4 changes: 0 additions & 4 deletions services/brig/src/Brig/API/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,6 @@ clientCapabilitiesCannotBeRemoved = Wai.mkError status409 "client-capabilities-c
noEmail :: Wai.Error
noEmail = Wai.mkError status403 "no-email" "This operation requires the user to have a verified email address."

-- todo(leif): remove later
invalidPwResetCode :: Wai.Error
invalidPwResetCode = Wai.mkError status400 "invalid-code" "Invalid password reset code."

emailExists :: Wai.Error
emailExists = Wai.mkError status409 "email-exists" "The given e-mail address is in use."

Expand Down
27 changes: 8 additions & 19 deletions services/brig/src/Brig/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ import Network.Wai.Predicate hiding (result, setStatus)
import Network.Wai.Routing
import Network.Wai.Utilities as Utilities
import Network.Wai.Utilities.Swagger (document, mkSwaggerApi)
import qualified Network.Wai.Utilities.Swagger as Doc
import Network.Wai.Utilities.ZAuth (zauthUserId)
import Polysemy
import Servant hiding (Handler, JSON, addHeader, respond)
Expand Down Expand Up @@ -190,7 +189,8 @@ servantSitemap ::
'[ BlacklistStore,
BlacklistPhonePrefixStore,
UserPendingActivationStore p,
PasswordResetStore
PasswordResetStore,
CodeStore
]
r =>
ServerT BrigAPI (Handler r)
Expand Down Expand Up @@ -229,6 +229,7 @@ servantSitemap = userAPI :<|> selfAPI :<|> accountAPI :<|> clientAPI :<|> prekey
:<|> Named @"post-activate" activateKey
:<|> Named @"post-activate-send" sendActivationCode
:<|> Named @"post-password-reset" beginPasswordReset
:<|> Named @"post-password-reset-complete" completePasswordReset

clientAPI :: ServerT ClientAPI (Handler r)
clientAPI =
Expand Down Expand Up @@ -317,16 +318,6 @@ sitemap ::
sitemap = do
-- /activate, /password-reset ----------------------------------

post "/password-reset/complete" (continue completePasswordResetH) $
accept "application" "json"
.&. jsonRequest @Public.CompletePasswordReset
document "POST" "completePasswordReset" $ do
Doc.summary "Complete a password reset."
Doc.body (Doc.ref Public.modelCompletePasswordReset) $
Doc.description "JSON body"
Doc.response 200 "Password reset successful." Doc.end
Doc.errorResponse invalidPwResetCode

post "/password-reset/:key" (continue deprecatedCompletePasswordResetH) $
accept "application" "json"
.&. capture "key"
Expand Down Expand Up @@ -772,14 +763,12 @@ beginPasswordReset (Public.NewPasswordReset target) = do
Left email -> sendPasswordResetMail email pair loc
Right phone -> wrapClient $ sendPasswordResetSms phone pair loc

completePasswordResetH ::
completePasswordReset ::
Members '[CodeStore, PasswordResetStore] r =>
JSON ::: JsonRequest Public.CompletePasswordReset ->
(Handler r) Response
completePasswordResetH (_ ::: req) = do
Public.CompletePasswordReset {..} <- parseJsonBody req
API.completePasswordReset cpwrIdent cpwrCode cpwrPassword !>> pwResetError
pure empty
Public.CompletePasswordReset ->
(Handler r) ()
completePasswordReset req = do
API.completePasswordReset (Public.cpwrIdent req) (Public.cpwrCode req) (Public.cpwrPassword req) !>> pwResetError

-- docs/reference/user/activation.md {#RefActivationRequest}
-- docs/reference/user/registration.md {#RefRegistration}
Expand Down