feat: support multiple client secrets per OIDC client (#1679)

This commit is contained in:
Alessandro (Ale) Segala
2026-08-11 00:54:52 +00:00
committed by GitHub
parent 03498e2f51
commit 155a1fcba0
29 changed files with 1353 additions and 165 deletions
+16 -2
View File
@@ -8,6 +8,8 @@ import type {
OidcClient,
OidcClientCreate,
OidcClientMetaData,
OidcClientSecret,
OidcClientSecretCreated,
OidcClientUpdate,
OidcClientWithAllowedUserGroups,
OidcClientWithAllowedUserGroupsCount,
@@ -88,8 +90,20 @@ class OidcService extends APIService {
cachedOidcClientLogo.bustCache(id, light);
};
createClientSecret = async (id: string) =>
(await this.api.post(`/oidc/clients/${encodeClientIdParam(id)}/secret`)).data.secret as string;
listClientSecrets = async (id: string) =>
(await this.api.get(`/oidc/clients/${encodeClientIdParam(id)}/secrets`))
.data as OidcClientSecret[];
createClientSecret = async (id: string, expiresAt: Date | null) =>
(
await this.api.post(`/oidc/clients/${encodeClientIdParam(id)}/secrets`, {
expiresAt: expiresAt?.toISOString() ?? null
})
).data as OidcClientSecretCreated;
deleteClientSecret = async (id: string, secretId: string) => {
await this.api.delete(`/oidc/clients/${encodeClientIdParam(id)}/secrets/${secretId}`);
};
updateAllowedUserGroups = async (id: string, userGroupIds: string[]) => {
const res = await this.api.put(`/oidc/clients/${encodeClientIdParam(id)}/allowed-user-groups`, {
+15 -4
View File
@@ -1,17 +1,28 @@
import { writable } from 'svelte/store';
const clientSecretStore = writable<string | null>(null);
// Holds the clear-text value of the client secrets created during the current page visit, keyed by secret ID.
// The server never returns those values again, so they are shown until the user navigates away and then forgotten.
const clientSecretStore = writable<Record<string, string>>({});
const set = (user: string) => {
clientSecretStore.set(user);
const set = (secretId: string, secret: string) => {
clientSecretStore.update((secrets) => ({ ...secrets, [secretId]: secret }));
};
const remove = (secretId: string) => {
clientSecretStore.update((secrets) => {
const remaining = { ...secrets };
delete remaining[secretId];
return remaining;
});
};
const clear = () => {
clientSecretStore.set(null);
clientSecretStore.set({});
};
export default {
subscribe: clientSecretStore.subscribe,
set,
remove,
clear
};
+15
View File
@@ -21,8 +21,23 @@ export type OidcClientFederatedIdentity = {
replayProtection: boolean;
};
export type OidcClientSecret = {
id: string;
// The first characters of the secret, empty for secrets created before Pocket ID supported multiple secrets
prefix: string;
createdAt: string;
expiresAt: string | null;
isActive: boolean;
};
// The clear-text value of a secret is only returned when it is created and cannot be retrieved afterwards
export type OidcClientSecretCreated = OidcClientSecret & {
secret: string;
};
export type OidcClientCredentials = {
federatedIdentities: OidcClientFederatedIdentity[];
secrets: OidcClientSecret[];
};
export type OidcDiscoveryConfiguration = {