diff --git a/modules/oidc/src/main/scala/docspell/oidc/CodeFlowRoutes.scala b/modules/oidc/src/main/scala/docspell/oidc/CodeFlowRoutes.scala index ca0e51b5..931fc8c1 100644 --- a/modules/oidc/src/main/scala/docspell/oidc/CodeFlowRoutes.scala +++ b/modules/oidc/src/main/scala/docspell/oidc/CodeFlowRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/Config.scala b/modules/restserver/src/main/scala/docspell/restserver/Config.scala index c0f7afe2..039f1848 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/Config.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/Config.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/auth/OpenId.scala b/modules/restserver/src/main/scala/docspell/restserver/auth/OpenId.scala index ae762755..76129fa8 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/auth/OpenId.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/auth/OpenId.scala @@ -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 => diff --git a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala index a26ce488..1f159926 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala @@ -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._ diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index f60b02a2..6bb45b65 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -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 diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index 280cb990..e0cce73b 100644 --- a/modules/webapp/src/main/elm/App/Update.elm +++ b/modules/webapp/src/main/elm/App/Update.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Page.elm b/modules/webapp/src/main/elm/Page.elm index a25fa305..c5f78960 100644 --- a/modules/webapp/src/main/elm/Page.elm +++ b/modules/webapp/src/main/elm/Page.elm @@ -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) diff --git a/modules/webapp/src/main/elm/Page/Login/Data.elm b/modules/webapp/src/main/elm/Page/Login/Data.elm index c3a17d2e..6628fec2 100644 --- a/modules/webapp/src/main/elm/Page/Login/Data.elm +++ b/modules/webapp/src/main/elm/Page/Login/Data.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Page/Login/Update.elm b/modules/webapp/src/main/elm/Page/Login/Update.elm index 31e1375f..3afa07cb 100644 --- a/modules/webapp/src/main/elm/Page/Login/Update.elm +++ b/modules/webapp/src/main/elm/Page/Login/Update.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Page/Login/View2.elm b/modules/webapp/src/main/elm/Page/Login/View2.elm index ddbecacf..79182722 100644 --- a/modules/webapp/src/main/elm/Page/Login/View2.elm +++ b/modules/webapp/src/main/elm/Page/Login/View2.elm @@ -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" ] diff --git a/modules/webapp/src/main/elm/Page/Register/Update.elm b/modules/webapp/src/main/elm/Page/Register/Update.elm index f0d72784..2479ce2c 100644 --- a/modules/webapp/src/main/elm/Page/Register/Update.elm +++ b/modules/webapp/src/main/elm/Page/Register/Update.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Page/Register/View2.elm b/modules/webapp/src/main/elm/Page/Register/View2.elm index 867f451b..e7dd97f5 100644 --- a/modules/webapp/src/main/elm/Page/Register/View2.elm +++ b/modules/webapp/src/main/elm/Page/Register/View2.elm @@ -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" ] []