mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-01 00:15:54 +01:00
Merge pull request #2769 from Sapd/openid-permissions
OpenID: Integrate permissions (Fixes #2523)
This commit is contained in:
commit
a9c9c447f1
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-4 mb-8">
|
||||
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-2 sm:p-4 mb-8">
|
||||
<div class="flex items-center mb-2">
|
||||
<slot name="header-prefix"></slot>
|
||||
<h1 class="text-xl">{{ headerText }}</h1>
|
||||
|
@ -5,7 +5,7 @@
|
||||
>{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em></label
|
||||
>
|
||||
</slot>
|
||||
<ui-text-input :placeholder="label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" :class="inputClass" @blur="inputBlurred" />
|
||||
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" :class="inputClass" @blur="inputBlurred" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -14,6 +14,7 @@ export default {
|
||||
props: {
|
||||
value: [String, Number],
|
||||
label: String,
|
||||
placeholder: String,
|
||||
note: String,
|
||||
type: {
|
||||
type: String,
|
||||
|
@ -59,28 +59,49 @@
|
||||
<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" />
|
||||
<p class="sm: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">
|
||||
<div class="flex sm:items-center flex-col sm:flex-row pt-1 mb-2">
|
||||
<div class="w-44">
|
||||
<ui-dropdown v-model="newAuthSettings.authOpenIDMatchExistingBy" small :items="matchingExistingOptions" :label="$strings.LabelMatchExistingUsersBy" :disabled="savingSettings" />
|
||||
</div>
|
||||
<p class="pl-4 text-sm text-gray-300 mt-5">{{ $strings.LabelMatchExistingUsersByDescription }}</p>
|
||||
<p class="sm:pl-4 text-sm text-gray-300 mt-2 sm:mt-5">{{ $strings.LabelMatchExistingUsersByDescription }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-4 px-1">
|
||||
<div class="flex items-center py-4 px-1 w-full">
|
||||
<ui-toggle-switch labeledBy="auto-redirect-toggle" v-model="newAuthSettings.authOpenIDAutoLaunch" :disabled="savingSettings" />
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-4 px-1">
|
||||
<div class="flex items-center py-4 px-1 w-full">
|
||||
<ui-toggle-switch labeledBy="auto-register-toggle" v-model="newAuthSettings.authOpenIDAutoRegister" :disabled="savingSettings" />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<p class="pt-6 mb-4 px-1">{{ $strings.LabelOpenIDClaims }}</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row mb-4">
|
||||
<div class="w-44 min-w-44">
|
||||
<ui-text-input-with-label ref="openidGroupClaim" v-model="newAuthSettings.authOpenIDGroupClaim" :disabled="savingSettings" :placeholder="'groups'" :label="'Group Claim'" />
|
||||
</div>
|
||||
<p class="sm:pl-4 pt-2 sm:pt-0 text-sm text-gray-300" v-html="$strings.LabelOpenIDGroupClaimDescription"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row mb-4">
|
||||
<div class="w-44 min-w-44">
|
||||
<ui-text-input-with-label ref="openidAdvancedPermsClaim" v-model="newAuthSettings.authOpenIDAdvancedPermsClaim" :disabled="savingSettings" :placeholder="'abspermissions'" :label="'Advanced Permission Claim'" />
|
||||
</div>
|
||||
<div class="sm:pl-4 pt-2 sm:pt-0 text-sm text-gray-300">
|
||||
<p v-html="$strings.LabelOpenIDAdvancedPermsClaimDescription"></p>
|
||||
<pre class="text-pre-wrap mt-2"
|
||||
>{{ newAuthSettings.authOpenIDSamplePermissions }}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
@ -222,6 +243,22 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return isValid
|
||||
},
|
||||
async saveSettings() {
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Nezahájeno",
|
||||
"LabelNumberOfBooks": "Počet knih",
|
||||
"LabelNumberOfEpisodes": "Počet epizod",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Otevřít RSS kanál",
|
||||
"LabelOverwrite": "Přepsat",
|
||||
"LabelPassword": "Heslo",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Ikke påbegyndt",
|
||||
"LabelNumberOfBooks": "Antal bøger",
|
||||
"LabelNumberOfEpisodes": "Antal episoder",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Åbn RSS-feed",
|
||||
"LabelOverwrite": "Overskriv",
|
||||
"LabelPassword": "Kodeord",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Nicht begonnen",
|
||||
"LabelNumberOfBooks": "Anzahl der Hörbücher",
|
||||
"LabelNumberOfEpisodes": "Anzahl der Episoden",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Name des OpenID-Claims, der erweiterte Berechtigungen für Benutzeraktionen innerhalb der Anwendung enthält, die auf Nicht-Admin-Rollen angewendet werden (<b>wenn konfiguriert</b>). Wenn der Claim in der Antwort fehlt, wird der Zugang zu ABS verweigert. Fehlt eine einzelne Option, wird sie als <code>false</code> behandelt. Stelle sicher, dass der Claim des Identitätsanbieters der erwarteten Struktur entspricht:",
|
||||
"LabelOpenIDClaims": "Lass die folgenden Optionen leer, um die erweiterte Zuweisung von Gruppen und Berechtigungen zu deaktivieren und automatisch die 'User'-Gruppe zuzuweisen.",
|
||||
"LabelOpenIDGroupClaimDescription": "Name des OpenID-Claims, der eine Liste der Benutzergruppen enthält. Wird häufig als <code>groups</code> bezeichnet. <b>Wenn konfiguriert</b>, wird die Anwendung automatisch Rollen basierend auf den Gruppenmitgliedschaften des Benutzers zuweisen, vorausgesetzt, dass diese Gruppen im Claim als 'admin', 'user' oder 'guest' benannt sind (Groß/Kleinschreibung ist irrelevant). Der Claim eine Liste sein, und wenn ein Benutzer mehreren Gruppen angehört, wird die Anwendung die Rolle zuordnen, die dem höchsten Zugriffslevel entspricht. Wenn keine Gruppe übereinstimmt, wird der Zugang verweigert.",
|
||||
"LabelOpenRSSFeed": "Öffne RSS-Feed",
|
||||
"LabelOverwrite": "Überschreiben",
|
||||
"LabelPassword": "Passwort",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Not Started",
|
||||
"LabelNumberOfBooks": "Number of Books",
|
||||
"LabelNumberOfEpisodes": "# of Episodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Open RSS Feed",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPassword": "Password",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Sin Iniciar",
|
||||
"LabelNumberOfBooks": "Numero de Libros",
|
||||
"LabelNumberOfEpisodes": "# de Episodios",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Abrir Fuente RSS",
|
||||
"LabelOverwrite": "Sobrescribir",
|
||||
"LabelPassword": "Contraseña",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Pole alustatud",
|
||||
"LabelNumberOfBooks": "Raamatute arv",
|
||||
"LabelNumberOfEpisodes": "Episoodide arv",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Ava RSS voog",
|
||||
"LabelOverwrite": "Kirjuta üle",
|
||||
"LabelPassword": "Parool",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Pas commencé",
|
||||
"LabelNumberOfBooks": "Nombre de livres",
|
||||
"LabelNumberOfEpisodes": "Nombre d’épisodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Ouvrir le flux RSS",
|
||||
"LabelOverwrite": "Écraser",
|
||||
"LabelPassword": "Mot de passe",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Not Started",
|
||||
"LabelNumberOfBooks": "Number of Books",
|
||||
"LabelNumberOfEpisodes": "# of Episodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Open RSS Feed",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPassword": "Password",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "לא התחיל",
|
||||
"LabelNumberOfBooks": "מספר הספרים",
|
||||
"LabelNumberOfEpisodes": "מספר הפרקים",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "פתח ערוץ RSS",
|
||||
"LabelOverwrite": "לשכפל",
|
||||
"LabelPassword": "סיסמה",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Not Started",
|
||||
"LabelNumberOfBooks": "Number of Books",
|
||||
"LabelNumberOfEpisodes": "# of Episodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Open RSS Feed",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPassword": "Password",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Not Started",
|
||||
"LabelNumberOfBooks": "Number of Books",
|
||||
"LabelNumberOfEpisodes": "# of Episodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Otvori RSS Feed",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPassword": "Lozinka",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Nem indult el",
|
||||
"LabelNumberOfBooks": "Könyvek száma",
|
||||
"LabelNumberOfEpisodes": "Epizódok száma",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "RSS hírcsatorna megnyitása",
|
||||
"LabelOverwrite": "Felülírás",
|
||||
"LabelPassword": "Jelszó",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Non iniziato",
|
||||
"LabelNumberOfBooks": "Numero di libri",
|
||||
"LabelNumberOfEpisodes": "# degli episodi",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Apri RSS Feed",
|
||||
"LabelOverwrite": "Sovrascrivi",
|
||||
"LabelPassword": "Password",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Nepasileista",
|
||||
"LabelNumberOfBooks": "Knygų skaičius",
|
||||
"LabelNumberOfEpisodes": "Epizodų skaičius",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Atidaryti RSS srautą",
|
||||
"LabelOverwrite": "Perrašyti",
|
||||
"LabelPassword": "Slaptažodis",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Niet Gestart",
|
||||
"LabelNumberOfBooks": "Aantal Boeken",
|
||||
"LabelNumberOfEpisodes": "# afleveringen",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Open RSS-feed",
|
||||
"LabelOverwrite": "Overschrijf",
|
||||
"LabelPassword": "Wachtwoord",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Ikke startet",
|
||||
"LabelNumberOfBooks": "Antall bøker",
|
||||
"LabelNumberOfEpisodes": "Antall episoder",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Åpne RSS Feed",
|
||||
"LabelOverwrite": "Overskriv",
|
||||
"LabelPassword": "Passord",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Nie rozpoęczto",
|
||||
"LabelNumberOfBooks": "Liczba książek",
|
||||
"LabelNumberOfEpisodes": "# odcinków",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Otwórz kanał RSS",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPassword": "Hasło",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Não iniciado",
|
||||
"LabelNumberOfBooks": "Número de Livros",
|
||||
"LabelNumberOfEpisodes": "# de Episódios",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Abrir Feed RSS",
|
||||
"LabelOverwrite": "Sobrescrever",
|
||||
"LabelPassword": "Senha",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Не запущено",
|
||||
"LabelNumberOfBooks": "Количество книг",
|
||||
"LabelNumberOfEpisodes": "# Эпизодов",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Открыть RSS-канал",
|
||||
"LabelOverwrite": "Перезаписать",
|
||||
"LabelPassword": "Пароль",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Inte påbörjad",
|
||||
"LabelNumberOfBooks": "Antal böcker",
|
||||
"LabelNumberOfEpisodes": "Antal avsnitt",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Öppna RSS-flöde",
|
||||
"LabelOverwrite": "Skriv över",
|
||||
"LabelPassword": "Lösenord",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "Chưa bắt đầu",
|
||||
"LabelNumberOfBooks": "Số lượng Sách",
|
||||
"LabelNumberOfEpisodes": "# của Tập",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "Mở RSS Feed",
|
||||
"LabelOverwrite": "Ghi đè",
|
||||
"LabelPassword": "Mật khẩu",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "未开始",
|
||||
"LabelNumberOfBooks": "图书数量",
|
||||
"LabelNumberOfEpisodes": "# 集",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "打开 RSS 源",
|
||||
"LabelOverwrite": "覆盖",
|
||||
"LabelPassword": "密码",
|
||||
|
@ -385,6 +385,9 @@
|
||||
"LabelNotStarted": "未開始",
|
||||
"LabelNumberOfBooks": "圖書數量",
|
||||
"LabelNumberOfEpisodes": "# 集",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "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:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "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.",
|
||||
"LabelOpenRSSFeed": "打開 RSS 源",
|
||||
"LabelOverwrite": "覆蓋",
|
||||
"LabelPassword": "密碼",
|
||||
|
240
server/Auth.js
240
server/Auth.js
@ -98,71 +98,198 @@ class Auth {
|
||||
scope: 'openid profile email'
|
||||
}
|
||||
}, async (tokenset, userinfo, done) => {
|
||||
Logger.debug(`[Auth] openid callback userinfo=`, userinfo)
|
||||
try {
|
||||
Logger.debug(`[Auth] openid callback userinfo=`, JSON.stringify(userinfo, null, 2))
|
||||
|
||||
let failureMessage = 'Unauthorized'
|
||||
if (!userinfo.sub) {
|
||||
Logger.error(`[Auth] openid callback invalid userinfo, no sub`)
|
||||
return done(null, null, failureMessage)
|
||||
if (!userinfo.sub) {
|
||||
throw new Error('Invalid userinfo, no sub')
|
||||
}
|
||||
|
||||
if (!this.validateGroupClaim(userinfo)) {
|
||||
throw new Error(`Group claim ${Database.serverSettings.authOpenIDGroupClaim} not found or empty in userinfo`)
|
||||
}
|
||||
|
||||
let user = await this.findOrCreateUser(userinfo)
|
||||
|
||||
if (!user?.isActive) {
|
||||
throw new Error('User not active or not found')
|
||||
}
|
||||
|
||||
await this.setUserGroup(user, userinfo)
|
||||
await this.updateUserPermissions(user, userinfo)
|
||||
|
||||
// We also have to save the id_token for later (used for logout) because we cannot set cookies here
|
||||
user.openid_id_token = tokenset.id_token
|
||||
|
||||
return done(null, user)
|
||||
} catch (error) {
|
||||
Logger.error(`[Auth] openid callback error: ${error?.message}\n${error?.stack}`)
|
||||
|
||||
return done(null, null, 'Unauthorized')
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// First check for matching user by sub
|
||||
let user = await Database.userModel.getUserByOpenIDSub(userinfo.sub)
|
||||
if (!user) {
|
||||
// Optionally match existing by email or username based on server setting "authOpenIDMatchExistingBy"
|
||||
if (Database.serverSettings.authOpenIDMatchExistingBy === 'email' && userinfo.email && userinfo.email_verified) {
|
||||
/**
|
||||
* Finds an existing user by OpenID subject identifier, or by email/username based on server settings,
|
||||
* or creates a new user if configured to do so.
|
||||
*/
|
||||
async findOrCreateUser(userinfo) {
|
||||
let user = await Database.userModel.getUserByOpenIDSub(userinfo.sub)
|
||||
|
||||
// Matched by sub
|
||||
if (user) {
|
||||
Logger.debug(`[Auth] openid: User found by sub`)
|
||||
return user
|
||||
}
|
||||
|
||||
// Match existing user by email
|
||||
if (Database.serverSettings.authOpenIDMatchExistingBy === 'email') {
|
||||
if (userinfo.email) {
|
||||
// Only disallow when email_verified explicitly set to false (allow both if not set or true)
|
||||
if (userinfo.email_verified === false) {
|
||||
Logger.warn(`[Auth] openid: User not found and email "${userinfo.email}" is not verified`)
|
||||
return null
|
||||
} else {
|
||||
Logger.info(`[Auth] openid: User not found, checking existing with email "${userinfo.email}"`)
|
||||
user = await Database.userModel.getUserByEmail(userinfo.email)
|
||||
// Check that user is not already matched
|
||||
|
||||
if (user?.authOpenIDSub) {
|
||||
Logger.warn(`[Auth] openid: User found with email "${userinfo.email}" but is already matched with sub "${user.authOpenIDSub}"`)
|
||||
// TODO: Message isn't actually returned to the user yet. Need to override the passport authenticated callback
|
||||
failureMessage = 'A matching user was found but is already matched with another user from your auth provider'
|
||||
user = null
|
||||
}
|
||||
} else if (Database.serverSettings.authOpenIDMatchExistingBy === 'username' && userinfo.preferred_username) {
|
||||
Logger.info(`[Auth] openid: User not found, checking existing with username "${userinfo.preferred_username}"`)
|
||||
user = await Database.userModel.getUserByUsername(userinfo.preferred_username)
|
||||
// Check that user is not already matched
|
||||
if (user?.authOpenIDSub) {
|
||||
Logger.warn(`[Auth] openid: User found with username "${userinfo.preferred_username}" but is already matched with sub "${user.authOpenIDSub}"`)
|
||||
// TODO: Message isn't actually returned to the user yet. Need to override the passport authenticated callback
|
||||
failureMessage = 'A matching user was found but is already matched with another user from your auth provider'
|
||||
user = null
|
||||
return null // User is linked to a different OpenID subject; do not proceed.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.warn(`[Auth] openid: User not found and no email in userinfo`)
|
||||
// We deny login, because if the admin whishes to match email, it makes sense to require it
|
||||
return null
|
||||
}
|
||||
}
|
||||
// Match existing user by username
|
||||
else if (Database.serverSettings.authOpenIDMatchExistingBy === 'username') {
|
||||
let username
|
||||
|
||||
// If existing user was matched and isActive then save sub to user
|
||||
if (user?.isActive) {
|
||||
Logger.info(`[Auth] openid: New user found matching existing user "${user.username}"`)
|
||||
user.authOpenIDSub = userinfo.sub
|
||||
await Database.userModel.updateFromOld(user)
|
||||
} else if (user && !user.isActive) {
|
||||
Logger.warn(`[Auth] openid: New user found matching existing user "${user.username}" but that user is deactivated`)
|
||||
}
|
||||
if (userinfo.preferred_username) {
|
||||
Logger.info(`[Auth] openid: User not found, checking existing with userinfo.preferred_username "${userinfo.preferred_username}"`)
|
||||
username = userinfo.preferred_username
|
||||
} else if (userinfo.username) {
|
||||
Logger.info(`[Auth] openid: User not found, checking existing with userinfo.username "${userinfo.username}"`)
|
||||
username = userinfo.username
|
||||
} else {
|
||||
Logger.warn(`[Auth] openid: User not found and neither preferred_username nor username in userinfo`)
|
||||
return null
|
||||
}
|
||||
|
||||
// Optionally auto register the user
|
||||
if (!user && Database.serverSettings.authOpenIDAutoRegister) {
|
||||
Logger.info(`[Auth] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
||||
user = await Database.userModel.createUserFromOpenIdUserInfo(userinfo, this)
|
||||
|
||||
user = await Database.userModel.getUserByUsername(username)
|
||||
|
||||
if (user?.authOpenIDSub) {
|
||||
Logger.warn(`[Auth] openid: User found with username "${username}" but is already matched with sub "${user.authOpenIDSub}"`)
|
||||
return null // User is linked to a different OpenID subject; do not proceed.
|
||||
}
|
||||
}
|
||||
|
||||
// Found existing user via email or username
|
||||
if (user) {
|
||||
if (!user.isActive) {
|
||||
Logger.warn(`[Auth] openid: User found but is not active`)
|
||||
return null
|
||||
}
|
||||
|
||||
user.authOpenIDSub = userinfo.sub
|
||||
await Database.userModel.updateFromOld(user)
|
||||
|
||||
Logger.debug(`[Auth] openid: User found by email/username`)
|
||||
return user
|
||||
}
|
||||
|
||||
// If no existing user was matched, auto-register if configured
|
||||
if (Database.serverSettings.authOpenIDAutoRegister) {
|
||||
Logger.info(`[Auth] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
||||
user = await Database.userModel.createUserFromOpenIdUserInfo(userinfo, this)
|
||||
return user
|
||||
}
|
||||
|
||||
Logger.warn(`[Auth] openid: User not found and auto-register is disabled`)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the presence and content of the group claim in userinfo.
|
||||
*/
|
||||
validateGroupClaim(userinfo) {
|
||||
const groupClaimName = Database.serverSettings.authOpenIDGroupClaim
|
||||
if (!groupClaimName) // Allow no group claim when configured like this
|
||||
return true
|
||||
|
||||
// If configured it must exist in userinfo
|
||||
if (!userinfo[groupClaimName]) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user group based on group claim in userinfo.
|
||||
*
|
||||
* @param {import('./objects/user/User')} user
|
||||
* @param {Object} userinfo
|
||||
*/
|
||||
async setUserGroup(user, userinfo) {
|
||||
const groupClaimName = Database.serverSettings.authOpenIDGroupClaim
|
||||
if (!groupClaimName) // No group claim configured, don't set anything
|
||||
return
|
||||
|
||||
if (!userinfo[groupClaimName])
|
||||
throw new Error(`Group claim ${groupClaimName} not found in userinfo`)
|
||||
|
||||
const groupsList = userinfo[groupClaimName].map(group => group.toLowerCase())
|
||||
const rolesInOrderOfPriority = ['admin', 'user', 'guest']
|
||||
|
||||
let userType = rolesInOrderOfPriority.find(role => groupsList.includes(role))
|
||||
if (userType) {
|
||||
if (user.type === 'root') {
|
||||
// Check OpenID Group
|
||||
if (userType !== 'admin') {
|
||||
throw new Error(`Root user "${user.username}" cannot be downgraded to ${userType}. Denying login.`)
|
||||
} else {
|
||||
// If root user is logging in via OpenID, we will not change the type
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!user?.isActive) {
|
||||
if (user && !user.isActive) {
|
||||
failureMessage = 'Unauthorized'
|
||||
}
|
||||
// deny login
|
||||
done(null, null, failureMessage)
|
||||
return
|
||||
if (user.type !== userType) {
|
||||
Logger.info(`[Auth] openid callback: Updating user "${user.username}" type to "${userType}" from "${user.type}"`)
|
||||
user.type = userType
|
||||
await Database.userModel.updateFromOld(user)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`No valid group found in userinfo: ${JSON.stringify(userinfo[groupClaimName], null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// We also have to save the id_token for later (used for logout) because we cannot set cookies here
|
||||
user.openid_id_token = tokenset.id_token
|
||||
/**
|
||||
* Updates user permissions based on the advanced permissions claim.
|
||||
*
|
||||
* @param {import('./objects/user/User')} user
|
||||
* @param {Object} userinfo
|
||||
*/
|
||||
async updateUserPermissions(user, userinfo) {
|
||||
const absPermissionsClaim = Database.serverSettings.authOpenIDAdvancedPermsClaim
|
||||
if (!absPermissionsClaim) // No advanced permissions claim configured, don't set anything
|
||||
return
|
||||
|
||||
// permit login
|
||||
return done(null, user)
|
||||
}))
|
||||
if (user.type === 'admin' || user.type === 'root')
|
||||
return
|
||||
|
||||
const absPermissions = userinfo[absPermissionsClaim]
|
||||
if (!absPermissions)
|
||||
throw new Error(`Advanced permissions claim ${absPermissionsClaim} not found in userinfo`)
|
||||
|
||||
if (user.updatePermissionsFromExternalJSON(absPermissions)) {
|
||||
Logger.info(`[Auth] openid callback: Updating advanced perms for user "${user.username}" using "${JSON.stringify(absPermissions)}"`)
|
||||
await Database.userModel.updateFromOld(user)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,10 +461,19 @@ class Auth {
|
||||
sso_redirect_uri: oidcStrategy._params.redirect_uri // Save the redirect_uri (for the SSO Provider) for the callback
|
||||
}
|
||||
|
||||
var scope = 'openid profile email'
|
||||
if (global.ServerSettings.authOpenIDGroupClaim) {
|
||||
scope += ' ' + global.ServerSettings.authOpenIDGroupClaim
|
||||
}
|
||||
if (global.ServerSettings.authOpenIDAdvancedPermsClaim) {
|
||||
scope += ' ' + global.ServerSettings.authOpenIDAdvancedPermsClaim
|
||||
}
|
||||
|
||||
const authorizationUrl = client.authorizationUrl({
|
||||
...oidcStrategy._params,
|
||||
state: state,
|
||||
response_type: 'code',
|
||||
scope: scope,
|
||||
code_challenge,
|
||||
code_challenge_method
|
||||
})
|
||||
@ -346,7 +482,7 @@ class Auth {
|
||||
|
||||
res.redirect(authorizationUrl)
|
||||
} catch (error) {
|
||||
Logger.error(`[Auth] Error in /auth/openid route: ${error}`)
|
||||
Logger.error(`[Auth] Error in /auth/openid route: ${error}\n${error?.stack}`)
|
||||
res.status(500).send('Internal Server Error')
|
||||
}
|
||||
|
||||
@ -402,7 +538,7 @@ class Auth {
|
||||
// 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}`)
|
||||
Logger.error(`[Auth] Error in /auth/openid/mobile-redirect route: ${error}\n${error?.stack}`)
|
||||
res.status(500).send('Internal Server Error')
|
||||
}
|
||||
})
|
||||
@ -424,12 +560,12 @@ class Auth {
|
||||
}
|
||||
|
||||
function handleAuthError(isMobile, errorCode, errorMessage, logMessage, response) {
|
||||
Logger.error(logMessage)
|
||||
Logger.error(JSON.stringify(logMessage, null, 2))
|
||||
if (response) {
|
||||
// Depending on the error, it can also have a body
|
||||
// We also log the request header the passport plugin sents for the URL
|
||||
const header = response.req?._header.replace(/Authorization: [^\r\n]*/i, 'Authorization: REDACTED')
|
||||
Logger.debug(header + '\n' + response.body?.toString() + '\n' + JSON.stringify(response.body, null, 2))
|
||||
Logger.debug(header + '\n' + JSON.stringify(response.body, null, 2))
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const packageJson = require('../../../package.json')
|
||||
const { BookshelfView } = require('../../utils/constants')
|
||||
const Logger = require('../../Logger')
|
||||
const User = require('../user/User')
|
||||
|
||||
class ServerSettings {
|
||||
constructor(settings) {
|
||||
@ -72,6 +73,8 @@ class ServerSettings {
|
||||
this.authOpenIDAutoRegister = false
|
||||
this.authOpenIDMatchExistingBy = null
|
||||
this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
|
||||
this.authOpenIDGroupClaim = ''
|
||||
this.authOpenIDAdvancedPermsClaim = ''
|
||||
|
||||
if (settings) {
|
||||
this.construct(settings)
|
||||
@ -129,6 +132,8 @@ class ServerSettings {
|
||||
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
||||
this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
|
||||
this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
|
||||
this.authOpenIDGroupClaim = settings.authOpenIDGroupClaim || ''
|
||||
this.authOpenIDAdvancedPermsClaim = settings.authOpenIDAdvancedPermsClaim || ''
|
||||
|
||||
if (!Array.isArray(this.authActiveAuthMethods)) {
|
||||
this.authActiveAuthMethods = ['local']
|
||||
@ -216,7 +221,9 @@ class ServerSettings {
|
||||
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
||||
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim // Do not return to client
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,6 +233,8 @@ class ServerSettings {
|
||||
delete json.authOpenIDClientID
|
||||
delete json.authOpenIDClientSecret
|
||||
delete json.authOpenIDMobileRedirectURIs
|
||||
delete json.authOpenIDGroupClaim
|
||||
delete json.authOpenIDAdvancedPermsClaim
|
||||
return json
|
||||
}
|
||||
|
||||
@ -262,7 +271,11 @@ class ServerSettings {
|
||||
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
||||
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
||||
|
||||
authOpenIDSamplePermissions: User.getSampleAbsPermissions()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,6 +268,111 @@ class User {
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
// List of expected permission properties from the client
|
||||
static permissionMapping = {
|
||||
canDownload: 'download',
|
||||
canUpload: 'upload',
|
||||
canDelete: 'delete',
|
||||
canUpdate: 'update',
|
||||
canAccessExplicitContent: 'accessExplicitContent',
|
||||
canAccessAllLibraries: 'accessAllLibraries',
|
||||
canAccessAllTags: 'accessAllTags',
|
||||
tagsAreDenylist: 'selectedTagsNotAccessible',
|
||||
// Direct mapping for array-based permissions
|
||||
allowedLibraries: 'librariesAccessible',
|
||||
allowedTags: 'itemTagsSelected'
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user permissions from external JSON
|
||||
*
|
||||
* @param {Object} absPermissions JSON containing user permissions
|
||||
* @returns {boolean} true if updates were made
|
||||
*/
|
||||
updatePermissionsFromExternalJSON(absPermissions) {
|
||||
let hasUpdates = false
|
||||
let updatedUserPermissions = {}
|
||||
|
||||
// Initialize all permissions to false first
|
||||
Object.keys(User.permissionMapping).forEach(mappingKey => {
|
||||
const userPermKey = User.permissionMapping[mappingKey]
|
||||
if (typeof this.permissions[userPermKey] === 'boolean') {
|
||||
updatedUserPermissions[userPermKey] = false // Default to false for boolean permissions
|
||||
}
|
||||
})
|
||||
|
||||
// Map the boolean permissions from absPermissions
|
||||
Object.keys(absPermissions).forEach(absKey => {
|
||||
const userPermKey = User.permissionMapping[absKey]
|
||||
if (!userPermKey) {
|
||||
throw new Error(`Unexpected permission property: ${absKey}`)
|
||||
}
|
||||
|
||||
if (updatedUserPermissions[userPermKey] !== undefined) {
|
||||
updatedUserPermissions[userPermKey] = !!absPermissions[absKey]
|
||||
}
|
||||
})
|
||||
|
||||
// Update user permissions if changes were made
|
||||
if (JSON.stringify(this.permissions) !== JSON.stringify(updatedUserPermissions)) {
|
||||
this.permissions = updatedUserPermissions
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Handle allowedLibraries
|
||||
if (this.permissions.accessAllLibraries) {
|
||||
if (this.librariesAccessible.length) {
|
||||
this.librariesAccessible = []
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (absPermissions.allowedLibraries?.length && absPermissions.allowedLibraries.join(',') !== this.librariesAccessible.join(',')) {
|
||||
if (absPermissions.allowedLibraries.some(lid => typeof lid !== 'string')) {
|
||||
throw new Error('Invalid permission property "allowedLibraries", expecting array of strings')
|
||||
}
|
||||
this.librariesAccessible = absPermissions.allowedLibraries
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Handle allowedTags
|
||||
if (this.permissions.accessAllTags) {
|
||||
if (this.itemTagsSelected.length) {
|
||||
this.itemTagsSelected = []
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (absPermissions.allowedTags?.length && absPermissions.allowedTags.join(',') !== this.itemTagsSelected.join(',')) {
|
||||
if (absPermissions.allowedTags.some(tag => typeof tag !== 'string')) {
|
||||
throw new Error('Invalid permission property "allowedTags", expecting array of strings')
|
||||
}
|
||||
this.itemTagsSelected = absPermissions.allowedTags
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a sample to show how a JSON for updatePermissionsFromExternalJSON should look like
|
||||
*
|
||||
* @returns {string} JSON string
|
||||
*/
|
||||
static getSampleAbsPermissions() {
|
||||
// Start with a template object where all permissions are false for simplicity
|
||||
const samplePermissions = Object.keys(User.permissionMapping).reduce((acc, key) => {
|
||||
// For array-based permissions, provide a sample array
|
||||
if (key === 'allowedLibraries') {
|
||||
acc[key] = [`5406ba8a-16e1-451d-96d7-4931b0a0d966`, `918fd848-7c1d-4a02-818a-847435a879ca`]
|
||||
} else if (key === 'allowedTags') {
|
||||
acc[key] = [`ExampleTag`, `AnotherTag`, `ThirdTag`]
|
||||
} else {
|
||||
acc[key] = false
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return JSON.stringify(samplePermissions, null, 2) // Pretty print the JSON
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first available library id for user
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user