2023-09-24 22:36:35 +02:00
< template >
2023-11-22 19:38:11 +01:00
< div id = "authentication-settings" >
2023-09-24 22:36:35 +02:00
< app -settings -content :header-text ="$strings.HeaderAuthentication" >
2024-01-27 00:08:23 +01:00
< div class = "w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25" >
< div class = "flex items-center" >
< ui -checkbox v -model = " showCustomLoginMessage " checkbox -bg = " bg " / >
< p class = "text-lg pl-4" > Custom Message on Login < / p >
< / div >
< transition name = "slide" >
< div v-if ="showCustomLoginMessage" class="w-full pt-4" >
< ui -rich -text -editor v -model = " newAuthSettings.authLoginCustomMessage " / >
< / div >
< / transition >
< / div >
2023-09-24 22:36:35 +02:00
< div class = "w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25" >
< div class = "flex items-center" >
< ui -checkbox v -model = " enableLocalAuth " checkbox -bg = " bg " / >
2023-11-22 19:55:01 +01:00
< p class = "text-lg pl-4" > { { $strings . HeaderPasswordAuthentication } } < / p >
2023-09-24 22:36:35 +02:00
< / div >
< / div >
< div class = "w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25" >
< div class = "flex items-center" >
< ui -checkbox v -model = " enableOpenIDAuth " checkbox -bg = " bg " / >
2023-11-22 19:55:01 +01:00
< p class = "text-lg pl-4" > { { $strings . HeaderOpenIDConnectAuthentication } } < / p >
2023-11-28 00:10:31 +01:00
< ui -tooltip :text ="$strings.LabelClickForMoreInfo" class = "inline-flex ml-2" >
< a href = "https://www.audiobookshelf.org/guides/oidc_authentication" target = "_blank" class = "inline-flex" >
< span class = "material-icons text-xl w-5 text-gray-200" > help _outline < / span >
< / a >
< / u i - t o o l t i p >
2023-09-24 22:36:35 +02:00
< / div >
2023-11-08 21:45:29 +01:00
< transition name = "slide" >
< div v-if ="enableOpenIDAuth" class="flex flex-wrap pt-4" >
< div class = "w-full flex items-center mb-2" >
< div class = "flex-grow" >
< ui -text -input -with -label ref = "issuerUrl" v -model = " newAuthSettings.authOpenIDIssuerURL " :disabled ="savingSettings" : label = "'Issuer URL'" / >
< / div >
< div class = "w-36 mx-1 mt-[1.375rem]" >
< ui -btn class = "h-[2.375rem] text-sm inline-flex items-center justify-center w-full" type = "button" :padding-y ="0" :padding-x ="4" @click.stop ="autoPopulateOIDCClick" >
< span class = "material-icons text-base" > auto _fix _high < / span >
< span class = "whitespace-nowrap break-keep pl-1" > Auto - populate < / span > < / u i - b t n
>
2023-11-05 21:11:37 +01:00
< / div >
2023-11-08 21:45:29 +01:00
< / div >
2023-09-24 22:36:35 +02:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "authorizationUrl" v -model = " newAuthSettings.authOpenIDAuthorizationURL " :disabled ="savingSettings" : label = "'Authorize URL'" class = "mb-2" / >
2023-09-24 22:36:35 +02:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "tokenUrl" v -model = " newAuthSettings.authOpenIDTokenURL " :disabled ="savingSettings" : label = "'Token URL'" class = "mb-2" / >
2023-09-24 22:36:35 +02:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "userInfoUrl" v -model = " newAuthSettings.authOpenIDUserInfoURL " :disabled ="savingSettings" : label = "'Userinfo URL'" class = "mb-2" / >
2023-09-24 22:36:35 +02:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "jwksUrl" v -model = " newAuthSettings.authOpenIDJwksURL " :disabled ="savingSettings" : label = "'JWKS URL'" class = "mb-2" / >
2023-11-04 21:36:43 +01:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "logoutUrl" v -model = " newAuthSettings.authOpenIDLogoutURL " :disabled ="savingSettings" : label = "'Logout URL'" class = "mb-2" / >
2023-11-04 21:36:43 +01:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "openidClientId" v -model = " newAuthSettings.authOpenIDClientID " :disabled ="savingSettings" : label = "'Client ID'" class = "mb-2" / >
2023-09-24 22:36:35 +02:00
2023-11-08 21:45:29 +01:00
< ui -text -input -with -label ref = "openidClientSecret" v -model = " newAuthSettings.authOpenIDClientSecret " :disabled ="savingSettings" : label = "'Client Secret'" class = "mb-2" / >
2023-11-02 19:55:01 +01:00
2023-12-04 22:36:34 +01:00
< ui -multi -select ref = "redirectUris" v -model = " newAuthSettings.authOpenIDMobileRedirectURIs " :items ="newAuthSettings.authOpenIDMobileRedirectURIs" :label ="$strings.LabelMobileRedirectURIs" class = "mb-2" :menuDisabled ="true" :disabled ="savingSettings" / >
< p class = "pl-4 text-sm text-gray-300 mb-2" v -html = " $ strings.LabelMobileRedirectURIsDescription " / >
2023-11-22 19:55:01 +01:00
< ui -text -input -with -label ref = "buttonTextInput" v -model = " newAuthSettings.authOpenIDButtonText " :disabled ="savingSettings" :label ="$strings.LabelButtonText" class = "mb-2" / >
2023-11-02 19:55:01 +01:00
2023-11-08 21:45:29 +01:00
< div class = "flex items-center pt-1 mb-2" >
< div class = "w-44" >
2023-11-22 19:55:01 +01:00
< ui -dropdown v -model = " newAuthSettings.authOpenIDMatchExistingBy " small :items ="matchingExistingOptions" :label ="$strings.LabelMatchExistingUsersBy" :disabled ="savingSettings" / >
2023-11-02 19:55:01 +01:00
< / div >
2023-11-22 19:55:01 +01:00
< p class = "pl-4 text-sm text-gray-300 mt-5" > { { $strings . LabelMatchExistingUsersByDescription } } < / p >
2023-09-24 22:36:35 +02:00
< / div >
2023-11-08 21:45:29 +01:00
2024-03-19 17:57:24 +01:00
< div class = "flex items-center py-4 px-1 w-full" >
2023-11-08 21:45:29 +01:00
< ui -toggle -switch labeledBy = "auto-redirect-toggle" v -model = " newAuthSettings.authOpenIDAutoLaunch " :disabled ="savingSettings" / >
2023-11-22 19:55:01 +01:00
< p id = "auto-redirect-toggle" class = "pl-4 whitespace-nowrap" > { { $strings . LabelAutoLaunch } } < / p >
< p class = "pl-4 text-sm text-gray-300" v -html = " $ strings.LabelAutoLaunchDescription " / >
2023-11-08 21:45:29 +01:00
< / div >
2024-03-19 17:57:24 +01:00
< div class = "flex items-center py-4 px-1 w-full" >
2023-11-08 21:45:29 +01:00
< ui -toggle -switch labeledBy = "auto-register-toggle" v -model = " newAuthSettings.authOpenIDAutoRegister " :disabled ="savingSettings" / >
2023-11-22 19:55:01 +01:00
< p id = "auto-register-toggle" class = "pl-4 whitespace-nowrap" > { { $strings . LabelAutoRegister } } < / p >
< p class = "pl-4 text-sm text-gray-300" > { { $strings . LabelAutoRegisterDescription } } < / p >
2023-11-08 21:45:29 +01:00
< / div >
2024-03-19 17:57:24 +01:00
< div class = "flex items-center pt-6 pb-1 px-1 w-full" > Leave the following options empty to disable advanced group and permissions assignment , automatically assigning 'User' group then . < / div >
< div class = "flex items-center mb-2" >
< div class = "w-96" >
< ui -text -input -with -label ref = "openidGroupClaim" v -model = " newAuthSettings.authOpenIDGroupClaim " :disabled ="savingSettings" :placeholder ="'groups'" : label = "'Group Claim'" / >
< / div >
< p class = "pl-4 text-sm text-gray-300 mt-5" >
Name of the OpenID claim that contains a list of the user 's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user' s group memberships , provided that these groups are named case - insensitively 'admin' , 'user' , or 'guest' in the claim . The claim should contain a list , and if a user belongs to
multiple groups , the application will assign the role corresponding to the highest level of access . If no group matches , access will be denied .
< / p >
< / div >
< div class = "flex mb-2" >
< div class = "w-96 pt-6" >
< ui -text -input -with -label ref = "openidAdvancedPermsClaim" v -model = " newAuthSettings.authOpenIDAdvancedPermsClaim " :disabled ="savingSettings" :placeholder ="'abspermissions'" : label = "'Advanced Permission Claim'" / >
< / div >
< div class = "pl-4 text-sm text-gray-300 mt-5 flex-column" >
< p class = "" >
Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non - admin roles ( < b > if configured < / b > ) . If the claim is missing from the response , access to ABS will be denied . If a single option is missing , it will be treated as < code > false < / code > . Ensure the identity provider ' s claim matches the expected structure :
< / p >
< pre class = "text-pre-wrap mt-2"
> { { newAuthSettings . authOpenIDSamplePermissions } }
< / pre >
< / div >
< / div >
2023-11-08 21:45:29 +01:00
< / div >
< / transition >
2023-09-24 22:36:35 +02:00
< / div >
< div class = "w-full flex items-center justify-end p-4" >
< ui -btn color = "success" :padding-x ="8" small class = "text-base" :loading ="savingSettings" @click ="saveSettings" > {{ $ strings.ButtonSave }} < / ui -btn >
< / div >
< / a p p - s e t t i n g s - c o n t e n t >
< / div >
< / template >
< script >
export default {
async asyncData ( { store , redirect , app } ) {
if ( ! store . getters [ 'user/getIsAdminOrUp' ] ) {
redirect ( '/' )
return
}
const authSettings = await app . $axios . $get ( '/api/auth-settings' ) . catch ( ( error ) => {
console . error ( 'Failed' , error )
return null
} )
if ( ! authSettings ) {
redirect ( '/config' )
return
}
return {
authSettings
}
} ,
data ( ) {
return {
enableLocalAuth : false ,
enableOpenIDAuth : false ,
2024-01-27 00:08:23 +01:00
showCustomLoginMessage : false ,
2023-09-24 22:36:35 +02:00
savingSettings : false ,
newAuthSettings : { }
}
} ,
computed : {
authMethods ( ) {
return this . authSettings . authActiveAuthMethods || [ ]
2023-11-08 21:45:29 +01:00
} ,
matchingExistingOptions ( ) {
return [
{
text : 'Do not match' ,
value : null
} ,
{
text : 'Match by email' ,
value : 'email'
} ,
{
text : 'Match by username' ,
value : 'username'
}
]
2023-09-24 22:36:35 +02:00
}
} ,
methods : {
2023-11-05 21:11:37 +01:00
autoPopulateOIDCClick ( ) {
if ( ! this . newAuthSettings . authOpenIDIssuerURL ) {
this . $toast . error ( 'Issuer URL required' )
return
}
// Remove trailing slash
let issuerUrl = this . newAuthSettings . authOpenIDIssuerURL
if ( issuerUrl . endsWith ( '/' ) ) issuerUrl = issuerUrl . slice ( 0 , - 1 )
// If the full config path is on the issuer url then remove it
if ( issuerUrl . endsWith ( '/.well-known/openid-configuration' ) ) {
issuerUrl = issuerUrl . replace ( '/.well-known/openid-configuration' , '' )
this . newAuthSettings . authOpenIDIssuerURL = this . newAuthSettings . authOpenIDIssuerURL . replace ( '/.well-known/openid-configuration' , '' )
}
this . $axios
. $get ( ` /auth/openid/config?issuer= ${ issuerUrl } ` )
. then ( ( data ) => {
if ( data . issuer ) this . newAuthSettings . authOpenIDIssuerURL = data . issuer
if ( data . authorization _endpoint ) this . newAuthSettings . authOpenIDAuthorizationURL = data . authorization _endpoint
if ( data . token _endpoint ) this . newAuthSettings . authOpenIDTokenURL = data . token _endpoint
if ( data . userinfo _endpoint ) this . newAuthSettings . authOpenIDUserInfoURL = data . userinfo _endpoint
if ( data . end _session _endpoint ) this . newAuthSettings . authOpenIDLogoutURL = data . end _session _endpoint
if ( data . jwks _uri ) this . newAuthSettings . authOpenIDJwksURL = data . jwks _uri
} )
. catch ( ( error ) => {
console . error ( 'Failed to receive data' , error )
const errorMsg = error . response ? . data || 'Unknown error'
this . $toast . error ( errorMsg )
} )
} ,
2023-09-24 22:36:35 +02:00
validateOpenID ( ) {
let isValid = true
if ( ! this . newAuthSettings . authOpenIDIssuerURL ) {
this . $toast . error ( 'Issuer URL required' )
isValid = false
}
if ( ! this . newAuthSettings . authOpenIDAuthorizationURL ) {
this . $toast . error ( 'Authorize URL required' )
isValid = false
}
if ( ! this . newAuthSettings . authOpenIDTokenURL ) {
this . $toast . error ( 'Token URL required' )
isValid = false
}
if ( ! this . newAuthSettings . authOpenIDUserInfoURL ) {
this . $toast . error ( 'Userinfo URL required' )
isValid = false
}
2023-11-04 21:36:43 +01:00
if ( ! this . newAuthSettings . authOpenIDJwksURL ) {
this . $toast . error ( 'JWKS URL required' )
isValid = false
}
2023-09-24 22:36:35 +02:00
if ( ! this . newAuthSettings . authOpenIDClientID ) {
this . $toast . error ( 'Client ID required' )
isValid = false
}
if ( ! this . newAuthSettings . authOpenIDClientSecret ) {
this . $toast . error ( 'Client Secret required' )
isValid = false
}
2023-12-04 22:36:34 +01:00
function isValidRedirectURI ( uri ) {
// Check for somestring://someother/string
2024-01-25 11:49:10 +01:00
const pattern = new RegExp ( '^\\w+://[\\w\\.-]+(/[\\w\\./-]*)*$' , 'i' )
2023-12-04 22:36:34 +01:00
return pattern . test ( uri )
}
const uris = this . newAuthSettings . authOpenIDMobileRedirectURIs
if ( uris . includes ( '*' ) && uris . length > 1 ) {
this . $toast . error ( 'Mobile Redirect URIs: Asterisk (*) must be the only entry if used' )
isValid = false
} else {
2023-12-08 00:01:33 +01:00
uris . forEach ( ( uri ) => {
2023-12-04 22:36:34 +01:00
if ( uri !== '*' && ! isValidRedirectURI ( uri ) ) {
this . $toast . error ( ` Mobile Redirect URIs: Invalid URI ${ uri } ` )
isValid = false
}
} )
}
2024-03-19 17:57:24 +01:00
function isValidClaim ( claim ) {
if ( claim === '' ) return true
const pattern = new RegExp ( '^[a-zA-Z][a-zA-Z0-9_-]*$' , 'i' )
return pattern . test ( claim )
}
if ( ! isValidClaim ( this . newAuthSettings . authOpenIDGroupClaim ) ) {
this . $toast . error ( 'Group Claim: Invalid claim name' )
isValid = false
}
if ( ! isValidClaim ( this . newAuthSettings . authOpenIDAdvancedPermsClaim ) ) {
this . $toast . error ( 'Advanced Permission Claim: Invalid claim name' )
isValid = false
}
2023-09-24 22:36:35 +02:00
return isValid
} ,
async saveSettings ( ) {
if ( ! this . enableLocalAuth && ! this . enableOpenIDAuth ) {
this . $toast . error ( 'Must have at least one authentication method enabled' )
return
}
if ( this . enableOpenIDAuth && ! this . validateOpenID ( ) ) {
return
}
2024-01-27 00:08:23 +01:00
if ( ! this . showCustomLoginMessage || ! this . newAuthSettings . authLoginCustomMessage ? . trim ( ) ) {
this . newAuthSettings . authLoginCustomMessage = null
}
2023-09-24 22:36:35 +02:00
this . newAuthSettings . authActiveAuthMethods = [ ]
if ( this . enableLocalAuth ) this . newAuthSettings . authActiveAuthMethods . push ( 'local' )
if ( this . enableOpenIDAuth ) this . newAuthSettings . authActiveAuthMethods . push ( 'openid' )
this . savingSettings = true
2023-11-10 23:11:51 +01:00
this . $axios
. $patch ( '/api/auth-settings' , this . newAuthSettings )
. then ( ( data ) => {
this . $store . commit ( 'setServerSettings' , data . serverSettings )
2023-12-08 00:01:33 +01:00
if ( data . updated ) {
this . $toast . success ( 'Server settings updated' )
} else {
this . $toast . info ( this . $strings . MessageNoUpdatesWereNecessary )
}
2023-11-10 23:11:51 +01:00
} )
. catch ( ( error ) => {
console . error ( 'Failed to update server settings' , error )
this . $toast . error ( 'Failed to update server settings' )
} )
. finally ( ( ) => {
this . savingSettings = false
} )
2023-09-24 22:36:35 +02:00
} ,
init ( ) {
this . newAuthSettings = {
... this . authSettings
}
this . enableLocalAuth = this . authMethods . includes ( 'local' )
this . enableOpenIDAuth = this . authMethods . includes ( 'openid' )
2024-01-27 00:08:23 +01:00
this . showCustomLoginMessage = ! ! this . authSettings . authLoginCustomMessage
2023-09-24 22:36:35 +02:00
}
} ,
mounted ( ) {
this . init ( )
}
}
< / script >
2023-11-22 19:38:11 +01:00
< style >
# authentication - settings code {
font - size : 0.8 rem ;
border - radius : 6 px ;
background - color : rgb ( 82 , 82 , 82 ) ;
color : white ;
padding : 2 px 4 px ;
white - space : nowrap ;
}
< / style >