mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Fix OTP authentication for external accounts
This commit is contained in:
parent
8158e36d40
commit
468ba90158
@ -9,7 +9,9 @@ package docspell.oidc
|
|||||||
import cats.data.{Kleisli, OptionT}
|
import cats.data.{Kleisli, OptionT}
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
import org.http4s.client.Client
|
import org.http4s.client.Client
|
||||||
|
@ -78,8 +78,8 @@ object Config {
|
|||||||
object FullTextSearch {}
|
object FullTextSearch {}
|
||||||
|
|
||||||
final case class OpenIdConfig(
|
final case class OpenIdConfig(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
display: String,
|
display: String,
|
||||||
collectiveKey: OpenId.UserInfo.Extractor,
|
collectiveKey: OpenId.UserInfo.Extractor,
|
||||||
userKey: String,
|
userKey: String,
|
||||||
provider: ProviderConfig
|
provider: ProviderConfig
|
||||||
|
@ -54,7 +54,9 @@ object OpenId {
|
|||||||
|
|
||||||
extractColl match {
|
extractColl match {
|
||||||
case ExtractResult.Failure(message) =>
|
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)
|
TemporaryRedirect(location)
|
||||||
|
|
||||||
case ExtractResult.Account(accountId) =>
|
case ExtractResult.Account(accountId) =>
|
||||||
@ -63,7 +65,9 @@ object OpenId {
|
|||||||
case ExtractResult.Identifier(coll) =>
|
case ExtractResult.Identifier(coll) =>
|
||||||
Extractor.Lookup(cfg.userKey).find(userJson) match {
|
Extractor.Lookup(cfg.userKey).find(userJson) match {
|
||||||
case ExtractResult.Failure(message) =>
|
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)
|
TemporaryRedirect(location)
|
||||||
|
|
||||||
case ExtractResult.Identifier(name) =>
|
case ExtractResult.Identifier(name) =>
|
||||||
@ -144,7 +148,15 @@ object OpenId {
|
|||||||
login <- backend.login.loginExternal(config.auth)(accountId)
|
login <- backend.login.loginExternal(config.auth)(accountId)
|
||||||
resp <- login match {
|
resp <- login match {
|
||||||
case Login.Result.Ok(session, _) =>
|
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)))
|
.map(_.addCookie(CookieData(session).asCookie(baseUrl)))
|
||||||
|
|
||||||
case failed =>
|
case failed =>
|
||||||
|
@ -9,6 +9,7 @@ package docspell.restserver.webapp
|
|||||||
import docspell.backend.signup.{Config => SignupConfig}
|
import docspell.backend.signup.{Config => SignupConfig}
|
||||||
import docspell.common.{Ident, LenientUri}
|
import docspell.common.{Ident, LenientUri}
|
||||||
import docspell.restserver.{BuildInfo, Config}
|
import docspell.restserver.{BuildInfo, Config}
|
||||||
|
|
||||||
import io.circe._
|
import io.circe._
|
||||||
import io.circe.generic.semiauto._
|
import io.circe.generic.semiauto._
|
||||||
import yamusca.implicits._
|
import yamusca.implicits._
|
||||||
|
@ -83,10 +83,7 @@ init key url flags_ settings =
|
|||||||
Page.CollectiveSettings.Data.init flags
|
Page.CollectiveSettings.Data.init flags
|
||||||
|
|
||||||
( loginm, loginc ) =
|
( loginm, loginc ) =
|
||||||
Page.Login.Data.init flags
|
Page.Login.Data.init flags (Page.loginPageReferrer page)
|
||||||
(Page.loginPageReferrer page
|
|
||||||
|> Tuple.second
|
|
||||||
)
|
|
||||||
|
|
||||||
homeViewMode =
|
homeViewMode =
|
||||||
if settings.searchMenuVisible then
|
if settings.searchMenuVisible then
|
||||||
|
@ -158,7 +158,7 @@ updateWithSub msg model =
|
|||||||
|
|
||||||
LogoutResp _ ->
|
LogoutResp _ ->
|
||||||
( { model | loginModel = Page.Login.Data.emptyModel }
|
( { model | loginModel = Page.Login.Data.emptyModel }
|
||||||
, Page.goto (LoginPage ( Nothing, False ))
|
, Page.goto (LoginPage Page.emptyLoginData)
|
||||||
, Sub.none
|
, Sub.none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
|
|
||||||
module Page exposing
|
module Page exposing
|
||||||
( Page(..)
|
( LoginData
|
||||||
|
, Page(..)
|
||||||
|
, emptyLoginData
|
||||||
, fromUrl
|
, fromUrl
|
||||||
, goto
|
, goto
|
||||||
, hasSidebar
|
, hasSidebar
|
||||||
@ -31,9 +33,24 @@ import Url.Parser.Query as Query
|
|||||||
import Util.Maybe
|
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
|
type Page
|
||||||
= HomePage
|
= HomePage
|
||||||
| LoginPage ( Maybe Page, Bool )
|
| LoginPage LoginData
|
||||||
| ManageDataPage
|
| ManageDataPage
|
||||||
| CollectiveSettingPage
|
| CollectiveSettingPage
|
||||||
| UserSettingPage
|
| UserSettingPage
|
||||||
@ -99,10 +116,10 @@ loginPage : Page -> Page
|
|||||||
loginPage p =
|
loginPage p =
|
||||||
case p of
|
case p of
|
||||||
LoginPage _ ->
|
LoginPage _ ->
|
||||||
LoginPage ( Nothing, False )
|
LoginPage emptyLoginData
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
LoginPage ( Just p, False )
|
LoginPage { emptyLoginData | referrer = Just p }
|
||||||
|
|
||||||
|
|
||||||
pageName : Page -> String
|
pageName : Page -> String
|
||||||
@ -144,14 +161,14 @@ pageName page =
|
|||||||
"Item"
|
"Item"
|
||||||
|
|
||||||
|
|
||||||
loginPageReferrer : Page -> ( Maybe Page, Bool )
|
loginPageReferrer : Page -> LoginData
|
||||||
loginPageReferrer page =
|
loginPageReferrer page =
|
||||||
case page of
|
case page of
|
||||||
LoginPage ( r, flag ) ->
|
LoginPage data ->
|
||||||
( r, flag )
|
data
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
( Nothing, False )
|
emptyLoginData
|
||||||
|
|
||||||
|
|
||||||
uploadId : Page -> Maybe String
|
uploadId : Page -> Maybe String
|
||||||
@ -170,8 +187,8 @@ pageToString page =
|
|||||||
HomePage ->
|
HomePage ->
|
||||||
"/app/home"
|
"/app/home"
|
||||||
|
|
||||||
LoginPage ( referer, _ ) ->
|
LoginPage data ->
|
||||||
case referer of
|
case data.referrer of
|
||||||
Just (LoginPage _) ->
|
Just (LoginPage _) ->
|
||||||
"/app/login"
|
"/app/login"
|
||||||
|
|
||||||
@ -280,14 +297,19 @@ fromString str =
|
|||||||
fromUrl url
|
fromUrl url
|
||||||
|
|
||||||
|
|
||||||
loginPageOAuthQuery : Query.Parser Bool
|
loginPageOAuthQuery : Query.Parser Int
|
||||||
loginPageOAuthQuery =
|
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 =
|
loginPageParser =
|
||||||
Query.map2 Tuple.pair pageQuery loginPageOAuthQuery
|
Query.map3 LoginData pageQuery loginPageSessionQuery loginPageOAuthQuery
|
||||||
|
|
||||||
|
|
||||||
pageQuery : Query.Parser (Maybe Page)
|
pageQuery : Query.Parser (Maybe Page)
|
||||||
|
@ -18,7 +18,7 @@ import Api
|
|||||||
import Api.Model.AuthResult exposing (AuthResult)
|
import Api.Model.AuthResult exposing (AuthResult)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Http
|
import Http
|
||||||
import Page exposing (Page(..))
|
import Page exposing (LoginData, Page(..))
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
@ -40,7 +40,7 @@ type FormState
|
|||||||
|
|
||||||
type AuthStep
|
type AuthStep
|
||||||
= StepLogin
|
= StepLogin
|
||||||
| StepOtp AuthResult
|
| StepOtp String
|
||||||
|
|
||||||
|
|
||||||
emptyModel : Model
|
emptyModel : Model
|
||||||
@ -54,11 +54,11 @@ emptyModel =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> Bool -> ( Model, Cmd Msg )
|
init : Flags -> LoginData -> ( Model, Cmd Msg )
|
||||||
init flags oauth =
|
init flags ld =
|
||||||
let
|
let
|
||||||
cmd =
|
cmd =
|
||||||
if oauth then
|
if ld.openid > 0 then
|
||||||
Api.loginSession flags AuthResp
|
Api.loginSession flags AuthResp
|
||||||
|
|
||||||
else
|
else
|
||||||
@ -74,4 +74,4 @@ type Msg
|
|||||||
| Authenticate
|
| Authenticate
|
||||||
| AuthResp (Result Http.Error AuthResult)
|
| AuthResp (Result Http.Error AuthResult)
|
||||||
| SetOtp String
|
| SetOtp String
|
||||||
| AuthOtp AuthResult
|
| AuthOtp String
|
||||||
|
@ -10,13 +10,13 @@ module Page.Login.Update exposing (update)
|
|||||||
import Api
|
import Api
|
||||||
import Api.Model.AuthResult exposing (AuthResult)
|
import Api.Model.AuthResult exposing (AuthResult)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Page exposing (Page(..))
|
import Page exposing (LoginData, Page(..))
|
||||||
import Page.Login.Data exposing (..)
|
import Page.Login.Data exposing (..)
|
||||||
import Ports
|
import Ports
|
||||||
|
|
||||||
|
|
||||||
update : ( Maybe Page, Bool ) -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult )
|
update : LoginData -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult )
|
||||||
update ( referrer, oauth ) flags msg model =
|
update loginData flags msg model =
|
||||||
case msg of
|
case msg of
|
||||||
SetUsername str ->
|
SetUsername str ->
|
||||||
( { model | username = str }, Cmd.none, Nothing )
|
( { model | username = str }, Cmd.none, Nothing )
|
||||||
@ -40,11 +40,11 @@ update ( referrer, oauth ) flags msg model =
|
|||||||
in
|
in
|
||||||
( model, Api.login flags userPass AuthResp, Nothing )
|
( model, Api.login flags userPass AuthResp, Nothing )
|
||||||
|
|
||||||
AuthOtp acc ->
|
AuthOtp token ->
|
||||||
let
|
let
|
||||||
sf =
|
sf =
|
||||||
{ rememberMe = model.rememberMe
|
{ rememberMe = model.rememberMe
|
||||||
, token = Maybe.withDefault "" acc.token
|
, token = token
|
||||||
, otp = model.otp
|
, otp = model.otp
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
@ -53,7 +53,7 @@ update ( referrer, oauth ) flags msg model =
|
|||||||
AuthResp (Ok lr) ->
|
AuthResp (Ok lr) ->
|
||||||
let
|
let
|
||||||
gotoRef =
|
gotoRef =
|
||||||
Maybe.withDefault HomePage referrer |> Page.goto
|
Maybe.withDefault HomePage loginData.referrer |> Page.goto
|
||||||
in
|
in
|
||||||
if lr.success && not lr.requireSecondFactor then
|
if lr.success && not lr.requireSecondFactor then
|
||||||
( { model | formState = AuthSuccess lr, password = "" }
|
( { model | formState = AuthSuccess lr, password = "" }
|
||||||
@ -62,7 +62,11 @@ update ( referrer, oauth ) flags msg model =
|
|||||||
)
|
)
|
||||||
|
|
||||||
else if lr.success && lr.requireSecondFactor then
|
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
|
, Cmd.none
|
||||||
, Nothing
|
, Nothing
|
||||||
)
|
)
|
||||||
@ -77,11 +81,22 @@ update ( referrer, oauth ) flags msg model =
|
|||||||
let
|
let
|
||||||
empty =
|
empty =
|
||||||
Api.Model.AuthResult.empty
|
Api.Model.AuthResult.empty
|
||||||
|
|
||||||
|
session =
|
||||||
|
Maybe.withDefault "" loginData.session
|
||||||
in
|
in
|
||||||
( { model | password = "", formState = HttpError err }
|
-- A value of 2 indicates that TOTP is required
|
||||||
, Ports.removeAccount ()
|
if loginData.openid == 2 then
|
||||||
, Just empty
|
( { model | formState = FormInitial, authStep = StepOtp session, password = "" }
|
||||||
)
|
, Cmd.none
|
||||||
|
, Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | password = "", formState = HttpError err }
|
||||||
|
, Ports.removeAccount ()
|
||||||
|
, Just empty
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
setAccount : AuthResult -> Cmd msg
|
setAccount : AuthResult -> Cmd msg
|
||||||
|
@ -104,11 +104,11 @@ openIdLinks texts flags =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
otpForm : Texts -> Flags -> Model -> AuthResult -> Html Msg
|
otpForm : Texts -> Flags -> Model -> String -> Html Msg
|
||||||
otpForm texts flags model acc =
|
otpForm texts flags model token =
|
||||||
Html.form
|
Html.form
|
||||||
[ action "#"
|
[ action "#"
|
||||||
, onSubmit (AuthOtp acc)
|
, onSubmit (AuthOtp token)
|
||||||
, autocomplete False
|
, autocomplete False
|
||||||
]
|
]
|
||||||
[ div [ class "flex flex-col mt-6" ]
|
[ div [ class "flex flex-col mt-6" ]
|
||||||
|
@ -97,7 +97,7 @@ update flags msg model =
|
|||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
if r.success then
|
if r.success then
|
||||||
Page.goto (LoginPage ( Nothing, False ))
|
Page.goto (LoginPage Page.emptyLoginData)
|
||||||
|
|
||||||
else
|
else
|
||||||
Cmd.none
|
Cmd.none
|
||||||
|
@ -232,7 +232,7 @@ viewContent texts flags _ model =
|
|||||||
[ text texts.alreadySignedUp
|
[ text texts.alreadySignedUp
|
||||||
]
|
]
|
||||||
, a
|
, a
|
||||||
[ Page.href (LoginPage ( Nothing, False ))
|
[ Page.href (LoginPage Page.emptyLoginData)
|
||||||
, class ("ml-2" ++ S.link)
|
, class ("ml-2" ++ S.link)
|
||||||
]
|
]
|
||||||
[ i [ class "fa fa-user-plus mr-1" ] []
|
[ i [ class "fa fa-user-plus mr-1" ] []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user