feat(auth): admin_role config; drop manual admin-subject from wizard
OIDC settings page + setup wizard now configure the bootstrap admin role instead of a hand-typed OIDC subject. The OIDC-only admin link is handled automatically by the backend admin-role auto-connect on first sign-in (explained inline in both the wizard and settings page). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ interface Settings {
|
||||
redirect_uri: string | null
|
||||
scopes: string | null
|
||||
post_login_redirect: string | null
|
||||
admin_role: string
|
||||
oidc_only: boolean
|
||||
effective_enabled: boolean
|
||||
source: string
|
||||
@@ -31,6 +32,7 @@ export default function OidcSettingsPage() {
|
||||
redirect_uri: '',
|
||||
scopes: 'openid email profile',
|
||||
post_login_redirect: '',
|
||||
admin_role: 'admin',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,6 +48,7 @@ export default function OidcSettingsPage() {
|
||||
redirect_uri: data.redirect_uri || '',
|
||||
scopes: data.scopes || 'openid email profile',
|
||||
post_login_redirect: data.post_login_redirect || '',
|
||||
admin_role: data.admin_role || 'admin',
|
||||
})
|
||||
})
|
||||
.catch((e) => setMessage(e.response?.data?.detail || 'Failed to load OIDC settings'))
|
||||
@@ -63,6 +66,7 @@ export default function OidcSettingsPage() {
|
||||
redirect_uri: form.redirect_uri.trim(),
|
||||
scopes: form.scopes.trim(),
|
||||
post_login_redirect: form.post_login_redirect.trim(),
|
||||
admin_role: form.admin_role.trim() || 'admin',
|
||||
}
|
||||
if (form.client_secret) payload.client_secret = form.client_secret
|
||||
const { data } = await api.put<Settings>('/auth/oidc/settings', payload)
|
||||
@@ -150,6 +154,14 @@ export default function OidcSettingsPage() {
|
||||
Post-login redirect (frontend)
|
||||
<input placeholder="https://hf.example.com/oidc/callback" value={form.post_login_redirect} onChange={(e) => setForm({ ...form, post_login_redirect: e.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
Admin role (bootstrap)
|
||||
<input placeholder="admin" value={form.admin_role} onChange={(e) => setForm({ ...form, admin_role: e.target.value })} />
|
||||
</label>
|
||||
<p className="text-dim">
|
||||
OIDC-only bootstrap: before any admin is linked, an IdP user whose token carries this role
|
||||
auto-connects to the HarborForge admin account on first sign-in. Disables itself once an admin is bound.
|
||||
</p>
|
||||
<button className="btn-primary" disabled={saving} onClick={save}>
|
||||
{saving ? 'Saving...' : 'Save OIDC Settings'}
|
||||
</button>
|
||||
|
||||
@@ -22,7 +22,7 @@ interface SetupForm {
|
||||
oidc_redirect_uri: string
|
||||
oidc_scopes: string
|
||||
oidc_post_login_redirect: string
|
||||
oidc_admin_subject: string
|
||||
oidc_admin_role: string
|
||||
}
|
||||
|
||||
const oidcOnly = getRuntimeOidcOnly() === true
|
||||
@@ -52,7 +52,7 @@ export default function SetupWizardPage({ initialWizardPort, onComplete }: Props
|
||||
oidc_redirect_uri: '',
|
||||
oidc_scopes: 'openid email profile',
|
||||
oidc_post_login_redirect: '',
|
||||
oidc_admin_subject: '',
|
||||
oidc_admin_role: 'admin',
|
||||
})
|
||||
|
||||
const set = (key: keyof SetupForm, value: string | number | boolean) =>
|
||||
@@ -85,8 +85,8 @@ export default function SetupWizardPage({ initialWizardPort, onComplete }: Props
|
||||
if (!form.oidc_client_id.trim()) return 'OIDC client ID is required'
|
||||
if (!form.oidc_client_secret.trim()) return 'OIDC client secret is required'
|
||||
if (!form.oidc_redirect_uri.trim()) return 'OIDC redirect/callback URL is required'
|
||||
if (oidcOnly && !form.oidc_admin_subject.trim()) {
|
||||
return "In OIDC-only mode the admin's OIDC subject is required so the admin can sign in"
|
||||
if (oidcOnly && !form.oidc_admin_role.trim()) {
|
||||
return 'In OIDC-only mode the admin role is required so the admin can bootstrap'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export default function SetupWizardPage({ initialWizardPort, onComplete }: Props
|
||||
redirect_uri: form.oidc_redirect_uri.trim(),
|
||||
scopes: form.oidc_scopes.trim() || 'openid email profile',
|
||||
post_login_redirect: form.oidc_post_login_redirect.trim() || undefined,
|
||||
admin_subject: form.oidc_admin_subject.trim() || undefined,
|
||||
admin_role: form.oidc_admin_role.trim() || 'admin',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,12 +230,16 @@ export default function SetupWizardPage({ initialWizardPort, onComplete }: Props
|
||||
<label>Redirect / Callback URL <input value={form.oidc_redirect_uri} onChange={(e) => set('oidc_redirect_uri', e.target.value)} placeholder="https://hf-api.example.com/auth/oidc/callback" /></label>
|
||||
<label>Scopes <input value={form.oidc_scopes} onChange={(e) => set('oidc_scopes', e.target.value)} /></label>
|
||||
<label>Post-login redirect (frontend) <input value={form.oidc_post_login_redirect} onChange={(e) => set('oidc_post_login_redirect', e.target.value)} placeholder="https://hf.example.com/oidc/callback" /></label>
|
||||
{oidcOnly && (
|
||||
<label>Admin OIDC subject (sub)
|
||||
<input value={form.oidc_admin_subject} onChange={(e) => set('oidc_admin_subject', e.target.value)} placeholder="the admin's `sub` claim at the IdP" />
|
||||
</label>
|
||||
)}
|
||||
<label>Admin role (bootstrap)
|
||||
<input value={form.oidc_admin_role} onChange={(e) => set('oidc_admin_role', e.target.value)} placeholder="admin" />
|
||||
</label>
|
||||
<p className="setup-hint">Register the Redirect / Callback URL above at your identity provider.</p>
|
||||
{oidcOnly && (
|
||||
<p className="setup-hint">
|
||||
OIDC-only: before any admin is linked, the first IdP user whose token carries the
|
||||
role above auto-connects to the HarborForge admin account. It then disables itself.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="setup-nav">
|
||||
|
||||
Reference in New Issue
Block a user