diff --git a/changelog.d/5-internal/brig-named-routes b/changelog.d/5-internal/brig-named-routes new file mode 100644 index 0000000000..1d7179a904 --- /dev/null +++ b/changelog.d/5-internal/brig-named-routes @@ -0,0 +1 @@ +Remove uses of servant-generics from brig diff --git a/libs/wire-api/src/Wire/API/ErrorDescription.hs b/libs/wire-api/src/Wire/API/ErrorDescription.hs index 444e131a02..99e5fbe2a9 100644 --- a/libs/wire-api/src/Wire/API/ErrorDescription.hs +++ b/libs/wire-api/src/Wire/API/ErrorDescription.hs @@ -341,7 +341,7 @@ type HandleManagedByScim = ErrorDescription 403 "managed-by-scim" "Updating hand type InvalidPhone = ErrorDescription 400 "invalid-phone" "Invalid mobile phone number" -type UserKeyExists = ErrorDescription 409 "key-exists" "The give e-mail address or phone number is in use." +type UserKeyExists = ErrorDescription 409 "key-exists" "The given e-mail address or phone number is in use." type BlacklistedPhone = ErrorDescription 403 "blacklisted-phone" "The given phone number has been blacklisted due to suspected abuse or a complaint." diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs index 02adeba83d..525b3aabf9 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE DerivingVia #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- This file is part of the Wire Server implementation. @@ -33,12 +32,12 @@ import Data.Swagger hiding (Contact, Header) import Imports hiding (head) import Servant (JSON) import Servant hiding (Handler, JSON, addHeader, respond) -import Servant.API.Generic import Servant.Swagger (HasSwagger (toSwagger)) import Servant.Swagger.Internal.Orphans () import Wire.API.Connection import Wire.API.ErrorDescription import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named import Wire.API.Routes.Public (ZConn, ZUser) import Wire.API.Routes.Public.Util import Wire.API.Routes.QualifiedCapture @@ -89,303 +88,293 @@ instance AsUnion DeleteSelfResponses (Maybe Timeout) where type ConnectionUpdateResponses = UpdateResponses "Connection unchanged" "Connection updated" UserConnection -data Api routes = Api - { -- See Note [ephemeral user sideeffect] - getUserUnqualified :: - routes - :- Summary "Get a user by UserId (deprecated)" +type UserAPI = + -- See Note [ephemeral user sideeffect] + Named + "get-user-unqualified" + ( Summary "Get a user by UserId (deprecated)" :> ZUser :> "users" :> CaptureUserId "uid" - :> GetUserVerb, + :> GetUserVerb + ) + :<|> -- See Note [ephemeral user sideeffect] - getUserQualified :: - routes - :- Summary "Get a user by Domain and UserId" - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> GetUserVerb, - getSelf :: - routes - :- Summary "Get your own profile" + Named + "get-user-qualified" + ( Summary "Get a user by Domain and UserId" + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> GetUserVerb + ) + :<|> Named + "update-user-email" + ( Summary "Resend email address validation email." + :> Description "If the user has a pending email validation, the validation email will be resent." + :> ZUser + :> "users" + :> CaptureUserId "uid" + :> "email" + :> ReqBody '[JSON] EmailUpdate + :> Put '[JSON] () + ) + :<|> Named + "get-handle-info-unqualified" + ( Summary "(deprecated, use /search/contacts) Get information on a user handle" + :> ZUser + :> "users" + :> "handles" + :> Capture' '[Description "The user handle"] "handle" Handle + :> MultiVerb + 'GET + '[JSON] + '[ HandleNotFound, + Respond 200 "User found" UserHandleInfo + ] + (Maybe UserHandleInfo) + ) + :<|> Named + "get-user-by-handle-qualified" + ( Summary "(deprecated, use /search/contacts) Get information on a user handle" + :> ZUser + :> "users" + :> "by-handle" + :> QualifiedCapture' '[Description "The user handle"] "handle" Handle + :> MultiVerb + 'GET + '[JSON] + '[ HandleNotFound, + Respond 200 "User found" UserProfile + ] + (Maybe UserProfile) + ) + :<|> + -- See Note [ephemeral user sideeffect] + Named + "list-users-by-unqualified-ids-or-handles" + ( Summary "List users (deprecated)" + :> Description "The 'ids' and 'handles' parameters are mutually exclusive." + :> ZUser + :> "users" + :> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId) + :> QueryParam' [Optional, Strict, Description "Handles of users to fetch, min 1 and max 4 (the check for handles is rather expensive)"] "handles" (Range 1 4 (CommaSeparatedList Handle)) + :> Get '[JSON] [UserProfile] + ) + :<|> + -- See Note [ephemeral user sideeffect] + Named + "list-users-by-ids-or-handles" + ( Summary "List users" + :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." + :> ZUser + :> "list-users" + :> ReqBody '[JSON] ListUsersQuery + :> Post '[JSON] [UserProfile] + ) + +type SelfAPI = + Named + "get-self" + ( Summary "Get your own profile" :> ZUser :> "self" - :> Get '[JSON] SelfProfile, + :> Get '[JSON] SelfProfile + ) + :<|> -- This endpoint can lead to the following events being sent: -- - UserDeleted event to contacts of self -- - MemberLeave event to members for all conversations the user was in (via galley) - deleteSelf :: - routes - :- Summary "Initiate account deletion." - :> Description - "if the account has a verified identity, a verification \ - \code is sent and needs to be confirmed to authorise the \ - \deletion. if the account has no verified identity but a \ - \password, it must be provided. if password is correct, or if neither \ - \a verified identity nor a password exists, account deletion \ - \is scheduled immediately." - :> CanThrow InvalidUser - :> CanThrow InvalidCode - :> CanThrow BadCredentials - :> CanThrow MissingAuth - :> CanThrow DeleteCodePending - :> CanThrow OwnerDeletingSelf - :> ZUser - :> "self" - :> ReqBody '[JSON] DeleteUser - :> MultiVerb 'DELETE '[JSON] DeleteSelfResponses (Maybe Timeout), + Named + "delete-self" + ( Summary "Initiate account deletion." + :> Description + "if the account has a verified identity, a verification \ + \code is sent and needs to be confirmed to authorise the \ + \deletion. if the account has no verified identity but a \ + \password, it must be provided. if password is correct, or if neither \ + \a verified identity nor a password exists, account deletion \ + \is scheduled immediately." + :> CanThrow InvalidUser + :> CanThrow InvalidCode + :> CanThrow BadCredentials + :> CanThrow MissingAuth + :> CanThrow DeleteCodePending + :> CanThrow OwnerDeletingSelf + :> ZUser + :> "self" + :> ReqBody '[JSON] DeleteUser + :> MultiVerb 'DELETE '[JSON] DeleteSelfResponses (Maybe Timeout) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - UserUpdated event to contacts of self - putSelf :: - routes - :- Summary "Update your profile." - :> ZUser - :> ZConn - :> "self" - :> ReqBody '[JSON] UserUpdate - :> MultiVerb 'PUT '[JSON] PutSelfResponses (Maybe UpdateProfileError), - changePhone :: - routes - :- Summary "Change your phone number." - :> ZUser - :> ZConn - :> "self" - :> "phone" - :> ReqBody '[JSON] PhoneUpdate - :> MultiVerb 'PUT '[JSON] ChangePhoneResponses (Maybe ChangePhoneError), + Named + "put-self" + ( Summary "Update your profile." + :> ZUser + :> ZConn + :> "self" + :> ReqBody '[JSON] UserUpdate + :> MultiVerb 'PUT '[JSON] PutSelfResponses (Maybe UpdateProfileError) + ) + :<|> Named + "change-phone" + ( Summary "Change your phone number." + :> ZUser + :> ZConn + :> "self" + :> "phone" + :> ReqBody '[JSON] PhoneUpdate + :> MultiVerb 'PUT '[JSON] ChangePhoneResponses (Maybe ChangePhoneError) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - UserIdentityRemoved event to self - removePhone :: - routes - :- Summary "Remove your phone number." - :> Description - "Your phone number can only be removed if you also have an \ - \email address and a password." - :> ZUser - :> ZConn - :> "self" - :> "phone" - :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError), + Named + "remove-phone" + ( Summary "Remove your phone number." + :> Description + "Your phone number can only be removed if you also have an \ + \email address and a password." + :> ZUser + :> ZConn + :> "self" + :> "phone" + :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - UserIdentityRemoved event to self - removeEmail :: - routes - :- Summary "Remove your email address." - :> Description - "Your email address can only be removed if you also have a \ - \phone number." - :> ZUser - :> ZConn - :> "self" - :> "email" - :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError), - checkPasswordExists :: - routes - :- Summary "Check that your password is set." - :> ZUser - :> "self" - :> "password" - :> MultiVerb - 'HEAD - '() - '[ RespondEmpty 404 "Password is not set", - RespondEmpty 200 "Password is set" - ] - Bool, - changePassword :: - routes - :- Summary "Change your password." - :> ZUser - :> "self" - :> "password" - :> ReqBody '[JSON] PasswordChange - :> MultiVerb 'PUT '[JSON] ChangePasswordResponses (Maybe ChangePasswordError), - changeLocale :: - routes - :- Summary "Change your locale." - :> ZUser - :> ZConn - :> "self" - :> "locale" - :> ReqBody '[JSON] LocaleUpdate - :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Local Changed"] (), - changeHandle :: - routes - :- Summary "Change your handle." - :> ZUser - :> ZConn - :> "self" - :> "handle" - :> ReqBody '[JSON] HandleUpdate - :> MultiVerb 'PUT '[JSON] ChangeHandleResponses (Maybe ChangeHandleError), - updateUserEmail :: - routes - :- Summary "Resend email address validation email." - :> Description "If the user has a pending email validation, the validation email will be resent." - :> ZUser - :> "users" - :> CaptureUserId "uid" - :> "email" - :> ReqBody '[JSON] EmailUpdate - :> Put '[JSON] (), - getHandleInfoUnqualified :: - routes - :- Summary "(deprecated, use /search/contacts) Get information on a user handle" - :> ZUser - :> "users" - :> "handles" - :> Capture' '[Description "The user handle"] "handle" Handle - :> MultiVerb - 'GET - '[JSON] - '[ HandleNotFound, - Respond 200 "User found" UserHandleInfo - ] - (Maybe UserHandleInfo), - getUserByHandleQualified :: - routes - :- Summary "(deprecated, use /search/contacts) Get information on a user handle" - :> ZUser - :> "users" - :> "by-handle" - :> QualifiedCapture' '[Description "The user handle"] "handle" Handle - :> MultiVerb - 'GET - '[JSON] - '[ HandleNotFound, - Respond 200 "User found" UserProfile - ] - (Maybe UserProfile), - -- See Note [ephemeral user sideeffect] - listUsersByUnqualifiedIdsOrHandles :: - routes - :- Summary "List users (deprecated)" - :> Description "The 'ids' and 'handles' parameters are mutually exclusive." - :> ZUser - :> "users" - :> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId) - :> QueryParam' [Optional, Strict, Description "Handles of users to fetch, min 1 and max 4 (the check for handles is rather expensive)"] "handles" (Range 1 4 (CommaSeparatedList Handle)) - :> Get '[JSON] [UserProfile], - -- See Note [ephemeral user sideeffect] - listUsersByIdsOrHandles :: - routes - :- Summary "List users" - :> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive." - :> ZUser - :> "list-users" - :> ReqBody '[JSON] ListUsersQuery - :> Post '[JSON] [UserProfile], - getUserClientsUnqualified :: - routes - :- Summary "Get all of a user's clients (deprecated)." - :> "users" - :> CaptureUserId "uid" - :> "clients" - :> Get '[JSON] [PubClient], - getUserClientsQualified :: - routes - :- Summary "Get all of a user's clients." - :> "users" - :> QualifiedCaptureUserId "uid" - :> "clients" - :> Get '[JSON] [PubClient], - getUserClientUnqualified :: - routes - :- Summary "Get a specific client of a user (deprecated)." - :> "users" - :> CaptureUserId "uid" - :> "clients" - :> CaptureClientId "client" - :> Get '[JSON] PubClient, - getUserClientQualified :: - routes - :- Summary "Get a specific client of a user." - :> "users" - :> QualifiedCaptureUserId "uid" - :> "clients" - :> CaptureClientId "client" - :> Get '[JSON] PubClient, - listClientsBulk :: - routes - :- Summary "List all clients for a set of user ids (deprecated, use /users/list-clients/v2)" - :> ZUser - :> "users" - :> "list-clients" - :> ReqBody '[JSON] (Range 1 MaxUsersForListClientsBulk [Qualified UserId]) - :> Post '[JSON] (QualifiedUserMap (Set PubClient)), - listClientsBulkV2 :: - routes - :- Summary "List all clients for a set of user ids" - :> ZUser - :> "users" - :> "list-clients" - :> "v2" - :> ReqBody '[JSON] (LimitedQualifiedUserIdList MaxUsersForListClientsBulk) - :> Post '[JSON] (WrappedQualifiedUserMap (Set PubClient)), - getUsersPrekeysClientUnqualified :: - routes - :- Summary "(deprecated) Get a prekey for a specific client of a user." + Named + "remove-email" + ( Summary "Remove your email address." + :> Description + "Your email address can only be removed if you also have a \ + \phone number." + :> ZUser + :> ZConn + :> "self" + :> "email" + :> MultiVerb 'DELETE '[JSON] RemoveIdentityResponses (Maybe RemoveIdentityError) + ) + :<|> Named + "check-password-exists" + ( Summary "Check that your password is set." + :> ZUser + :> "self" + :> "password" + :> MultiVerb + 'HEAD + '() + '[ RespondEmpty 404 "Password is not set", + RespondEmpty 200 "Password is set" + ] + Bool + ) + :<|> Named + "change-password" + ( Summary "Change your password." + :> ZUser + :> "self" + :> "password" + :> ReqBody '[JSON] PasswordChange + :> MultiVerb 'PUT '[JSON] ChangePasswordResponses (Maybe ChangePasswordError) + ) + :<|> Named + "change-locale" + ( Summary "Change your locale." + :> ZUser + :> ZConn + :> "self" + :> "locale" + :> ReqBody '[JSON] LocaleUpdate + :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Local Changed"] () + ) + :<|> Named + "change-handle" + ( Summary "Change your handle." + :> ZUser + :> ZConn + :> "self" + :> "handle" + :> ReqBody '[JSON] HandleUpdate + :> MultiVerb 'PUT '[JSON] ChangeHandleResponses (Maybe ChangeHandleError) + ) + +type PrekeyAPI = + Named + "get-users-prekeys-client-unqualified" + ( Summary "(deprecated) Get a prekey for a specific client of a user." :> ZUser :> "users" :> CaptureUserId "uid" :> "prekeys" :> CaptureClientId "client" - :> Get '[JSON] ClientPrekey, - getUsersPrekeysClientQualified :: - routes - :- Summary "Get a prekey for a specific client of a user." - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> "prekeys" - :> CaptureClientId "client" - :> Get '[JSON] ClientPrekey, - getUsersPrekeyBundleUnqualified :: - routes - :- Summary "(deprecated) Get a prekey for each client of a user." - :> ZUser - :> "users" - :> CaptureUserId "uid" - :> "prekeys" - :> Get '[JSON] PrekeyBundle, - getUsersPrekeyBundleQualified :: - routes - :- Summary "Get a prekey for each client of a user." - :> ZUser - :> "users" - :> QualifiedCaptureUserId "uid" - :> "prekeys" - :> Get '[JSON] PrekeyBundle, - getMultiUserPrekeyBundleUnqualified :: - routes - :- Summary - "(deprecated) Given a map of user IDs to client IDs return a \ - \prekey for each one. You can't request information for more users than \ - \maximum conversation size." - :> ZUser - :> "users" - :> "prekeys" - :> ReqBody '[JSON] UserClients - :> Post '[JSON] UserClientPrekeyMap, - getMultiUserPrekeyBundleQualified :: - routes - :- Summary - "Given a map of domain to (map of user IDs to client IDs) return a \ - \prekey for each one. You can't request information for more users than \ - \maximum conversation size." - :> ZUser - :> "users" - :> "list-prekeys" - :> ReqBody '[JSON] QualifiedUserClients - :> Post '[JSON] QualifiedUserClientPrekeyMap, - -- User Client API ---------------------------------------------------- + :> Get '[JSON] ClientPrekey + ) + :<|> Named + "get-users-prekeys-client-qualified" + ( Summary "Get a prekey for a specific client of a user." + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> "prekeys" + :> CaptureClientId "client" + :> Get '[JSON] ClientPrekey + ) + :<|> Named + "get-users-prekey-bundle-unqualified" + ( Summary "(deprecated) Get a prekey for each client of a user." + :> ZUser + :> "users" + :> CaptureUserId "uid" + :> "prekeys" + :> Get '[JSON] PrekeyBundle + ) + :<|> Named + "get-users-prekey-bundle-qualified" + ( Summary "Get a prekey for each client of a user." + :> ZUser + :> "users" + :> QualifiedCaptureUserId "uid" + :> "prekeys" + :> Get '[JSON] PrekeyBundle + ) + :<|> Named + "get-multi-user-prekey-bundle-unqualified" + ( Summary + "(deprecated) Given a map of user IDs to client IDs return a \ + \prekey for each one. You can't request information for more users than \ + \maximum conversation size." + :> ZUser + :> "users" + :> "prekeys" + :> ReqBody '[JSON] UserClients + :> Post '[JSON] UserClientPrekeyMap + ) + :<|> Named + "get-multi-user-prekey-bundle-qualified" + ( Summary + "Given a map of domain to (map of user IDs to client IDs) return a \ + \prekey for each one. You can't request information for more users than \ + \maximum conversation size." + :> ZUser + :> "users" + :> "list-prekeys" + :> ReqBody '[JSON] QualifiedUserClients + :> Post '[JSON] QualifiedUserClientPrekeyMap + ) - -- This endpoint can lead to the following events being sent: - -- - ClientAdded event to self - -- - ClientRemoved event to self, if removing old clients due to max number - addClient :: - routes :- Summary "Register a new client" +type UserClientAPI = + -- User Client API ---------------------------------------------------- + + -- This endpoint can lead to the following events being sent: + -- - ClientAdded event to self + -- - ClientRemoved event to self, if removing old clients due to max number + Named + "add-client" + ( Summary "Register a new client" :> CanThrow TooManyClients :> CanThrow MissingAuth :> CanThrow MalformedPrekeys @@ -394,66 +383,138 @@ data Api routes = Api :> "clients" :> Header "X-Forwarded-For" IpAddr :> ReqBody '[JSON] NewClient - :> Verb 'POST 201 '[JSON] NewClientResponse, - updateClient :: - routes :- Summary "Update a registered client" - :> CanThrow MalformedPrekeys - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> ReqBody '[JSON] UpdateClient - :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Client updated"] (), + :> Verb 'POST 201 '[JSON] NewClientResponse + ) + :<|> Named + "update-client" + ( Summary "Update a registered client" + :> CanThrow MalformedPrekeys + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> ReqBody '[JSON] UpdateClient + :> MultiVerb 'PUT '[JSON] '[RespondEmpty 200 "Client updated"] () + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ClientRemoved event to self - deleteClient :: - routes :- Summary "Delete an existing client" - :> ZUser - :> ZConn - :> "clients" - :> CaptureClientId "client" - :> ReqBody '[JSON] RmClient - :> MultiVerb 'DELETE '[JSON] '[RespondEmpty 200 "Client deleted"] (), - listClients :: - routes :- Summary "List the registered clients" - :> ZUser - :> "clients" - :> Get '[JSON] [Client], - getClient :: - routes :- Summary "Get a registered client by ID" - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Client not found", - Respond 200 "Client found" Client - ] - (Maybe Client), - getClientCapabilities :: - routes :- Summary "Read back what the client has been posting about itself" - :> ZUser - :> "clients" - :> CaptureClientId "client" - :> "capabilities" - :> Get '[JSON] ClientCapabilityList, - getClientPrekeys :: - routes :- Summary "List the remaining prekey IDs of a client" - :> ZUser + Named + "delete-client" + ( Summary "Delete an existing client" + :> ZUser + :> ZConn + :> "clients" + :> CaptureClientId "client" + :> ReqBody '[JSON] RmClient + :> MultiVerb 'DELETE '[JSON] '[RespondEmpty 200 "Client deleted"] () + ) + :<|> Named + "list-clients" + ( Summary "List the registered clients" + :> ZUser + :> "clients" + :> Get '[JSON] [Client] + ) + :<|> Named + "get-client" + ( Summary "Get a registered client by ID" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Client not found", + Respond 200 "Client found" Client + ] + (Maybe Client) + ) + :<|> Named + "get-client-capabilities" + ( Summary "Read back what the client has been posting about itself" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> "capabilities" + :> Get '[JSON] ClientCapabilityList + ) + :<|> Named + "get-client-prekeys" + ( Summary "List the remaining prekey IDs of a client" + :> ZUser + :> "clients" + :> CaptureClientId "client" + :> "prekeys" + :> Get '[JSON] [PrekeyId] + ) + +type ClientAPI = + Named + "get-user-clients-unqualified" + ( Summary "Get all of a user's clients (deprecated)." + :> "users" + :> CaptureUserId "uid" :> "clients" - :> CaptureClientId "client" - :> "prekeys" - :> Get '[JSON] [PrekeyId], - -- Connection API ----------------------------------------------------- - -- - -- This endpoint can lead to the following events being sent: - -- - ConnectionUpdated event to self and other, if any side's connection state changes - -- - MemberJoin event to self and other, if joining an existing connect conversation (via galley) - -- - ConvCreate event to self, if creating a connect conversation (via galley) - -- - ConvConnect event to self, in some cases (via galley), - -- for details see 'Galley.API.Create.createConnectConversation' - createConnectionUnqualified :: - routes :- Summary "Create a connection to another user. (deprecated)" + :> Get '[JSON] [PubClient] + ) + :<|> Named + "get-user-clients-qualified" + ( Summary "Get all of a user's clients." + :> "users" + :> QualifiedCaptureUserId "uid" + :> "clients" + :> Get '[JSON] [PubClient] + ) + :<|> Named + "get-user-client-unqualified" + ( Summary "Get a specific client of a user (deprecated)." + :> "users" + :> CaptureUserId "uid" + :> "clients" + :> CaptureClientId "client" + :> Get '[JSON] PubClient + ) + :<|> Named + "get-user-client-qualified" + ( Summary "Get a specific client of a user." + :> "users" + :> QualifiedCaptureUserId "uid" + :> "clients" + :> CaptureClientId "client" + :> Get '[JSON] PubClient + ) + :<|> Named + "list-clients-bulk" + ( Summary "List all clients for a set of user ids (deprecated, use /users/list-clients/v2)" + :> ZUser + :> "users" + :> "list-clients" + :> ReqBody '[JSON] (Range 1 MaxUsersForListClientsBulk [Qualified UserId]) + :> Post '[JSON] (QualifiedUserMap (Set PubClient)) + ) + :<|> Named + "list-clients-bulk-v2" + ( Summary "List all clients for a set of user ids" + :> ZUser + :> "users" + :> "list-clients" + :> "v2" + :> ReqBody '[JSON] (LimitedQualifiedUserIdList MaxUsersForListClientsBulk) + :> Post '[JSON] (WrappedQualifiedUserMap (Set PubClient)) + ) + +-- Connection API ----------------------------------------------------- +-- +-- This endpoint can lead to the following events being sent: +-- - ConnectionUpdated event to self and other, if any side's connection state changes +-- - MemberJoin event to self and other, if joining an existing connect conversation (via galley) +-- - ConvCreate event to self, if creating a connect conversation (via galley) +-- - ConvConnect event to self, in some cases (via galley), +-- for details see 'Galley.API.Create.createConnectConversation' +type ConnectionAPI = + Named + "create-connection-unqualified" + ( Summary "Create a connection to another user (deprecated)" :> CanThrow MissingLegalholdConsent :> CanThrow InvalidUser :> CanThrow ConnectionLimitReached @@ -471,125 +532,142 @@ data Api routes = Api 'POST '[JSON] (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) - (ResponseForExistedCreated UserConnection), - createConnection :: - routes :- Summary "Create a connection to another user" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NoIdentity - -- Config value 'setUserMaxConnections' value in production/by default - -- is currently 1000 and has not changed in the last few years. - -- While it would be more correct to use the config value here, that - -- might not be time well spent. - :> Description "You can have no more than 1000 connections in accepted or sent state" - :> ZUser - :> ZConn - :> "connections" - :> QualifiedCaptureUserId "uid" - :> MultiVerb - 'POST - '[JSON] - (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) - (ResponseForExistedCreated UserConnection), - listLocalConnections :: - routes :- Summary "List the local connections to other users. (deprecated)" - :> ZUser - :> "connections" - :> QueryParam' '[Optional, Strict, Description "User ID to start from when paginating"] "start" UserId - :> QueryParam' '[Optional, Strict, Description "Number of results to return (default 100, max 500)"] "size" (Range 1 500 Int32) - :> Get '[JSON] UserConnectionList, - listConnections :: - routes :- Summary "List the connections to other users, including remote users." - :> ZUser - :> "list-connections" - :> ReqBody '[JSON] ListConnectionsRequestPaginated - :> Post '[JSON] ConnectionsPage, - getConnectionUnqualified :: - routes :- Summary "Get an existing connection to another user. (deprecated)" - :> ZUser - :> "connections" - :> CaptureUserId "uid" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Connection not found", - Respond 200 "Connection found" UserConnection - ] - (Maybe UserConnection), - getConnection :: - routes :- Summary "Get an existing connection to another user (local or remote)." - :> ZUser - :> "connections" - :> QualifiedCaptureUserId "uid" - :> MultiVerb - 'GET - '[JSON] - '[ EmptyErrorForLegacyReasons 404 "Connection not found", - Respond 200 "Connection found" UserConnection - ] - (Maybe UserConnection), + (ResponseForExistedCreated UserConnection) + ) + :<|> Named + "create-connection" + ( Summary "Create a connection to another user" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NoIdentity + -- Config value 'setUserMaxConnections' value in production/by default + -- is currently 1000 and has not changed in the last few years. + -- While it would be more correct to use the config value here, that + -- might not be time well spent. + :> Description "You can have no more than 1000 connections in accepted or sent state" + :> ZUser + :> ZConn + :> "connections" + :> QualifiedCaptureUserId "uid" + :> MultiVerb + 'POST + '[JSON] + (ResponsesForExistedCreated "Connection existed" "Connection was created" UserConnection) + (ResponseForExistedCreated UserConnection) + ) + :<|> Named + "list-local-connections" + ( Summary "List the local connections to other users (deprecated)" + :> ZUser + :> "connections" + :> QueryParam' '[Optional, Strict, Description "User ID to start from when paginating"] "start" UserId + :> QueryParam' '[Optional, Strict, Description "Number of results to return (default 100, max 500)"] "size" (Range 1 500 Int32) + :> Get '[JSON] UserConnectionList + ) + :<|> Named + "list-connections" + ( Summary "List the connections to other users, including remote users" + :> ZUser + :> "list-connections" + :> ReqBody '[JSON] ListConnectionsRequestPaginated + :> Post '[JSON] ConnectionsPage + ) + :<|> Named + "get-connection-unqualified" + ( Summary "Get an existing connection to another user (deprecated)" + :> ZUser + :> "connections" + :> CaptureUserId "uid" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Connection not found", + Respond 200 "Connection found" UserConnection + ] + (Maybe UserConnection) + ) + :<|> Named + "get-connection" + ( Summary "Get an existing connection to another user (local or remote)" + :> ZUser + :> "connections" + :> QualifiedCaptureUserId "uid" + :> MultiVerb + 'GET + '[JSON] + '[ EmptyErrorForLegacyReasons 404 "Connection not found", + Respond 200 "Connection found" UserConnection + ] + (Maybe UserConnection) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ConnectionUpdated event to self and other, if their connection states change -- -- When changing the connection state to Sent or Accepted, this can cause events to be sent -- when joining the connect conversation: -- - MemberJoin event to self and other (via galley) - updateConnectionUnqualified :: - routes :- Summary "Update a connection to another user. (deprecated)" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NotConnected - :> CanThrow InvalidTransition - :> CanThrow NoIdentity - :> ZUser - :> ZConn - :> "connections" - :> CaptureUserId "uid" - :> ReqBody '[JSON] ConnectionUpdate - :> MultiVerb - 'PUT - '[JSON] - ConnectionUpdateResponses - (UpdateResult UserConnection), + Named + "update-connection-unqualified" + ( Summary "Update a connection to another user (deprecated)" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NotConnected + :> CanThrow InvalidTransition + :> CanThrow NoIdentity + :> ZUser + :> ZConn + :> "connections" + :> CaptureUserId "uid" + :> ReqBody '[JSON] ConnectionUpdate + :> MultiVerb + 'PUT + '[JSON] + ConnectionUpdateResponses + (UpdateResult UserConnection) + ) + :<|> -- This endpoint can lead to the following events being sent: -- - ConnectionUpdated event to self and other, if their connection states change -- -- When changing the connection state to Sent or Accepted, this can cause events to be sent -- when joining the connect conversation: -- - MemberJoin event to self and other (via galley) - updateConnection :: - routes :- Summary "Update a connection to another user. (deprecated)" - :> CanThrow MissingLegalholdConsent - :> CanThrow InvalidUser - :> CanThrow ConnectionLimitReached - :> CanThrow NotConnected - :> CanThrow InvalidTransition - :> CanThrow NoIdentity - :> ZUser - :> ZConn - :> "connections" - :> QualifiedCaptureUserId "uid" - :> ReqBody '[JSON] ConnectionUpdate - :> MultiVerb - 'PUT - '[JSON] - ConnectionUpdateResponses - (UpdateResult UserConnection), - searchContacts :: - routes :- Summary "Search for users" - :> ZUser - :> "search" - :> "contacts" - :> QueryParam' '[Required, Strict, Description "Search query"] "q" Text - :> QueryParam' '[Optional, Strict, Description "Searched domain. Note: This is optional only for backwards compatibility, future versions will mandate this."] "domain" Domain - :> QueryParam' '[Optional, Strict, Description "Number of results to return (min: 1, max: 500, default 15)"] "size" (Range 1 500 Int32) - :> Get '[Servant.JSON] (SearchResult Contact) - } - deriving (Generic) + Named + "update-connection" + ( Summary "Update a connection to another user (deprecatd)" + :> CanThrow MissingLegalholdConsent + :> CanThrow InvalidUser + :> CanThrow ConnectionLimitReached + :> CanThrow NotConnected + :> CanThrow InvalidTransition + :> CanThrow NoIdentity + :> ZUser + :> ZConn + :> "connections" + :> QualifiedCaptureUserId "uid" + :> ReqBody '[JSON] ConnectionUpdate + :> MultiVerb + 'PUT + '[JSON] + ConnectionUpdateResponses + (UpdateResult UserConnection) + ) + :<|> Named + "search-contacts" + ( Summary "Search for users" + :> ZUser + :> "search" + :> "contacts" + :> QueryParam' '[Required, Strict, Description "Search query"] "q" Text + :> QueryParam' '[Optional, Strict, Description "Searched domain. Note: This is optional only for backwards compatibility, future versions will mandate this."] "domain" Domain + :> QueryParam' '[Optional, Strict, Description "Number of results to return (min: 1, max: 500, default 15)"] "size" (Range 1 500 Int32) + :> Get '[Servant.JSON] (SearchResult Contact) + ) -type ServantAPI = ToServantApi Api +type BrigAPI = UserAPI :<|> SelfAPI :<|> ClientAPI :<|> PrekeyAPI :<|> UserClientAPI :<|> ConnectionAPI -swagger :: Swagger -swagger = toSwagger (Proxy @ServantAPI) +brigSwagger :: Swagger +brigSwagger = toSwagger (Proxy @BrigAPI) diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index 850705a433..3d46601e1c 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -22,7 +22,6 @@ module Brig.API.Public apiDocs, servantSitemap, swaggerDocsAPI, - ServantAPI, SwaggerDocsAPI, ) where @@ -93,7 +92,6 @@ import qualified Network.Wai.Utilities.Swagger as Doc import Network.Wai.Utilities.ZAuth (zauthConnId, zauthUserId) import Servant hiding (Handler, JSON, addHeader, respond) import qualified Servant -import Servant.Server.Generic (genericServerT) import Servant.Swagger.Internal.Orphans () import Servant.Swagger.UI import qualified System.Logger.Class as Log @@ -102,8 +100,8 @@ import qualified Wire.API.Connection as Public import Wire.API.ErrorDescription import qualified Wire.API.Properties as Public import qualified Wire.API.Routes.MultiTablePaging as Public -import Wire.API.Routes.Public.Brig (Api (updateConnectionUnqualified)) -import qualified Wire.API.Routes.Public.Brig as BrigAPI +import Wire.API.Routes.Named +import Wire.API.Routes.Public.Brig import qualified Wire.API.Routes.Public.Cannon as CannonAPI import qualified Wire.API.Routes.Public.Cargohold as CargoholdAPI import qualified Wire.API.Routes.Public.Galley as GalleyAPI @@ -128,12 +126,10 @@ import qualified Wire.API.Wrapped as Public type SwaggerDocsAPI = "api" :> SwaggerSchemaUI "swagger-ui" "swagger.json" -type ServantAPI = BrigAPI.ServantAPI - swaggerDocsAPI :: Servant.Server SwaggerDocsAPI swaggerDocsAPI = swaggerSchemaUIServer $ - ( BrigAPI.swagger + ( brigSwagger <> GalleyAPI.swaggerDoc <> LegalHoldAPI.swaggerDoc <> SparAPI.swaggerDoc @@ -162,56 +158,71 @@ swaggerDocsAPI = . (S.required %~ nubOrd) . (S.enum_ . _Just %~ nub) -servantSitemap :: ServerT ServantAPI Handler -servantSitemap = - genericServerT $ - BrigAPI.Api - { BrigAPI.getUserUnqualified = getUserUnqualifiedH, - BrigAPI.getUserQualified = getUser, - BrigAPI.getSelf = getSelf, - BrigAPI.deleteSelf = deleteUser, - BrigAPI.putSelf = updateUser, - BrigAPI.changePhone = changePhone, - BrigAPI.removePhone = removePhone, - BrigAPI.removeEmail = removeEmail, - BrigAPI.checkPasswordExists = checkPasswordExists, - BrigAPI.changePassword = changePassword, - BrigAPI.changeLocale = changeLocale, - BrigAPI.changeHandle = changeHandle, - BrigAPI.updateUserEmail = updateUserEmail, - BrigAPI.getHandleInfoUnqualified = getHandleInfoUnqualifiedH, - BrigAPI.getUserByHandleQualified = Handle.getHandleInfo, - BrigAPI.listUsersByUnqualifiedIdsOrHandles = listUsersByUnqualifiedIdsOrHandles, - BrigAPI.listUsersByIdsOrHandles = listUsersByIdsOrHandles, - BrigAPI.getUserClientsUnqualified = getUserClientsUnqualified, - BrigAPI.getUserClientsQualified = getUserClientsQualified, - BrigAPI.getUserClientUnqualified = getUserClientUnqualified, - BrigAPI.getUserClientQualified = getUserClientQualified, - BrigAPI.listClientsBulk = listClientsBulk, - BrigAPI.listClientsBulkV2 = listClientsBulkV2, - BrigAPI.getUsersPrekeysClientUnqualified = getPrekeyUnqualifiedH, - BrigAPI.getUsersPrekeysClientQualified = getPrekeyH, - BrigAPI.getUsersPrekeyBundleUnqualified = getPrekeyBundleUnqualifiedH, - BrigAPI.getUsersPrekeyBundleQualified = getPrekeyBundleH, - BrigAPI.getMultiUserPrekeyBundleUnqualified = getMultiUserPrekeyBundleUnqualifiedH, - BrigAPI.getMultiUserPrekeyBundleQualified = getMultiUserPrekeyBundleH, - BrigAPI.addClient = addClient, - BrigAPI.updateClient = updateClient, - BrigAPI.deleteClient = deleteClient, - BrigAPI.listClients = listClients, - BrigAPI.getClient = getClient, - BrigAPI.getClientCapabilities = getClientCapabilities, - BrigAPI.getClientPrekeys = getClientPrekeys, - BrigAPI.createConnectionUnqualified = createConnectionUnqualified, - BrigAPI.createConnection = createConnection, - BrigAPI.listLocalConnections = listLocalConnections, - BrigAPI.listConnections = listConnections, - BrigAPI.getConnectionUnqualified = getLocalConnection, - BrigAPI.getConnection = getConnection, - BrigAPI.updateConnectionUnqualified = updateLocalConnection, - BrigAPI.updateConnection = updateConnection, - BrigAPI.searchContacts = Search.search - } +servantSitemap :: ServerT BrigAPI Handler +servantSitemap = userAPI :<|> selfAPI :<|> clientAPI :<|> prekeyAPI :<|> userClientAPI :<|> connectionAPI + where + userAPI :: ServerT UserAPI Handler + userAPI = + Named @"get-user-unqualified" getUserUnqualifiedH + :<|> Named @"get-user-qualified" getUser + :<|> Named @"update-user-email" updateUserEmail + :<|> Named @"get-handle-info-unqualified" getHandleInfoUnqualifiedH + :<|> Named @"get-user-by-handle-qualified" Handle.getHandleInfo + :<|> Named @"list-users-by-unqualified-ids-or-handles" listUsersByUnqualifiedIdsOrHandles + :<|> Named @"list-users-by-ids-or-handles" listUsersByIdsOrHandles + + selfAPI :: ServerT SelfAPI Handler + selfAPI = + Named @"get-self" getSelf + :<|> Named @"delete-self" deleteUser + :<|> Named @"put-self" updateUser + :<|> Named @"change-phone" changePhone + :<|> Named @"remove-phone" removePhone + :<|> Named @"remove-email" removeEmail + :<|> Named @"check-password-exists" checkPasswordExists + :<|> Named @"change-password" changePassword + :<|> Named @"change-locale" changeLocale + :<|> Named @"change-handle" changeHandle + + clientAPI :: ServerT ClientAPI Handler + clientAPI = + Named @"get-user-clients-unqualified" getUserClientsUnqualified + :<|> Named @"get-user-clients-qualified" getUserClientsQualified + :<|> Named @"get-user-client-unqualified" getUserClientUnqualified + :<|> Named @"get-user-client-qualified" getUserClientQualified + :<|> Named @"list-clients-bulk" listClientsBulk + :<|> Named @"list-clients-bulk-v2" listClientsBulkV2 + + prekeyAPI :: ServerT PrekeyAPI Handler + prekeyAPI = + Named @"get-users-prekeys-client-unqualified" getPrekeyUnqualifiedH + :<|> Named @"get-users-prekeys-client-qualified" getPrekeyH + :<|> Named @"get-users-prekey-bundle-unqualified" getPrekeyBundleUnqualifiedH + :<|> Named @"get-users-prekey-bundle-qualified" getPrekeyBundleH + :<|> Named @"get-multi-user-prekey-bundle-unqualified" getMultiUserPrekeyBundleUnqualifiedH + :<|> Named @"get-multi-user-prekey-bundle-qualified" getMultiUserPrekeyBundleH + + userClientAPI :: ServerT UserClientAPI Handler + userClientAPI = + Named @"add-client" addClient + :<|> Named @"update-client" updateClient + :<|> Named @"delete-client" deleteClient + :<|> Named @"list-clients" listClients + :<|> Named @"get-client" getClient + :<|> Named @"get-client-capabilities" getClientCapabilities + :<|> Named @"get-client-prekeys" getClientPrekeys + + connectionAPI :: ServerT ConnectionAPI Handler + connectionAPI = + Named @"create-connection-unqualified" createConnectionUnqualified + :<|> Named @"create-connection" createConnection + :<|> Named @"list-local-connections" listLocalConnections + :<|> Named @"list-connections" listConnections + :<|> Named @"get-connection-unqualified" getLocalConnection + :<|> Named @"get-connection" getConnection + :<|> Named @"update-connection-unqualified" updateLocalConnection + :<|> Named @"update-connection" updateConnection + :<|> Named @"search-contacts" Search.search -- Note [ephemeral user sideeffect] -- If the user is ephemeral and expired, it will be removed upon calling @@ -588,14 +599,14 @@ getMultiUserPrekeyBundleH zusr qualUserClients = do throwErrorDescriptionType @TooManyClients API.claimMultiPrekeyBundles (ProtectedUser zusr) qualUserClients !>> clientError -addClient :: UserId -> ConnId -> Maybe IpAddr -> Public.NewClient -> Handler BrigAPI.NewClientResponse +addClient :: UserId -> ConnId -> Maybe IpAddr -> Public.NewClient -> Handler NewClientResponse addClient usr con ip new = do -- Users can't add legal hold clients when (Public.newClientType new == Public.LegalHoldClientType) $ throwE (clientError ClientLegalHoldCannotBeAdded) clientResponse <$> API.addClient usr (Just con) (ipAddr <$> ip) new !>> clientError where - clientResponse :: Public.Client -> BrigAPI.NewClientResponse + clientResponse :: Public.Client -> NewClientResponse clientResponse client = Servant.addHeader (Public.clientId client) client deleteClient :: UserId -> ConnId -> ClientId -> Public.RmClient -> Handler () @@ -626,11 +637,11 @@ getUserClientUnqualified uid cid = do x <- API.lookupPubClient (Qualified uid localdomain) cid !>> clientError ifNothing (notFound "client not found") x -listClientsBulk :: UserId -> Range 1 BrigAPI.MaxUsersForListClientsBulk [Qualified UserId] -> Handler (Public.QualifiedUserMap (Set Public.PubClient)) +listClientsBulk :: UserId -> Range 1 MaxUsersForListClientsBulk [Qualified UserId] -> Handler (Public.QualifiedUserMap (Set Public.PubClient)) listClientsBulk _zusr limitedUids = API.lookupPubClientsBulk (fromRange limitedUids) !>> clientError -listClientsBulkV2 :: UserId -> Public.LimitedQualifiedUserIdList BrigAPI.MaxUsersForListClientsBulk -> Handler (Public.WrappedQualifiedUserMap (Set Public.PubClient)) +listClientsBulkV2 :: UserId -> Public.LimitedQualifiedUserIdList MaxUsersForListClientsBulk -> Handler (Public.WrappedQualifiedUserMap (Set Public.PubClient)) listClientsBulkV2 zusr userIds = Public.Wrapped <$> listClientsBulk zusr (Public.qualifiedUsers userIds) getUserClientQualified :: Qualified UserId -> ClientId -> Handler Public.PubClient diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index 579b5746b4..f435276912 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -27,7 +27,7 @@ import Brig.API (sitemap) import Brig.API.Federation import Brig.API.Handler import qualified Brig.API.Internal as IAPI -import Brig.API.Public (ServantAPI, SwaggerDocsAPI, servantSitemap, swaggerDocsAPI) +import Brig.API.Public (SwaggerDocsAPI, servantSitemap, swaggerDocsAPI) import qualified Brig.API.User as API import Brig.AWS (sesQueue) import qualified Brig.AWS as AWS @@ -68,6 +68,7 @@ import qualified Servant import System.Logger (msg, val, (.=), (~~)) import System.Logger.Class (MonadLogger, err) import Util.Options +import Wire.API.Routes.Public.Brig -- FUTUREWORK: If any of these async threads die, we will have no clue about it -- and brig could start misbehaving. We should ensure that brig dies whenever a @@ -123,7 +124,7 @@ mkApp o = do (Proxy @ServantCombinedAPI) (customFormatters :. Servant.EmptyContext) ( swaggerDocsAPI - :<|> Servant.hoistServer (Proxy @ServantAPI) (toServantHandler e) servantSitemap + :<|> Servant.hoistServer (Proxy @BrigAPI) (toServantHandler e) servantSitemap :<|> Servant.hoistServer (Proxy @IAPI.API) (toServantHandler e) IAPI.servantSitemap :<|> Servant.hoistServer (Proxy @FederationAPI) (toServantHandler e) federationSitemap :<|> Servant.Tagged (app e) @@ -131,7 +132,7 @@ mkApp o = do type ServantCombinedAPI = ( SwaggerDocsAPI - :<|> ServantAPI + :<|> BrigAPI :<|> IAPI.API :<|> FederationAPI :<|> Servant.Raw