Skip to content

Commit

Permalink
2nd Auth Factor for "Create authentication token for SCIM service" an…
Browse files Browse the repository at this point in the history
…d "login" API (#2124)

(types & swagger only, no implementation)

Co-authored-by: fisx <[email protected]>
  • Loading branch information
battermann and fisx authored Feb 15, 2022
1 parent b435f30 commit ae447b1
Show file tree
Hide file tree
Showing 37 changed files with 204 additions and 45 deletions.
1 change: 1 addition & 0 deletions changelog.d/0-release-notes/pr-2124
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This change requires an nginz upgrade to expose the newly added endpoint for sending a verification code.
1 change: 1 addition & 0 deletions changelog.d/1-api-changes/pr-2124
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New endpoint (`POST /verification-code/send`) for generating and sending a verification code for 2nd factor authentication actions.
4 changes: 4 additions & 0 deletions charts/nginz/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ nginx_conf:
- path: ~* ^/teams/([^/]*)/search$
envs:
- all
- path: /verification-code/send
envs:
- all
disable_zauth: true
galley:
- path: /conversations/code-check
disable_zauth: true
Expand Down
5 changes: 5 additions & 0 deletions deploy/services-demo/conf/nginz/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ http {
proxy_pass http://brig;
}

location /verification-code/send {
include common_response_no_zauth.conf;
proxy_pass http://brig;
}

## brig authenticated endpoints

location /self {
Expand Down
2 changes: 1 addition & 1 deletion libs/api-bot/src/Network/Wire/Bot/Monad.hs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ mkBot :: BotTag -> User -> PlainTextPassword -> BotNet Bot
mkBot tag user pw = do
log Info $ botLogFields (userId user) tag . msg (val "Login")
let ident = fromMaybe (error "No email") (userEmail user)
let cred = PasswordLogin (LoginByEmail ident) pw Nothing
let cred = PasswordLogin (LoginByEmail ident) pw Nothing Nothing
auth <- login cred >>= maybe (throwM LoginFailed) return
aref <- nextAuthRefresh auth
env <- BotNet ask
Expand Down
8 changes: 8 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ type UserAPI =
:> ReqBody '[JSON] ListUsersQuery
:> Post '[JSON] [UserProfile]
)
:<|> Named
"send-verification-code"
( Summary "Send a verification code to a given email address."
:> "verification-code"
:> "send"
:> ReqBody '[JSON] SendVerificationCode
:> MultiVerb 'POST '[JSON] '[RespondEmpty 200 "Verification code sent."] ()
)

type SelfAPI =
Named
Expand Down
40 changes: 40 additions & 0 deletions libs/wire-api/src/Wire/API/User.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ module Wire.API.User
modelUser,
modelUserIdList,
modelVerifyDelete,

-- * 2nd factor auth
SndFactorPasswordChallengeAction (..),
SendVerificationCode (..),
TeamFeatureSndFPasswordChallengeNotImplemented (..),
)
where

Expand Down Expand Up @@ -1151,3 +1156,38 @@ instance S.ToSchema ListUsersQuery where
& S.description ?~ "exactly one of qualified_ids or qualified_handles must be provided."
& S.properties .~ InsOrdHashMap.fromList [("qualified_ids", uids), ("qualified_handles", handles)]
& S.example ?~ toJSON (ListUsersByIds [Qualified (Id UUID.nil) (Domain "example.com")])

-----------------------------------------------------------------------------
-- SndFactorPasswordChallenge

-- | remove this type once we have an implementation in order to find all the places where we need to touch code.
data TeamFeatureSndFPasswordChallengeNotImplemented
= TeamFeatureSndFPasswordChallengeNotImplemented

data SndFactorPasswordChallengeAction = GenerateScimToken | Login
deriving stock (Eq, Show, Enum, Bounded, Generic)
deriving (Arbitrary) via (GenericUniform SndFactorPasswordChallengeAction)
deriving (FromJSON, ToJSON, S.ToSchema) via (Schema SndFactorPasswordChallengeAction)

instance ToSchema SndFactorPasswordChallengeAction where
schema =
enum @Text "SndFactorPasswordChallengeAction" $
mconcat
[ element "generate_scim_token" GenerateScimToken,
element "login" Login
]

data SendVerificationCode = SendVerificationCode
{ svcAction :: SndFactorPasswordChallengeAction,
svcEmail :: Email
}
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform SendVerificationCode)
deriving (FromJSON, ToJSON, S.ToSchema) via Schema SendVerificationCode

instance ToSchema SendVerificationCode where
schema =
object "SendVerificationCode" $
SendVerificationCode
<$> svcAction .= field "action" schema
<*> svcEmail .= field "email" schema
1 change: 1 addition & 0 deletions libs/wire-api/src/Wire/API/User/Activation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ newtype ActivationKey = ActivationKey

-- | A random code for use with an 'ActivationKey' that is usually transmitted
-- out-of-band, e.g. via email or sms.
-- FUTUREWORK(leif): rename to VerificationCode
newtype ActivationCode = ActivationCode
{fromActivationCode :: AsciiBase64Url}
deriving stock (Eq, Show, Generic)
Expand Down
19 changes: 14 additions & 5 deletions libs/wire-api/src/Wire/API/User/Auth.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
import Data.Time.Clock (UTCTime)
import Imports
import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..))
import Wire.API.User.Activation
import Wire.API.User.Identity (Email, Phone)

--------------------------------------------------------------------------------
-- Login

-- | Different kinds of logins.
data Login
= PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel)
= PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel) (Maybe ActivationCode)
| SmsLogin Phone LoginCode (Maybe CookieLabel)
deriving stock (Eq, Show, Generic)
deriving (Arbitrary) via (GenericUniform Login)
Expand Down Expand Up @@ -103,11 +104,19 @@ modelLogin = Doc.defineModel "Login" $ do
\to allow targeted revocation of all cookies granted to that \
\specific client."
Doc.optional
Doc.property "verification_code" Doc.string' $ do
Doc.description "The login verification code for 2nd factor authentication. Required only if SndFactorPasswordChallenge is enabled for the team/server."
Doc.optional

instance ToJSON Login where
toJSON (SmsLogin p c l) = object ["phone" .= p, "code" .= c, "label" .= l]
toJSON (PasswordLogin login password label) =
object ["password" .= password, "label" .= label, loginIdPair login]
toJSON (PasswordLogin login password label mbCode) =
object
[ "password" .= password,
"label" .= label,
loginIdPair login,
"verification_code" .= mbCode
]

instance FromJSON Login where
parseJSON = withObject "Login" $ \o -> do
Expand All @@ -117,10 +126,10 @@ instance FromJSON Login where
SmsLogin <$> o .: "phone" <*> o .: "code" <*> o .:? "label"
Just pw -> do
loginId <- parseJSON (Object o)
PasswordLogin loginId pw <$> o .:? "label"
PasswordLogin loginId pw <$> (o .:? "label") <*> (o .:? "verification_code")

loginLabel :: Login -> Maybe CookieLabel
loginLabel (PasswordLogin _ _ l) = l
loginLabel (PasswordLogin _ _ l _) = l
loginLabel (SmsLogin _ _ l) = l

--------------------------------------------------------------------------------
Expand Down
12 changes: 9 additions & 3 deletions libs/wire-api/src/Wire/API/User/Scim.hs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import Web.Scim.Schema.Schema (Schema (CustomSchema))
import qualified Web.Scim.Schema.Schema as Scim
import qualified Web.Scim.Schema.User as Scim
import qualified Web.Scim.Schema.User as Scim.User
import Wire.API.User.Activation
import Wire.API.User.Identity (Email)
import Wire.API.User.Profile as BT
import qualified Wire.API.User.RichInfo as RI
Expand Down Expand Up @@ -365,22 +366,26 @@ data CreateScimToken = CreateScimToken
{ -- | Token description (as memory aid for whoever is creating the token)
createScimTokenDescr :: !Text,
-- | User password, which we ask for because creating a token is a "powerful" operation
createScimTokenPassword :: !(Maybe PlainTextPassword)
createScimTokenPassword :: !(Maybe PlainTextPassword),
-- | User code (sent by email), for 2nd factor to 'createScimTokenPassword'
createScimTokenCode :: !(Maybe ActivationCode)
}
deriving (Eq, Show)

instance A.FromJSON CreateScimToken where
parseJSON = A.withObject "CreateScimToken" $ \o -> do
createScimTokenDescr <- o A..: "description"
createScimTokenPassword <- o A..:? "password"
createScimTokenCode <- o A..:? "code"
pure CreateScimToken {..}

-- Used for integration tests
instance A.ToJSON CreateScimToken where
toJSON CreateScimToken {..} =
A.object
[ "description" A..= createScimTokenDescr,
"password" A..= createScimTokenPassword
"password" A..= createScimTokenPassword,
"code" A..= createScimTokenCode
]

-- | Type used for the response of 'APIScimTokenCreate'.
Expand Down Expand Up @@ -463,7 +468,8 @@ instance ToSchema CreateScimToken where
& type_ .~ Just SwaggerObject
& properties
.~ [ ("description", textSchema),
("password", textSchema)
("password", textSchema),
("code", textSchema)
]
& required .~ ["description"]

Expand Down
8 changes: 7 additions & 1 deletion libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ import qualified Test.Wire.API.Golden.Generated.ServiceToken_provider
import qualified Test.Wire.API.Golden.Generated.Service_provider
import qualified Test.Wire.API.Golden.Generated.SimpleMember_user
import qualified Test.Wire.API.Golden.Generated.SimpleMembers_user
import qualified Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user
import qualified Test.Wire.API.Golden.Generated.TeamBinding_team
import qualified Test.Wire.API.Golden.Generated.TeamContact_user
import qualified Test.Wire.API.Golden.Generated.TeamConversationList_team
Expand Down Expand Up @@ -1206,5 +1207,10 @@ tests =
testGroup "Golden: TeamSearchVisibility_team" $
testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_1, "testObject_TeamSearchVisibility_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_2, "testObject_TeamSearchVisibility_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_3, "testObject_TeamSearchVisibility_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_4, "testObject_TeamSearchVisibility_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_5, "testObject_TeamSearchVisibility_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_6, "testObject_TeamSearchVisibility_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_7, "testObject_TeamSearchVisibility_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_8, "testObject_TeamSearchVisibility_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_9, "testObject_TeamSearchVisibility_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_10, "testObject_TeamSearchVisibility_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_11, "testObject_TeamSearchVisibility_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_12, "testObject_TeamSearchVisibility_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_13, "testObject_TeamSearchVisibility_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_14, "testObject_TeamSearchVisibility_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_15, "testObject_TeamSearchVisibility_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_16, "testObject_TeamSearchVisibility_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_17, "testObject_TeamSearchVisibility_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_18, "testObject_TeamSearchVisibility_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_19, "testObject_TeamSearchVisibility_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_20, "testObject_TeamSearchVisibility_team_20.json")],
testGroup "Golden: TeamSearchVisibilityView_team" $
testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_1, "testObject_TeamSearchVisibilityView_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_2, "testObject_TeamSearchVisibilityView_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_3, "testObject_TeamSearchVisibilityView_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_4, "testObject_TeamSearchVisibilityView_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_5, "testObject_TeamSearchVisibilityView_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_6, "testObject_TeamSearchVisibilityView_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_7, "testObject_TeamSearchVisibilityView_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_8, "testObject_TeamSearchVisibilityView_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_9, "testObject_TeamSearchVisibilityView_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_10, "testObject_TeamSearchVisibilityView_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_11, "testObject_TeamSearchVisibilityView_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_12, "testObject_TeamSearchVisibilityView_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_13, "testObject_TeamSearchVisibilityView_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_14, "testObject_TeamSearchVisibilityView_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_15, "testObject_TeamSearchVisibilityView_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_16, "testObject_TeamSearchVisibilityView_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_17, "testObject_TeamSearchVisibilityView_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_18, "testObject_TeamSearchVisibilityView_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_19, "testObject_TeamSearchVisibilityView_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_20, "testObject_TeamSearchVisibilityView_team_20.json")]
testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_1, "testObject_TeamSearchVisibilityView_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_2, "testObject_TeamSearchVisibilityView_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_3, "testObject_TeamSearchVisibilityView_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_4, "testObject_TeamSearchVisibilityView_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_5, "testObject_TeamSearchVisibilityView_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_6, "testObject_TeamSearchVisibilityView_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_7, "testObject_TeamSearchVisibilityView_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_8, "testObject_TeamSearchVisibilityView_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_9, "testObject_TeamSearchVisibilityView_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_10, "testObject_TeamSearchVisibilityView_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_11, "testObject_TeamSearchVisibilityView_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_12, "testObject_TeamSearchVisibilityView_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_13, "testObject_TeamSearchVisibilityView_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_14, "testObject_TeamSearchVisibilityView_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_15, "testObject_TeamSearchVisibilityView_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_16, "testObject_TeamSearchVisibilityView_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_17, "testObject_TeamSearchVisibilityView_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_18, "testObject_TeamSearchVisibilityView_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_19, "testObject_TeamSearchVisibilityView_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_20, "testObject_TeamSearchVisibilityView_team_20.json")],
testGroup "Golden: SndFactorPasswordChallengeAction_user" $
testObjects
[ (Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_1, "testObject_SndFactorPasswordChallengeAction_user_1"),
(Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_2, "testObject_SndFactorPasswordChallengeAction_user_2")
]
]
Loading

0 comments on commit ae447b1

Please sign in to comment.