oidc: authenticate SPA users via Authentik
- Add OIDC proxy endpoints (GET config, POST token) to API server - Implement PKCE Authorization Code flow in SPA - Enable Authentik login tab in Config page - Handle callback + auto-refresh + session restore - Add restart: unless-stopped to all persistent services - Configure OIDC issuer + client_id in docker-compose
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { Label } from '$lib/components/ui/label'
|
||||
import { Button } from '$lib/components/ui/button'
|
||||
import { fetchWithAuth, setConfig, initConfig, getConfig, clearConfig } from '$lib/config'
|
||||
import { startLogin, logout as oidcLogout, getUser, isOIDCConfigured } from '$lib/oidc'
|
||||
|
||||
let { onConnected, onCancel }: { onConnected: () => void; onCancel?: () => void } = $props()
|
||||
|
||||
@@ -13,12 +14,18 @@
|
||||
let token = $state(existing.token ?? '')
|
||||
let connecting = $state(false)
|
||||
let error = $state('')
|
||||
let oidcLoggingIn = $state(false)
|
||||
let oidcUser = $state(getUser())
|
||||
let oidcConfigured = $state(isOIDCConfigured())
|
||||
|
||||
function disconnect() {
|
||||
clearConfig()
|
||||
oidcLogout()
|
||||
apiUrl = ''
|
||||
token = ''
|
||||
error = ''
|
||||
oidcUser = null
|
||||
oidcConfigured = false
|
||||
}
|
||||
|
||||
async function connect() {
|
||||
@@ -43,6 +50,23 @@
|
||||
connecting = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loginWithAuthentik() {
|
||||
error = ''
|
||||
oidcLoggingIn = true
|
||||
try {
|
||||
await startLogin()
|
||||
} catch (e: any) {
|
||||
error = e.message || 'OIDC login failed'
|
||||
oidcLoggingIn = false
|
||||
}
|
||||
}
|
||||
|
||||
function logoutOIDC() {
|
||||
oidcLogout()
|
||||
oidcUser = null
|
||||
oidcConfigured = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-svh items-center justify-center p-6">
|
||||
@@ -52,10 +76,10 @@
|
||||
<Card.Description>Enter the server URL and your access token.</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<Tabs.Root value="token">
|
||||
<Tabs.Root value={oidcConfigured ? 'oidc' : 'token'}>
|
||||
<Tabs.List class="mb-4 grid w-full grid-cols-2">
|
||||
<Tabs.Trigger value="token">Token</Tabs.Trigger>
|
||||
<Tabs.Trigger value="oidc" disabled>Login with Authentik (coming soon)</Tabs.Trigger>
|
||||
<Tabs.Trigger value="oidc">Login with Authentik</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="token">
|
||||
<form class="flex flex-col gap-4" onsubmit={(e) => { e.preventDefault(); connect() }}>
|
||||
@@ -90,6 +114,36 @@
|
||||
{/if}
|
||||
</form>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="oidc">
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if oidcConfigured && oidcUser}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Logged in as <span class="font-medium text-foreground">{oidcUser}</span>
|
||||
</p>
|
||||
<Button type="button" variant="default" onclick={() => onConnected()}>
|
||||
Continue to Dashboard
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="sm" onclick={logoutOIDC}>
|
||||
Log out
|
||||
</Button>
|
||||
{:else}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Sign in with your Authentik account to access the control room.
|
||||
</p>
|
||||
{#if error}
|
||||
<p class="text-sm text-destructive">{error}</p>
|
||||
{/if}
|
||||
<Button type="button" disabled={oidcLoggingIn} onclick={loginWithAuthentik}>
|
||||
{oidcLoggingIn ? 'Redirecting to Authentik…' : 'Login with Authentik'}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if existing.token}
|
||||
<Button type="button" variant="ghost" size="sm" onclick={disconnect}>
|
||||
Forget saved connection
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
Reference in New Issue
Block a user