Skip to content
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7969711
Refactor effects around password resetting
Apr 22, 2022
b943d47
Add a changelog
Apr 25, 2022
c28c6eb
Code formatting
May 20, 2022
2a3055d
Add the logging effect to Brig
May 26, 2022
866dcc5
Introduce the UserQuery effect
May 24, 2022
b9d94af
Implement the getName action (replace lookupName)
May 24, 2022
e2271c1
Implement the getLocale action (was lookupLocale)
May 24, 2022
8775800
Define the BudgetStore effect (to replace Brig.Budget)
May 25, 2022
f5cbebf
Implement the getAuthentication action
May 25, 2022
e0547e2
Removing MonadReader in the UserKey module
Jun 3, 2022
c7c7f57
WIP: Almost compiles with getUserAuthentication or something like it
Jun 7, 2022
41835f0
Lots of new effects
Jun 15, 2022
24dc4ef
Interpretation for an ActivationKeyStore action
Jun 15, 2022
21b2822
It compiles (though with undefined's and commented out code)
Jun 15, 2022
708fde4
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Aug 12, 2022
9b79b8d
Get rid of one undefined
Aug 12, 2022
142e740
Drop undefined in ssoLogin
Aug 12, 2022
980342d
Drop undefined from Brig.API.User.mkActivationKey
Aug 12, 2022
011b2c3
Add back code for Birg.User.Auth.legalHoldLogin
Aug 12, 2022
419a63e
Move a utility function
Aug 12, 2022
3b95148
Define the GundeckAccess effect
Aug 12, 2022
8930372
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Aug 18, 2022
69da294
Use the GundeckAccess effect
Aug 18, 2022
1153327
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Aug 25, 2022
08adb08
Remove temporary Polysemy crutches
Aug 25, 2022
3f0b894
Handle a TODO in Brig.API.User.deleteAccount
Aug 26, 2022
975d3ef
Move an error interpreter into a separate module
Aug 26, 2022
7b31840
Change TODO to FUTUREWORK notes
Aug 26, 2022
556b850
Remove commented out code in Brig.IO.Intra
Aug 26, 2022
7212fa9
Fix missing Cql instances for activation types
Aug 26, 2022
3ee9aac
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Aug 30, 2022
19df7e1
Move the FireAndForget effect from Galley into zoo
Aug 30, 2022
48c289b
Simplify the interface of Brig.IO.Intra.notify
Aug 30, 2022
be904d9
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Sep 19, 2022
dc55d2e
Resolve a non-TODO
Sep 19, 2022
f105822
Remove the Polysemy.Async effect
Sep 19, 2022
eef87c2
Replace List1 with NonEmpty
Sep 20, 2022
1428418
Remove some in-line effect interpretations
Sep 20, 2022
b462175
Improve the interface for a Twilio effect action
Sep 20, 2022
d29eefd
Linting of BudgetStore
Sep 20, 2022
6a1379f
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Sep 27, 2022
f5e9ed0
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Sep 30, 2022
0f69444
Polysemise Brig.Provider.API.deleteBot
Oct 4, 2022
b23a3f6
Merge remote-tracking branch 'origin/develop' into polysemy/brig/user…
Oct 5, 2022
a41b0fb
Add a TODO to replace the Async effect
Oct 5, 2022
fd08303
Fix one use site of deleteBot
Oct 5, 2022
a3b9a93
Propagate effect constraints (compiles again)
Oct 5, 2022
995ac55
Use a ClientStore action instead of its interpretation
Oct 6, 2022
fa52ece
Introduce the CookieStore effect
Oct 6, 2022
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/user-effects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the UserQuery and supporting effects
81 changes: 81 additions & 0 deletions libs/brig-types/src/Brig/Types/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ module Brig.Types.Common
isValidPhonePrefix,
allPrefixes,
ExcludedPrefix (..),

-- * misc
foldKey,
keyText,
mkPhoneKey,
mkEmailKey,
EmailKey (..),
PhoneKey (..),
UserKey (..),
)
where

Expand All @@ -39,6 +48,7 @@ import Data.ByteString.Conversion
import qualified Data.Text as Text
import Data.Time.Clock (NominalDiffTime)
import Imports
import Wire.API.User.Identity

------------------------------------------------------------------------------
--- PhoneBudgetTimeout
Expand Down Expand Up @@ -111,3 +121,74 @@ instance FromJSON ExcludedPrefix where

instance ToJSON ExcludedPrefix where
toJSON (ExcludedPrefix p c) = object ["phone_prefix" .= p, "comment" .= c]

-------------------------------------------------------------------------------
-- Unique Keys

-- | An 'EmailKey' is an 'Email' in a form that serves as a unique lookup key.
data EmailKey = EmailKey
{ emailKeyUniq :: !Text,
emailKeyOrig :: !Email
}

instance Show EmailKey where
showsPrec _ = shows . emailKeyUniq

instance Eq EmailKey where
(EmailKey k _) == (EmailKey k' _) = k == k'

-- | Turn an 'Email' into an 'EmailKey'.
--
-- The following transformations are performed:
--
-- * Both local and domain parts are forced to lowercase to make

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it isn't common, the local part is "domain dependent" and can be case-sensitive. We probably shouldn't case fold it by default, though we can if we know the domain does handle the local part case-insensitively.

Case-folding by default will make for some very hard to trace issues if the domain does use case-sensitive local parts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like the rest in this module, this is not new code added by me in the PR; rather, it's just being reorganised and moved from other modules. That said, I don't think this PR should be about addressing your concern and I believe you should open a ticket and bring it to the team's attention.

-- e-mail addresses fully case-insensitive.
-- * "+" suffixes on the local part are stripped unless the domain

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also far from universal. If we are modeling off of GMail we would also need to strip all . characters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not new code added by me in this PR; rather, it's old code I moved around.

-- part is contained in a trusted whitelist.
mkEmailKey :: Email -> EmailKey
mkEmailKey orig@(Email localPart domain) =
let uniq = Text.toLower localPart' <> "@" <> Text.toLower domain
in EmailKey uniq orig
where
localPart'
| domain `notElem` trusted = Text.takeWhile (/= '+') localPart
| otherwise = localPart
trusted = ["wearezeta.com", "wire.com", "simulator.amazonses.com"]

data PhoneKey = PhoneKey
{ -- | canonical form of 'phoneKeyOrig', without whitespace.
phoneKeyUniq :: !Text,
-- | phone number with whitespace.
phoneKeyOrig :: !Phone
}

instance Show PhoneKey where
showsPrec _ = shows . phoneKeyUniq

instance Eq PhoneKey where
(PhoneKey k _) == (PhoneKey k' _) = k == k'

mkPhoneKey :: Phone -> PhoneKey
mkPhoneKey orig =
let uniq = Text.filter (not . isSpace) (fromPhone orig)
in PhoneKey uniq orig

-- | A natural identifier (i.e. unique key) of a user.
data UserKey
= UserEmailKey !EmailKey
| UserPhoneKey !PhoneKey

instance Eq UserKey where
(UserEmailKey k) == (UserEmailKey k') = k == k'
(UserPhoneKey k) == (UserPhoneKey k') = k == k'
_ == _ = False

-- | Get the normalised text of a 'UserKey'.
keyText :: UserKey -> Text
keyText (UserEmailKey k) = emailKeyUniq k
keyText (UserPhoneKey k) = phoneKeyUniq k

foldKey :: (Email -> a) -> (Phone -> a) -> UserKey -> a
foldKey f g k = case k of
UserEmailKey ek -> f (emailKeyOrig ek)
UserPhoneKey pk -> g (phoneKeyOrig pk)
30 changes: 29 additions & 1 deletion services/brig/brig.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ library
Brig.AWS
Brig.AWS.SesNotification
Brig.AWS.Types
Brig.Budget
Brig.Calling
Brig.Calling.API
Brig.Calling.Internal
Expand Down Expand Up @@ -88,11 +87,37 @@ library
Brig.Queue.Stomp
Brig.Queue.Types
Brig.RPC
Brig.RPC.Decode
Brig.Run
Brig.Sem.ActivationKeyStore
Brig.Sem.ActivationKeyStore.Cassandra
Brig.Sem.ActivationSupply
Brig.Sem.ActivationSupply.IO
Brig.Sem.BudgetStore
Brig.Sem.BudgetStore.Cassandra
Brig.Sem.CodeStore
Brig.Sem.CodeStore.Cassandra
Brig.Sem.Common
Brig.Sem.GalleyAccess
Brig.Sem.GalleyAccess.Http
Brig.Sem.GundeckAccess
Brig.Sem.GundeckAccess.Http
Brig.Sem.PasswordResetStore
Brig.Sem.PasswordResetStore.CodeStore
Brig.Sem.PasswordResetSupply
Brig.Sem.PasswordResetSupply.IO
Brig.Sem.Twilio
Brig.Sem.Twilio.IO
Brig.Sem.UniqueClaimsStore
Brig.Sem.UniqueClaimsStore.Cassandra
Brig.Sem.UserHandleStore
Brig.Sem.UserHandleStore.Cassandra
Brig.Sem.UserKeyStore
Brig.Sem.UserKeyStore.Cassandra
Brig.Sem.UserQuery
Brig.Sem.UserQuery.Cassandra
Brig.Sem.VerificationCodeStore
Brig.Sem.VerificationCodeStore.Cassandra
Brig.SMTP
Brig.Team.API
Brig.Team.DB
Expand Down Expand Up @@ -223,6 +248,7 @@ library
, imports
, insert-ordered-containers
, iproute >=1.5
, iso3166-country-codes
, iso639 >=0.1
, lens >=3.8
, lens-aeson >=1.0
Expand All @@ -239,6 +265,8 @@ library
, optparse-applicative >=0.11
, pem >=0.2
, polysemy
, polysemy-conc
, polysemy-time
, polysemy-wire-zoo
, proto-lens >=0.1
, random-shuffle >=0.0.3
Expand Down
3 changes: 3 additions & 0 deletions services/brig/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ library:
- imports
- insert-ordered-containers
- iproute >=1.5
- iso3166-country-codes
- iso639 >=0.1
- lens >=3.8
- lens-aeson >=1.0
Expand All @@ -85,6 +86,8 @@ library:
- optparse-applicative >=0.11
- pem >=0.2
- polysemy
- polysemy-conc
- polysemy-time
- polysemy-wire-zoo
- proto-lens >=0.1
- random-shuffle >=0.0.3
Expand Down
44 changes: 41 additions & 3 deletions services/brig/src/Brig/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,58 @@ where
import Brig.API.Handler (Handler)
import qualified Brig.API.Internal as Internal
import qualified Brig.API.Public as Public
import Brig.API.Types
import Brig.Effects.BlacklistPhonePrefixStore (BlacklistPhonePrefixStore)
import Brig.Effects.BlacklistStore (BlacklistStore)
import Brig.Sem.ActivationKeyStore
import Brig.Sem.ActivationSupply
import Brig.Sem.BudgetStore
import Brig.Sem.CodeStore
import Brig.Sem.GalleyAccess
import Brig.Sem.PasswordResetStore (PasswordResetStore)
import Brig.Sem.PasswordResetSupply (PasswordResetSupply)
import Brig.Sem.Twilio
import Brig.Sem.UniqueClaimsStore
import Brig.Sem.UserHandleStore
import Brig.Sem.UserKeyStore
import Brig.Sem.UserQuery
import Brig.Sem.VerificationCodeStore
import Data.Qualified
import qualified Data.Swagger.Build.Api as Doc
import Network.Wai.Routing (Routes)
import Polysemy
import Polysemy.Async
import Polysemy.Conc.Effect.Race
import Polysemy.Error
import Polysemy.Input
import Polysemy.Resource
import qualified Polysemy.TinyLog as P
import qualified Ropes.Twilio as Twilio

sitemap ::
Members
'[ CodeStore,
PasswordResetStore,
'[ ActivationKeyStore,
ActivationSupply,
Async,
BlacklistStore,
BlacklistPhonePrefixStore
BlacklistPhonePrefixStore,
BudgetStore,
CodeStore,
Error ReAuthError,
Error Twilio.ErrorResponse,
GalleyAccess,
Input (Local ()),
P.TinyLog,
PasswordResetStore,
PasswordResetSupply,
Race,
Resource,
Twilio,
UniqueClaimsStore,
UserHandleStore,
UserKeyStore,
UserQuery,
VerificationCodeStore
]
r =>
Routes Doc.ApiBuilder (Handler r) ()
Expand Down
46 changes: 27 additions & 19 deletions services/brig/src/Brig/API/Client.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ import qualified Brig.Federation.Client as Federation
import Brig.IO.Intra (guardLegalhold)
import qualified Brig.IO.Intra as Intra
import qualified Brig.Options as Opt
import Brig.Sem.GalleyAccess
import Brig.Sem.UserQuery (UserQuery)
import Brig.Sem.VerificationCodeStore
import Brig.Types.Intra
import Brig.Types.Team.LegalHold (LegalHoldClientRequest (..))
import Brig.Types.User.Event
Expand All @@ -79,7 +82,10 @@ import Data.Misc (PlainTextPassword (..))
import Data.Qualified
import qualified Data.Set as Set
import Imports
import Network.Wai.Utilities
import Network.Wai.Utilities hiding (Error)
import Polysemy
import Polysemy.Error
import Polysemy.Input
import System.Logger.Class (field, msg, val, (~~))
import qualified System.Logger.Class as Log
import UnliftIO.Async (Concurrently (Concurrently, runConcurrently))
Expand Down Expand Up @@ -129,6 +135,7 @@ lookupLocalPubClientsBulk :: [UserId] -> ExceptT ClientError (AppT r) (UserMap (
lookupLocalPubClientsBulk = lift . wrapClient . Data.lookupPubClientsBulk

addClient ::
Members '[GalleyAccess, Input (Local ()), UserQuery, VerificationCodeStore] r =>
UserId ->
Maybe ConnId ->
Maybe IP ->
Expand All @@ -139,15 +146,18 @@ addClient = addClientWithReAuthPolicy Data.reAuthForNewClients
-- nb. We must ensure that the set of clients known to brig is always
-- a superset of the clients known to galley.
addClientWithReAuthPolicy ::
forall r.
Members '[GalleyAccess, Input (Local ()), UserQuery, VerificationCodeStore] r =>
Data.ReAuthPolicy ->
UserId ->
Maybe ConnId ->
Maybe IP ->
NewClient ->
ExceptT ClientError (AppT r) Client
addClientWithReAuthPolicy policy u con ip new = do
acc <- lift (wrapClient $ Data.lookupAccount u) >>= maybe (throwE (ClientUserNotFound u)) pure
wrapHttpClientE $ verifyCode (newClientVerificationCode new) (userId . accountUser $ acc)
locale <- Opt.setDefaultUserLocale <$> view settings
acc <- lift (liftSem $ Data.lookupAccount locale u) >>= maybe (throwE (ClientUserNotFound u)) pure
verifyCodeThrow (newClientVerificationCode new) (userId . accountUser $ acc)
loc <- maybe (pure Nothing) locationOf ip
maxPermClients <- fromMaybe Opt.defUserMaxPermClients . Opt.setUserMaxPermClients <$> view settings
let caps :: Maybe (Set ClientCapability)
Expand All @@ -159,8 +169,7 @@ addClientWithReAuthPolicy policy u con ip new = do
else id
lhcaps = ClientSupportsLegalholdImplicitConsent
(clt0, old, count) <-
wrapClientE
(Data.addClientWithReAuthPolicy policy u clientId' new maxPermClients loc caps)
(Data.addClientWithReAuthPolicy policy u clientId' new maxPermClients loc caps)
!>> ClientDataError
let clt = clt0 {clientMLSPublicKeys = newClientMLSPublicKeys new}
let usr = accountUser acc
Expand All @@ -177,19 +186,11 @@ addClientWithReAuthPolicy policy u con ip new = do
where
clientId' = clientIdFromPrekey (unpackLastPrekey $ newClientLastKey new)

verifyCode ::
( MonadReader Env m,
MonadMask m,
MonadHttp m,
MonadIO m,
HasRequestId m,
Log.MonadLogger m,
MonadClient m
) =>
verifyCodeThrow ::
Maybe Code.Value ->
UserId ->
ExceptT ClientError m ()
verifyCode mbCode userId =
ExceptT ClientError (AppT r) ()
verifyCodeThrow mbCode userId =
-- this only happens inside the login flow (in particular, when logging in from a new device)
-- the code obtained for logging in is used a second time for adding the device
UserAuth.verifyCode mbCode Code.Login userId `catchE` \case
Expand All @@ -212,18 +213,25 @@ updateClient u c r = do

-- nb. We must ensure that the set of clients known to brig is always
-- a superset of the clients known to galley.
rmClient :: UserId -> ConnId -> ClientId -> Maybe PlainTextPassword -> ExceptT ClientError (AppT r) ()
rmClient ::
Members '[Error ReAuthError, Input (Local ()), UserQuery] r =>
UserId ->
ConnId ->
ClientId ->
Maybe PlainTextPassword ->
ExceptT ClientError (AppT r) ()
rmClient u con clt pw =
maybe (throwE ClientNotFound) fn =<< lift (wrapClient $ Data.lookupClient u clt)
where
fn client = do
locale <- Opt.setDefaultUserLocale <$> view settings
case clientType client of
-- Legal hold clients can't be removed
LegalHoldClientType -> throwE ClientLegalHoldCannotBeRemoved
-- Temporary clients don't need to re-auth
TemporaryClientType -> pure ()
-- All other clients must authenticate
_ -> wrapClientE (Data.reauthenticate u pw) !>> ClientDataError . ClientReAuthError
_ -> lift (liftSem (Data.reauthenticate locale u pw)) !>> ClientDataError . ClientReAuthError
lift $ execDelete u (Just con) client

claimPrekey ::
Expand Down Expand Up @@ -380,7 +388,7 @@ claimLocalMultiPrekeyBundles protectee userClients = do
-- Utilities

-- | Perform an orderly deletion of an existing client.
execDelete :: UserId -> Maybe ConnId -> Client -> (AppT r) ()
execDelete :: UserId -> Maybe ConnId -> Client -> AppT r ()
execDelete u con c = do
wrapHttp $ Intra.rmClient u (clientId c)
for_ (clientCookie c) $ \l -> wrapClient $ Auth.revokeCookies u [] [l]
Expand Down
Loading