feat: prompt admin with PKCE client support hint (#1499)

Co-authored-by: james <james@goldfish.net>
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
Co-authored-by: Elias Schneider <login@eliasschneider.com>
Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
This commit is contained in:
James18232
2026-06-29 04:10:30 +10:00
committed by GitHub
parent 1b3ba3f6c2
commit 97bd466f38
15 changed files with 106 additions and 13 deletions

View File

@@ -530,5 +530,7 @@
"emails_verified_by_default_description": "When enabled, users' email addresses will be marked as verified by default upon signup or when their email address is changed.",
"user_has_no_passkeys_yet": "This user has no passkeys yet.",
"replay_protection": "Replay Protection",
"replay_protection_description": "If enabled the provided token can only be used once. If your provider uses the same token multiple times, you may need to disable this option."
"replay_protection_description": "If enabled the provided token can only be used once. If your provider uses the same token multiple times, you may need to disable this option.",
"pkce_supported_client_title": "This client supports PKCE",
"pkce_supported_client_description": "This client supports Proof Key for Code Exchange (PKCE). PKCE is a security feature that helps protect against certain attacks during the OAuth 2.0 authorization process. It's recommended to enable it."
}

View File

@@ -32,6 +32,7 @@ export type OidcClient = OidcClientMetaData & {
credentials?: OidcClientCredentials;
launchURL?: string;
isGroupRestricted: boolean;
pkceSupported: boolean;
};
export type OidcClientWithAllowedUserGroups = OidcClient & {
@@ -42,7 +43,7 @@ export type OidcClientWithAllowedUserGroupsCount = OidcClient & {
allowedUserGroupsCount: number;
};
export type OidcClientUpdate = Omit<OidcClient, 'id' | 'logoURL' | 'hasLogo' | 'hasDarkLogo'>;
export type OidcClientUpdate = Omit<OidcClient, 'id' | 'logoURL' | 'hasLogo' | 'hasDarkLogo' | 'pkceSupported'>;
export type OidcClientCreate = OidcClientUpdate & {
id?: string;
};

View File

@@ -4,6 +4,7 @@
import CollapsibleCard from '$lib/components/collapsible-card.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog';
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
import * as Alert from '$lib/components/ui/alert';
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import * as Field from '$lib/components/ui/field';
@@ -16,7 +17,7 @@
import type { ScimServiceProviderCreate } from '$lib/types/scim.type';
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideChevronLeft, LucideRefreshCcw } from '@lucide/svelte';
import { LucideChevronLeft, LucideInfo, LucideRefreshCcw } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import { backNavigate } from '../../users/navigate-back-util';
@@ -90,6 +91,9 @@
if (updatedClient.darkLogo !== undefined || updatedClient.darkLogoUrl !== undefined) {
client.hasDarkLogo = updatedClient.darkLogo !== null || !!updatedClient.darkLogoUrl;
}
if (updatedClient.pkceEnabled) {
client.pkceEnabled = updatedClient.pkceEnabled;
}
toast.success(m.oidc_client_updated_successfully());
})
.catch((e) => {
@@ -209,11 +213,22 @@
>
{/snippet}
{#if client.pkceSupported && !client.pkceEnabled}
<Alert.Root variant="info">
<LucideInfo class="size-4" />
<Alert.Title>{m.pkce_supported_client_title()}</Alert.Title>
<Alert.Description>
{m.pkce_supported_client_description()}
</Alert.Description>
</Alert.Root>
{/if}
<div>
<button type="button" class="text-muted-foreground flex text-sm" onclick={backNavigation.go}
><LucideChevronLeft class="size-5" /> {m.back()}</button
>
</div>
<Card.Root>
<Card.Header>
<Card.Title>{client.name}</Card.Title>

View File

@@ -57,7 +57,8 @@
federatedIdentities: existingClient?.credentials?.federatedIdentities || []
},
logoUrl: '',
darkLogoUrl: ''
darkLogoUrl: '',
pkceSupported: existingClient?.pkceSupported || false
};
const formSchema = z.object({
@@ -98,6 +99,8 @@
type FormSchema = typeof formSchema;
const { inputs, errors, ...form } = createForm<FormSchema>(formSchema, client);
const pkcePromptNeeded = $derived(!$inputs.pkceEnabled.value && client.pkceSupported);
async function onSubmit() {
const data = form.validate();
if (!data) return;
@@ -215,13 +218,19 @@
}}
bind:checked={$inputs.isPublic.value}
/>
<SwitchWithLabel
id="pkce"
label={m.pkce()}
description={m.proof_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks()}
disabled={$inputs.isPublic.value}
bind:checked={$inputs.pkceEnabled.value}
/>
<div
class="rounded-lg transition-all duration-200"
class:[&_[data-switch-root]]:ring-2={pkcePromptNeeded}
class:[&_[data-switch-root]]:ring-blue-500={pkcePromptNeeded}
>
<SwitchWithLabel
id="pkce"
label={m.pkce()}
description={m.proof_key_code_exchange_is_a_security_feature_to_prevent_csrf_and_authorization_code_interception_attacks()}
disabled={$inputs.isPublic.value}
bind:checked={$inputs.pkceEnabled.value}
/>
</div>
<SwitchWithLabel
id="requires-reauthentication"
label={m.requires_reauthentication()}