Skip to content

Commit dffa163

Browse files
akshaymankarfisx
andauthored
Brig: Servantify POST /register and POST /i/users endpoint (#2121)
Noteworthy things: - I servantified `POST /i/users` because it had pretty much the same API and was using the same types. - While registering a user into a team, if the team has legal hold, the user can only be added if the fanoutLimit has not been reached. This check happens in galley, which generates a `Wai.Error`, brig just throws this error and so isn't aware of internals. This kind of error cannot be transformed into and `ErrorDescription`. So, for now (until galley's internal API is servantiified), brig throws this error using `throwM`, this error gets caught and handled by the `Handler` monad when it executes requests. This is of course not ideal. But hopefully we get to servantify Galley's internal endpoints and we can get rid of this hack. - The parser for `NewUser` is fairly complex some fields depend on other fields. To help with this, I created `NewUserRaw` which just parses individual fields. The schema for `NewUser` type uses `withParser` to do the validation and make composite fields. - Remove ToJSON/FromJSON for UserIdentity, use Schema for User. The ToJSON for UserIdentity encoded nulls, but every other object which used it didn't encode nulls. So, it was better to remove it. Co-authored-by: Matthias Fischmann <[email protected]>
1 parent ad40381 commit dffa163

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+660
-762
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Servantify `POST /register` and `POST /i/users` endpoints

libs/wire-api/package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ tests:
123123
- pretty
124124
- proto-lens
125125
- QuickCheck
126+
- schema-profunctor
126127
- string-conversions
127128
- swagger2
128129
- tasty

libs/wire-api/src/Wire/API/ErrorDescription.hs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,29 @@ type MLSIdentityMismatch =
387387
403
388388
"mls-identity-mismatch"
389389
"Prekey credential does not match qualified client ID"
390+
391+
type WhitelistError = ErrorDescription 403 "unauthorized" "Unauthorized e-mail address or phone number."
392+
393+
type InvalidInvitationCode = ErrorDescription 400 "invalid-invitation-code" "Invalid invitation code."
394+
395+
type MissingIdentity = ErrorDescription 403 "missing-identity" "Using an invitation code requires registering the given email and/or phone."
396+
397+
type BlacklistedEmail =
398+
ErrorDescription
399+
403
400+
"blacklisted-email"
401+
"The given e-mail address has been blacklisted due to a permanent bounce \
402+
\or a complaint."
403+
404+
type InvalidEmail = ErrorDescription 400 "invalid-email" "Invalid e-mail address."
405+
406+
type InvalidActivationCode msg = ErrorDescription 404 "invalid-code" msg
407+
408+
type InvalidActivationCodeWrongUser = InvalidActivationCode "User does not exist."
409+
410+
type InvalidActivationCodeWrongCode = InvalidActivationCode "Invalid activation code"
411+
412+
type TooManyTeamMembers = ErrorDescription 403 "too-many-team-members" "Too many members in this team."
413+
414+
-- | docs/reference/user/registration.md {#RefRestrictRegistration}.
415+
type UserCreationRestricted = ErrorDescription 403 "user-creation-restricted" "This instance does not allow creation of personal users or teams."

libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
module Wire.API.Routes.Internal.Brig
1919
( API,
20+
EJPD_API,
21+
AccountAPI,
2022
EJPDRequest,
2123
GetAccountFeatureConfig,
2224
PutAccountFeatureConfig,
@@ -39,7 +41,10 @@ import Servant.Swagger.UI
3941
import Wire.API.Connection
4042
import Wire.API.Routes.Internal.Brig.Connection
4143
import Wire.API.Routes.Internal.Brig.EJPD
44+
import Wire.API.Routes.MultiVerb
45+
import Wire.API.Routes.Named
4246
import qualified Wire.API.Team.Feature as ApiFt
47+
import Wire.API.User
4348

4449
type EJPDRequest =
4550
Summary
@@ -109,15 +114,29 @@ type GetAllConnections =
109114
:> ReqBody '[Servant.JSON] ConnectionsStatusRequestV2
110115
:> Post '[Servant.JSON] [ConnectionStatusV2]
111116

117+
type EJPD_API =
118+
( EJPDRequest
119+
:<|> GetAccountFeatureConfig
120+
:<|> PutAccountFeatureConfig
121+
:<|> DeleteAccountFeatureConfig
122+
:<|> GetAllConnectionsUnqualified
123+
:<|> GetAllConnections
124+
)
125+
126+
type AccountAPI =
127+
-- This endpoint can lead to the following events being sent:
128+
-- - UserActivated event to created user, if it is a team invitation or user has an SSO ID
129+
-- - UserIdentityUpdated event to created user, if email or phone get activated
130+
Named
131+
"createUserNoVerify"
132+
( "users"
133+
:> ReqBody '[Servant.JSON] NewUser
134+
:> MultiVerb 'POST '[Servant.JSON] RegisterInternalResponses (Either RegisterError SelfProfile)
135+
)
136+
112137
type API =
113138
"i"
114-
:> ( EJPDRequest
115-
:<|> GetAccountFeatureConfig
116-
:<|> PutAccountFeatureConfig
117-
:<|> DeleteAccountFeatureConfig
118-
:<|> GetAllConnectionsUnqualified
119-
:<|> GetAllConnections
120-
)
139+
:> (EJPD_API :<|> AccountAPI)
121140

122141
type SwaggerDocsAPI = "api" :> "internal" :> SwaggerSchemaUI "swagger-ui" "swagger.json"
123142

libs/wire-api/src/Wire/API/Routes/Named.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Data.Proxy
2222
import GHC.TypeLits
2323
import Imports
2424
import Servant
25+
import Servant.Client
2526
import Servant.Swagger
2627

2728
newtype Named named x = Named {unnamed :: x}
@@ -40,6 +41,11 @@ instance HasServer api ctx => HasServer (Named name api) ctx where
4041
instance RoutesToPaths api => RoutesToPaths (Named name api) where
4142
getRoutes = getRoutes @api
4243

44+
instance HasClient m api => HasClient m (Named n api) where
45+
type Client m (Named n api) = Client m api
46+
clientWithRoute pm _ req = clientWithRoute pm (Proxy @api) req
47+
hoistClientMonad pm _ f = hoistClientMonad pm (Proxy @api) f
48+
4349
type family FindName n (api :: *) :: (n, *) where
4450
FindName n (Named name api) = '(name, api)
4551
FindName n (x :> api) = AddPrefix x (FindName n api)

libs/wire-api/src/Wire/API/Routes/Public/Brig.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,24 @@ type SelfAPI =
312312
:> MultiVerb 'PUT '[JSON] ChangeHandleResponses (Maybe ChangeHandleError)
313313
)
314314

315+
type AccountAPI =
316+
-- docs/reference/user/registration.md {#RefRegistration}
317+
--
318+
-- This endpoint can lead to the following events being sent:
319+
-- - UserActivated event to created user, if it is a team invitation or user has an SSO ID
320+
-- - UserIdentityUpdated event to created user, if email code or phone code is provided
321+
Named
322+
"register"
323+
( Summary "Register a new user."
324+
:> Description
325+
"If the environment where the registration takes \
326+
\place is private and a registered email address or phone \
327+
\number is not whitelisted, a 403 error is returned."
328+
:> "register"
329+
:> ReqBody '[JSON] NewUserPublic
330+
:> MultiVerb 'POST '[JSON] RegisterResponses (Either RegisterError RegisterSuccess)
331+
)
332+
315333
type PrekeyAPI =
316334
Named
317335
"get-users-prekeys-client-unqualified"
@@ -714,6 +732,7 @@ type MLSAPI = LiftNamed (ZLocalUser :> "mls" :> MLSKeyPackageAPI)
714732
type BrigAPI =
715733
UserAPI
716734
:<|> SelfAPI
735+
:<|> AccountAPI
717736
:<|> ClientAPI
718737
:<|> PrekeyAPI
719738
:<|> UserClientAPI

libs/wire-api/src/Wire/API/Swagger.hs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ models =
113113
Push.Token.modelPushTokenList,
114114
Team.modelTeam,
115115
Team.modelTeamList,
116-
Team.modelNewBindingTeam,
117116
Team.modelNewNonBindingTeam,
118117
Team.modelUpdateData,
119118
Team.modelTeamDelete,
@@ -141,7 +140,6 @@ models =
141140
Team.SearchVisibility.modelTeamSearchVisibility,
142141
User.modelUserIdList,
143142
User.modelUser,
144-
User.modelNewUser,
145143
User.modelEmailUpdate,
146144
User.modelDelete,
147145
User.modelVerifyDelete,

libs/wire-api/src/Wire/API/Team.hs

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module Wire.API.Team
3939

4040
-- * NewTeam
4141
BindingNewTeam (..),
42+
bindingNewTeamObjectSchema,
4243
NonBindingNewTeam (..),
4344
NewTeam (..),
4445
newNewTeam,
@@ -62,7 +63,6 @@ module Wire.API.Team
6263
-- * Swagger
6364
modelTeam,
6465
modelTeamList,
65-
modelNewBindingTeam,
6666
modelNewNonBindingTeam,
6767
modelUpdateData,
6868
modelTeamDelete,
@@ -181,24 +181,14 @@ newtype BindingNewTeam = BindingNewTeam (NewTeam ())
181181
deriving stock (Eq, Show, Generic)
182182
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema BindingNewTeam)
183183

184-
modelNewBindingTeam :: Doc.Model
185-
modelNewBindingTeam = Doc.defineModel "NewBindingTeam" $ do
186-
Doc.description "Required data when creating new teams"
187-
Doc.property "name" Doc.string' $
188-
Doc.description "team name"
189-
Doc.property "icon" Doc.string' $
190-
Doc.description "team icon (asset ID)"
191-
Doc.property "icon_key" Doc.string' $ do
192-
Doc.description "team icon asset key"
193-
Doc.optional
194-
195184
instance ToSchema BindingNewTeam where
196-
schema = BindingNewTeam <$> unwrap .= newTeamSchema "BindingNewTeam" sch
197-
where
198-
unwrap (BindingNewTeam nt) = nt
185+
schema = object "BindingNewTeam" bindingNewTeamObjectSchema
199186

200-
sch :: ValueSchema SwaggerDoc ()
201-
sch = null_
187+
bindingNewTeamObjectSchema :: ObjectSchema SwaggerDoc BindingNewTeam
188+
bindingNewTeamObjectSchema =
189+
BindingNewTeam <$> unwrap .= newTeamObjectSchema null_
190+
where
191+
unwrap (BindingNewTeam nt) = nt
202192

203193
-- FUTUREWORK: since new team members do not get serialized, we zero them here.
204194
-- it may be worth looking into how this can be solved in the types.
@@ -214,7 +204,10 @@ newtype NonBindingNewTeam = NonBindingNewTeam (NewTeam (Range 1 127 [TeamMember]
214204
deriving (FromJSON, ToJSON, S.ToSchema) via (Schema NonBindingNewTeam)
215205

216206
instance ToSchema NonBindingNewTeam where
217-
schema = NonBindingNewTeam <$> unwrap .= newTeamSchema "NonBindingNewTeam" sch
207+
schema =
208+
object "NonBindingNewTeam" $
209+
NonBindingNewTeam
210+
<$> unwrap .= newTeamObjectSchema sch
218211
where
219212
unwrap (NonBindingNewTeam nt) = nt
220213

@@ -247,14 +240,13 @@ data NewTeam a = NewTeam
247240
newNewTeam :: Range 1 256 Text -> Range 1 256 Text -> NewTeam a
248241
newNewTeam nme ico = NewTeam nme ico Nothing Nothing
249242

250-
newTeamSchema :: HasSchemaRef d => Text -> ValueSchema d a -> ValueSchema NamedSwaggerDoc (NewTeam a)
251-
newTeamSchema name sch =
252-
object name $
253-
NewTeam
254-
<$> _newTeamName .= field "name" schema
255-
<*> _newTeamIcon .= field "icon" schema
256-
<*> _newTeamIconKey .= maybe_ (optField "icon_key" schema)
257-
<*> _newTeamMembers .= maybe_ (optField "members" sch)
243+
newTeamObjectSchema :: ValueSchema SwaggerDoc a -> ObjectSchema SwaggerDoc (NewTeam a)
244+
newTeamObjectSchema sch =
245+
NewTeam
246+
<$> _newTeamName .= field "name" schema
247+
<*> _newTeamIcon .= field "icon" schema
248+
<*> _newTeamIconKey .= maybe_ (optField "icon_key" schema)
249+
<*> _newTeamMembers .= maybe_ (optField "members" sch)
258250

259251
--------------------------------------------------------------------------------
260252
-- TeamUpdateData

0 commit comments

Comments
 (0)