diff --git a/deploy/services-demo/conf/nginz/nginx.conf b/deploy/services-demo/conf/nginz/nginx.conf index 0b86eb2c1b..25317c07ae 100644 --- a/deploy/services-demo/conf/nginz/nginx.conf +++ b/deploy/services-demo/conf/nginz/nginx.conf @@ -194,7 +194,7 @@ http { proxy_pass http://brig; } - location /register { + location ~* (/v[0-9]+)?/register { include common_response_no_zauth.conf; proxy_pass http://brig; } diff --git a/libs/wire-api/src/Wire/API/Routes/Version.hs b/libs/wire-api/src/Wire/API/Routes/Version.hs index 03cd25604e..14954596b4 100644 --- a/libs/wire-api/src/Wire/API/Routes/Version.hs +++ b/libs/wire-api/src/Wire/API/Routes/Version.hs @@ -32,6 +32,9 @@ where import Control.Lens ((?~)) import Data.Aeson (FromJSON, ToJSON (..)) import qualified Data.Aeson as Aeson +import Data.Attoparsec.ByteString.Char8 +import Data.ByteString.Builder +import Data.ByteString.Conversion import Data.Domain import Data.Schema import qualified Data.Swagger as S @@ -47,12 +50,24 @@ data Version = V0 | V1 deriving stock (Eq, Ord, Bounded, Enum, Show) deriving (FromJSON, ToJSON) via (Schema Version) +versionNumber :: Version -> Integer +versionNumber V0 = 0 +versionNumber V1 = 1 + instance ToSchema Version where schema = - enum @Integer "Version" . mconcat $ - [ element 0 V0, - element 1 V1 - ] + enum @Integer "Version" $ + foldMap + (\v -> element (versionNumber v) v) + [minBound .. maxBound] + +instance FromByteString Version where + parser = + maybe (fail "Unsupported version") pure + =<< fmap mkVersion decimal + +instance ToByteString Version where + builder = integerDec . versionNumber readVersionNumber :: Text -> Maybe Integer readVersionNumber v = do diff --git a/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs index b0ba0c2ef8..0c65e4a3cf 100644 --- a/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs +++ b/libs/wire-api/src/Wire/API/Routes/Version/Wai.hs @@ -17,6 +17,7 @@ module Wire.API.Routes.Version.Wai where +import Data.ByteString.Conversion import qualified Data.Text.Lazy as LText import Imports import qualified Network.HTTP.Types as HTTP @@ -44,4 +45,12 @@ parseVersion req = do [] -> Nothing (x : xs) -> pure (x, xs) n <- readVersionNumber version - pure (rewriteRequestPure (\(_, q) _ -> (pinfo, q)) req, n) + let req' = + rewriteRequestPure + (\(_, q) _ -> (pinfo, q)) + req + { requestHeaders = + requestHeaders req + <> [("Wire-API-Version", toByteString' n)] + } + pure (req', n) diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index afffe05776..1d4d1d84fc 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -366,7 +366,8 @@ sitemap = do -- - UserActivated event to created user, if it is a team invitation or user has an SSO ID -- - UserIdentityUpdated event to created user, if email code or phone code is provided post "/register" (continue createUserH) $ - accept "application" "json" + opt (header "Wire-API-Version") + .&. accept "application" "json" .&. jsonRequest @Public.NewUserPublic document "POST" "register" $ do Doc.summary "Register a new user." @@ -669,10 +670,10 @@ getClientPrekeys :: UserId -> ClientId -> (Handler r) [Public.PrekeyId] getClientPrekeys usr clt = lift (API.lookupPrekeyIds usr clt) -- docs/reference/user/registration.md {#RefRegistration} -createUserH :: JSON ::: JsonRequest Public.NewUserPublic -> (Handler r) Response -createUserH (_ ::: req) = do +createUserH :: Maybe Version ::: JSON ::: JsonRequest Public.NewUserPublic -> (Handler r) Response +createUserH (v ::: _ ::: req) = do CreateUserResponse cok loc prof <- createUser =<< parseJsonBody req - lift . Auth.setResponseCookie cok + lift . Auth.setResponseCookie v cok . setStatus status201 . addHeader "Location" (toByteString' loc) $ json prof diff --git a/services/brig/src/Brig/User/API/Auth.hs b/services/brig/src/Brig/User/API/Auth.hs index fdd67fd5fd..d2b217215e 100644 --- a/services/brig/src/Brig/User/API/Auth.hs +++ b/services/brig/src/Brig/User/API/Auth.hs @@ -55,6 +55,7 @@ import qualified Network.Wai.Utilities.Response as WaiResp import Network.Wai.Utilities.Swagger (document) import qualified Network.Wai.Utilities.Swagger as Doc import Wire.API.ErrorDescription +import Wire.API.Routes.Version import qualified Wire.API.User as Public import Wire.API.User.Auth as Public import Wire.Swagger as Doc (pendingLoginError) @@ -62,7 +63,8 @@ import Wire.Swagger as Doc (pendingLoginError) routesPublic :: Routes Doc.ApiBuilder (Handler r) () routesPublic = do post "/access" (continue renewH) $ - accept "application" "json" + opt (header "Wire-API-Version") + .&. accept "application" "json" .&. tokenRequest document "POST" "newAccessToken" $ do Doc.summary "Obtain an access tokens for a cookie." @@ -101,7 +103,8 @@ routesPublic = do Doc.errorResponse' loginCodePending Doc.pendingLoginError post "/login" (continue loginH) $ - jsonRequest @Public.Login + opt (header "Wire-API-Version") + .&. jsonRequest @Public.Login .&. def False (query "persist") .&. accept "application" "json" document "POST" "login" $ do @@ -190,7 +193,8 @@ routesInternal = do .&. accept "application" "json" post "/i/sso-login" (continue ssoLoginH) $ - jsonRequest @SsoLogin + opt (header "Wire-API-Version") + .&. jsonRequest @SsoLogin .&. def False (query "persist") .&. accept "application" "json" @@ -231,18 +235,18 @@ reAuthUser :: UserId -> ReAuthUser -> (Handler r) () reAuthUser uid body = do User.reauthenticate uid (reAuthPassword body) !>> reauthError -loginH :: JsonRequest Public.Login ::: Bool ::: JSON -> (Handler r) Response -loginH (req ::: persist ::: _) = do - lift . tokenResponse =<< flip login persist =<< parseJsonBody req +loginH :: Maybe Version ::: JsonRequest Public.Login ::: Bool ::: JSON -> (Handler r) Response +loginH (v ::: req ::: persist ::: _) = do + lift . tokenResponse v =<< flip login persist =<< parseJsonBody req login :: Public.Login -> Bool -> (Handler r) (Auth.Access ZAuth.User) login l persist = do let typ = if persist then PersistentCookie else SessionCookie Auth.login l typ !>> loginError -ssoLoginH :: JsonRequest SsoLogin ::: Bool ::: JSON -> (Handler r) Response -ssoLoginH (req ::: persist ::: _) = do - lift . tokenResponse =<< flip ssoLogin persist =<< parseJsonBody req +ssoLoginH :: Maybe Version ::: JsonRequest SsoLogin ::: Bool ::: JSON -> (Handler r) Response +ssoLoginH (v ::: req ::: persist ::: _) = do + lift . tokenResponse v =<< flip ssoLogin persist =<< parseJsonBody req ssoLogin :: SsoLogin -> Bool -> (Handler r) (Auth.Access ZAuth.User) ssoLogin l persist = do @@ -251,7 +255,7 @@ ssoLogin l persist = do legalHoldLoginH :: JsonRequest LegalHoldLogin ::: JSON -> (Handler r) Response legalHoldLoginH (req ::: _) = do - lift . tokenResponse =<< legalHoldLogin =<< parseJsonBody req + lift . tokenResponse Nothing =<< legalHoldLogin =<< parseJsonBody req legalHoldLogin :: LegalHoldLogin -> (Handler r) (Auth.Access ZAuth.LegalHoldUser) legalHoldLogin l = do @@ -320,8 +324,13 @@ rmCookies :: UserId -> Public.RemoveCookies -> (Handler r) () rmCookies uid (Public.RemoveCookies pw lls ids) = do Auth.revokeAccess uid pw ids lls !>> authError -renewH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> (Handler r) Response -renewH (_ ::: ut ::: at) = lift . either tokenResponse tokenResponse =<< renew ut at +renewH :: + Maybe Version + ::: JSON + ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) + ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> + (Handler r) Response +renewH (v ::: _ ::: ut ::: at) = lift . either (tokenResponse v) (tokenResponse v) =<< renew ut at -- | renew access for either: -- * a user with user token and optional access token, or @@ -406,9 +415,9 @@ tokenRequest = opt (userToken ||| legalHoldUserToken) .&. opt (accessToken ||| l ) Just t -> return t -tokenResponse :: ZAuth.UserTokenLike u => Auth.Access u -> (AppIO r) Response -tokenResponse (Auth.Access t Nothing) = pure $ json t -tokenResponse (Auth.Access t (Just c)) = Auth.setResponseCookie c (json t) +tokenResponse :: ZAuth.UserTokenLike u => Maybe Version -> Auth.Access u -> (AppIO r) Response +tokenResponse _ (Auth.Access t Nothing) = pure $ json t +tokenResponse v (Auth.Access t (Just c)) = Auth.setResponseCookie v c (json t) -- | Internal utilities: These functions are nearly copies verbatim from the original -- project: https://gitlab.com/twittner/wai-predicates/-/blob/develop/src/Network/Wai/Predicate.hs#L106-112 diff --git a/services/brig/src/Brig/User/Auth/Cookie.hs b/services/brig/src/Brig/User/Auth/Cookie.hs index 28023f5863..abff3fe519 100644 --- a/services/brig/src/Brig/User/Auth/Cookie.hs +++ b/services/brig/src/Brig/User/Auth/Cookie.hs @@ -59,6 +59,7 @@ import Network.Wai.Utilities.Response (addHeader) import System.Logger.Class (field, msg, val, (~~)) import qualified System.Logger.Class as Log import qualified Web.Cookie as WebCookie +import Wire.API.Routes.Version -------------------------------------------------------------------------------- -- Basic Cookie Management @@ -222,19 +223,22 @@ newCookieLimited u typ label = do setResponseCookie :: (Monad m, MonadReader Env m, ZAuth.UserTokenLike u) => + Maybe Version -> Cookie (ZAuth.Token u) -> Response -> m Response -setResponseCookie c r = do +setResponseCookie mv c r = do s <- view settings let hdr = toByteString' (WebCookie.renderSetCookie (cookie s)) return (addHeader "Set-Cookie" hdr r) where + versionPrefix :: ByteString + versionPrefix = foldMap (("/v" <>) . toByteString') mv cookie s = WebCookie.def { WebCookie.setCookieName = "zuid", WebCookie.setCookieValue = toByteString' (cookieValue c), - WebCookie.setCookiePath = Just "/access", + WebCookie.setCookiePath = Just (versionPrefix <> "/access"), WebCookie.setCookieExpires = if cookieType c == PersistentCookie then Just (cookieExpires c)