diff --git a/packages/console/src/consts/logs.ts b/packages/console/src/consts/logs.ts index c4c256833dc..0156bf85266 100644 --- a/packages/console/src/consts/logs.ts +++ b/packages/console/src/consts/logs.ts @@ -1,4 +1,4 @@ -import type { AuditLogKey, LogKey, WebhookLogKey } from '@logto/schemas'; +import type { AuditLogKey, WebhookLogKey, JwtCustomizerLogKey, LogKey } from '@logto/schemas'; import { type Optional } from '@silverhand/essentials'; export const auditLogEventTitle: Record> & @@ -86,7 +86,14 @@ const webhookLogEventTitle: Record> & 'TriggerHook.PostSignIn': undefined, }); +const jwtCustomizerLogEventTitle: Record> & + Record> = Object.freeze({ + 'JwtCustomizer.AccessToken': undefined, + 'JwtCustomizer.ClientCredentials': undefined, +}); + export const logEventTitle: Record> & Record> = { ...auditLogEventTitle, ...webhookLogEventTitle, + ...jwtCustomizerLogEventTitle, }; diff --git a/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx b/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx index 290c73fb29b..e4a24c6d167 100644 --- a/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx +++ b/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx @@ -21,7 +21,7 @@ import { type WebhookDetailsOutletContext } from '../types'; import * as styles from './index.module.scss'; -const hooLogEventOptions = Object.values(HookEvent).map((event) => ({ +const hookLogEventOptions = Object.values(HookEvent).map((event) => ({ title: , value: hookEventLogKey[event], })); @@ -64,7 +64,7 @@ function WebhookLogs() {
{ updateSearchParameters({ event, page: undefined }); }} diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index 4f017de3410..a2ca1738d1c 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -17,7 +17,10 @@ import { LogtoJwtTokenPath, ExtraParamsKey, type Json, + jwtCustomizer as jwtCustomizerLog, + LogResult, } from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; import { conditional, trySafe, tryThat } from '@silverhand/essentials'; import i18next from 'i18next'; import koaBody from 'koa-body'; @@ -29,7 +32,7 @@ import RequestError from '#src/errors/RequestError/index.js'; import { addOidcEventListeners } from '#src/event-listeners/index.js'; import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js'; import { type LogtoConfigLibrary } from '#src/libraries/logto-config.js'; -import koaAuditLog from '#src/middleware/koa-audit-log.js'; +import koaAuditLog, { LogEntry } from '#src/middleware/koa-audit-log.js'; import koaBodyEtag from '#src/middleware/koa-body-etag.js'; import postgresAdapter from '#src/oidc/adapter.js'; import { @@ -65,6 +68,7 @@ export default function initOidc( resources: { findDefaultResource }, users: { findUserById }, organizations, + logs: { insertLog }, } = queries; const logoutSource = readFileSync('static/html/logout.html', 'utf8'); const logoutSuccessSource = readFileSync('static/html/post-logout/index.html', 'utf8'); @@ -206,7 +210,7 @@ export default function initOidc( }, }, extraParams: Object.values(ExtraParamsKey), - + // eslint-disable-next-line complexity extraTokenClaims: async (ctx, token) => { const { isDevFeaturesEnabled, isCloud } = EnvSet.values; @@ -215,9 +219,14 @@ export default function initOidc( return; } - try { - const isTokenClientCredentials = token instanceof ctx.oidc.provider.ClientCredentials; + const isTokenClientCredentials = token instanceof ctx.oidc.provider.ClientCredentials; + try { + /** + * It is by design to use `trySafe` here to catch the error but not log it since we do not + * want to insert an error log every time the OIDC provider issues a token when the JWT + * customizer is not configured. + */ const { script, envVars } = (await trySafe( logtoConfigs.getJwtCustomizer( @@ -270,8 +279,28 @@ export default function initOidc( context: { user: logtoUserInfo as Record }, }, }); - } catch { - // TODO: Log the error + } catch (error: unknown) { + const entry = new LogEntry( + `${jwtCustomizerLog.prefix}.${ + isTokenClientCredentials + ? jwtCustomizerLog.Type.ClientCredentials + : jwtCustomizerLog.Type.AccessToken + }` + ); + entry.append({ + result: LogResult.Error, + error: { message: String(error) }, + }); + const { payload } = entry; + await insertLog({ + id: generateStandardId(), + key: payload.key, + payload: { + ...payload, + tenantId: envSet.tenantId, + token, + }, + }); } }, extraClientMetadata: { diff --git a/packages/schemas/src/types/log/index.ts b/packages/schemas/src/types/log/index.ts index 71b759b7ac2..02577ab6529 100644 --- a/packages/schemas/src/types/log/index.ts +++ b/packages/schemas/src/types/log/index.ts @@ -1,16 +1,19 @@ import type * as hook from './hook.js'; import type * as interaction from './interaction.js'; +import type * as jwtCustomizer from './jwt-customizer.js'; import type * as token from './token.js'; export * as interaction from './interaction.js'; export * as token from './token.js'; export * as hook from './hook.js'; +export * as jwtCustomizer from './jwt-customizer.js'; /** Fallback for empty or unrecognized log keys. */ export const LogKeyUnknown = 'Unknown'; export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey; export type WebhookLogKey = hook.LogKey; +export type JwtCustomizerLogKey = jwtCustomizer.LogKey; /** * The union type of all available log keys. @@ -19,4 +22,4 @@ export type WebhookLogKey = hook.LogKey; * @see {@link interaction.LogKey} for interaction log keys. * @see {@link token.LogKey} for token log keys. **/ -export type LogKey = AuditLogKey | WebhookLogKey; +export type LogKey = AuditLogKey | WebhookLogKey | JwtCustomizerLogKey; diff --git a/packages/schemas/src/types/log/jwt-customizer.ts b/packages/schemas/src/types/log/jwt-customizer.ts new file mode 100644 index 00000000000..5524a0c5903 --- /dev/null +++ b/packages/schemas/src/types/log/jwt-customizer.ts @@ -0,0 +1,11 @@ +export type Prefix = 'JwtCustomizer'; + +export const prefix: Prefix = 'JwtCustomizer'; + +/** The type of a custom JWT scenario. */ +export enum Type { + AccessToken = 'AccessToken', + ClientCredentials = 'ClientCredentials', +} + +export type LogKey = `${Prefix}.${Type.AccessToken | Type.ClientCredentials}`;