Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
39 changes: 39 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,37 @@ 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

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
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,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 @@ -1211,5 +1212,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