Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In (the unlikely) case your server config file contains `setWhitelist:`, you need to change this before the upgrade! It used to refer to a whitelisting service, which is now replaced with a local list of allowed domains and phone numbers. See [docs](https://docs.wire.com/developer/reference/user/activation.html?highlight=whitelist#phone-email-whitelist) for details. Migration path: add new config fields; upgrade, remove old config fields.
7 changes: 5 additions & 2 deletions charts/brig/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,11 @@ data:
{{- if .setSftListAllServers }}
setSftListAllServers: {{ .setSftListAllServers }}
{{- end }}
{{- if .setWhitelist }}
setWhitelist: {{ toYaml .setWhitelist | nindent 8 }}
{{- if .setAllowlistEmailDomains }}
setAllowlistEmailDomains: {{ toYaml .setAllowlistEmailDomains | nindent 8 }}
{{- end }}
{{- if .setAllowlistPhonePrefixes }}
setAllowlistPhonePrefixes: {{ toYaml .setAllowlistPhonePrefixes | nindent 8 }}
{{- end }}
{{- if .setFeatureFlags }}
setFeatureFlags: {{ toYaml .setFeatureFlags | nindent 8 }}
Expand Down
9 changes: 5 additions & 4 deletions docs/src/developer/developer/pr-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ See `docs/legacy/developer/changelog.md` for more information.

## Schema migrations

Don't delete columns that are still used by versions that are deployed. If you delete columns then the old version will fail in the deployment process. Rather than deleting keep the unused columns around and comment them as being discontinued in the schema migration code.
Don't delete columns that are still used by versions that are deployed. If you delete columns then the old version will fail in the deployment process. Rather than deleting keep the unused columns around and comment them as being discontinued in the schema migration code.

If a cassandra schema migration has been added then add this to the checklist:

Expand Down Expand Up @@ -84,7 +84,7 @@ If a PR adds new configuration options for say brig, the following files need to
* [ ] The integration test config: `services/brig/brig.integration.yaml`
* [ ] The charts: `charts/brig/templates/configmap.yaml`
* [ ] The default values: `charts/brig/values.yaml`
* [ ] The values files for CI: `hack/helm_vars/wire-server/values.yaml`
* [ ] The values files for CI: `hack/helm_vars/wire-server/values.yaml.gotmpl`
* [ ] The configuration docs: `docs/src/developer/reference/config-options.md`

If any new configuration value is required and has no default, then:
Expand All @@ -100,8 +100,9 @@ Remove them with the PR from wire-server `./charts` folder, as charts are linked

### Renaming configuration flags

Avoid doing this. If you must, see Removing/adding sections above. But please note that all people who have an installation of wire also may have overridden any of the configuration option you may wish to change the name of. As this is not type checked, it's very error prone and people may find themselves with default configuration values being used instead of their intended configuration settings. Guideline: only rename for good reasons, not for aesthetics; or be prepared to spend a significant
amount on documenting and communication about this change.
Avoid doing this, it's usually viable to introduce an at-least-equally-good name and remove the old one, that admins can first add the new options, then uprade the software, then remove the old ones.

If you must, see Removing/adding sections above. But please note that all people who have an installation of wire also may have overridden any of the configuration option you may wish to change the name of. As this is not type checked, it's very error prone and people may find themselves with default configuration values being used instead of their intended configuration settings. Guideline: only rename for good reasons, not for aesthetics; or be prepared to spend a significant amount on documenting and communication about this change.

## Changes to developer workflow

Expand Down
33 changes: 17 additions & 16 deletions docs/src/developer/reference/user/activation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A user is called _activated_ they have a verified identity -- e.g. a phone numbe

A user that has been provisioned via single sign-on is always considered to be activated.

## Activated vs. non-activated users
## Activated vs. non-activated users
(RefActivationBenefits)=

Non-activated users can not [connect](connection.md) to others, nor can connection requests be made to anonymous accounts from verified accounts. As a result:
Expand All @@ -19,10 +19,10 @@ Non-activated users can not [connect](connection.md) to others, nor can connecti

The only flow where it makes sense for non-activated users to exist is the [wireless flow](RefRegistrationWireless) used for [guest rooms](https://wire.com/en/features/encrypted-guest-rooms/)

## API
## API
(RefActivationApi)=

### Requesting an activation code
### Requesting an activation code
(RefActivationRequest)=

During the [standard registration flow](RefRegistrationStandard), the user submits an email address or phone number by making a request to `POST /activate/send`. A six-digit activation code will be sent to that email address / phone number. Sample request and response:
Expand All @@ -44,7 +44,7 @@ The user can submit the activation code during registration to prove that they o

The same `POST /activate/send` endpoint can be used to re-request an activation code. Please use this ability sparingly! To avoid unnecessary activation code requests, users should be warned that it might take up to a few minutes for an email or text message to arrive.

### Activating an existing account
### Activating an existing account
(RefActivationSubmit)=

If the account [has not been activated during verification](RefRegistrationNoPreverification), it can be activated afterwards by submitting an activation code to `POST /activate`. Sample request and response:
Expand Down Expand Up @@ -80,7 +80,7 @@ If the email or phone has been verified already, `POST /activate` will return st

There is a maximum of 3 activation attempts per activation code. On the third failed attempt the code is invalidated and a new one must be requested.

### Activation event
### Activation event
(RefActivationEvent)=

When the user becomes activated, they receive an event:
Expand All @@ -92,7 +92,7 @@ When the user becomes activated, they receive an event:
}
```

### Detecting activation in the self profile
### Detecting activation in the self profile
(RefActivationProfile)=

In addition to the [activation event](RefActivationEvent), activation can be detected by polling the self profile:
Expand All @@ -114,7 +114,7 @@ GET /self

If the profile includes `"email"` or `"phone"`, the account is activated.

## Automating activation via email
## Automating activation via email
(RefActivationEmailHeaders)=

Our email verification messages contain headers that can be used to automate the activation process.
Expand All @@ -134,20 +134,23 @@ X-Zeta-Key: ...
X-Zeta-Code: 123456
```

## Phone/email whitelist
## Phone/email whitelist
(RefActivationWhitelist)=

The backend can be configured to only allow specific phone numbers or email addresses to register. The following options have to be set in `brig.yaml`:
The backend can be configured to only allow specific phone number prefixes and email address domains to register. The following options have to be set in `brig.yaml`:

```yaml
optSettings:
setWhitelist:
whitelistUrl: ... # Checker URL
whitelistUser: ... # Basic auth username
whitelistPass: ... # Basic auth password
setAllowlistEmailDomains:
- wire.com
- example.com
- notagoodexample.com
setAllowlistPhonePrefixes:
- +49
- +1555555
```

When those options are present, the backend will do a GET request at `<whitelistUrl>?email=...` or `<whitelistUrl>?mobile=...` for every activation request it receives. It will expect either status code 200 ("everything good") or 404 ("provided email/phone is not on the whitelist").
When those options are present, the backend will match every activation request against these lists.

If an email address or phone number are rejected by the whitelist, `POST /activate/send` or `POST /register` will return `403 Forbidden`:

Expand All @@ -158,5 +161,3 @@ If an email address or phone number are rejected by the whitelist, `POST /activa
"message": "Unauthorized e-mail address or phone number."
}
```

Currently emails at `@wire.com` are always considered whitelisted, regardless of the whitelist service's response.
4 changes: 4 additions & 0 deletions services/brig/brig.integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ optSettings:
setDpopTokenExpirationTimeSecs: 300 # 5 minutes
setPublicKeyBundle: test/resources/jwt/ed25519_bundle.pem
setEnableMLS: true
setAllowlistEmailDomains:
- wire.com
setAllowlistPhonePrefixes:
- +1555555

logLevel: Warn
# ^ NOTE: We log too much in brig, if we set this to Info like other services, running tests
Expand Down
14 changes: 6 additions & 8 deletions services/brig/src/Brig/API/Handler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ module Brig.API.Handler
)
where

import Bilge (MonadHttp, RequestId (..))
import Bilge (RequestId (..))
import Brig.API.Error
import qualified Brig.AWS as AWS
import Brig.App
import Brig.CanonicalInterpreter (BrigCanonicalEffects, runBrigToIO)
import Brig.Email (Email)
import Brig.Options (setWhitelist)
import Brig.Options (setAllowlistEmailDomains, setAllowlistPhonePrefixes)
import Brig.Phone (Phone, PhoneException (..))
import qualified Brig.Whitelist as Whitelist

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.

Maybe worth going all in and changing terms everywhere? :)

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.

oh man! :)

cd42e8d

also 8c1626b

what else did i miss?

import Control.Error
Expand Down Expand Up @@ -171,14 +171,12 @@ parseJsonBody req = parseBody req !>> StdError . badRequest
checkWhitelist :: Either Email Phone -> (Handler r) ()
checkWhitelist = wrapHttpClientE . checkWhitelistWithError (StdError whitelistError)

checkWhitelistWithError :: (Monad m, MonadReader Env m, MonadIO m, Catch.MonadMask m, MonadHttp m, MonadError e m) => e -> Either Email Phone -> m ()
checkWhitelistWithError :: (MonadReader Env m, MonadError e m) => e -> Either Email Phone -> m ()
checkWhitelistWithError e key = do
ok <- isWhiteListed key
unless ok (throwError e)

isWhiteListed :: (Monad m, MonadReader Env m, MonadIO m, Catch.MonadMask m, MonadHttp m) => Either Email Phone -> m Bool
isWhiteListed :: (MonadReader Env m) => Either Email Phone -> m Bool
isWhiteListed key = do
eb <- setWhitelist <$> view settings
case eb of
Nothing -> pure True
Just b -> Whitelist.verify b key
env <- view settings
pure $ Whitelist.verify (setAllowlistEmailDomains env) (setAllowlistPhonePrefixes env) key
5 changes: 3 additions & 2 deletions services/brig/src/Brig/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module Brig.Options where
import Brig.Queue.Types (Queue (..))
import Brig.SMTP (SMTPConnType (..))
import Brig.User.Auth.Cookie.Limit
import Brig.Whitelist (Whitelist (..))
import Brig.Whitelist (AllowlistEmailDomains (..), AllowlistPhonePrefixes (..))
import qualified Brig.ZAuth as ZAuth
import Control.Applicative
import qualified Control.Lens as Lens
Expand Down Expand Up @@ -492,7 +492,8 @@ data Settings = Settings
-- | STOMP broker credentials
setStomp :: !(Maybe FilePathSecrets),
-- | Whitelist of allowed emails/phones
setWhitelist :: !(Maybe Whitelist),
setAllowlistEmailDomains :: !(Maybe AllowlistEmailDomains),
setAllowlistPhonePrefixes :: !(Maybe AllowlistPhonePrefixes),
-- | Max. number of sent/accepted
-- connections per user
setUserMaxConnections :: !Int64,
Expand Down
62 changes: 17 additions & 45 deletions services/brig/src/Brig/Whitelist.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,60 +19,32 @@
--
-- Email/phone whitelist.
module Brig.Whitelist
( Whitelist (..),
( AllowlistEmailDomains (..),
AllowlistPhonePrefixes (..),
verify,
)
where

import Bilge.IO
import Bilge.Request
import Bilge.Response
import Bilge.Retry
import Control.Monad.Catch (MonadMask, throwM)
import Control.Retry
import Data.Aeson
import Data.Text
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Text as Text
import Imports
import Network.HTTP.Client (HttpExceptionContent (..))
import Wire.API.User.Identity

-- | A service providing a whitelist of allowed email addresses and phone numbers
data Whitelist = Whitelist
{ -- | Service URL
whitelistUrl :: !Text,
-- | Username
whitelistUser :: !Text,
-- | Password
whitelistPass :: !Text
}
data AllowlistEmailDomains = AllowlistEmailDomains [Text]
deriving (Show, Generic)

instance FromJSON Whitelist
instance FromJSON AllowlistEmailDomains

-- | Do a request to the whitelist service and verify that the provided email/phone address is
-- whitelisted.
verify :: (MonadIO m, MonadMask m, MonadHttp m) => Whitelist -> Either Email Phone -> m Bool
verify (Whitelist url user pass) key =
if isKnownDomain key
then pure True
else recovering x3 httpHandlers . const $ do
rq <- parseRequest $ unpack url
rsp <- get' rq $ req (encodeUtf8 user) (encodeUtf8 pass)
case statusCode rsp of
200 -> pure True
404 -> pure False
_ ->
throwM $
HttpExceptionRequest rq (StatusCodeException (rsp {responseBody = ()}) mempty)
where
isKnownDomain (Left e) = emailDomain e == "wire.com"
isKnownDomain _ = False
urlEmail = queryItem "email" . encodeUtf8 . fromEmail
urlPhone = queryItem "mobile" . encodeUtf8 . fromPhone
req u p =
port 443
. secure
. either urlEmail urlPhone key
. applyBasicAuth u p
x3 = limitRetries 3 <> exponentialBackoff 100000
data AllowlistPhonePrefixes = AllowlistPhonePrefixes [Text]
deriving (Show, Generic)

instance FromJSON AllowlistPhonePrefixes

-- | Consult the whitelist settings in brig's config file and verify that the provided
-- email/phone address is whitelisted.
verify :: Maybe AllowlistEmailDomains -> Maybe AllowlistPhonePrefixes -> Either Email Phone -> Bool
verify (Just (AllowlistEmailDomains allowed)) _ (Left email) = emailDomain email `elem` allowed
verify _ (Just (AllowlistPhonePrefixes allowed)) (Right phone) = any (`Text.isPrefixOf` fromPhone phone) allowed
verify Nothing _ (Left _) = True
verify _ Nothing (Right _) = True