mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01: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,
 | 
			
		||||
    disabled: Boolean,
 | 
			
		||||
    readonly: Boolean,
 | 
			
		||||
    showEdit: Boolean
 | 
			
		||||
    showEdit: Boolean,
 | 
			
		||||
    menuDisabled: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
@ -77,7 +81,7 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    showMenu() {
 | 
			
		||||
      return this.isFocused
 | 
			
		||||
      return this.isFocused && !this.menuDisabled
 | 
			
		||||
    },
 | 
			
		||||
    wrapperClass() {
 | 
			
		||||
      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-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" />
 | 
			
		||||
 | 
			
		||||
            <div class="flex items-center pt-1 mb-2">
 | 
			
		||||
@ -187,6 +190,25 @@ export default {
 | 
			
		||||
        this.$toast.error('Client Secret required')
 | 
			
		||||
        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
 | 
			
		||||
    },
 | 
			
		||||
    async saveSettings() {
 | 
			
		||||
@ -208,7 +230,11 @@ export default {
 | 
			
		||||
        .$patch('/api/auth-settings', this.newAuthSettings)
 | 
			
		||||
        .then((data) => {
 | 
			
		||||
          this.$store.commit('setServerSettings', data.serverSettings)
 | 
			
		||||
          if (data.updated) {
 | 
			
		||||
            this.$toast.success('Server settings updated')
 | 
			
		||||
          } else {
 | 
			
		||||
            this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed to update server settings', error)
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuta",
 | 
			
		||||
  "LabelMissing": "Chybějící",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Více informací",
 | 
			
		||||
  "LabelName": "Jméno",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minut",
 | 
			
		||||
  "LabelMissing": "Mangler",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Mere info",
 | 
			
		||||
  "LabelName": "Navn",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minute",
 | 
			
		||||
  "LabelMissing": "Fehlend",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Mehr Info",
 | 
			
		||||
  "LabelName": "Name",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minute",
 | 
			
		||||
  "LabelMissing": "Missing",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "More Info",
 | 
			
		||||
  "LabelName": "Name",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuto",
 | 
			
		||||
  "LabelMissing": "Ausente",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Más Información",
 | 
			
		||||
  "LabelName": "Nombre",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minute",
 | 
			
		||||
  "LabelMissing": "Manquant",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Plus d’info",
 | 
			
		||||
  "LabelName": "Nom",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minute",
 | 
			
		||||
  "LabelMissing": "Missing",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "More Info",
 | 
			
		||||
  "LabelName": "Name",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minute",
 | 
			
		||||
  "LabelMissing": "Missing",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "More Info",
 | 
			
		||||
  "LabelName": "Name",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuta",
 | 
			
		||||
  "LabelMissing": "Nedostaje",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "More Info",
 | 
			
		||||
  "LabelName": "Ime",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuto",
 | 
			
		||||
  "LabelMissing": "Altro",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Più Info",
 | 
			
		||||
  "LabelName": "Nome",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minutė",
 | 
			
		||||
  "LabelMissing": "Trūksta",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Daugiau informacijos",
 | 
			
		||||
  "LabelName": "Pavadinimas",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuut",
 | 
			
		||||
  "LabelMissing": "Ontbrekend",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Meer info",
 | 
			
		||||
  "LabelName": "Naam",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minutt",
 | 
			
		||||
  "LabelMissing": "Mangler",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Mer info",
 | 
			
		||||
  "LabelName": "Navn",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minuta",
 | 
			
		||||
  "LabelMissing": "Brakujący",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "More Info",
 | 
			
		||||
  "LabelName": "Nazwa",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Минуты",
 | 
			
		||||
  "LabelMissing": "Потеряно",
 | 
			
		||||
  "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": "Еще",
 | 
			
		||||
  "LabelMoreInfo": "Больше информации",
 | 
			
		||||
  "LabelName": "Имя",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "Minut",
 | 
			
		||||
  "LabelMissing": "Saknad",
 | 
			
		||||
  "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",
 | 
			
		||||
  "LabelMoreInfo": "Mer information",
 | 
			
		||||
  "LabelName": "Namn",
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,8 @@
 | 
			
		||||
  "LabelMinute": "分钟",
 | 
			
		||||
  "LabelMissing": "丢失",
 | 
			
		||||
  "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": "更多",
 | 
			
		||||
  "LabelMoreInfo": "更多..",
 | 
			
		||||
  "LabelName": "名称",
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
 | 
			
		||||
const OpenIDClient = require('openid-client')
 | 
			
		||||
const Database = require('./Database')
 | 
			
		||||
const Logger = require('./Logger')
 | 
			
		||||
const e = require('express')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @class Class for handling all the authentication related functionality.
 | 
			
		||||
@ -15,6 +16,8 @@ const Logger = require('./Logger')
 | 
			
		||||
class Auth {
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
   */
 | 
			
		||||
  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 
 | 
			
		||||
      res.cookie('is_rest', req.query.isRest.toLowerCase(), {
 | 
			
		||||
      res.cookie('is_rest', 'true', {
 | 
			
		||||
        maxAge: 120000, // 2 min
 | 
			
		||||
        httpOnly: true
 | 
			
		||||
      })
 | 
			
		||||
@ -283,8 +287,27 @@ class Auth {
 | 
			
		||||
        //    for API or mobile clients
 | 
			
		||||
        const oidcStrategy = passport._strategy('openid-client')
 | 
			
		||||
        const protocol = (req.secure || req.get('x-forwarded-proto') === 'https') ? 'https' : 'http'
 | 
			
		||||
 | 
			
		||||
        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] Set oidc redirect_uri=${oidcStrategy._params.redirect_uri}`)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Logger.debug(`[Auth] Oidc redirect_uri=${oidcStrategy._params.redirect_uri}`)
 | 
			
		||||
        const client = oidcStrategy._client
 | 
			
		||||
        const sessionKey = oidcStrategy._key
 | 
			
		||||
 | 
			
		||||
@ -324,16 +347,21 @@ class Auth {
 | 
			
		||||
        req.session[sessionKey] = {
 | 
			
		||||
          ...req.session[sessionKey],
 | 
			
		||||
          ...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
 | 
			
		||||
        const authorizationUrl = client.authorizationUrl({
 | 
			
		||||
          ...params,
 | 
			
		||||
          scope: 'openid profile email',
 | 
			
		||||
          response_type: 'code',
 | 
			
		||||
          code_challenge,
 | 
			
		||||
          code_challenge_method,
 | 
			
		||||
          code_challenge_method
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // 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)
 | 
			
		||||
    router.get('/auth/openid/callback', (req, res, next) => {
 | 
			
		||||
      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
 | 
			
		||||
      // 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) {
 | 
			
		||||
        return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' }, passportCallback(req, res, next))(req, res, next)
 | 
			
		||||
      } else {
 | 
			
		||||
        return passport.authenticate('openid-client', passportCallback(req, res, next))(req, res, next)
 | 
			
		||||
      }
 | 
			
		||||
      // We set it here again because the passport param can change between requests
 | 
			
		||||
      return passport.authenticate('openid-client', { redirect_uri: req.session[sessionKey].sso_redirect_uri }, passportCallback(req, res, next))(req, res, next)
 | 
			
		||||
    },
 | 
			
		||||
      // on a successfull login: read the cookies and react like the client requested (callback or json)
 | 
			
		||||
      this.handleLoginSuccessBasedOnCookie.bind(this))
 | 
			
		||||
 | 
			
		||||
@ -629,6 +629,27 @@ class MiscController {
 | 
			
		||||
        } else {
 | 
			
		||||
          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 {
 | 
			
		||||
        const updatedValueType = typeof settingsUpdate[key]
 | 
			
		||||
        if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
 | 
			
		||||
@ -671,6 +692,7 @@ class MiscController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.json({
 | 
			
		||||
      updated: hasUpdates,
 | 
			
		||||
      serverSettings: Database.serverSettings.toJSONForBrowser()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,7 @@ class ServerSettings {
 | 
			
		||||
    this.authOpenIDAutoLaunch = false
 | 
			
		||||
    this.authOpenIDAutoRegister = false
 | 
			
		||||
    this.authOpenIDMatchExistingBy = null
 | 
			
		||||
    this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
 | 
			
		||||
 | 
			
		||||
    if (settings) {
 | 
			
		||||
      this.construct(settings)
 | 
			
		||||
@ -126,6 +127,7 @@ class ServerSettings {
 | 
			
		||||
    this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
 | 
			
		||||
    this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
 | 
			
		||||
    this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
 | 
			
		||||
    this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
 | 
			
		||||
 | 
			
		||||
    if (!Array.isArray(this.authActiveAuthMethods)) {
 | 
			
		||||
      this.authActiveAuthMethods = ['local']
 | 
			
		||||
@ -211,7 +213,8 @@ class ServerSettings {
 | 
			
		||||
      authOpenIDButtonText: this.authOpenIDButtonText,
 | 
			
		||||
      authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
 | 
			
		||||
      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.authOpenIDClientID
 | 
			
		||||
    delete json.authOpenIDClientSecret
 | 
			
		||||
    delete json.authOpenIDMobileRedirectURIs
 | 
			
		||||
    return json
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -254,7 +258,8 @@ class ServerSettings {
 | 
			
		||||
      authOpenIDButtonText: this.authOpenIDButtonText,
 | 
			
		||||
      authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
 | 
			
		||||
      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