mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-04 01:17:19 +02:00
Merge pull request #2386 from Sapd/sso-redirecturi
SSO/OpenID: Use a mobile-redirect route (Fixes #2379 and #2381)
This commit is contained in:
commit
b8c8d2a02e
@ -50,7 +50,11 @@ export default {
|
|||||||
label: String,
|
label: String,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
readonly: Boolean,
|
readonly: Boolean,
|
||||||
showEdit: Boolean
|
showEdit: Boolean,
|
||||||
|
menuDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -77,7 +81,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showMenu() {
|
showMenu() {
|
||||||
return this.isFocused
|
return this.isFocused && !this.menuDisabled
|
||||||
},
|
},
|
||||||
wrapperClass() {
|
wrapperClass() {
|
||||||
var classes = []
|
var classes = []
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
|
|
||||||
<ui-text-input-with-label ref="openidClientSecret" v-model="newAuthSettings.authOpenIDClientSecret" :disabled="savingSettings" :label="'Client Secret'" class="mb-2" />
|
<ui-text-input-with-label ref="openidClientSecret" v-model="newAuthSettings.authOpenIDClientSecret" :disabled="savingSettings" :label="'Client Secret'" class="mb-2" />
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
<ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" />
|
<ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" />
|
||||||
|
|
||||||
<div class="flex items-center pt-1 mb-2">
|
<div class="flex items-center pt-1 mb-2">
|
||||||
@ -187,6 +190,25 @@ export default {
|
|||||||
this.$toast.error('Client Secret required')
|
this.$toast.error('Client Secret required')
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidRedirectURI(uri) {
|
||||||
|
// Check for somestring://someother/string
|
||||||
|
const pattern = new RegExp('^\\w+://[\\w\\.-]+$', 'i')
|
||||||
|
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 {
|
||||||
|
uris.forEach((uri) => {
|
||||||
|
if (uri !== '*' && !isValidRedirectURI(uri)) {
|
||||||
|
this.$toast.error(`Mobile Redirect URIs: Invalid URI ${uri}`)
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return isValid
|
return isValid
|
||||||
},
|
},
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
@ -208,7 +230,11 @@ export default {
|
|||||||
.$patch('/api/auth-settings', this.newAuthSettings)
|
.$patch('/api/auth-settings', this.newAuthSettings)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.$store.commit('setServerSettings', data.serverSettings)
|
this.$store.commit('setServerSettings', data.serverSettings)
|
||||||
this.$toast.success('Server settings updated')
|
if (data.updated) {
|
||||||
|
this.$toast.success('Server settings updated')
|
||||||
|
} else {
|
||||||
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to update server settings', error)
|
console.error('Failed to update server settings', error)
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuta",
|
"LabelMinute": "Minuta",
|
||||||
"LabelMissing": "Chybějící",
|
"LabelMissing": "Chybějící",
|
||||||
"LabelMissingParts": "Chybějící díly",
|
"LabelMissingParts": "Chybějící díly",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Více",
|
"LabelMore": "Více",
|
||||||
"LabelMoreInfo": "Více informací",
|
"LabelMoreInfo": "Více informací",
|
||||||
"LabelName": "Jméno",
|
"LabelName": "Jméno",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minut",
|
"LabelMinute": "Minut",
|
||||||
"LabelMissing": "Mangler",
|
"LabelMissing": "Mangler",
|
||||||
"LabelMissingParts": "Manglende dele",
|
"LabelMissingParts": "Manglende dele",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Mere",
|
"LabelMore": "Mere",
|
||||||
"LabelMoreInfo": "Mere info",
|
"LabelMoreInfo": "Mere info",
|
||||||
"LabelName": "Navn",
|
"LabelName": "Navn",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
"LabelMissing": "Fehlend",
|
"LabelMissing": "Fehlend",
|
||||||
"LabelMissingParts": "Fehlende Teile",
|
"LabelMissingParts": "Fehlende Teile",
|
||||||
|
"LabelMobileRedirectURIs": "Erlaubte Weiterleitungs-URIs für die mobile App",
|
||||||
|
"LabelMobileRedirectURIsDescription": "Dies ist eine Whitelist gültiger Umleitungs-URIs für mobile Apps. Der Standardwert ist <code>audiobookshelf://oauth</code>, den Sie entfernen oder durch zusätzliche URIs für die Integration von Drittanbieter-Apps ergänzen können. Die Verwendung eines Sternchens (<code>*</code>) als alleiniger Eintrag erlaubt jede URI.",
|
||||||
"LabelMore": "Mehr",
|
"LabelMore": "Mehr",
|
||||||
"LabelMoreInfo": "Mehr Info",
|
"LabelMoreInfo": "Mehr Info",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
"LabelMissing": "Missing",
|
"LabelMissing": "Missing",
|
||||||
"LabelMissingParts": "Missing Parts",
|
"LabelMissingParts": "Missing Parts",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "More",
|
"LabelMore": "More",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "More Info",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuto",
|
"LabelMinute": "Minuto",
|
||||||
"LabelMissing": "Ausente",
|
"LabelMissing": "Ausente",
|
||||||
"LabelMissingParts": "Partes Ausentes",
|
"LabelMissingParts": "Partes Ausentes",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Más",
|
"LabelMore": "Más",
|
||||||
"LabelMoreInfo": "Más Información",
|
"LabelMoreInfo": "Más Información",
|
||||||
"LabelName": "Nombre",
|
"LabelName": "Nombre",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
"LabelMissing": "Manquant",
|
"LabelMissing": "Manquant",
|
||||||
"LabelMissingParts": "Parties manquantes",
|
"LabelMissingParts": "Parties manquantes",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Plus",
|
"LabelMore": "Plus",
|
||||||
"LabelMoreInfo": "Plus d’info",
|
"LabelMoreInfo": "Plus d’info",
|
||||||
"LabelName": "Nom",
|
"LabelName": "Nom",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
"LabelMissing": "Missing",
|
"LabelMissing": "Missing",
|
||||||
"LabelMissingParts": "Missing Parts",
|
"LabelMissingParts": "Missing Parts",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "More",
|
"LabelMore": "More",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "More Info",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
"LabelMissing": "Missing",
|
"LabelMissing": "Missing",
|
||||||
"LabelMissingParts": "Missing Parts",
|
"LabelMissingParts": "Missing Parts",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "More",
|
"LabelMore": "More",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "More Info",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuta",
|
"LabelMinute": "Minuta",
|
||||||
"LabelMissing": "Nedostaje",
|
"LabelMissing": "Nedostaje",
|
||||||
"LabelMissingParts": "Nedostajali dijelovi",
|
"LabelMissingParts": "Nedostajali dijelovi",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Više",
|
"LabelMore": "Više",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "More Info",
|
||||||
"LabelName": "Ime",
|
"LabelName": "Ime",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuto",
|
"LabelMinute": "Minuto",
|
||||||
"LabelMissing": "Altro",
|
"LabelMissing": "Altro",
|
||||||
"LabelMissingParts": "Parti rimantenti",
|
"LabelMissingParts": "Parti rimantenti",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Molto",
|
"LabelMore": "Molto",
|
||||||
"LabelMoreInfo": "Più Info",
|
"LabelMoreInfo": "Più Info",
|
||||||
"LabelName": "Nome",
|
"LabelName": "Nome",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minutė",
|
"LabelMinute": "Minutė",
|
||||||
"LabelMissing": "Trūksta",
|
"LabelMissing": "Trūksta",
|
||||||
"LabelMissingParts": "Trūkstamos dalys",
|
"LabelMissingParts": "Trūkstamos dalys",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Daugiau",
|
"LabelMore": "Daugiau",
|
||||||
"LabelMoreInfo": "Daugiau informacijos",
|
"LabelMoreInfo": "Daugiau informacijos",
|
||||||
"LabelName": "Pavadinimas",
|
"LabelName": "Pavadinimas",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuut",
|
"LabelMinute": "Minuut",
|
||||||
"LabelMissing": "Ontbrekend",
|
"LabelMissing": "Ontbrekend",
|
||||||
"LabelMissingParts": "Ontbrekende delen",
|
"LabelMissingParts": "Ontbrekende delen",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Meer",
|
"LabelMore": "Meer",
|
||||||
"LabelMoreInfo": "Meer info",
|
"LabelMoreInfo": "Meer info",
|
||||||
"LabelName": "Naam",
|
"LabelName": "Naam",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minutt",
|
"LabelMinute": "Minutt",
|
||||||
"LabelMissing": "Mangler",
|
"LabelMissing": "Mangler",
|
||||||
"LabelMissingParts": "Manglende deler",
|
"LabelMissingParts": "Manglende deler",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Mer",
|
"LabelMore": "Mer",
|
||||||
"LabelMoreInfo": "Mer info",
|
"LabelMoreInfo": "Mer info",
|
||||||
"LabelName": "Navn",
|
"LabelName": "Navn",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minuta",
|
"LabelMinute": "Minuta",
|
||||||
"LabelMissing": "Brakujący",
|
"LabelMissing": "Brakujący",
|
||||||
"LabelMissingParts": "Brakujące cześci",
|
"LabelMissingParts": "Brakujące cześci",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Więcej",
|
"LabelMore": "Więcej",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "More Info",
|
||||||
"LabelName": "Nazwa",
|
"LabelName": "Nazwa",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Минуты",
|
"LabelMinute": "Минуты",
|
||||||
"LabelMissing": "Потеряно",
|
"LabelMissing": "Потеряно",
|
||||||
"LabelMissingParts": "Потерянные части",
|
"LabelMissingParts": "Потерянные части",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Еще",
|
"LabelMore": "Еще",
|
||||||
"LabelMoreInfo": "Больше информации",
|
"LabelMoreInfo": "Больше информации",
|
||||||
"LabelName": "Имя",
|
"LabelName": "Имя",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "Minut",
|
"LabelMinute": "Minut",
|
||||||
"LabelMissing": "Saknad",
|
"LabelMissing": "Saknad",
|
||||||
"LabelMissingParts": "Saknade delar",
|
"LabelMissingParts": "Saknade delar",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "Mer",
|
"LabelMore": "Mer",
|
||||||
"LabelMoreInfo": "Mer information",
|
"LabelMoreInfo": "Mer information",
|
||||||
"LabelName": "Namn",
|
"LabelName": "Namn",
|
||||||
|
@ -343,6 +343,8 @@
|
|||||||
"LabelMinute": "分钟",
|
"LabelMinute": "分钟",
|
||||||
"LabelMissing": "丢失",
|
"LabelMissing": "丢失",
|
||||||
"LabelMissingParts": "丢失的部分",
|
"LabelMissingParts": "丢失的部分",
|
||||||
|
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
|
||||||
|
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
|
||||||
"LabelMore": "更多",
|
"LabelMore": "更多",
|
||||||
"LabelMoreInfo": "更多..",
|
"LabelMoreInfo": "更多..",
|
||||||
"LabelName": "名称",
|
"LabelName": "名称",
|
||||||
|
@ -8,6 +8,7 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
|
|||||||
const OpenIDClient = require('openid-client')
|
const OpenIDClient = require('openid-client')
|
||||||
const Database = require('./Database')
|
const Database = require('./Database')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
|
const e = require('express')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Class for handling all the authentication related functionality.
|
* @class Class for handling all the authentication related functionality.
|
||||||
@ -15,6 +16,8 @@ const Logger = require('./Logger')
|
|||||||
class Auth {
|
class Auth {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// Map of openId sessions indexed by oauth2 state-variable
|
||||||
|
this.openIdAuthSession = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,9 +190,10 @@ class Auth {
|
|||||||
* @param {import('express').Response} res
|
* @param {import('express').Response} res
|
||||||
*/
|
*/
|
||||||
paramsToCookies(req, res) {
|
paramsToCookies(req, res) {
|
||||||
if (req.query.isRest?.toLowerCase() == 'true') {
|
// Set if isRest flag is set or if mobile oauth flow is used
|
||||||
|
if (req.query.isRest?.toLowerCase() == 'true' || req.query.redirect_uri) {
|
||||||
// store the isRest flag to the is_rest cookie
|
// store the isRest flag to the is_rest cookie
|
||||||
res.cookie('is_rest', req.query.isRest.toLowerCase(), {
|
res.cookie('is_rest', 'true', {
|
||||||
maxAge: 120000, // 2 min
|
maxAge: 120000, // 2 min
|
||||||
httpOnly: true
|
httpOnly: true
|
||||||
})
|
})
|
||||||
@ -283,8 +287,27 @@ class Auth {
|
|||||||
// for API or mobile clients
|
// for API or mobile clients
|
||||||
const oidcStrategy = passport._strategy('openid-client')
|
const oidcStrategy = passport._strategy('openid-client')
|
||||||
const protocol = (req.secure || req.get('x-forwarded-proto') === 'https') ? 'https' : 'http'
|
const protocol = (req.secure || req.get('x-forwarded-proto') === 'https') ? 'https' : 'http'
|
||||||
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/callback`).toString()
|
|
||||||
Logger.debug(`[Auth] Set oidc redirect_uri=${oidcStrategy._params.redirect_uri}`)
|
let mobile_redirect_uri = null
|
||||||
|
|
||||||
|
// The client wishes a different redirect_uri
|
||||||
|
// We will allow if it is in the whitelist, by saving it into this.openIdAuthSession and setting the redirect uri to /auth/openid/mobile-redirect
|
||||||
|
// where we will handle the redirect to it
|
||||||
|
if (req.query.redirect_uri) {
|
||||||
|
// Check if the redirect_uri is in the whitelist
|
||||||
|
if (Database.serverSettings.authOpenIDMobileRedirectURIs.includes(req.query.redirect_uri) ||
|
||||||
|
(Database.serverSettings.authOpenIDMobileRedirectURIs.length === 1 && Database.serverSettings.authOpenIDMobileRedirectURIs[0] === '*')) {
|
||||||
|
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/mobile-redirect`).toString()
|
||||||
|
mobile_redirect_uri = req.query.redirect_uri
|
||||||
|
} else {
|
||||||
|
Logger.debug(`[Auth] Invalid redirect_uri=${req.query.redirect_uri} - not in whitelist`)
|
||||||
|
return res.status(400).send('Invalid redirect_uri')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/callback`).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.debug(`[Auth] Oidc redirect_uri=${oidcStrategy._params.redirect_uri}`)
|
||||||
const client = oidcStrategy._client
|
const client = oidcStrategy._client
|
||||||
const sessionKey = oidcStrategy._key
|
const sessionKey = oidcStrategy._key
|
||||||
|
|
||||||
@ -324,16 +347,21 @@ class Auth {
|
|||||||
req.session[sessionKey] = {
|
req.session[sessionKey] = {
|
||||||
...req.session[sessionKey],
|
...req.session[sessionKey],
|
||||||
...pick(params, 'nonce', 'state', 'max_age', 'response_type'),
|
...pick(params, 'nonce', 'state', 'max_age', 'response_type'),
|
||||||
mobile: req.query.isRest?.toLowerCase() === 'true' // Used in the abs callback later
|
mobile: req.query.redirect_uri, // Used in the abs callback later, set mobile if redirect_uri is filled out
|
||||||
|
sso_redirect_uri: oidcStrategy._params.redirect_uri // Save the redirect_uri (for the SSO Provider) for the callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We cannot save redirect_uri in the session, because it the mobile client uses browser instead of the API
|
||||||
|
// for the request to mobile-redirect and as such the session is not shared
|
||||||
|
this.openIdAuthSession.set(params.state, { mobile_redirect_uri: mobile_redirect_uri })
|
||||||
|
|
||||||
// Now get the URL to direct to
|
// Now get the URL to direct to
|
||||||
const authorizationUrl = client.authorizationUrl({
|
const authorizationUrl = client.authorizationUrl({
|
||||||
...params,
|
...params,
|
||||||
scope: 'openid profile email',
|
scope: 'openid profile email',
|
||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
code_challenge,
|
code_challenge,
|
||||||
code_challenge_method,
|
code_challenge_method
|
||||||
})
|
})
|
||||||
|
|
||||||
// params (isRest, callback) to a cookie that will be send to the client
|
// params (isRest, callback) to a cookie that will be send to the client
|
||||||
@ -347,6 +375,37 @@ class Auth {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// This will be the oauth2 callback route for mobile clients
|
||||||
|
// It will redirect to an app-link like audiobookshelf://oauth
|
||||||
|
router.get('/auth/openid/mobile-redirect', (req, res) => {
|
||||||
|
try {
|
||||||
|
// Extract the state parameter from the request
|
||||||
|
const { state, code } = req.query
|
||||||
|
|
||||||
|
// Check if the state provided is in our list
|
||||||
|
if (!state || !this.openIdAuthSession.has(state)) {
|
||||||
|
Logger.error('[Auth] /auth/openid/mobile-redirect route: State parameter mismatch')
|
||||||
|
return res.status(400).send('State parameter mismatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
let mobile_redirect_uri = this.openIdAuthSession.get(state).mobile_redirect_uri
|
||||||
|
|
||||||
|
if (!mobile_redirect_uri) {
|
||||||
|
Logger.error('[Auth] No redirect URI')
|
||||||
|
return res.status(400).send('No redirect URI')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openIdAuthSession.delete(state)
|
||||||
|
|
||||||
|
const redirectUri = `${mobile_redirect_uri}?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`
|
||||||
|
// Redirect to the overwrite URI saved in the map
|
||||||
|
res.redirect(redirectUri)
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[Auth] Error in /auth/openid/mobile-redirect route: ${error}`)
|
||||||
|
res.status(500).send('Internal Server Error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// openid strategy callback route (this receives the token from the configured openid login provider)
|
// openid strategy callback route (this receives the token from the configured openid login provider)
|
||||||
router.get('/auth/openid/callback', (req, res, next) => {
|
router.get('/auth/openid/callback', (req, res, next) => {
|
||||||
const oidcStrategy = passport._strategy('openid-client')
|
const oidcStrategy = passport._strategy('openid-client')
|
||||||
@ -403,11 +462,8 @@ class Auth {
|
|||||||
|
|
||||||
// While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request
|
// While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request
|
||||||
// We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided
|
// We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided
|
||||||
if (req.session[sessionKey].mobile) {
|
// We set it here again because the passport param can change between requests
|
||||||
return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' }, passportCallback(req, res, next))(req, res, next)
|
return passport.authenticate('openid-client', { redirect_uri: req.session[sessionKey].sso_redirect_uri }, passportCallback(req, res, next))(req, res, next)
|
||||||
} else {
|
|
||||||
return passport.authenticate('openid-client', passportCallback(req, res, next))(req, res, next)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// on a successfull login: read the cookies and react like the client requested (callback or json)
|
// on a successfull login: read the cookies and react like the client requested (callback or json)
|
||||||
this.handleLoginSuccessBasedOnCookie.bind(this))
|
this.handleLoginSuccessBasedOnCookie.bind(this))
|
||||||
|
@ -629,6 +629,27 @@ class MiscController {
|
|||||||
} else {
|
} else {
|
||||||
Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`)
|
Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`)
|
||||||
}
|
}
|
||||||
|
} else if (key === 'authOpenIDMobileRedirectURIs') {
|
||||||
|
function isValidRedirectURI(uri) {
|
||||||
|
if (typeof uri !== 'string') return false
|
||||||
|
const pattern = new RegExp('^\\w+://[\\w.-]+$', 'i')
|
||||||
|
return pattern.test(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uris = settingsUpdate[key]
|
||||||
|
if (!Array.isArray(uris) ||
|
||||||
|
(uris.includes('*') && uris.length > 1) ||
|
||||||
|
uris.some(uri => uri !== '*' && !isValidRedirectURI(uri))) {
|
||||||
|
Logger.warn(`[MiscController] Invalid value for authOpenIDMobileRedirectURIs`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the URIs
|
||||||
|
if (Database.serverSettings[key].some(uri => !uris.includes(uri)) || uris.some(uri => !Database.serverSettings[key].includes(uri))) {
|
||||||
|
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${Database.serverSettings[key]}" to "${uris}"`)
|
||||||
|
Database.serverSettings[key] = uris
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const updatedValueType = typeof settingsUpdate[key]
|
const updatedValueType = typeof settingsUpdate[key]
|
||||||
if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
|
if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
|
||||||
@ -671,6 +692,7 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
updated: hasUpdates,
|
||||||
serverSettings: Database.serverSettings.toJSONForBrowser()
|
serverSettings: Database.serverSettings.toJSONForBrowser()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@ class ServerSettings {
|
|||||||
this.authOpenIDAutoLaunch = false
|
this.authOpenIDAutoLaunch = false
|
||||||
this.authOpenIDAutoRegister = false
|
this.authOpenIDAutoRegister = false
|
||||||
this.authOpenIDMatchExistingBy = null
|
this.authOpenIDMatchExistingBy = null
|
||||||
|
this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
this.construct(settings)
|
this.construct(settings)
|
||||||
@ -126,6 +127,7 @@ class ServerSettings {
|
|||||||
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
|
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
|
||||||
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
||||||
this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
|
this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
|
||||||
|
this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
|
||||||
|
|
||||||
if (!Array.isArray(this.authActiveAuthMethods)) {
|
if (!Array.isArray(this.authActiveAuthMethods)) {
|
||||||
this.authActiveAuthMethods = ['local']
|
this.authActiveAuthMethods = ['local']
|
||||||
@ -211,7 +213,8 @@ class ServerSettings {
|
|||||||
authOpenIDButtonText: this.authOpenIDButtonText,
|
authOpenIDButtonText: this.authOpenIDButtonText,
|
||||||
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
||||||
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
||||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy
|
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||||
|
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +223,7 @@ class ServerSettings {
|
|||||||
delete json.tokenSecret
|
delete json.tokenSecret
|
||||||
delete json.authOpenIDClientID
|
delete json.authOpenIDClientID
|
||||||
delete json.authOpenIDClientSecret
|
delete json.authOpenIDClientSecret
|
||||||
|
delete json.authOpenIDMobileRedirectURIs
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +258,8 @@ class ServerSettings {
|
|||||||
authOpenIDButtonText: this.authOpenIDButtonText,
|
authOpenIDButtonText: this.authOpenIDButtonText,
|
||||||
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
||||||
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
||||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy
|
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||||
|
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user