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/WPB-11000
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Additional test for password reset, port tests to new integration test suite
2 changes: 2 additions & 0 deletions integration/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
, Cabal
, case-insensitive
, containers
, cookie
, cql
, cql-io
, crypton
Expand Down Expand Up @@ -115,6 +116,7 @@ mkDerivation {
bytestring-conversion
case-insensitive
containers
cookie
cql
cql-io
crypton
Expand Down
2 changes: 2 additions & 0 deletions integration/integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ library
Test.MLS.Unreachable
Test.Notifications
Test.OAuth
Test.PasswordReset
Test.Presence
Test.Property
Test.Provider
Expand Down Expand Up @@ -193,6 +194,7 @@ library
, bytestring-conversion
, case-insensitive
, containers
, cookie
, cql
, cql-io
, crypton
Expand Down
20 changes: 20 additions & 0 deletions integration/test/API/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -795,3 +795,23 @@ listInvitations :: (HasCallStack, MakesValue user) => user -> String -> App Resp
listInvitations user tid = do
req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "invitations"]
submit "GET" req

passwordReset :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
passwordReset domain email = do
req <- baseRequest domain Brig Versioned "password-reset"
submit "POST" $ req & addJSONObject ["email" .= email]

completePasswordReset :: (HasCallStack, MakesValue domain) => domain -> String -> String -> String -> App Response
completePasswordReset domain key code pw = do
req <- baseRequest domain Brig Versioned $ joinHttpPath ["password-reset", "complete"]
submit "POST" $ req & addJSONObject ["key" .= key, "code" .= code, "password" .= pw]

login :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
login domain email password = do
req <- baseRequest domain Brig Versioned "login"
submit "POST" $ req & addJSONObject ["email" .= email, "password" .= password] & addQueryParams [("persist", "true")]

updateEmail :: (HasCallStack, MakesValue user) => user -> String -> String -> String -> App Response
updateEmail user email cookie token = do
req <- baseRequest user Brig Versioned $ joinHttpPath ["access", "self", "email"]
submit "PUT" $ req & addJSONObject ["email" .= email] & setCookie cookie & addHeader "Authorization" ("Bearer " <> token)
5 changes: 5 additions & 0 deletions integration/test/API/BrigInternal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,8 @@ getActivationCode :: (HasCallStack, MakesValue domain) => domain -> String -> Ap
getActivationCode domain email = do
req <- baseRequest domain Brig Unversioned "i/users/activation-code"
submit "GET" $ req & addQueryParams [("email", email)]

getPasswordResetCode :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
getPasswordResetCode domain email = do
req <- baseRequest domain Brig Unversioned "i/users/password-reset-code"
submit "GET" $ req & addQueryParams [("email", email)]
105 changes: 105 additions & 0 deletions integration/test/Test/PasswordReset.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module Test.PasswordReset where

import API.Brig
import API.BrigInternal hiding (activate)
import API.Common
import SetupHelpers
import Testlib.Prelude

-- @SF.Provisioning @TSFI.RESTfulAPI @S1
--
-- This test checks the password reset functionality of the application.
-- Besides a successful password reset the following scenarios are tested:
-- - Subsequent password reset requests should succeed without errors.
-- - Attempting to reset the password with an incorrect key or code should fail.
-- - Attempting to log in with the old password after a successful reset should fail.
-- - Attempting to log in with the new password after a successful reset should succeed.
-- - Attempting to reset the password again to the same new password should fail.
testPasswordResetShouldSucceedButFailOnWrongInputs :: (HasCallStack) => App ()
testPasswordResetShouldSucceedButFailOnWrongInputs = do
u <- randomUser OwnDomain def
email <- u %. "email" & asString
passwordReset u email >>= assertSuccess
-- Even though a password reset is now in progress
-- we expect a successful response from a subsequent request to not leak any information
-- about the requested email.
passwordReset u email >>= assertSuccess

(key, code) <- getPasswordResetData email
let newPassword = "newpassword"

-- complete password reset with incorrect key/code should fail
completePasswordReset u "wrong-key" code newPassword >>= assertStatus 400
login u email newPassword >>= assertStatus 403
completePasswordReset u key "wrong-code" newPassword >>= assertStatus 400
login u email newPassword >>= assertStatus 403

-- complete password reset with correct key and code should succeed
completePasswordReset u key code newPassword >>= assertSuccess

-- try login with old password should fail
login u email defPassword >>= assertStatus 403
-- login with new password should succeed
login u email newPassword >>= assertSuccess
-- reset password again to the same new password should fail
passwordReset u email >>= assertSuccess
(nextKey, nextCode) <- getPasswordResetData email
bindResponse (completePasswordReset u nextKey nextCode newPassword) $ \resp -> do
resp.status `shouldMatchInt` 409
resp.json %. "label" `shouldMatch` "password-must-differ"

-- @END

testPasswordResetAfterEmailUpdate :: (HasCallStack) => App ()
testPasswordResetAfterEmailUpdate = do
u <- randomUser OwnDomain def
email <- u %. "email" & asString
(cookie, token) <- bindResponse (login u email defPassword) $ \resp -> do
resp.status `shouldMatchInt` 200
token <- resp.json %. "access_token" & asString
let cookie = fromJust $ getCookie "zuid" resp
pure ("zuid=" <> cookie, token)

-- initiate email update
newEmail <- randomEmail
updateEmail u newEmail cookie token >>= assertSuccess

-- initiate password reset
passwordReset u email >>= assertSuccess
(key, code) <- getPasswordResetData email

-- activate new email
bindResponse (getActivationCode u newEmail) $ \resp -> do
resp.status `shouldMatchInt` 200
activationKey <- resp.json %. "key" & asString
activationCode <- resp.json %. "code" & asString
activate u activationKey activationCode >>= assertSuccess

bindResponse (getSelf u) $ \resp -> do
actualEmail <- resp.json %. "email"
actualEmail `shouldMatch` newEmail

-- attempting to complete password reset should fail
bindResponse (completePasswordReset u key code "newpassword") $ \resp -> do
resp.status `shouldMatchInt` 400
resp.json %. "label" `shouldMatch` "invalid-code"

testPasswordResetInvalidPasswordLength :: App ()
testPasswordResetInvalidPasswordLength = do
u <- randomUser OwnDomain def
email <- u %. "email" & asString
passwordReset u email >>= assertSuccess
(key, code) <- getPasswordResetData email

-- complete password reset with a password that is too short should fail
let shortPassword = "123456"
completePasswordReset u key code shortPassword >>= assertStatus 400

-- try login with new password should fail
login u email shortPassword >>= assertStatus 403

getPasswordResetData :: String -> App (String, String)
getPasswordResetData email = do
bindResponse (getPasswordResetCode OwnDomain email) $ \resp -> do
resp.status `shouldMatchInt` 200
(,) <$> (resp.json %. "key" & asString) <*> (resp.json %. "code" & asString)
6 changes: 6 additions & 0 deletions integration/test/Testlib/HTTP.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Testlib.Assertions
import Testlib.Env
import Testlib.JSON
import Testlib.Types
import Web.Cookie
import Prelude

splitHttpPath :: String -> [String]
Expand Down Expand Up @@ -89,6 +90,11 @@ setCookie :: String -> HTTP.Request -> HTTP.Request
setCookie c r =
addHeader "Cookie" (cs c) r

getCookie :: String -> Response -> Maybe String
getCookie name resp = do
cookieHeader <- lookup (CI.mk $ cs "set-cookie") resp.headers
cs <$> lookup (cs name) (parseCookies cookieHeader)

addQueryParams :: [(String, String)] -> HTTP.Request -> HTTP.Request
addQueryParams params req =
HTTP.setQueryString (map (\(k, v) -> (cs k, Just (cs v))) params) req
Expand Down
1 change: 0 additions & 1 deletion services/brig/brig.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ executable brig-integration
API.User.Client
API.User.Connection
API.User.Handles
API.User.PasswordReset
API.User.RichInfo
API.User.Util
API.UserPendingActivation
Expand Down
2 changes: 0 additions & 2 deletions services/brig/test/integration/API/User.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import API.User.Auth qualified
import API.User.Client qualified
import API.User.Connection qualified
import API.User.Handles qualified
import API.User.PasswordReset qualified
import API.User.RichInfo qualified
import API.User.Util
import Bilge hiding (accept, timeout)
Expand Down Expand Up @@ -67,7 +66,6 @@ tests conf fbc p b c ch g n aws db userJournalWatcher = do
API.User.Auth.tests conf p z db b g n,
API.User.Connection.tests cl at p b c g fbc db,
API.User.Handles.tests cl at conf p b c g,
API.User.PasswordReset.tests db cl at conf p b c g,
API.User.RichInfo.tests cl at conf p b c g
]

Expand Down
127 changes: 0 additions & 127 deletions services/brig/test/integration/API/User/PasswordReset.hs

This file was deleted.