diff --git a/frontend/auth/admin/AdminApp.vue b/frontend/auth/admin/AdminApp.vue index ea53db5..5012cba 100644 --- a/frontend/auth/admin/AdminApp.vue +++ b/frontend/auth/admin/AdminApp.vue @@ -406,18 +406,44 @@ function deletePermission(p) { } // OIDC Client actions -function createOidcClient() { - openDialog('oidc-create', { name: '', redirect_uris: '' }) +async function createOidcClient() { + try { + // Create the record on the server immediately, then open edit dialog + const result = await apiJson('/auth/api/admin/oidc-clients', { method: 'POST', body: {} }) + await loadOidcClients() + openDialog('oidc-edit', { + client_id: result.client_id, + client_secret: result.client_secret, + isNew: true, + name: '', + redirect_uris: '' + }) + } catch (e) { + authStore.showMessage(e.message || 'Failed to create OIDC client', 'error') + } } function editOidcClient(client) { openDialog('oidc-edit', { - client, + client_id: client.uuid, name: client.name, redirect_uris: client.redirect_uris.join('\n') }) } +async function resetOidcSecret(clientId) { + try { + const result = await apiJson(`/auth/api/admin/oidc-clients/${clientId}/reset-secret`, { method: 'POST' }) + // Update the dialog data in place so the new secret is shown + if (dialog.value.type === 'oidc-edit' && dialog.value.data?.client_id === clientId) { + dialog.value.data.client_secret = result.client_secret + } + authStore.showMessage('Client secret has been reset.', 'success', 2500) + } catch (e) { + authStore.showMessage(e.message || 'Failed to reset client secret', 'error') + } +} + function deleteOidcClient(client) { openDialog('confirm', { message: `Delete OIDC client "${client.name}"? This will break any applications using this client.`, @@ -771,51 +797,18 @@ async function submitDialog() { authStore.showMessage(e.message || 'Failed to create permission', 'error') }) return // Don't call closeDialog() again - } else if (t === 'oidc-create') { - const name = dialog.value.data.name?.trim() - const uris = dialog.value.data.redirect_uris?.trim() - if (!name) throw new Error('Client name required') - if (!uris) throw new Error('Redirect URIs required') - - const redirect_uris = uris.split('\n').map(u => u.trim()).filter(u => u) - if (redirect_uris.length === 0) throw new Error('At least one redirect URI required') - - // Close dialog immediately, then perform async operation - closeDialog() - apiJson('/auth/api/admin/oidc-clients', { method: 'POST', body: { name, redirect_uris } }) - .then((result) => { - // Show success dialog with client credentials - openDialog('oidc-created', { - client_id: result.client_id, - client_secret: result.client_secret - }) - loadOidcClients() - }) - .catch(e => { - authStore.showMessage(e.message || 'Failed to create OIDC client', 'error') - }) - return // Don't call closeDialog() again } else if (t === 'oidc-edit') { - const { client } = dialog.value.data + const { client_id } = 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') - if (!uris) throw new Error('Redirect URIs required') - const redirect_uris = uris.split('\n').map(u => u.trim()).filter(u => u) - if (redirect_uris.length === 0) throw new Error('At least one redirect URI required') + const redirect_uris = uris ? uris.split('\n').map(u => u.trim()).filter(u => u) : [] // Close dialog immediately, then perform async operation closeDialog() - // Check if anything changed - const oldUris = [...client.redirect_uris].sort().join('\n') - const newUris = [...redirect_uris].sort().join('\n') - if (name === client.name && oldUris === newUris) { - return // No changes - } - - apiJson(`/auth/api/admin/oidc-clients/${client.uuid}`, { method: 'PATCH', body: { name, redirect_uris } }) + apiJson(`/auth/api/admin/oidc-clients/${client_id}`, { method: 'PATCH', body: { name, redirect_uris } }) .then(() => { authStore.showMessage(`OIDC client "${name}" updated.`, 'success', 2500) loadOidcClients() @@ -942,6 +935,7 @@ async function submitDialog() { :settings="authStore.settings" @submit-dialog="submitDialog" @close-dialog="closeDialog" + @reset-oidc-secret="resetOidcSecret" /> diff --git a/frontend/src/admin/AdminDialogs.vue b/frontend/src/admin/AdminDialogs.vue index 438ebc9..e3b3880 100644 --- a/frontend/src/admin/AdminDialogs.vue +++ b/frontend/src/admin/AdminDialogs.vue @@ -2,6 +2,7 @@ import { computed } from 'vue' import Modal from '@/components/Modal.vue' import NameEditForm from '@/components/NameEditForm.vue' +import { useAuthStore } from '@/stores/auth' const props = defineProps({ dialog: Object, @@ -9,11 +10,20 @@ const props = defineProps({ settings: Object }) -const emit = defineEmits(['submitDialog', 'closeDialog']) +const emit = defineEmits(['submitDialog', 'closeDialog', 'resetOidcSecret']) const NAME_EDIT_TYPES = new Set(['org-update', 'role-update', 'user-update-name']) -const NO_SUBMIT_TYPES = new Set(['oidc-created']) +const NO_SUBMIT_TYPES = new Set([]) const rpId = computed(() => props.settings?.rp_id || 'the configured domain') +const discoveryUrl = computed(() => `${window.location.origin}/.well-known/openid-configuration`) + +// Copy-to-clipboard helper +const authStore = useAuthStore() +function copyText(value, label) { + navigator.clipboard.writeText(value).then(() => { + authStore.showMessage(`${label} copied to clipboard`, 'success', 1500) + }) +} -