diff --git a/libs/wire-subsystems/src/Wire/MiniBackend.hs b/libs/wire-subsystems/src/Wire/MiniBackend.hs index adc1bdd91d9..86c5b06206b 100644 --- a/libs/wire-subsystems/src/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/src/Wire/MiniBackend.hs @@ -134,7 +134,6 @@ data MiniBackend = MkMiniBackend -- invariant: for each key, the user.id and the key are the same users :: [StoredUser], userPassword :: Map UserId Password, - userKeys :: Map UserKey UserId, passwordResetCodes :: Map PasswordResetKey (PasswordResetCode, UserId) } @@ -142,9 +141,8 @@ instance Default MiniBackend where def = MkMiniBackend { users = mempty, - userKeys = mempty, - passwordResetCodes = mempty, - userPassword = mempty + userPassword = mempty, + passwordResetCodes = mempty } -- | represents an entire federated, stateful world of backends @@ -426,7 +424,7 @@ staticCodeStore :: InterpreterFor CodeStore r staticCodeStore = interpret \case MkPasswordResetKey uid -> - pure . PasswordResetKey . encodeBase64Url $ toByteString' uid + pure . PasswordResetKey . encodeBase64Url $ toByteString' uid -- TODO(fisx): understand how this work on develop first, then write tests, *then* port it! GenerateEmailCode -> pure . PasswordResetCode . encodeBase64Url $ "email-code" GeneratePhoneCode -> (error "deprecated") @@ -518,12 +516,18 @@ miniEventInterpreter = interpret \case GenerateUserEvent uid _mconn e -> modify (MkMiniEvent uid e :) staticUserKeyStoreInterpreter :: - Member (State MiniBackend) r => + (HasCallStack, Member (State MiniBackend) r) => InterpreterFor UserKeyStore r staticUserKeyStoreInterpreter = interpret $ \case LookupKey key -> do - keys <- gets (.userKeys) - pure $ M.lookup key keys + usrs <- gets (.users) + let matchinUsrs = case key of + UserEmailKey eml -> listToMaybe $ filter ((== eml) . (.email)) usrs + UserPhoneKey phn -> listToMaybe $ filter ((== phn) . (.phone)) usrs + pure case matchingUsrs of + [usr] -> usr.id + [] -> Nothing + bad@(_:_:_) -> error (show bad) InsertKey uid key -> modify $ \b -> b {userKeys = M.insert key uid (userKeys b)} DeleteKey key -> diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem.hs b/libs/wire-subsystems/src/Wire/UserSubsystem.hs index 712de836d14..2beffaec3bf 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem.hs @@ -28,7 +28,7 @@ data UserSubsystemError | UserSubsystemHandleExists | UserSubsystemInvalidHandle | UserSubsystemProfileNotFound - | UserSubsystemInvalidPasswordResetKey + | UserSubsystemInvalidPasswordResetKey Text | UserSubsystemPasswordResetInProgress | UserSubSystemInvalidPasswordResetCode | UserSubsystemResetPasswordMustDiffer @@ -44,7 +44,7 @@ userSubsystemErrorToWai = UserSubsystemHandleExists -> dynError @(MapError E.HandleExists) UserSubsystemInvalidHandle -> dynError @(MapError E.InvalidHandle) UserSubsystemHandleManagedByScim -> dynError @(MapError E.HandleManagedByScim) - UserSubsystemInvalidPasswordResetKey -> dynError @(MapError E.InvalidPasswordResetKey) + UserSubsystemInvalidPasswordResetKey _ -> dynError @(MapError E.InvalidPasswordResetKey) UserSubsystemPasswordResetInProgress -> dynError @(MapError E.PasswordResetInProgress) UserSubSystemInvalidPasswordResetCode -> dynError @(MapError E.InvalidPasswordResetCode) UserSubsystemResetPasswordMustDiffer -> dynError @(MapError E.ResetPasswordMustDiffer) diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 1fac78fb8ee..92dd6d73a71 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -487,11 +487,11 @@ passwordResetInitImpl :: Sem r (UserId, PasswordResetPair) passwordResetInitImpl target = do let key = either UKS.userEmailKey UKS.userPhoneKey target - user <- UKS.lookupKey key >>= maybe (throw UserSubsystemInvalidPasswordResetKey) pure + user <- UKS.lookupKey key >>= maybe (throw (UserSubsystemInvalidPasswordResetKey "init_lookup")) pure Log.debug $ field "user" (toByteString user) . field "action" (val "User.beginPasswordReset") status <- US.lookupStatus user unless (status == Just Active) $ - throw UserSubsystemInvalidPasswordResetKey + throw (UserSubsystemInvalidPasswordResetKey "init_inactive") code <- PWS.lookupPasswordResetCode user when (isJust code) $ throw UserSubsystemPasswordResetInProgress @@ -536,6 +536,7 @@ checkNewIsDifferent uid pw = do | (verifyPassword pw currpw) -> throw UserSubsystemResetPasswordMustDiffer _ -> pure () +-- TODO: refactor this function? or at least rename it! mkPasswordResetKey' :: (Member (Error UserSubsystemError) r, Member UserKeyStore r, Member CodeStore r) => PasswordResetIdentity -> @@ -545,8 +546,8 @@ mkPasswordResetKey' ident = case ident of PasswordResetEmailIdentity e -> do lookupKey (userEmailKey e) >>= traverse mkPasswordResetKey - >>= maybe (throw UserSubsystemInvalidPasswordResetKey) pure + >>= maybe (throw (UserSubsystemInvalidPasswordResetKey "complete_email")) pure PasswordResetPhoneIdentity p -> lookupKey (userPhoneKey p) >>= traverse mkPasswordResetKey - >>= maybe (throw UserSubsystemInvalidPasswordResetKey) pure + >>= maybe (throw (UserSubsystemInvalidPasswordResetKey "complete_phone")) pure diff --git a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs index c68ea34eb5e..f4382b941ad 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs @@ -30,7 +30,7 @@ import Wire.API.Team.Feature import Wire.API.Team.Member import Wire.API.Team.Permission import Wire.API.User hiding (DeleteUser) -import Wire.API.User.Password (PasswordResetIdentity (PasswordResetIdentityKey)) +import Wire.API.User.Password (PasswordResetIdentity (..)) import Wire.API.UserEvent import Wire.MiniBackend import Wire.StoredUser as SU @@ -454,16 +454,21 @@ spec = describe "UserSubsystem.Interpreter" do else newSupportedProtocols in actualSupportedProtocols === expectedSupportedProtocols - describe "password reset" do + focus $ describe "password reset" do it "happy path" do - user :: StoredUser <- generate arbitrary + user :: StoredUser <- + generate arbitrary <&> \u -> + u + { email = parseEmail "a29c950a-2a51-11ef-bc05-ebc0f6d21339@example.com" + {- TODO(fisx): generate random uuids here for different test runs. -} + } config :: UserSubsystemConfig <- generate arbitrary let localBackend = def {users = [user]} password = plainTextPassword8Unsafe "newsecret" (actualState, ()) = runNoFederationStackState localBackend Nothing config do - (_, (key, code)) <- passwordResetInit (Left $ fromMaybe (error "no email") user.email) - passwordResetComplete (PasswordResetIdentityKey key) code password + (_, (_, code)) <- passwordResetInit (Left $ fromMaybe (error "no email") user.email) + passwordResetComplete (PasswordResetEmailIdentity (fromJust user.email)) code password Just actualPassword = Map.lookup user.id actualState.userPassword verifyPassword password actualPassword `shouldBe` True