Add OpenID support to webapp

This commit is contained in:
eikek
2021-09-05 23:43:07 +02:00
parent f8362329a9
commit 984dda9da0
13 changed files with 138 additions and 32 deletions

View File

@ -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] =

View File

@ -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

View File

@ -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
]
)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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" ] []