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 4964909d..a26ce488 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala @@ -7,9 +7,8 @@ package docspell.restserver.webapp import docspell.backend.signup.{Config => SignupConfig} -import docspell.common.LenientUri +import docspell.common.{Ident, LenientUri} import docspell.restserver.{BuildInfo, Config} - import io.circe._ import io.circe.generic.semiauto._ import yamusca.implicits._ @@ -25,7 +24,8 @@ case class Flags( maxPageSize: Int, maxNoteLength: Int, showClassificationSettings: Boolean, - uiVersion: Int + uiVersion: Int, + openIdAuth: List[Flags.OpenIdAuth] ) object Flags { @@ -40,9 +40,20 @@ object Flags { cfg.maxItemPageSize, cfg.maxNoteLength, cfg.showClassificationSettings, - uiVersion + uiVersion, + cfg.openid.filter(_.enabled).map(c => OpenIdAuth(c.provider.providerId, c.display)) ) + final case class OpenIdAuth(provider: Ident, name: String) + + object OpenIdAuth { + implicit val jsonDecoder: Decoder[OpenIdAuth] = + deriveDecoder[OpenIdAuth] + + implicit val jsonEncoder: Encoder[OpenIdAuth] = + deriveEncoder[OpenIdAuth] + } + private def getBaseUrl(cfg: Config): String = if (cfg.baseUrl.isLocal) cfg.baseUrl.rootPathToEmpty.path.asString else cfg.baseUrl.rootPathToEmpty.asString @@ -50,6 +61,10 @@ object Flags { implicit val jsonEncoder: Encoder[Flags] = deriveEncoder[Flags] + implicit def yamuscaIdentConverter: ValueConverter[Ident] = + ValueConverter.of(id => Value.fromString(id.id)) + implicit def yamuscaOpenIdAuthConverter: ValueConverter[OpenIdAuth] = + ValueConverter.deriveConverter[OpenIdAuth] implicit def yamuscaSignupModeConverter: ValueConverter[SignupConfig.Mode] = ValueConverter.of(m => Value.fromString(m.name)) implicit def yamuscaUriConverter: ValueConverter[LenientUri] = diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index c9486ec4..e9588df1 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -87,6 +87,7 @@ module Api exposing , mergeItems , moveAttachmentBefore , newInvite + , openIdAuthLink , postCustomField , postEquipment , postNewUser @@ -935,6 +936,11 @@ newInvite flags req receive = --- Login +openIdAuthLink : Flags -> String -> String +openIdAuthLink flags provider = + flags.config.baseUrl ++ "/api/v1/open/auth/openid/" ++ provider + + login : Flags -> UserPass -> (Result Http.Error AuthResult -> msg) -> Cmd msg login flags up receive = Http.post diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index fe44da24..f60b02a2 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -82,6 +82,12 @@ init key url flags_ settings = ( csm, csc ) = Page.CollectiveSettings.Data.init flags + ( loginm, loginc ) = + Page.Login.Data.init flags + (Page.loginPageReferrer page + |> Tuple.second + ) + homeViewMode = if settings.searchMenuVisible then Page.Home.Data.SearchView @@ -94,7 +100,7 @@ init key url flags_ settings = , page = page , version = Api.Model.VersionInfo.empty , homeModel = Page.Home.Data.init flags homeViewMode - , loginModel = Page.Login.Data.emptyModel + , loginModel = loginm , manageDataModel = mdm , collSettingsModel = csm , userSettingsModel = um @@ -116,6 +122,7 @@ init key url flags_ settings = [ Cmd.map UserSettingsMsg uc , Cmd.map ManageDataMsg mdc , Cmd.map CollSettingsMsg csc + , Cmd.map LoginMsg loginc ] ) diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index b5381433..280cb990 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) + , Page.goto (LoginPage ( Nothing, False )) , Sub.none ) @@ -216,20 +216,24 @@ updateWithSub msg model = NavRequest req -> case req of Internal url -> - let - isCurrent = - Page.fromUrl url - |> Maybe.map (\p -> p == model.page) - |> Maybe.withDefault True - in - ( model - , if isCurrent then - Cmd.none + if String.startsWith "/app" url.path then + let + isCurrent = + Page.fromUrl url + |> Maybe.map (\p -> p == model.page) + |> Maybe.withDefault True + in + ( model + , if isCurrent then + Cmd.none - else - Nav.pushUrl model.key (Url.toString url) - , Sub.none - ) + else + Nav.pushUrl model.key (Url.toString url) + , Sub.none + ) + + else + ( model, Nav.load <| Url.toString url, Sub.none ) External url -> ( model diff --git a/modules/webapp/src/main/elm/Data/Flags.elm b/modules/webapp/src/main/elm/Data/Flags.elm index df841b51..ddcf4f73 100644 --- a/modules/webapp/src/main/elm/Data/Flags.elm +++ b/modules/webapp/src/main/elm/Data/Flags.elm @@ -17,6 +17,12 @@ module Data.Flags exposing import Api.Model.AuthResult exposing (AuthResult) +type alias OpenIdAuth = + { provider : String + , name : String + } + + type alias Config = { appName : String , baseUrl : String @@ -27,6 +33,7 @@ type alias Config = , maxPageSize : Int , maxNoteLength : Int , showClassificationSettings : Bool + , openIdAuth : List OpenIdAuth } diff --git a/modules/webapp/src/main/elm/Messages/Comp/HttpError.elm b/modules/webapp/src/main/elm/Messages/Comp/HttpError.elm index 99cbaa7d..3209de14 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/HttpError.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/HttpError.elm @@ -26,6 +26,7 @@ gb err = , invalidInput = "Invalid input when processing the request." , notFound = "The requested resource doesn't exist." , invalidBody = \str -> "There was an error decoding the response: " ++ str + , accessDenied = "Access denied" } in errorToString texts err @@ -44,6 +45,7 @@ de err = , invalidInput = "Die Daten im Request waren ungültig." , notFound = "Die angegebene Ressource wurde nicht gefunden." , invalidBody = \str -> "Es gab einen Fehler beim Dekodieren der Antwort: " ++ str + , accessDenied = "Zugriff verweigert" } in errorToString texts err @@ -61,6 +63,7 @@ type alias Texts = , invalidInput : String , notFound : String , invalidBody : String -> String + , accessDenied : String } @@ -90,6 +93,9 @@ errorToString texts error = if sc == 404 then texts.notFound + else if sc == 403 then + texts.accessDenied + else if sc >= 400 && sc < 500 then texts.invalidInput diff --git a/modules/webapp/src/main/elm/Messages/Page/Login.elm b/modules/webapp/src/main/elm/Messages/Page/Login.elm index 10f8c4bf..1b7c606a 100644 --- a/modules/webapp/src/main/elm/Messages/Page/Login.elm +++ b/modules/webapp/src/main/elm/Messages/Page/Login.elm @@ -29,6 +29,7 @@ type alias Texts = , noAccount : String , signupLink : String , otpCode : String + , or : String } @@ -47,6 +48,7 @@ gb = , noAccount = "No account?" , signupLink = "Sign up!" , otpCode = "Authentication code" + , or = "Or" } @@ -65,4 +67,5 @@ de = , noAccount = "Kein Konto?" , signupLink = "Hier registrieren!" , otpCode = "Authentifizierungscode" + , or = "Oder" } diff --git a/modules/webapp/src/main/elm/Page.elm b/modules/webapp/src/main/elm/Page.elm index d6a4afa4..a25fa305 100644 --- a/modules/webapp/src/main/elm/Page.elm +++ b/modules/webapp/src/main/elm/Page.elm @@ -33,7 +33,7 @@ import Util.Maybe type Page = HomePage - | LoginPage (Maybe Page) + | LoginPage ( Maybe Page, Bool ) | ManageDataPage | CollectiveSettingPage | UserSettingPage @@ -99,10 +99,10 @@ loginPage : Page -> Page loginPage p = case p of LoginPage _ -> - LoginPage Nothing + LoginPage ( Nothing, False ) _ -> - LoginPage (Just p) + LoginPage ( Just p, False ) pageName : Page -> String @@ -144,14 +144,14 @@ pageName page = "Item" -loginPageReferrer : Page -> Maybe Page +loginPageReferrer : Page -> ( Maybe Page, Bool ) loginPageReferrer page = case page of - LoginPage r -> - r + LoginPage ( r, flag ) -> + ( r, flag ) _ -> - Nothing + ( Nothing, False ) uploadId : Page -> Maybe String @@ -170,7 +170,7 @@ pageToString page = HomePage -> "/app/home" - LoginPage referer -> + LoginPage ( referer, _ ) -> case referer of Just (LoginPage _) -> "/app/login" @@ -253,7 +253,7 @@ parser = , s pathPrefix s "home" ] ) - , Parser.map LoginPage (s pathPrefix s "login" pageQuery) + , Parser.map LoginPage (s pathPrefix s "login" loginPageParser) , Parser.map ManageDataPage (s pathPrefix s "managedata") , Parser.map CollectiveSettingPage (s pathPrefix s "csettings") , Parser.map UserSettingPage (s pathPrefix s "usettings") @@ -280,6 +280,16 @@ fromString str = fromUrl url +loginPageOAuthQuery : Query.Parser Bool +loginPageOAuthQuery = + Query.map Util.Maybe.nonEmpty (Query.string "openid") + + +loginPageParser : Query.Parser ( Maybe Page, Bool ) +loginPageParser = + Query.map2 Tuple.pair pageQuery loginPageOAuthQuery + + pageQuery : Query.Parser (Maybe Page) pageQuery = let diff --git a/modules/webapp/src/main/elm/Page/Login/Data.elm b/modules/webapp/src/main/elm/Page/Login/Data.elm index 33bd3913..c3a17d2e 100644 --- a/modules/webapp/src/main/elm/Page/Login/Data.elm +++ b/modules/webapp/src/main/elm/Page/Login/Data.elm @@ -11,9 +11,12 @@ module Page.Login.Data exposing , Model , Msg(..) , emptyModel + , init ) +import Api import Api.Model.AuthResult exposing (AuthResult) +import Data.Flags exposing (Flags) import Http import Page exposing (Page(..)) @@ -51,6 +54,19 @@ emptyModel = } +init : Flags -> Bool -> ( Model, Cmd Msg ) +init flags oauth = + let + cmd = + if oauth then + Api.loginSession flags AuthResp + + else + Cmd.none + in + ( emptyModel, cmd ) + + type Msg = SetUsername String | SetPassword String diff --git a/modules/webapp/src/main/elm/Page/Login/Update.elm b/modules/webapp/src/main/elm/Page/Login/Update.elm index 6f3c82e6..31e1375f 100644 --- a/modules/webapp/src/main/elm/Page/Login/Update.elm +++ b/modules/webapp/src/main/elm/Page/Login/Update.elm @@ -15,8 +15,8 @@ import Page.Login.Data exposing (..) import Ports -update : Maybe Page -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult ) -update referrer flags msg model = +update : ( Maybe Page, Bool ) -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe AuthResult ) +update ( referrer, oauth ) flags msg model = case msg of SetUsername str -> ( { model | username = str }, Cmd.none, Nothing ) diff --git a/modules/webapp/src/main/elm/Page/Login/View2.elm b/modules/webapp/src/main/elm/Page/Login/View2.elm index c5e06a2b..ddbecacf 100644 --- a/modules/webapp/src/main/elm/Page/Login/View2.elm +++ b/modules/webapp/src/main/elm/Page/Login/View2.elm @@ -7,8 +7,10 @@ module Page.Login.View2 exposing (viewContent, viewSidebar) +import Api import Api.Model.AuthResult exposing (AuthResult) import Api.Model.VersionInfo exposing (VersionInfo) +import Comp.Basic as B import Data.Flags exposing (Flags) import Data.UiSettings exposing (UiSettings) import Html exposing (..) @@ -53,6 +55,7 @@ viewContent texts flags versionInfo _ model = StepLogin -> loginForm texts flags model + , openIdLinks texts flags ] , a [ class "inline-flex items-center mt-4 text-xs opacity-50 hover:opacity-90" @@ -72,6 +75,35 @@ viewContent texts flags versionInfo _ model = ] +openIdLinks : Texts -> Flags -> Html Msg +openIdLinks texts flags = + let + renderLink prov = + a + [ href (Api.openIdAuthLink flags prov.provider) + , class S.link + ] + [ i [ class "fab fa-openid mr-1" ] [] + , text prov.name + ] + in + case flags.config.openIdAuth of + [] -> + span [ class "hidden" ] [] + + provs -> + div [ class "mt-3" ] + [ B.horizontalDivider + { label = texts.or + , topCss = "w-2/3 mb-4 hidden md:inline-flex w-full" + , labelCss = "px-4 bg-gray-200 bg-opacity-50" + , lineColor = "bg-gray-300 dark:bg-bluegray-600" + } + , div [ class "flex flex-row space-x-4 items-center justify-center" ] + (List.map renderLink provs) + ] + + otpForm : Texts -> Flags -> Model -> AuthResult -> Html Msg otpForm texts flags model acc = Html.form diff --git a/modules/webapp/src/main/elm/Page/Register/Update.elm b/modules/webapp/src/main/elm/Page/Register/Update.elm index 8eb22439..f0d72784 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) + Page.goto (LoginPage ( Nothing, False )) 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 2942c0c0..867f451b 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) + [ Page.href (LoginPage ( Nothing, False )) , class ("ml-2" ++ S.link) ] [ i [ class "fa fa-user-plus mr-1" ] []