Adopt login process for two-factor auth

This commit is contained in:
eikek
2021-08-31 21:29:07 +02:00
parent 999c39833a
commit 1afc005a6c
14 changed files with 356 additions and 126 deletions

View File

@ -141,6 +141,7 @@ module Api exposing
, startReIndex
, submitNotifyDueItems
, toggleTags
, twoFactor
, unconfirmMultiple
, updateNotifyDueItems
, updateScanMailbox
@ -209,6 +210,7 @@ import Api.Model.Registration exposing (Registration)
import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
import Api.Model.SearchStats exposing (SearchStats)
import Api.Model.SecondFactor exposing (SecondFactor)
import Api.Model.SentMails exposing (SentMails)
import Api.Model.SimpleMail exposing (SimpleMail)
import Api.Model.SourceAndTags exposing (SourceAndTags)
@ -942,6 +944,15 @@ login flags up receive =
}
twoFactor : Flags -> SecondFactor -> (Result Http.Error AuthResult -> msg) -> Cmd msg
twoFactor flags sf receive =
Http.post
{ url = flags.config.baseUrl ++ "/api/v1/open/auth/two-factor"
, body = Http.jsonBody (Api.Model.SecondFactor.encode sf)
, expect = Http.expectJson receive Api.Model.AuthResult.decoder
}
logout : Flags -> (Result Http.Error () -> msg) -> Cmd msg
logout flags receive =
Http2.authPost

View File

@ -28,6 +28,7 @@ type alias Texts =
, loginSuccessful : String
, noAccount : String
, signupLink : String
, otpCode : String
}
@ -45,6 +46,7 @@ gb =
, loginSuccessful = "Login successful"
, noAccount = "No account?"
, signupLink = "Sign up!"
, otpCode = "Authentication code"
}
@ -62,4 +64,5 @@ de =
, loginSuccessful = "Anmeldung erfolgreich"
, noAccount = "Kein Konto?"
, signupLink = "Hier registrieren!"
, otpCode = "Authentifizierungscode"
}

View File

@ -128,5 +128,5 @@ E-Mail-Einstellungen (IMAP) notwendig."""
ist es gut, die Kriterien so zu gestalten, dass die
gleichen E-Mails möglichst nicht noch einmal eingelesen
werden."""
, otpMenu = "Zwei Faktor Auth"
, otpMenu = "Zwei-Faktor-Authentifizierung"
}

View File

@ -6,7 +6,8 @@
module Page.Login.Data exposing
( FormState(..)
( AuthStep(..)
, FormState(..)
, Model
, Msg(..)
, emptyModel
@ -20,8 +21,10 @@ import Page exposing (Page(..))
type alias Model =
{ username : String
, password : String
, otp : String
, rememberMe : Bool
, formState : FormState
, authStep : AuthStep
}
@ -32,12 +35,19 @@ type FormState
| FormInitial
type AuthStep
= StepLogin
| StepOtp AuthResult
emptyModel : Model
emptyModel =
{ username = ""
, password = ""
, otp = ""
, rememberMe = False
, formState = FormInitial
, authStep = StepLogin
}
@ -47,3 +57,5 @@ type Msg
| ToggleRememberMe
| Authenticate
| AuthResp (Result Http.Error AuthResult)
| SetOtp String
| AuthOtp AuthResult

View File

@ -24,6 +24,9 @@ update referrer flags msg model =
SetPassword str ->
( { model | password = str }, Cmd.none, Nothing )
SetOtp str ->
( { model | otp = str }, Cmd.none, Nothing )
ToggleRememberMe ->
( { model | rememberMe = not model.rememberMe }, Cmd.none, Nothing )
@ -37,17 +40,33 @@ update referrer flags msg model =
in
( model, Api.login flags userPass AuthResp, Nothing )
AuthOtp acc ->
let
sf =
{ rememberMe = model.rememberMe
, token = Maybe.withDefault "" acc.token
, otp = model.otp
}
in
( model, Api.twoFactor flags sf AuthResp, Nothing )
AuthResp (Ok lr) ->
let
gotoRef =
Maybe.withDefault HomePage referrer |> Page.goto
in
if lr.success then
if lr.success && not lr.requireSecondFactor then
( { model | formState = AuthSuccess lr, password = "" }
, Cmd.batch [ setAccount lr, gotoRef ]
, Just lr
)
else if lr.success && lr.requireSecondFactor then
( { model | formState = FormInitial, authStep = StepOtp lr, password = "" }
, Cmd.none
, Nothing
)
else
( { model | formState = AuthFailed lr, password = "" }
, Ports.removeAccount ()

View File

@ -7,6 +7,7 @@
module Page.Login.View2 exposing (viewContent, viewSidebar)
import Api.Model.AuthResult exposing (AuthResult)
import Api.Model.VersionInfo exposing (VersionInfo)
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
@ -46,104 +47,12 @@ viewContent texts flags versionInfo _ model =
, div [ class "font-medium self-center text-xl sm:text-2xl" ]
[ text texts.loginToDocspell
]
, Html.form
[ action "#"
, onSubmit Authenticate
, autocomplete False
]
[ div [ class "flex flex-col mt-6" ]
[ label
[ for "username"
, class S.inputLabel
]
[ text texts.username
]
, div [ class "relative" ]
[ div [ class S.inputIcon ]
[ i [ class "fa fa-user" ] []
]
, input
[ type_ "text"
, name "username"
, autocomplete False
, onInput SetUsername
, value model.username
, autofocus True
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
, placeholder texts.collectiveSlashLogin
]
[]
]
]
, div [ class "flex flex-col my-3" ]
[ label
[ for "password"
, class S.inputLabel
]
[ text texts.password
]
, div [ class "relative" ]
[ div [ class S.inputIcon ]
[ i [ class "fa fa-lock" ] []
]
, input
[ type_ "password"
, name "password"
, autocomplete False
, onInput SetPassword
, value model.password
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
, placeholder texts.password
]
[]
]
]
, div [ class "flex flex-col my-3" ]
[ label
[ class "inline-flex items-center"
, for "rememberme"
]
[ input
[ id "rememberme"
, type_ "checkbox"
, onCheck (\_ -> ToggleRememberMe)
, checked model.rememberMe
, name "rememberme"
, class S.checkboxInput
]
[]
, span
[ class "mb-1 ml-2 text-xs sm:text-sm tracking-wide my-1"
]
[ text texts.rememberMe
]
]
]
, div [ class "flex flex-col my-3" ]
[ button
[ type_ "submit"
, class S.primaryButton
]
[ text texts.loginButton
]
]
, resultMessage texts model
, div
[ class "flex justify-end text-sm pt-4"
, classList [ ( "hidden", flags.config.signupMode == "closed" ) ]
]
[ span []
[ text texts.noAccount
]
, a
[ Page.href RegisterPage
, class ("ml-2" ++ S.link)
]
[ i [ class "fa fa-user-plus mr-1" ] []
, text texts.signupLink
]
]
]
, case model.authStep of
StepOtp token ->
otpForm texts flags model token
StepLogin ->
loginForm texts flags model
]
, a
[ class "inline-flex items-center mt-4 text-xs opacity-50 hover:opacity-90"
@ -163,6 +72,151 @@ viewContent texts flags versionInfo _ model =
]
otpForm : Texts -> Flags -> Model -> AuthResult -> Html Msg
otpForm texts flags model acc =
Html.form
[ action "#"
, onSubmit (AuthOtp acc)
, autocomplete False
]
[ div [ class "flex flex-col mt-6" ]
[ label
[ for "otp"
, class S.inputLabel
]
[ text texts.otpCode
]
, div [ class "relative" ]
[ div [ class S.inputIcon ]
[ i [ class "fa fa-key" ] []
]
, input
[ type_ "text"
, name "otp"
, autocomplete False
, onInput SetOtp
, value model.otp
, autofocus True
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
, placeholder "123456"
]
[]
]
, div [ class "flex flex-col my-3" ]
[ button
[ type_ "submit"
, class S.primaryButton
]
[ text texts.loginButton
]
]
, resultMessage texts model
]
]
loginForm : Texts -> Flags -> Model -> Html Msg
loginForm texts flags model =
Html.form
[ action "#"
, onSubmit Authenticate
, autocomplete False
]
[ div [ class "flex flex-col mt-6" ]
[ label
[ for "username"
, class S.inputLabel
]
[ text texts.username
]
, div [ class "relative" ]
[ div [ class S.inputIcon ]
[ i [ class "fa fa-user" ] []
]
, input
[ type_ "text"
, name "username"
, autocomplete False
, onInput SetUsername
, value model.username
, autofocus True
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
, placeholder texts.collectiveSlashLogin
]
[]
]
]
, div [ class "flex flex-col my-3" ]
[ label
[ for "password"
, class S.inputLabel
]
[ text texts.password
]
, div [ class "relative" ]
[ div [ class S.inputIcon ]
[ i [ class "fa fa-lock" ] []
]
, input
[ type_ "password"
, name "password"
, autocomplete False
, onInput SetPassword
, value model.password
, class ("pl-10 pr-4 py-2 rounded-lg" ++ S.textInput)
, placeholder texts.password
]
[]
]
]
, div [ class "flex flex-col my-3" ]
[ label
[ class "inline-flex items-center"
, for "rememberme"
]
[ input
[ id "rememberme"
, type_ "checkbox"
, onCheck (\_ -> ToggleRememberMe)
, checked model.rememberMe
, name "rememberme"
, class S.checkboxInput
]
[]
, span
[ class "mb-1 ml-2 text-xs sm:text-sm tracking-wide my-1"
]
[ text texts.rememberMe
]
]
]
, div [ class "flex flex-col my-3" ]
[ button
[ type_ "submit"
, class S.primaryButton
]
[ text texts.loginButton
]
]
, resultMessage texts model
, div
[ class "flex justify-end text-sm pt-4"
, classList [ ( "hidden", flags.config.signupMode == "closed" ) ]
]
[ span []
[ text texts.noAccount
]
, a
[ Page.href RegisterPage
, class ("ml-2" ++ S.link)
]
[ i [ class "fa fa-user-plus mr-1" ] []
, text texts.signupLink
]
]
]
resultMessage : Texts -> Model -> Html Msg
resultMessage texts model =
case model.formState of