diff --git a/libs/wire-api/default.nix b/libs/wire-api/default.nix index 9b6c2c3bd56..0a2cfe34065 100644 --- a/libs/wire-api/default.nix +++ b/libs/wire-api/default.nix @@ -230,6 +230,7 @@ mkDerivation { containers crypton currency-codes + data-default filepath hex hspec diff --git a/libs/wire-api/src/Wire/API/EnterpriseLogin.hs b/libs/wire-api/src/Wire/API/EnterpriseLogin.hs index 919deab7d95..fe344637a32 100644 --- a/libs/wire-api/src/Wire/API/EnterpriseLogin.hs +++ b/libs/wire-api/src/Wire/API/EnterpriseLogin.hs @@ -220,54 +220,59 @@ data DomainRegistrationResponse = DomainRegistrationResponse deriving (ToJSON, FromJSON, S.ToSchema) via Schema DomainRegistrationResponse mkDomainRegistrationResponse :: DomainRegistration -> DomainRegistrationResponse -mkDomainRegistrationResponse DomainRegistration {..} = DomainRegistrationResponse {..} +mkDomainRegistrationResponse = prs . domainRegistrationToRow + where + prs DomainRegistrationRow {..} = DomainRegistrationResponse {..} instance ToSchema DomainRegistrationResponse where schema = object "DomainRegistrationResponse" $ DomainRegistrationResponse <$> (.domain) .= field "domain" schema - <*> (.authorizedTeam) .= maybe_ (optField "authorized_team" schema) + <*> (.authorizedTeam) .= maybe_ (optField "authorized_team" schema) -- TODO: change to "team" to make it consistent with client->server talk <*> (.domainRedirect) .= domainRedirectSchema <*> (.teamInvite) .= teamInviteObjectSchema <*> (.dnsVerificationToken) .= optField "dns_verification_token" (maybeWithDefault Aeson.Null schema) ----------------------------------------------------------------------- - -data DomainRegistration' = DomainRegistration' - { settings :: Maybe DomainRegistrationSettings', +-- | See 'domainRegistration{From,To}Row'. +data DomainRegistration = DomainRegistration + { domain :: Domain, + settings :: Maybe DomainRegistrationSettings, dnsVerificationToken :: Maybe DnsVerificationToken, authTokenHash :: Maybe Token } deriving (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform DomainRegistration') + deriving (Arbitrary) via (GenericUniform DomainRegistration) + +instance {-# OVERLAPPING #-} Default (Domain -> DomainRegistration) where + def dom = DomainRegistration dom Nothing Nothing Nothing -data DomainRegistrationSettings' - = Locked' - | PreAuthorized' - | NoRegistration' +data DomainRegistrationSettings + = DomainLocked + | DomainPreAuthorized + | DomainNoRegistration | DomainForBackend HttpsUrl | DomainForLocalTeam TeamId (Maybe SAML.IdPId) deriving (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform DomainRegistrationSettings') + deriving (Arbitrary) via (GenericUniform DomainRegistrationSettings) -newToOld :: Domain -> DomainRegistration' -> DomainRegistration -newToOld domain DomainRegistration' {..} = DomainRegistration {..} +domainRegistrationToRow :: DomainRegistration -> DomainRegistrationRow +domainRegistrationToRow DomainRegistration {..} = DomainRegistrationRow {..} where domainRedirect = case settings of Nothing -> None - Just Locked' -> Locked - Just PreAuthorized' -> PreAuthorized - Just NoRegistration' -> NoRegistration + Just DomainLocked -> Locked + Just DomainPreAuthorized -> PreAuthorized + Just DomainNoRegistration -> NoRegistration Just (DomainForBackend url) -> Backend url Just (DomainForLocalTeam _tid Nothing) -> None Just (DomainForLocalTeam _tid (Just idpid)) -> SSO idpid teamInvite = case settings of Nothing -> Allowed - Just Locked' -> Allowed - Just PreAuthorized' -> Allowed - Just NoRegistration' -> NotAllowed -- TODO: where does the spec cover this case? + Just DomainLocked -> Allowed + Just DomainPreAuthorized -> Allowed + Just DomainNoRegistration -> NotAllowed Just (DomainForBackend _) -> NotAllowed Just (DomainForLocalTeam tid _) -> Team tid @@ -275,24 +280,35 @@ newToOld domain DomainRegistration' {..} = DomainRegistration {..} Just (DomainForLocalTeam tid _) -> Just tid _ -> Nothing -oldToNew :: DomainRegistration -> Either String (Domain, DomainRegistration') -oldToNew DomainRegistration {..} = do - settings :: Maybe DomainRegistrationSettings' <- do - case (domainRedirect, teamInvite, authorizedTeam) of - (None, Allowed, Nothing) -> Right Nothing - (Locked, Allowed, Nothing) -> Right (Just Locked') - (PreAuthorized, Allowed, Nothing) -> Right (Just PreAuthorized') - (NoRegistration, NotAllowed, Nothing) -> Right (Just NoRegistration') - (Backend url, NotAllowed, Nothing) -> Right (Just (DomainForBackend url)) - (None, Team tid, Just tid') | tid == tid' -> Right (Just (DomainForLocalTeam tid Nothing)) - (SSO idpid, Team tid, Just tid') | tid == tid' -> Right (Just (DomainForLocalTeam tid (Just idpid))) - _ -> Left ("domainRedirect, teamInvite, authorizedTeam mismatch: " <> show (domainRedirect, teamInvite, authorizedTeam)) - - Right (domain, DomainRegistration' {..}) - ----------------------------------------------------------------------- - -data DomainRegistration = DomainRegistration +domainRegistrationSettingsFromRow :: DomainRedirect -> TeamInvite -> Maybe TeamId -> Either String (Maybe DomainRegistrationSettings) +domainRegistrationSettingsFromRow domainRedirect teamInvite authorizedTeam = + case (domainRedirect, teamInvite, authorizedTeam) of + (None, Allowed, Nothing) -> Right Nothing + (Locked, Allowed, Nothing) -> Right (Just DomainLocked) + (PreAuthorized, Allowed, Nothing) -> Right (Just DomainPreAuthorized) + (NoRegistration, NotAllowed, Nothing) -> Right (Just DomainNoRegistration) + (Backend url, NotAllowed, Nothing) -> Right (Just (DomainForBackend url)) + (None, Team tid, Just tid') | tid == tid' -> Right (Just (DomainForLocalTeam tid Nothing)) + (SSO idpid, Team tid, Just tid') | tid == tid' -> Right (Just (DomainForLocalTeam tid (Just idpid))) + _ -> Left ("domainRedirect, teamInvite, authorizedTeam mismatch: " <> show (domainRedirect, teamInvite, authorizedTeam)) + +domainRegistrationFromRow :: DomainRegistrationRow -> Either String DomainRegistration +domainRegistrationFromRow DomainRegistrationRow {..} = do + settings <- domainRegistrationSettingsFromRow domainRedirect teamInvite authorizedTeam + Right DomainRegistration {..} + +domainRegistrationFromUpdate :: DomainRegistration -> DomainRegistrationUpdate -> Either String DomainRegistration +domainRegistrationFromUpdate reg upd = do + let authorizedTeam = case upd.teamInvite of + Allowed -> Nothing + NotAllowed -> Nothing + Team tid -> Just tid + newSettings <- domainRegistrationSettingsFromRow upd.domainRedirect upd.teamInvite authorizedTeam + Right reg {settings = newSettings} + +-- | This type only server as a helper for json (and cql, for historical reasons). App logic +-- should always use 'DomainRegistration' instead. See 'domainRegistration{From,To}Row'. +data DomainRegistrationRow = DomainRegistrationRow { domain :: Domain, authorizedTeam :: Maybe TeamId, domainRedirect :: DomainRedirect, @@ -301,11 +317,11 @@ data DomainRegistration = DomainRegistration authTokenHash :: Maybe Token } deriving stock (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform DomainRegistration) + deriving (Arbitrary) via (GenericUniform DomainRegistrationRow) -instance {-# OVERLAPPING #-} Default (Domain -> DomainRegistration) where +instance {-# OVERLAPPING #-} Default (Domain -> DomainRegistrationRow) where def domain = - DomainRegistration + DomainRegistrationRow { domain, authorizedTeam = Nothing, domainRedirect = def, diff --git a/libs/wire-api/src/Wire/API/Error/Brig.hs b/libs/wire-api/src/Wire/API/Error/Brig.hs index 0d852cbee5d..93823b1fa85 100644 --- a/libs/wire-api/src/Wire/API/Error/Brig.hs +++ b/libs/wire-api/src/Wire/API/Error/Brig.hs @@ -105,6 +105,7 @@ data BrigError | NotificationQueueConnectionError | DomainVerificationErrorNotFound | DomainVerificationInvalidDomain + | DomainVerificationInvalidDomainUpdate | DomainVerificationDomainVerificationFailed | DomainVerificationOperationForbidden | DomainVerificationAuthFailure @@ -320,6 +321,8 @@ type instance MapError 'DomainVerificationErrorNotFound = 'StaticError 404 "not- type instance MapError 'DomainVerificationInvalidDomain = 'StaticError 400 "invalid-domain" "Invalid domain" +type instance MapError 'DomainVerificationInvalidDomainUpdate = 'StaticError 400 "invalid-domain-update" "Invalid domain update" + type instance MapError 'DomainVerificationDomainVerificationFailed = 'StaticError 403 "domain-verification-failed" "Domain verification failed" type instance MapError 'DomainVerificationOperationForbidden = 'StaticError 403 "operation-forbidden-for-domain-registration-state" "Invalid domain registration state update" diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs index 8823dee095a..6eb231345ff 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs @@ -1,3 +1,5 @@ +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} + -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2022 Wire Swiss GmbH @@ -19,10 +21,13 @@ module Test.Wire.API.Roundtrip.Aeson (tests) where import Data.Aeson (FromJSON, ToJSON, parseJSON, toJSON) import Data.Aeson.Types (parseEither) +import Data.Default +import Data.Domain import Data.Id (ConvId) import Data.OpenApi (ToSchema, validatePrettyToJSON) import Imports import Test.Tasty qualified as T +import Test.Tasty.HUnit (testCase, (@?=)) import Test.Tasty.QuickCheck (Arbitrary, counterexample, testProperty, (.&&.), (===)) import Type.Reflection (typeRep) import Wire.API.Asset qualified as Asset @@ -351,8 +356,17 @@ tests = testRoundTrip @TeamsIntra.TeamStatusUpdate, testRoundTrip @TeamsIntra.TeamData, testRoundTrip @TeamsIntra.TeamName, - testProperty "EnterpriseLogin.EnterpriseLogin'" $ \(dom, new) -> do - EnterpriseLogin.oldToNew (EnterpriseLogin.newToOld dom new) === Right (dom, new) + T.testGroup "EnterpriseLogin.DomainRegistration{,Row}" $ + [ -- TODO: move this group to a better place + testCase "default values match" $ do + let Right dom = mkDomain "example.com" + Right (def dom :: EnterpriseLogin.DomainRegistration) + @?= EnterpriseLogin.domainRegistrationFromRow (def dom) + (def dom :: EnterpriseLogin.DomainRegistrationRow) + @?= EnterpriseLogin.domainRegistrationToRow (def dom), + testProperty "to, from row" $ \new -> do + EnterpriseLogin.domainRegistrationFromRow (EnterpriseLogin.domainRegistrationToRow new) === Right new + ] ] testRoundTrip :: diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 974dd9c4987..77f86aed972 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -699,6 +699,7 @@ test-suite wire-api-tests , cassava , containers >=0.5 , crypton + , data-default , filepath , hex , hspec diff --git a/libs/wire-subsystems/src/Wire/DomainRegistrationStore.hs b/libs/wire-subsystems/src/Wire/DomainRegistrationStore.hs index f8cec63148e..e3c4cce7be4 100644 --- a/libs/wire-subsystems/src/Wire/DomainRegistrationStore.hs +++ b/libs/wire-subsystems/src/Wire/DomainRegistrationStore.hs @@ -103,13 +103,17 @@ logInvalidDomainRegistrationError domain = delete :: (Member DomainRegistrationStore r) => Domain -> Sem r () delete = send . DeleteInternal . mkDomainKey +-- | Inconsistently stored domain registrations (according to 'domainRegistrationFromRow') are +-- discarded. fromStored :: StoredDomainRegistration -> Maybe DomainRegistration -fromStored sdr = - DomainRegistration (unmkDomainKey sdr.domain) sdr.authorizedTeam - <$> getDomainRedirect sdr - <*> getTeamInvite sdr - <*> pure sdr.dnsVerificationToken - <*> pure sdr.authTokenHash +fromStored sdr = do + row <- + DomainRegistrationRow (unmkDomainKey sdr.domain) sdr.authorizedTeam + <$> getDomainRedirect sdr + <*> getTeamInvite sdr + <*> pure sdr.dnsVerificationToken + <*> pure sdr.authTokenHash + either (const Nothing) Just (domainRegistrationFromRow row) where getTeamInvite :: StoredDomainRegistration -> Maybe TeamInvite getTeamInvite = \case @@ -133,7 +137,7 @@ fromStored sdr = _ -> Nothing toStored :: DomainRegistration -> StoredDomainRegistration -toStored dr = +toStored (domainRegistrationToRow -> dr) = let (domainRedirect, idpId, backendUrl) = fromDomainRedirect dr.domainRedirect (teamInvite, team) = fromTeamInvite dr.teamInvite in StoredDomainRegistration diff --git a/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Error.hs b/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Error.hs index f293e19f84d..a557ad72445 100644 --- a/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Error.hs +++ b/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Error.hs @@ -9,6 +9,7 @@ import Wire.Error data EnterpriseLoginSubsystemError = EnterpriseLoginSubsystemErrorNotFound | EnterpriseLoginSubsystemInvalidDomain + | EnterpriseLoginSubsystemInvalidDomainUpdate String | EnterpriseLoginSubsystemDomainVerificationFailed | EnterpriseLoginSubsystemOperationForbidden | EnterpriseLoginSubsystemAuthFailure @@ -25,6 +26,7 @@ enterpriseLoginSubsystemErrorToHttpError = StdError . \case EnterpriseLoginSubsystemErrorNotFound -> errorToWai @DomainVerificationErrorNotFound EnterpriseLoginSubsystemInvalidDomain -> errorToWai @DomainVerificationInvalidDomain + EnterpriseLoginSubsystemInvalidDomainUpdate _msg -> errorToWai @DomainVerificationInvalidDomainUpdate EnterpriseLoginSubsystemDomainVerificationFailed -> errorToWai @DomainVerificationDomainVerificationFailed EnterpriseLoginSubsystemOperationForbidden -> errorToWai @DomainVerificationOperationForbidden EnterpriseLoginSubsystemPaymentRequired -> errorToWai @DomainVerificationPaymentRequired diff --git a/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Interpreter.hs index 3061602390b..1ad291c91bf 100644 --- a/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/EnterpriseLoginSubsystem/Interpreter.hs @@ -153,8 +153,10 @@ deleteTeamDomainImpl :: deleteTeamDomainImpl lusr tid domain = do void $ guardTeamAdminAccessWithTeamIdCheck (Just tid) lusr domainReg <- lookup domain >>= note EnterpriseLoginSubsystemErrorNotFound - unless (domainReg.authorizedTeam == Just tid) $ - throw EnterpriseLoginSubsystemOperationForbidden + case domainReg.settings of + Just (DomainForLocalTeam tid' Nothing) | tid' == tid -> pure () + Just (DomainForLocalTeam tid' (Just _)) | tid' == tid -> pure () + _ -> throw EnterpriseLoginSubsystemOperationForbidden delete domain getRegisteredDomainsImpl :: @@ -187,9 +189,15 @@ authorizeTeamImpl lusr domain (DomainOwnershipToken token) = do tid <- guardTeamAdminAccess lusr mDomainReg <- lookup domain domainReg <- checkDomainOwnership mDomainReg token - when (domainReg.domainRedirect == Locked) $ - throw EnterpriseLoginSubsystemOperationForbidden - upsert domainReg {authorizedTeam = Just tid} + newSettings <- case domainReg.settings of + Nothing -> error "TODO: what does the spec say?" + Just DomainLocked -> throw EnterpriseLoginSubsystemOperationForbidden + Just same@DomainPreAuthorized -> pure same + Just same@DomainNoRegistration -> pure same + Just same@(DomainForBackend url) -> pure same + Just (DomainForLocalTeam _oldTid Nothing) -> pure (DomainForLocalTeam tid Nothing) + Just (DomainForLocalTeam _oldTid (Just idpid)) -> pure (DomainForLocalTeam tid (Just idpid)) + upsert domainReg {settings = Just newSettings} checkDomainOwnership :: (Member (Error EnterpriseLoginSubsystemError) r) => Maybe DomainRegistration -> Token -> Sem r DomainRegistration checkDomainOwnership mDomainReg ownershipToken = do @@ -285,14 +293,14 @@ unauthorizeImpl :: Sem r () unauthorizeImpl domain = do old <- lookupOrThrow domain - let new = old {domainRedirect = None} :: DomainRegistration - case old.domainRedirect of - PreAuthorized -> audit old new *> upsert new - Backend _ -> audit old new *> upsert new - NoRegistration -> audit old new *> upsert new - None -> pure () - Locked -> throw EnterpriseLoginSubsystemOperationForbidden - SSO _ -> throw EnterpriseLoginSubsystemOperationForbidden + let new = old {settings = Nothing} :: DomainRegistration + case old.settings of + Nothing -> pure () + Just DomainLocked -> throw EnterpriseLoginSubsystemOperationForbidden + Just DomainPreAuthorized -> audit old new *> upsert new + Just DomainNoRegistration -> audit old new *> upsert new + Just (DomainForBackend _) -> audit old new *> upsert new + Just (DomainForLocalTeam _ _) -> throw EnterpriseLoginSubsystemOperationForbidden where audit :: DomainRegistration -> DomainRegistration -> Sem r () audit old new = sendAuditMail url "Domain unauthorized" (Just old) (Just new) @@ -318,8 +326,9 @@ updateDomainRegistrationImpl domain update = do validate update mOld <- lookup domain let old = fromMaybe (def domain) mOld - new = - old {teamInvite = update.teamInvite, domainRedirect = update.domainRedirect} :: DomainRegistration + new <- + let err _ = throw $ EnterpriseLoginSubsystemInvalidDomainUpdate (show (domain, update)) + in either err pure (domainRegistrationFromUpdate old update) audit mOld new *> upsert new where audit :: Maybe DomainRegistration -> DomainRegistration -> Sem r () @@ -330,6 +339,9 @@ updateDomainRegistrationImpl domain update = do "PUT /i/domain-registration/" <> fromText (domainText domain) +-- TODO: this changes behavior in this PR: Locked overwrites all other data stored in +-- DomainRegistrationSettings, like authorized team or sso code. is this what we want? (i +-- think it is and we do!) lockDomainImpl :: forall r. ( Member DomainRegistrationStore r, @@ -341,7 +353,7 @@ lockDomainImpl :: Sem r () lockDomainImpl domain = do mOld <- lookup domain - let new = (def domain) {domainRedirect = Locked} :: DomainRegistration + let new = (def domain) {settings = Just DomainLocked} :: DomainRegistration audit mOld new *> upsert new where url :: Builder @@ -353,6 +365,7 @@ lockDomainImpl domain = do audit :: Maybe DomainRegistration -> DomainRegistration -> Sem r () audit old new = sendAuditMail url "Domain locked" old (Just new) +-- TODO: overwrites all previous settings unlockDomainImpl :: forall r. ( Member DomainRegistrationStore r, @@ -365,10 +378,10 @@ unlockDomainImpl :: Sem r () unlockDomainImpl domain = do old <- lookupOrThrow domain - let new = old {domainRedirect = None} :: DomainRegistration - case old.domainRedirect of - Locked -> audit old new *> upsert new - _ -> throw EnterpriseLoginSubsystemOperationForbidden + let new = old {settings = Nothing} :: DomainRegistration + case old.settings of + Just DomainLocked -> audit old new *> upsert new + _ -> throw EnterpriseLoginSubsystemOperationForbidden -- TODO: why not do nothing? where url :: Builder url = @@ -379,6 +392,7 @@ unlockDomainImpl domain = do audit :: DomainRegistration -> DomainRegistration -> Sem r () audit old new = sendAuditMail url "Domain locked" (Just old) (Just new) +-- TODO: overwrites all previous settings preAuthorizeImpl :: forall r. ( Member DomainRegistrationStore r, @@ -392,10 +406,10 @@ preAuthorizeImpl :: preAuthorizeImpl domain = do mOld <- lookup domain let old = fromMaybe (def domain) mOld - new = old {domainRedirect = PreAuthorized} :: DomainRegistration - case old.domainRedirect of - PreAuthorized -> pure () - None -> audit mOld new *> upsert new + new = old {settings = Just DomainPreAuthorized} :: DomainRegistration + case old.settings of + Just DomainPreAuthorized -> pure () + Nothing -> audit mOld new *> upsert new _ -> throw $ EnterpriseLoginSubsystemOperationForbidden where url :: Builder @@ -578,13 +592,16 @@ updateTeamInviteImpl luid domain config = do tid <- guardTeamAdminAccess luid mbDomainReg <- lookup domain domainReg <- note EnterpriseLoginSubsystemOperationForbidden mbDomainReg - unless (domainReg.authorizedTeam == Just tid) $ - throw EnterpriseLoginSubsystemOperationForbidden + case domainReg.settings of + Just (DomainForLocalTeam tid' _) | tid' == tid -> throw EnterpriseLoginSubsystemOperationForbidden + _ -> pure () update <- validateUpdate tid domainReg config updateDomainRegistrationImpl domain update where validateUpdate :: TeamId -> DomainRegistration -> TeamInviteConfig -> Sem r DomainRegistrationUpdate validateUpdate tid domReg conf = do + -- TODO: remove this function, validation should happen near declarations of + -- DomainRegistration, DomainRegistrationUpdate when (domReg.domainRedirect == Locked) $ throw EnterpriseLoginSubsystemOperationForbidden when (isJust $ domReg.domainRedirect ^? _Backend) $ diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index b23dc96b1c9..d2354ee375d 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -221,13 +221,14 @@ guardRegisterUserImpl email = do -- `Domain`. & either (throwGuardFailed . InvalidDomain) DRS.lookup for_ mReg $ \reg -> do - case reg.domainRedirect of - None -> pure () - Locked -> pure () - SSO _ -> throwGuardFailed DomRedirSetToSSO - Backend _ -> throwGuardFailed DomRedirSetToBackend - NoRegistration -> throwGuardFailed DomRedirSetToNoRegistration - PreAuthorized -> pure () + case reg.settings of + Nothing -> pure () + Just DomainLocked -> pure () + Just DomainPreAuthorized -> pure () + Just DomainNoRegistration -> throwGuardFailed DomRedirSetToNoRegistration + Just (DomainForBackend _) -> throwGuardFailed DomRedirSetToBackend + Just (DomainForLocalTeam _ Nothing) -> throwGuardFailed TeamInviteRestrictedToOtherTeam + Just (DomainForLocalTeam _ (Just _)) -> throwGuardFailed DomRedirSetToSSO isBlockedImpl :: (Member BlockListStore r) => EmailAddress -> Sem r Bool isBlockedImpl = BlockList.exists . mkEmailKey