mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-04 23:19:42 +00:00
Add OIDC Back-Channel Logout support
Implement OIDC Back-Channel Logout 1.0 (RFC). When enabled, the IdP can POST a signed logout_token JWT to invalidate user sessions server-side. - Add BackchannelLogoutHandler: JWT verification via jose, jti replay protection with bounded cache, session destruction by sub or sid - Add oidcSessionId column to sessions table with index for fast lookups - Add backchannel logout route (POST /auth/openid/backchannel-logout) - Notify connected clients via socket to redirect to login page - Add authOpenIDBackchannelLogoutEnabled toggle in schema-driven settings UI - Migration v2.34.0 adds oidcSessionId column and index - Polish settings UI: auto-populate loading state, subfolder dropdown options, KeyValueEditor fixes, localized descriptions via descriptionKey, duplicate key detection, success/error toasts - Localize backchannel logout toast (ToastSessionEndedByProvider) - OidcAuthStrategy tests now use real class via require-cache stubbing
This commit is contained in:
parent
33bee70a12
commit
073eff74ef
16 changed files with 886 additions and 104 deletions
|
|
@ -3,7 +3,7 @@
|
|||
<p v-if="label" class="text-sm font-semibold px-1 mb-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p>
|
||||
<div v-for="(entry, index) in entries" :key="index" class="flex items-center gap-2 mb-2">
|
||||
<div class="flex-grow">
|
||||
<input type="text" :value="entry.key" :disabled="disabled" class="w-full rounded-sm bg-primary border border-gray-600 text-sm px-3 py-2" placeholder="Group name" @input="updateKey(index, $event.target.value)" />
|
||||
<input type="text" :value="entry.key" :disabled="disabled" class="w-full rounded-sm bg-primary text-sm px-3 py-2" :class="isDuplicateKey(entry.key, index) ? 'border border-warning' : 'border border-gray-600'" :title="isDuplicateKey(entry.key, index) ? 'Duplicate group name' : ''" placeholder="Group name" @input="updateKey(index, $event.target.value)" />
|
||||
</div>
|
||||
<div class="w-32">
|
||||
<select :value="entry.value" :disabled="disabled" class="w-full rounded-sm bg-primary border border-gray-600 text-sm px-2 py-2" @change="updateValue(index, $event.target.value)">
|
||||
|
|
@ -14,7 +14,8 @@
|
|||
<span class="material-symbols text-xl">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" :disabled="disabled" class="flex items-center text-sm text-gray-300 hover:text-white" @click="addEntry">
|
||||
<p v-if="hasDuplicates" class="text-warning text-xs px-1 mb-1">Duplicate group names — only the last entry will be kept</p>
|
||||
<button type="button" :disabled="disabled" class="flex items-center text-sm text-gray-300 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed" @click="addEntry">
|
||||
<span class="material-symbols text-lg mr-1">add</span>
|
||||
<span>Add mapping</span>
|
||||
</button>
|
||||
|
|
@ -40,15 +41,33 @@ export default {
|
|||
entries: Object.entries(this.value || {}).map(([key, value]) => ({ key, value }))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasDuplicates() {
|
||||
const keys = this.entries.map((e) => e.key).filter((k) => k)
|
||||
return new Set(keys).size !== keys.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.entries = Object.entries(newVal || {}).map(([key, value]) => ({ key, value }))
|
||||
// Only rebuild entries if the prop differs from what local state would emit.
|
||||
// This prevents re-rendering (and closing dropdowns) when our own emit echoes back.
|
||||
const currentOutput = {}
|
||||
for (const entry of this.entries) {
|
||||
if (entry.key) currentOutput[entry.key] = entry.value
|
||||
}
|
||||
if (JSON.stringify(newVal || {}) !== JSON.stringify(currentOutput)) {
|
||||
this.entries = Object.entries(newVal || {}).map(([key, value]) => ({ key, value }))
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isDuplicateKey(key, index) {
|
||||
if (!key) return false
|
||||
return this.entries.some((e, i) => i !== index && e.key === key)
|
||||
},
|
||||
emitUpdate() {
|
||||
const obj = {}
|
||||
for (const entry of this.entries) {
|
||||
|
|
@ -72,7 +91,6 @@ export default {
|
|||
},
|
||||
addEntry() {
|
||||
this.entries.push({ key: '', value: this.valueOptions[0] || '' })
|
||||
this.emitUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue