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
2 changes: 2 additions & 0 deletions integration/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
, Cabal
, case-insensitive
, containers
, cookie
, cql
, cql-io
, crypton
Expand Down Expand Up @@ -103,6 +104,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 @@ -136,6 +136,7 @@ library
Test.MLS.SubConversation
Test.MLS.Unreachable
Test.Notifications
Test.PasswordReset
Test.Presence
Test.Roles
Test.Search
Expand Down Expand Up @@ -179,6 +180,7 @@ library
, bytestring-conversion
, case-insensitive
, containers
, cookie
, cql
, cql-io
, crypton
Expand Down
27 changes: 27 additions & 0 deletions integration/test/API/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,30 @@ renewToken :: (HasCallStack, MakesValue uid) => uid -> String -> App Response
renewToken caller cookie = do
req <- baseRequest caller Brig Versioned "access"
submit "POST" (addHeader "Cookie" ("zuid=" <> cookie) req)

activate :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
activate domain key code = do
req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["activate"]
submit "GET" $
req
& addQueryParams [("key", key), ("code", code)]

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)
10 changes: 10 additions & 0 deletions integration/test/API/BrigInternal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,13 @@ deleteFeatureForUser user featureName = do
uid <- objId user
req <- baseRequest user Brig Unversioned $ joinHttpPath ["i", "users", uid, "features", featureName]
submit "DELETE" req

getActivationCode :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
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)]
100 changes: 100 additions & 0 deletions integration/test/Test/PasswordReset.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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:
-- - 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

(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 @@ -27,6 +27,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 @@ -74,6 +75,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 @@ -421,7 +421,6 @@ executable brig-integration
API.User.Client
API.User.Connection
API.User.Handles
API.User.PasswordReset
API.User.Property
API.User.RichInfo
API.User.Util
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.Property qualified
import API.User.RichInfo qualified
import API.User.Util
Expand Down Expand Up @@ -68,7 +67,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.Property.tests cl at conf p b c g,
API.User.RichInfo.tests cl at conf p b c g
]
Expand Down
121 changes: 0 additions & 121 deletions services/brig/test/integration/API/User/PasswordReset.hs

This file was deleted.