Fix OTP authentication for external accounts

This commit is contained in:
eikek 2021-09-06 01:07:31 +02:00
parent 8158e36d40
commit 468ba90158
12 changed files with 95 additions and 46 deletions

View File

@ -9,7 +9,9 @@ package docspell.oidc
import cats.data.{Kleisli, OptionT}
import cats.effect._
import cats.implicits._
import docspell.common._
import org.http4s.HttpRoutes
import org.http4s._
import org.http4s.client.Client

View File

@ -78,8 +78,8 @@ object Config {
object FullTextSearch {}
final case class OpenIdConfig(
enabled: Boolean,
display: String,
enabled: Boolean,
display: String,
collectiveKey: OpenId.UserInfo.Extractor,
userKey: String,
provider: ProviderConfig

View File

@ -54,7 +54,9 @@ object OpenId {
extractColl match {
case ExtractResult.Failure(message) =>
logger.warn(s"Can't retrieve user data using collective-key=${cfg.collectiveKey.asString}: $message") *>
logger.warn(
s"Can't retrieve user data using collective-key=${cfg.collectiveKey.asString}: $message"
) *>
TemporaryRedirect(location)
case ExtractResult.Account(accountId) =>
@ -63,7 +65,9 @@ object OpenId {
case ExtractResult.Identifier(coll) =>
Extractor.Lookup(cfg.userKey).find(userJson) match {
case ExtractResult.Failure(message) =>
logger.warn(s"Can't retrieve user data using user-key=${cfg.userKey}: $message") *>
logger.warn(
s"Can't retrieve user data using user-key=${cfg.userKey}: $message"
) *>
TemporaryRedirect(location)
case ExtractResult.Identifier(name) =>
@ -144,7 +148,15 @@ object OpenId {
login <- backend.login.loginExternal(config.auth)(accountId)
resp <- login match {
case Login.Result.Ok(session, _) =>
TemporaryRedirect(location)
val loc =
if (session.requireSecondFactor)
location.copy(uri =
location.uri
.withQueryParam("openid", "2")
.withQueryParam("auth", session.asString)
)
else location
TemporaryRedirect(loc)
.map(_.addCookie(CookieData(session).asCookie(baseUrl)))
case failed =>

View File

@ -9,6 +9,7 @@ package docspell.restserver.webapp
import docspell.backend.signup.{Config => SignupConfig}
import docspell.common.{Ident, LenientUri}
import docspell.restserver.{BuildInfo, Config}
import io.circe._
import io.circe.generic.semiauto._
import yamusca.implicits._

View File

@ -83,10 +83,7 @@ init key url flags_ settings =
Page.CollectiveSettings.Data.init flags
( loginm, loginc ) =
Page.Login.Data.init flags
(Page.loginPageReferrer page
|> Tuple.second
)
Page.Login.Data.init flags (Page.loginPageReferrer page)
homeViewMode =
if settings.searchMenuVisible then

View File

@ -158,7 +158,7 @@ updateWithSub msg model =
LogoutResp _ ->
( { model | loginModel = Page.Login.Data.emptyModel }
, Page.goto (LoginPage ( Nothing, False ))
, Page.goto (LoginPage Page.emptyLoginData)
, Sub.none
)

View File

@ -6,7 +6,9 @@
module Page exposing
( Page(..)
( LoginData
, Page(..)
, emptyLoginData
, fromUrl
, goto
, hasSidebar
@ -31,9 +33,24 @@ import Url.Parser.Query as Query
import Util.Maybe
type alias LoginData =
{ referrer : Maybe Page
, session : Maybe String
, openid : Int
}
emptyLoginData : LoginData
emptyLoginData =
{ referrer = Nothing
, session = Nothing
, openid = 0
}
type Page
= HomePage
| LoginPage ( Maybe Page, Bool )
| LoginPage LoginData
| ManageDataPage
| CollectiveSettingPage
| UserSettingPage
@ -99,10 +116,10 @@ loginPage : Page -> Page
loginPage p =
case p of
LoginPage _ ->
LoginPage ( Nothing, False )
LoginPage emptyLoginData
_ ->
LoginPage ( Just p, False )
LoginPage { emptyLoginData | referrer = Just p }
pageName : Page -> String
@ -144,14 +161,14 @@ pageName page =
"Item"
loginPageReferrer : Page -> ( Maybe Page, Bool )
loginPageReferrer : Page -> LoginData
loginPageReferrer page =
case page of
LoginPage ( r, flag ) ->
( r, flag )
LoginPage data ->
data
_ ->
( Nothing, False )
emptyLoginData
uploadId : Page -> Maybe String
@ -170,8 +187,8 @@ pageToString page =
HomePage ->
"/app/home"
LoginPage ( referer, _ ) ->
case referer of
LoginPage data ->
case data.referrer of
Just (LoginPage _) ->
"/app/login"
@ -280,14 +297,19 @@ fromString str =
fromUrl url
loginPageOAuthQuery : Query.Parser Bool
loginPageOAuthQuery : Query.Parser Int
loginPageOAuthQuery =
Query.map Util.Maybe.nonEmpty (Query.string "openid")
Query.map (Maybe.withDefault 0) (Query.int "openid")
loginPageParser : Query.Parser ( Maybe Page, Bool )
loginPageSessionQuery : Query.Parser (Maybe String)
loginPageSessionQuery =
Query.string "auth"
loginPageParser : Query.Parser LoginData
loginPageParser =
Query.map2 Tuple.pair pageQuery loginPageOAuthQuery
Query.map3 LoginData pageQuery loginPageSessionQuery loginPageOAuthQuery
pageQuery : Query.Parser (Maybe Page)

View File

@ -18,7 +18,7 @@ import Api
import Api.Model.AuthResult exposing (AuthResult)
import Data.Flags exposing (Flags)
import Http
import Page exposing (Page(..))
import Page exposing (LoginData, Page(..))
type alias Model =
@ -40,7 +40,7 @@ type FormState
type AuthStep
= StepLogin
| StepOtp AuthResult
| StepOtp String
emptyModel : Model
@ -54,11 +54,11 @@ emptyModel =
}
init : Flags -> Bool -> ( Model, Cmd Msg )
init flags oauth =
init : Flags -> LoginData -> ( Model, Cmd Msg )
init flags ld =
let
cmd =
if oauth then
if ld.openid > 0 then
Api.loginSession flags AuthResp
else
@ -74,4 +74,4 @@ type Msg
| Authenticate
| AuthResp (Result Http.Error AuthResult)
| SetOtp String
| AuthOtp AuthResult
| AuthOtp String

View File

@ -10,13 +10,13 @@ module Page.Login.Update exposing (update)
import Api
import Api.Model.AuthResult exposing (AuthResult)
import Data.Flags exposing (Flags)
import Page exposing (Page(..))
import Page exposing (LoginData, Page(..))
import Page.Login.Data exposing (..)
import Ports
update : ( Maybe Page, Bool ) -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult )
update ( referrer, oauth ) flags msg model =
update : LoginData -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult )
update loginData flags msg model =
case msg of
SetUsername str ->
( { model | username = str }, Cmd.none, Nothing )
@ -40,11 +40,11 @@ update ( referrer, oauth ) flags msg model =
in
( model, Api.login flags userPass AuthResp, Nothing )
AuthOtp acc ->
AuthOtp token ->
let
sf =
{ rememberMe = model.rememberMe
, token = Maybe.withDefault "" acc.token
, token = token
, otp = model.otp
}
in
@ -53,7 +53,7 @@ update ( referrer, oauth ) flags msg model =
AuthResp (Ok lr) ->
let
gotoRef =
Maybe.withDefault HomePage referrer |> Page.goto
Maybe.withDefault HomePage loginData.referrer |> Page.goto
in
if lr.success && not lr.requireSecondFactor then
( { model | formState = AuthSuccess lr, password = "" }
@ -62,7 +62,11 @@ update ( referrer, oauth ) flags msg model =
)
else if lr.success && lr.requireSecondFactor then
( { model | formState = FormInitial, authStep = StepOtp lr, password = "" }
( { model
| formState = FormInitial
, authStep = StepOtp <| Maybe.withDefault "" lr.token
, password = ""
}
, Cmd.none
, Nothing
)
@ -77,11 +81,22 @@ update ( referrer, oauth ) flags msg model =
let
empty =
Api.Model.AuthResult.empty
session =
Maybe.withDefault "" loginData.session
in
( { model | password = "", formState = HttpError err }
, Ports.removeAccount ()
, Just empty
)
-- A value of 2 indicates that TOTP is required
if loginData.openid == 2 then
( { model | formState = FormInitial, authStep = StepOtp session, password = "" }
, Cmd.none
, Nothing
)
else
( { model | password = "", formState = HttpError err }
, Ports.removeAccount ()
, Just empty
)
setAccount : AuthResult -> Cmd msg

View File

@ -104,11 +104,11 @@ openIdLinks texts flags =
]
otpForm : Texts -> Flags -> Model -> AuthResult -> Html Msg
otpForm texts flags model acc =
otpForm : Texts -> Flags -> Model -> String -> Html Msg
otpForm texts flags model token =
Html.form
[ action "#"
, onSubmit (AuthOtp acc)
, onSubmit (AuthOtp token)
, autocomplete False
]
[ div [ class "flex flex-col mt-6" ]

View File

@ -97,7 +97,7 @@ update flags msg model =
cmd =
if r.success then
Page.goto (LoginPage ( Nothing, False ))
Page.goto (LoginPage Page.emptyLoginData)
else
Cmd.none

View File

@ -232,7 +232,7 @@ viewContent texts flags _ model =
[ text texts.alreadySignedUp
]
, a
[ Page.href (LoginPage ( Nothing, False ))
[ Page.href (LoginPage Page.emptyLoginData)
, class ("ml-2" ++ S.link)
]
[ i [ class "fa fa-user-plus mr-1" ] []