feat: add PocketID OAuth provider support (#7318)

* feat(oauth): add Pocket-ID OAuth provider component

- Implements PocketIdSetting.svelte following Keycloak pattern
- Configures OIDC endpoints for Pocket-ID (/authorize, /api/oidc/*)
- Supports standard OIDC scopes (openid, profile, email)
- Uses passkey-only authentication via Pocket-ID

Refs #5678

* feat(oauth): register Pocket-ID in SSO provider list

- Import PocketIdSetting component
- Add Pocket-ID to provider list in SSO tab
- Update exclusion filter to prevent duplicate custom entries

Refs #5678

* fix(oauth): add missing PocketID icon and fix component integration

- Create PocketIdIcon.svelte component with user profile icon
- Register pocket-id in APP_TO_ICON_COMPONENT mapping
- Fix PocketIdSetting to use IconedResourceType pattern matching other OAuth providers

This resolves the issue where PocketID toggle was not appearing in SSO settings.

Refs #5678

* refactor: migrate PocketIdSetting to Svelte 5 runes syntax

- Use $props() with $bindable() for reactive prop binding
- Use $state() for local reactive state
- Use $derived() for computed values
- Use $effect() for reactive side effects
- Replace on:change with onchange event handler
- Pre-populate base URL from existing config when editing
- Clean up bracket notation to dot notation for value properties

Addresses reviewer feedback

* fix: rename pocket-id to pocketid for naming convention compliance

Change identifier from 'pocket-id' to 'pocketid' to match Windmill's naming convention.
No OAuth provider uses hyphens - all custom SSO providers (keycloak, authentik, authelia,
kanidm, zitadel) use no separator.

Changes:
- AuthSettings.svelte: oauths['pocket-id'] → oauths['pocketid'] (2 locations)
- PocketIdSetting.svelte: name={'pocket-id'} → name={'pocketid'}
- icons/index.ts: 'pocket-id': PocketIdIcon → pocketid: PocketIdIcon

Note: PocketID does not need oauth_connect.json entry as it's a custom SSO provider
with user-configured endpoints, similar to Keycloak/Authentik.

Addresses reviewer feedback

* fix: use TextInput component for consistency

---------

Co-authored-by: hugocasa <hugo@casademont.ch>
This commit is contained in:
Devdatta Talele
2026-01-30 22:57:44 +05:30
committed by GitHub
parent 5323bae61a
commit f83cafcbac
4 changed files with 98 additions and 2 deletions

View File

@@ -9,6 +9,7 @@
import CustomSso from './CustomSso.svelte'
import AuthentikSetting from '$lib/components/AuthentikSetting.svelte'
import AutheliaSetting from '$lib/components/AutheliaSetting.svelte'
import PocketIdSetting from '$lib/components/PocketIdSetting.svelte'
import KanidmSetting from '$lib/components/KanidmSetting.svelte'
import ZitadelSetting from '$lib/components/ZitadelSetting.svelte'
import NextcloudSetting from '$lib/components/NextcloudSetting.svelte'
@@ -258,11 +259,12 @@
<KeycloakSetting bind:value={oauths['keycloak']} />
<AuthentikSetting bind:value={oauths['authentik']} />
<AutheliaSetting bind:value={oauths['authelia']} />
<PocketIdSetting bind:value={oauths['pocketid']} />
<KanidmSetting bind:value={oauths['kanidm']} />
<ZitadelSetting bind:value={oauths['zitadel']} />
<NextcloudSetting bind:value={oauths['nextcloud']} {baseUrl} />
{#each Object.keys(oauths) as k}
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel', 'nextcloud'].includes(k) && oauths[k] && 'login_config' in oauths[k]}
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel', 'nextcloud', 'pocketid'].includes(k) && oauths[k] && 'login_config' in oauths[k]}
{#if oauths[k]}
<div class="flex flex-col gap-2 pb-4">
<div class="flex flex-row items-center gap-2">

View File

@@ -0,0 +1,80 @@
<script lang="ts">
import IconedResourceType from './IconedResourceType.svelte'
import TextInput from './text_input/TextInput.svelte'
import Toggle from './Toggle.svelte'
let { value = $bindable() }: { value: any } = $props()
let enabled = $derived(value != undefined)
// Initialize org from existing config or empty string
let org = $state(value?.connect_config?.auth_url?.replace('/authorize', '') ?? '')
// Update configs when org changes
$effect(() => {
if (value && org) {
value = {
...value,
connect_config: {
auth_url: `${org}/authorize`,
token_url: `${org}/api/oidc/token`,
scopes: ['openid', 'profile', 'email']
},
login_config: {
auth_url: `${org}/authorize`,
token_url: `${org}/api/oidc/token`,
userinfo_url: `${org}/api/oidc/userinfo`,
scopes: ['openid', 'profile', 'email']
}
}
}
})
function handleToggle(e: CustomEvent<boolean>) {
if (e.detail) {
value = { id: '', secret: '' }
org = ''
} else {
value = undefined
org = ''
}
}
</script>
<div class="flex flex-col gap-1">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="text-xs font-semibold text-emphasis flex gap-4 items-center">
<div class="w-[120px]"><IconedResourceType name={'pocketid'} after={true} /></div>
<Toggle checked={enabled} onchange={handleToggle} />
</label>
{#if enabled}
<div class="border rounded p-4 flex flex-col gap-6">
<label class="flex flex-col gap-1">
<span class="text-emphasis font-semibold text-xs">Pocket ID Url</span>
<span class="text-secondary font-normal text-xs">{'POCKET_ID_URL/authorize'}</span>
<TextInput inputProps={{ type: 'text', placeholder: 'https://id.example.com' }} bind:value={org} />
</label>
<label class="flex flex-col gap-1">
<span class="text-emphasis font-semibold text-xs">Custom Name</span>
<TextInput
inputProps={{ type: 'text', placeholder: 'Pocket ID' }}
bind:value={value['display_name']}
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-emphasis font-semibold text-xs">Client Id</span>
<TextInput
inputProps={{ type: 'text', placeholder: 'Client Id' }}
bind:value={value['id']}
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-emphasis font-semibold text-xs">Client Secret</span>
<TextInput
inputProps={{ type: 'text', placeholder: 'Client Secret' }}
bind:value={value['secret']}
/>
</label>
</div>
{/if}
</div>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
interface Props {
height?: string;
width?: string;
}
let { height = '24px', width = '24px' }: Props = $props();
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {width} {height} fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
</svg>

View File

@@ -103,6 +103,7 @@ import ApifyIcon from './ApifyIcon.svelte'
import McpIcon from './McpIcon.svelte'
import SageIcon from './SageIcon.svelte'
import ZohoIcon from './ZohoIcon.svelte'
import PocketIdIcon from './PocketIdIcon.svelte'
import type { Component } from 'svelte'
export const APP_TO_ICON_COMPONENT = {
postgresql: PostgresIcon,
@@ -211,7 +212,8 @@ export const APP_TO_ICON_COMPONENT = {
mqtt: MqttIcon,
apify: ApifyIcon,
mcp: McpIcon,
zoho: ZohoIcon
zoho: ZohoIcon,
pocketid: PocketIdIcon
} as unknown as Record<string, Component> // to generate correct svelte package types
export {