Frontend: domain dialog fixes and dead-code removal

- Empty-origins default shows as a '*' placeholder row that is not
  persisted unless edited (open+save no longer tightens any-scheme to
  https-only)
- Foreign wildcards are flagged invalid instead of being classified as
  related origins; over-cap related list disables Save
- Single-label rp-ids accepted (matching backend validate_rp_id)
- Auth-host mark follows row edits; row menu state resets on dialog close
- rp-id/origin keys lowercased for classification and submit
- settings cache: stale in-flight responses no longer overwrite a forced
  refresh
- Remove the dead oidc-edit dialog path and other unused code; fix stale
  comments (realm→domain, '*' semantics, per-domain discovery URLs)
This commit is contained in:
2026-09-07 13:24:19 +00:00
parent 3ff41ca354
commit 0d1d18e8a7
8 changed files with 87 additions and 110 deletions
+14 -40
View File
@@ -17,7 +17,7 @@ import { apiJson, SessionValidator, settings as paskiaSettings } from 'paskia'
import { updateThemeFromSession } from '@/utils/theme'
import { uuidv7 } from 'uuidv7'
import { getDirection } from '@/utils/keynav'
import { goBack, originDisplayEntries } from '@/utils/helpers'
import { originDisplayEntries } from '@/utils/helpers'
const info = ref(null)
const loading = ref(true)
@@ -465,10 +465,6 @@ function resetOidcSecret(clientId) {
if (editingOidcClient.value?.client_id === clientId) {
editingOidcClient.value = { ...editingOidcClient.value, client_secret }
}
// Also update dialog if open (for backwards compatibility)
if (dialog.value.type === 'oidc-edit' && dialog.value.data?.client_id === clientId) {
dialog.value.data.client_secret = client_secret
}
}
function createPermissionForClient(clientId) {
@@ -483,14 +479,15 @@ function createDomain() {
auth_host: '',
origins: [],
originValidation: [],
originPlaceholders: [],
wellKnownCheck: null,
})
}
function openDomain(domain) {
// One combined list for editing, in display order: in-domain sites and
// related origins, classified by hostname. The default is always shown
// explicitly as the '*' entry.
// related origins, classified by hostname. An empty origins object shows
// as a '*' placeholder row, omitted again on submit unless edited.
const rows = originDisplayEntries(domain)
openDialog('domain-edit', {
isNew: false,
@@ -499,6 +496,7 @@ function openDomain(domain) {
auth_host: rows.find(r => r.auth)?.key || '',
origins: rows.map(r => r.key),
originValidation: rows.map(() => null),
originPlaceholders: rows.map(r => !!r.placeholder),
wellKnownCheck: null,
})
}
@@ -910,49 +908,27 @@ async function submitDialog() {
authStore.showMessage(e.message || 'Failed to create permission', 'error')
})
return // Don't call closeDialog() again
} else if (t === 'oidc-edit') {
const { client_id, client_secret, isNew } = dialog.value.data
const name = dialog.value.data.name?.trim()
const uris = dialog.value.data.redirect_uris?.trim()
if (!name) throw new Error('Client name required')
const redirect_uris = uris ? uris.split('\n').map(u => u.trim()).filter(u => u) : []
// Close dialog immediately, then perform async operation
closeDialog()
const req = client_secret
? sha256Hex(client_secret).then(secret_hash => isNew
? apiJson('/auth/api/admin/oidc-clients', { method: 'POST', body: { client_id, secret_hash, name, redirect_uris } })
: apiJson(`/auth/api/admin/oidc-clients/${client_id}`, { method: 'PATCH', body: { name, redirect_uris, secret_hash } }))
: apiJson(`/auth/api/admin/oidc-clients/${client_id}`, { method: 'PATCH', body: { name, redirect_uris } })
req
.then(() => {
authStore.showMessage(`OIDC client "${name}" ${isNew ? 'created' : 'updated'}.`, 'success', 2500)
loadAdminData()
})
.catch(e => {
authStore.showMessage(e.message || `Failed to ${isNew ? 'create' : 'update'} OIDC client`, 'error')
})
return // Don't call closeDialog() again
} else if (t === 'domain-edit') {
const d = dialog.value.data
const rp_id = d.rp_id?.trim().toLowerCase()
if (!rp_id) throw new Error('Domain (rp-id) required')
const rp_name = d.rp_name?.trim() || ''
const auth_host = d.auth_host?.trim() || ''
const auth_host = d.auth_host?.trim().toLowerCase() || ''
// The combined origins list is split by hostname: entries on the
// rp-id domain form the in-domain origins object (the auth host
// entry is marked), entries elsewhere are related origins (ROR).
// Wildcards ('*.app.example.com') classify by their base domain.
// Keys are stored without the https:// scheme.
const keyOf = o => o.replace(/^https:\/\//, '').replace(/\/+$/, '')
// Keys are stored lowercased, without the https:// scheme.
const keyOf = o => o.replace(/^https:\/\//i, '').replace(/\/+$/, '').toLowerCase()
const origins = {}
const related = {}
for (const o of (d.origins || []).map(o => o.trim()).filter(o => o)) {
const key = keyOf(o)
for (const [i, o] of (d.origins || []).entries()) {
const key = keyOf(o.trim())
// An untouched placeholder row only displays the empty-origins
// default (any scheme in-domain) — don't persist it as '*'
if (!key || d.originPlaceholders?.[i]) continue
if (key === '*') {
origins['*'] = true // anything in-domain, any scheme/port
origins['*'] = true // shorthand for '*.{rp-id}' (https-only outside localhost)
continue
}
let hn = null
@@ -1119,8 +1095,6 @@ async function submitDialog() {
:permission-id-pattern="PERMISSION_ID_PATTERN"
@submit-dialog="submitDialog"
@close-dialog="closeDialog"
@reset-oidc-secret="resetOidcSecret"
@create-permission-for-client="createPermissionForClient"
/>
</div>
</template>