diff --git a/packages/core/src/include.d/oidc-provider.d.ts b/packages/core/src/include.d/oidc-provider.d.ts new file mode 100644 index 000000000..d5b24b138 --- /dev/null +++ b/packages/core/src/include.d/oidc-provider.d.ts @@ -0,0 +1,6 @@ +import { CustomClientMetadata } from '@logto/schemas'; +import { AllClientMetadata } from 'oidc-provider'; + +declare module 'oidc-provider' { + export interface AllClientMetadata extends CustomClientMetadata {} +} diff --git a/packages/core/src/oidc/adapter.ts b/packages/core/src/oidc/adapter.ts index 091ecba4d..96477ac41 100644 --- a/packages/core/src/oidc/adapter.ts +++ b/packages/core/src/oidc/adapter.ts @@ -23,6 +23,7 @@ export default function postgresAdapter(modelName: string): ReturnType ({ client_id, client_name, @@ -30,6 +31,7 @@ export default function postgresAdapter(modelName: string): ReturnType { const keys = [await fromKeyLike(privateKey)]; @@ -71,6 +72,15 @@ export default async function initOidc(app: Koa): Promise { } }, }, + extraClientMetadata: { + properties: Object.keys(CustomClientMetadataType), + validator: (_ctx, key, value) => { + const result = customClientMetadataGuard.pick({ [key]: true }).safeParse({ key: value }); + if (!result.success) { + throw new errors.InvalidClientMetadata(key); + } + }, + }, clientBasedCORS: (_, origin) => { console.log('origin', origin); return origin.startsWith('http://localhost:3000'); @@ -89,6 +99,19 @@ export default async function initOidc(app: Koa): Promise { }, }; }, + ttl: { + /** + * [OIDC Provider Default Settings](https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#ttl) + */ + IdToken: (ctx, token, client) => { + const { idTokenTtl } = client.metadata(); + return idTokenTtl ?? defaultIdTokenTtl; + }, + RefreshToken: (ctx, token, client) => { + const { refreshTokenTtl } = client.metadata(); + return refreshTokenTtl ?? defaultRefreshTokenTtl; + }, + }, }); app.use(mount('/oidc', oidc.app)); return oidc; diff --git a/packages/schemas/src/db-entries/application.ts b/packages/schemas/src/db-entries/application.ts index 1cc38d97a..610e8bc94 100644 --- a/packages/schemas/src/db-entries/application.ts +++ b/packages/schemas/src/db-entries/application.ts @@ -5,6 +5,8 @@ import { z } from 'zod'; import { OidcClientMetadata, oidcClientMetadataGuard, + CustomClientMetadata, + customClientMetadataGuard, GeneratedSchema, Guard, } from '../foundations'; @@ -16,8 +18,7 @@ export type ApplicationUpdate = { description?: string | null; type: ApplicationType; oidcClientMetadata: OidcClientMetadata; - idTokenTtl?: number; - refreshTokenTtl?: number; + customClientMetadata?: CustomClientMetadata; createdAt?: number; }; @@ -27,8 +28,7 @@ export type Application = { description: string | null; type: ApplicationType; oidcClientMetadata: OidcClientMetadata; - idTokenTtl: number; - refreshTokenTtl: number; + customClientMetadata: CustomClientMetadata; createdAt: number; }; @@ -38,8 +38,7 @@ const guard: Guard = z.object({ description: z.string().optional(), type: z.nativeEnum(ApplicationType), oidcClientMetadata: oidcClientMetadataGuard, - idTokenTtl: z.number().optional(), - refreshTokenTtl: z.number().optional(), + customClientMetadata: customClientMetadataGuard.optional(), createdAt: z.number().optional(), }); @@ -52,8 +51,7 @@ export const Applications: GeneratedSchema = Object.freeze({ description: 'description', type: 'type', oidcClientMetadata: 'oidc_client_metadata', - idTokenTtl: 'id_token_ttl', - refreshTokenTtl: 'refresh_token_ttl', + customClientMetadata: 'custom_client_metadata', createdAt: 'created_at', }, fieldKeys: [ @@ -62,8 +60,7 @@ export const Applications: GeneratedSchema = Object.freeze({ 'description', 'type', 'oidcClientMetadata', - 'idTokenTtl', - 'refreshTokenTtl', + 'customClientMetadata', 'createdAt', ], guard, diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 893443833..7f84b184f 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -22,6 +22,18 @@ export const oidcClientMetadataGuard = z.object({ export type OidcClientMetadata = z.infer; +export enum CustomClientMetadataType { + idTokenTtl = 'idTokenTtl', + refreshTokenTtl = 'refreshTokenTtl', +} + +export const customClientMetadataGuard = z.object({ + [CustomClientMetadataType.idTokenTtl]: z.number().optional(), + [CustomClientMetadataType.refreshTokenTtl]: z.number().optional(), +}); + +export type CustomClientMetadata = z.infer; + export const userLogPayloadGuard = z.object({ ip: z.string().optional(), userAgent: z.string().optional(), diff --git a/packages/schemas/tables/applications.sql b/packages/schemas/tables/applications.sql index f44ac2626..aea5742cd 100644 --- a/packages/schemas/tables/applications.sql +++ b/packages/schemas/tables/applications.sql @@ -6,8 +6,7 @@ create table applications ( description text, type application_type not null, oidc_client_metadata jsonb /* @use OidcClientMetadata */ not null, - id_token_ttl bigint not null default(86400), - refresh_token_ttl bigint not null default(2592000), + custom_client_metadata jsonb /* @use CustomClientMetadata */ not null default '{}'::jsonb, created_at timestamptz not null default(now()), primary key (id) );