From cf31e3a5af179a5cd3dbe69da697bde09df403eb Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 31 Jul 2024 18:38:34 +0800 Subject: [PATCH] feat(core,schemas): add auditLogs to experience API (#6361) * refactor(core): refactor backup code generate flow refactor backup code generate flow * fix(core): fix api payload fix api payload * fix(core): fix rebase issue fix rebase issue * feat(core,schemas): add auditLogs to experience API add auditLogs to experience API --- .../classes/experience-interaction.ts | 9 +++- .../core/src/routes/experience/classes/mfa.ts | 21 ++++++-- .../src/routes/experience/classes/profile.ts | 9 +++- packages/core/src/routes/experience/index.ts | 33 ++++++++++--- .../middleware/koa-experience-interaction.ts | 24 ++++++--- .../koa-experience-verifications-audit-log.ts | 36 ++++++++++++++ .../src/routes/experience/profile-routes.ts | 26 ++++++++-- .../backup-code-verification.ts | 20 +++++++- .../enterprise-sso-verification.ts | 25 ++++++++++ .../new-password-identity-verification.ts | 17 ++++++- .../password-verification.ts | 15 +++++- .../social-verification.ts | 28 ++++++++++- .../verification-routes/totp-verification.ts | 19 ++++++- .../verification-routes/verification-code.ts | 49 ++++++++++++++++++- .../web-authn-verification.ts | 36 +++++++++++++- packages/schemas/src/types/log/interaction.ts | 9 ++-- 16 files changed, 341 insertions(+), 35 deletions(-) create mode 100644 packages/core/src/routes/experience/middleware/koa-experience-verifications-audit-log.ts diff --git a/packages/core/src/routes/experience/classes/experience-interaction.ts b/packages/core/src/routes/experience/classes/experience-interaction.ts index 4e98b0d4e..79b5219da 100644 --- a/packages/core/src/routes/experience/classes/experience-interaction.ts +++ b/packages/core/src/routes/experience/classes/experience-interaction.ts @@ -5,6 +5,7 @@ import { conditional } from '@silverhand/essentials'; import { z } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; +import { type LogEntry } from '#src/middleware/koa-audit-log.js'; import type TenantContext from '#src/tenants/TenantContext.js'; import assertThat from '#src/utils/assert-that.js'; @@ -180,9 +181,13 @@ export default class ExperienceInteraction { * @see {@link identifyExistingUser} for more exceptions that can be thrown in the SignIn and ForgotPassword events. * @see {@link createNewUser} for more exceptions that can be thrown in the Register event. **/ - public async identifyUser(verificationId: string, linkSocialIdentity?: boolean) { + public async identifyUser(verificationId: string, linkSocialIdentity?: boolean, log?: LogEntry) { const verificationRecord = this.getVerificationRecordById(verificationId); + log?.append({ + verification: verificationRecord?.toJson(), + }); + assertThat( this.interactionEvent, new RequestError({ code: 'session.interaction_not_found', status: 404 }) @@ -442,6 +447,8 @@ export default class ExperienceInteraction { // Note: The profile data is not saved to the user profile until the user submits the interaction. // Also no need to validate the synced profile data availability as it is already validated during the identification process. if (syncedProfile) { + const log = this.ctx.createLog(`Interaction.${this.interactionEvent}.Profile.Update`); + log.append({ syncedProfile }); this.profile.unsafeSet(syncedProfile); } } diff --git a/packages/core/src/routes/experience/classes/mfa.ts b/packages/core/src/routes/experience/classes/mfa.ts index 90dcefe1e..9545c91e2 100644 --- a/packages/core/src/routes/experience/classes/mfa.ts +++ b/packages/core/src/routes/experience/classes/mfa.ts @@ -18,6 +18,7 @@ import { deduplicate } from '@silverhand/essentials'; import { z } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; +import { type LogEntry } from '#src/middleware/koa-audit-log.js'; import type Libraries from '#src/tenants/Libraries.js'; import type Queries from '#src/tenants/Queries.js'; import assertThat from '#src/utils/assert-that.js'; @@ -170,11 +171,16 @@ export class Mfa { * * - Any existing TOTP factor will be replaced with the new one. */ - async addTotpByVerificationId(verificationId: string) { + async addTotpByVerificationId(verificationId: string, log?: LogEntry) { const verificationRecord = this.interactionContext.getVerificationRecordByTypeAndId( VerificationType.TOTP, verificationId ); + + log?.append({ + verification: verificationRecord.toJson(), + }); + const bindTotp = verificationRecord.toBindMfa(); await this.checkMfaFactorsEnabledInSignInExperience([MfaFactor.TOTP]); @@ -198,11 +204,16 @@ export class Mfa { * @throws {RequestError} with status 404 if the verification record is not found * @throws {RequestError} with status 400 if WebAuthn is not enabled in the sign-in experience */ - async addWebAuthnByVerificationId(verificationId: string) { + async addWebAuthnByVerificationId(verificationId: string, log?: LogEntry) { const verificationRecord = this.interactionContext.getVerificationRecordByTypeAndId( VerificationType.WebAuthn, verificationId ); + + log?.append({ + verification: verificationRecord.toJson(), + }); + const bindWebAuthn = verificationRecord.toBindMfa(); await this.checkMfaFactorsEnabledInSignInExperience([MfaFactor.WebAuthn]); @@ -218,12 +229,16 @@ export class Mfa { * @throws {RequestError} with status 400 if Backup Code is not enabled in the sign-in experience * @throws {RequestError} with status 422 if the backup code is the only MFA factor */ - async addBackupCodeByVerificationId(verificationId: string) { + async addBackupCodeByVerificationId(verificationId: string, log?: LogEntry) { const verificationRecord = this.interactionContext.getVerificationRecordByTypeAndId( VerificationType.BackupCode, verificationId ); + log?.append({ + verification: verificationRecord.toJson(), + }); + await this.checkMfaFactorsEnabledInSignInExperience([MfaFactor.BackupCode]); const { mfaVerifications } = await this.interactionContext.getIdentifiedUser(); diff --git a/packages/core/src/routes/experience/classes/profile.ts b/packages/core/src/routes/experience/classes/profile.ts index 53c691c8b..0a3d1ad16 100644 --- a/packages/core/src/routes/experience/classes/profile.ts +++ b/packages/core/src/routes/experience/classes/profile.ts @@ -1,6 +1,7 @@ import { type VerificationType } from '@logto/schemas'; import RequestError from '#src/errors/RequestError/index.js'; +import { type LogEntry } from '#src/middleware/koa-audit-log.js'; import type Libraries from '#src/tenants/Libraries.js'; import type Queries from '#src/tenants/Queries.js'; @@ -38,12 +39,18 @@ export class Profile { */ async setProfileByVerificationRecord( type: VerificationType.EmailVerificationCode | VerificationType.PhoneVerificationCode, - verificationId: string + verificationId: string, + log?: LogEntry ) { const verificationRecord = this.interactionContext.getVerificationRecordByTypeAndId( type, verificationId ); + + log?.append({ + verification: verificationRecord.toJson(), + }); + const profile = verificationRecord.toUserProfile(); await this.setProfileWithValidation(profile); } diff --git a/packages/core/src/routes/experience/index.ts b/packages/core/src/routes/experience/index.ts index e1169e1f8..27c252eae 100644 --- a/packages/core/src/routes/experience/index.ts +++ b/packages/core/src/routes/experience/index.ts @@ -62,7 +62,7 @@ export default function experienceApiRoutes( const { interactionEvent } = ctx.guard.body; const { createLog } = ctx; - createLog(`Interaction.${interactionEvent}.Update`); + createLog(`Interaction.${interactionEvent}.Create`); const experienceInteraction = new ExperienceInteraction(ctx, tenant, interactionEvent); @@ -70,6 +70,8 @@ export default function experienceApiRoutes( // This will overwrite any existing interaction data in the storage. await experienceInteraction.save(); + ctx.experienceInteraction = experienceInteraction; + ctx.status = 204; return next(); @@ -89,13 +91,12 @@ export default function experienceApiRoutes( const { createLog, experienceInteraction } = ctx; const eventLog = createLog(`Interaction.${experienceInteraction.interactionEvent}.Update`); - - await experienceInteraction.setInteractionEvent(interactionEvent); - eventLog.append({ interactionEvent, }); + await experienceInteraction.setInteractionEvent(interactionEvent); + await experienceInteraction.save(); ctx.status = 204; @@ -112,9 +113,20 @@ export default function experienceApiRoutes( }), async (ctx, next) => { const { verificationId, linkSocialIdentity } = ctx.guard.body; - const { experienceInteraction } = ctx; + const { experienceInteraction, createLog } = ctx; - await experienceInteraction.identifyUser(verificationId, linkSocialIdentity); + const log = createLog( + `Interaction.${experienceInteraction.interactionEvent}.Identifier.Submit` + ); + + log.append({ + payload: { + verificationId, + linkSocialIdentity, + }, + }); + + await experienceInteraction.identifyUser(verificationId, linkSocialIdentity, log); await experienceInteraction.save(); @@ -136,7 +148,16 @@ export default function experienceApiRoutes( .optional(), }), async (ctx, next) => { + const { createLog, experienceInteraction } = ctx; + + const log = createLog(`Interaction.${experienceInteraction.interactionEvent}.Submit`); + await ctx.experienceInteraction.submit(); + + log.append({ + interaction: ctx.experienceInteraction.toJson(), + }); + ctx.status = 200; return next(); } diff --git a/packages/core/src/routes/experience/middleware/koa-experience-interaction.ts b/packages/core/src/routes/experience/middleware/koa-experience-interaction.ts index b48aa7ca9..407dc8be7 100644 --- a/packages/core/src/routes/experience/middleware/koa-experience-interaction.ts +++ b/packages/core/src/routes/experience/middleware/koa-experience-interaction.ts @@ -1,16 +1,17 @@ import type { MiddlewareType } from 'koa'; +import { type IRouterParamContext } from 'koa-router'; -import { type WithLogContext } from '#src/middleware/koa-audit-log.js'; import type TenantContext from '#src/tenants/TenantContext.js'; import ExperienceInteraction from '../classes/experience-interaction.js'; import { experienceRoutes } from '../const.js'; import { type WithHooksAndLogsContext } from '../types.js'; -export type WithExperienceInteractionContext = - ContextT & { - experienceInteraction: ExperienceInteraction; - }; +export type WithExperienceInteractionContext< + ContextT extends IRouterParamContext = IRouterParamContext, +> = ContextT & { + experienceInteraction: ExperienceInteraction; +}; /** * @overview This middleware initializes the `ExperienceInteraction` for the current request. @@ -40,6 +41,17 @@ export default function koaExperienceInteraction< ctx.experienceInteraction = new ExperienceInteraction(ctx, tenant, interactionDetails); - return next(); + try { + await next(); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- make sure the interaction is initialized + if (ctx.experienceInteraction) { + ctx.prependAllLogEntries({ + interaction: ctx.experienceInteraction.toJson(), + }); + } + + throw error; + } }; } diff --git a/packages/core/src/routes/experience/middleware/koa-experience-verifications-audit-log.ts b/packages/core/src/routes/experience/middleware/koa-experience-verifications-audit-log.ts new file mode 100644 index 000000000..784046beb --- /dev/null +++ b/packages/core/src/routes/experience/middleware/koa-experience-verifications-audit-log.ts @@ -0,0 +1,36 @@ +import { type VerificationType } from '@logto/schemas'; +import { type Action } from '@logto/schemas/lib/types/log/interaction.js'; +import { type MiddlewareType } from 'koa'; +import { type IRouterParamContext } from 'koa-router'; + +import { type LogContext, type LogEntry } from '#src/middleware/koa-audit-log.js'; + +import { type WithExperienceInteractionContext } from './koa-experience-interaction.js'; + +type WithExperienceVerificationAuditLogContext = ContextT & { + verificationAuditLog: LogEntry; +}; + +export default function koaExperienceVerificationsAuditLog< + StateT, + ContextT extends WithExperienceInteractionContext & LogContext, + ResponseT, +>({ + type, + action, +}: { + type: VerificationType; + action: Action; +}): MiddlewareType, ResponseT> { + return async (ctx, next) => { + const { experienceInteraction, createLog } = ctx; + + const log = createLog( + `Interaction.${experienceInteraction.interactionEvent}.Verification.${type}.${action}` + ); + + ctx.verificationAuditLog = log; + + return next(); + }; +} diff --git a/packages/core/src/routes/experience/profile-routes.ts b/packages/core/src/routes/experience/profile-routes.ts index c6959ed50..7619624b4 100644 --- a/packages/core/src/routes/experience/profile-routes.ts +++ b/packages/core/src/routes/experience/profile-routes.ts @@ -59,9 +59,15 @@ export default function interactionProfileRoutes { - const { experienceInteraction, guard } = ctx; + const { experienceInteraction, guard, createLog } = ctx; const profilePayload = guard.body; + const log = createLog(`Interaction.${experienceInteraction.interactionEvent}.Profile.Update`); + + log.append({ + payload: profilePayload, + }); + switch (profilePayload.type) { case SignInIdentifier.Email: case SignInIdentifier.Phone: { @@ -100,7 +106,7 @@ export default function interactionProfileRoutes { - const { experienceInteraction, guard } = ctx; + const { experienceInteraction, guard, createLog } = ctx; const { password } = guard.body; assertThat( @@ -111,6 +117,8 @@ export default function interactionProfileRoutes( @@ -25,6 +27,10 @@ export default function backupCodeVerificationRoutes { const { experienceInteraction } = ctx; @@ -60,10 +66,20 @@ export default function backupCodeVerificationRoutes { - const { experienceInteraction } = ctx; + const { experienceInteraction, verificationAuditLog } = ctx; const { code } = ctx.guard.body; + verificationAuditLog.append({ + payload: { + code, + }, + }); + assertThat(experienceInteraction.identifiedUserId, 'session.identifier_not_found'); const backupCodeVerificationRecord = BackupCodeVerification.create( diff --git a/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts b/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts index 72b4a8c1f..53806693a 100644 --- a/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/enterprise-sso-verification.ts @@ -3,6 +3,7 @@ import { socialAuthorizationUrlPayloadGuard, socialVerificationCallbackPayloadGuard, } from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; @@ -13,6 +14,7 @@ import assertThat from '#src/utils/assert-that.js'; import { EnterpriseSsoVerification } from '../classes/verifications/enterprise-sso-verification.js'; import { experienceRoutes } from '../const.js'; +import koaExperienceVerificationsAuditLog from '../middleware/koa-experience-verifications-audit-log.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; export default function enterpriseSsoVerificationRoutes< @@ -33,9 +35,20 @@ export default function enterpriseSsoVerificationRoutes< }), status: [200, 400, 404, 500], }), + koaExperienceVerificationsAuditLog({ + type: VerificationType.EnterpriseSso, + action: Action.Create, + }), async (ctx, next) => { const { connectorId } = ctx.guard.params; + ctx.verificationAuditLog.append({ + payload: { + connectorId, + ...ctx.guard.body, + }, + }); + const enterpriseSsoVerification = EnterpriseSsoVerification.create( libraries, queries, @@ -73,10 +86,22 @@ export default function enterpriseSsoVerificationRoutes< }), status: [200, 400, 404, 500], }), + koaExperienceVerificationsAuditLog({ + type: VerificationType.EnterpriseSso, + action: Action.Submit, + }), async (ctx, next) => { const { connectorId } = ctx.params; const { connectorData, verificationId } = ctx.guard.body; + ctx.verificationAuditLog.append({ + payload: { + connectorId, + verificationId, + connectorData, + }, + }); + const enterpriseSsoVerificationRecord = ctx.experienceInteraction.getVerificationRecordByTypeAndId( VerificationType.EnterpriseSso, diff --git a/packages/core/src/routes/experience/verification-routes/new-password-identity-verification.ts b/packages/core/src/routes/experience/verification-routes/new-password-identity-verification.ts index 3699c754d..113748f8e 100644 --- a/packages/core/src/routes/experience/verification-routes/new-password-identity-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/new-password-identity-verification.ts @@ -1,4 +1,5 @@ -import { newPasswordIdentityVerificationPayloadGuard } from '@logto/schemas'; +import { newPasswordIdentityVerificationPayloadGuard, VerificationType } from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; @@ -7,6 +8,7 @@ import type TenantContext from '#src/tenants/TenantContext.js'; import { NewPasswordIdentityVerification } from '../classes/verifications/new-password-identity-verification.js'; import { experienceRoutes } from '../const.js'; +import koaExperienceVerificationsAuditLog from '../middleware/koa-experience-verifications-audit-log.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; export default function newPasswordIdentityVerificationRoutes< @@ -21,9 +23,20 @@ export default function newPasswordIdentityVerificationRoutes< verificationId: z.string(), }), }), + koaExperienceVerificationsAuditLog({ + type: VerificationType.NewPasswordIdentity, + action: Action.Submit, + }), async (ctx, next) => { const { identifier, password } = ctx.guard.body; - const { experienceInteraction } = ctx; + const { experienceInteraction, verificationAuditLog } = ctx; + + verificationAuditLog.append({ + payload: { + identifier, + password, + }, + }); const newPasswordIdentityVerification = NewPasswordIdentityVerification.create( libraries, diff --git a/packages/core/src/routes/experience/verification-routes/password-verification.ts b/packages/core/src/routes/experience/verification-routes/password-verification.ts index a52980e8a..e13940815 100644 --- a/packages/core/src/routes/experience/verification-routes/password-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/password-verification.ts @@ -1,4 +1,5 @@ -import { passwordVerificationPayloadGuard } from '@logto/schemas'; +import { passwordVerificationPayloadGuard, VerificationType } from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; @@ -7,6 +8,7 @@ import type TenantContext from '#src/tenants/TenantContext.js'; import { PasswordVerification } from '../classes/verifications/password-verification.js'; import { experienceRoutes } from '../const.js'; +import koaExperienceVerificationsAuditLog from '../middleware/koa-experience-verifications-audit-log.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; export default function passwordVerificationRoutes( @@ -22,9 +24,20 @@ export default function passwordVerificationRoutes { const { identifier, password } = ctx.guard.body; + ctx.verificationAuditLog.append({ + payload: { + identifier, + password, + }, + }); + const passwordVerification = PasswordVerification.create(libraries, queries, identifier); await passwordVerification.verify(password); ctx.experienceInteraction.setVerificationRecord(passwordVerification); diff --git a/packages/core/src/routes/experience/verification-routes/social-verification.ts b/packages/core/src/routes/experience/verification-routes/social-verification.ts index 45d4352c1..ceeebbd09 100644 --- a/packages/core/src/routes/experience/verification-routes/social-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/social-verification.ts @@ -3,6 +3,7 @@ import { socialAuthorizationUrlPayloadGuard, socialVerificationCallbackPayloadGuard, } from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; @@ -13,6 +14,7 @@ import assertThat from '#src/utils/assert-that.js'; import { SocialVerification } from '../classes/verifications/social-verification.js'; import { experienceRoutes } from '../const.js'; +import koaExperienceVerificationsAuditLog from '../middleware/koa-experience-verifications-audit-log.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; export default function socialVerificationRoutes( @@ -34,8 +36,20 @@ export default function socialVerificationRoutes { const { connectorId } = ctx.guard.params; + const { verificationAuditLog } = ctx; + + verificationAuditLog.append({ + payload: { + connectorId, + ...ctx.guard.body, + }, + }); const socialVerification = SocialVerification.create(libraries, queries, connectorId); @@ -70,9 +84,22 @@ export default function socialVerificationRoutes { const { connectorId } = ctx.params; const { connectorData, verificationId } = ctx.guard.body; + const { verificationAuditLog } = ctx; + + verificationAuditLog.append({ + payload: { + connectorId, + verificationId, + connectorData, + }, + }); const socialVerificationRecord = ctx.experienceInteraction.getVerificationRecordByTypeAndId( VerificationType.Social, @@ -85,7 +112,6 @@ export default function socialVerificationRoutes( @@ -27,6 +29,10 @@ export default function totpVerificationRoutes { const { experienceInteraction } = ctx; @@ -63,10 +69,21 @@ export default function totpVerificationRoutes { - const { experienceInteraction } = ctx; + const { experienceInteraction, verificationAuditLog } = ctx; const { verificationId, code } = ctx.guard.body; + verificationAuditLog.append({ + payload: { + verificationId, + code, + }, + }); + assertThat(experienceInteraction.identifiedUserId, 'session.identifier_not_found'); // Verify new generated secret diff --git a/packages/core/src/routes/experience/verification-routes/verification-code.ts b/packages/core/src/routes/experience/verification-routes/verification-code.ts index f49a31601..d2ef61764 100644 --- a/packages/core/src/routes/experience/verification-routes/verification-code.ts +++ b/packages/core/src/routes/experience/verification-routes/verification-code.ts @@ -1,15 +1,33 @@ -import { InteractionEvent, verificationCodeIdentifierGuard } from '@logto/schemas'; +import { + InteractionEvent, + type VerificationCodeIdentifier, + verificationCodeIdentifierGuard, +} from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; +import { type LogContext } from '#src/middleware/koa-audit-log.js'; import koaGuard from '#src/middleware/koa-guard.js'; import type TenantContext from '#src/tenants/TenantContext.js'; +import type ExperienceInteraction from '../classes/experience-interaction.js'; import { codeVerificationIdentifierRecordTypeMap } from '../classes/utils.js'; import { createNewCodeVerificationRecord } from '../classes/verifications/code-verification.js'; import { experienceRoutes } from '../const.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; +const createVerificationCodeAuditLog = ( + { createLog }: LogContext, + { interactionEvent }: ExperienceInteraction, + identifier: VerificationCodeIdentifier, + action: Action +) => { + const verificationType = codeVerificationIdentifierRecordTypeMap[identifier.type]; + + return createLog(`Interaction.${interactionEvent}.Verification.${verificationType}.${action}`); +}; + export default function verificationCodeRoutes( router: Router, { libraries, queries }: TenantContext @@ -30,6 +48,20 @@ export default function verificationCodeRoutes { const { identifier, interactionEvent } = ctx.guard.body; + const log = createVerificationCodeAuditLog( + ctx, + ctx.experienceInteraction, + identifier, + Action.Create + ); + + log.append({ + payload: { + identifier, + interactionEvent, + }, + }); + const codeVerification = createNewCodeVerificationRecord( libraries, queries, @@ -68,6 +100,21 @@ export default function verificationCodeRoutes { const { verificationId, code, identifier } = ctx.guard.body; + const log = createVerificationCodeAuditLog( + ctx, + ctx.experienceInteraction, + identifier, + Action.Submit + ); + + log.append({ + payload: { + identifier, + verificationId, + code, + }, + }); + const codeVerificationRecord = ctx.experienceInteraction.getVerificationRecordByTypeAndId( codeVerificationIdentifierRecordTypeMap[identifier.type], verificationId diff --git a/packages/core/src/routes/experience/verification-routes/web-authn-verification.ts b/packages/core/src/routes/experience/verification-routes/web-authn-verification.ts index 3185c8d76..7cb1f39f5 100644 --- a/packages/core/src/routes/experience/verification-routes/web-authn-verification.ts +++ b/packages/core/src/routes/experience/verification-routes/web-authn-verification.ts @@ -4,6 +4,7 @@ import { webAuthnRegistrationOptionsGuard, webAuthnVerificationPayloadGuard, } from '@logto/schemas'; +import { Action } from '@logto/schemas/lib/types/log/interaction.js'; import type Router from 'koa-router'; import { z } from 'zod'; @@ -14,6 +15,7 @@ import assertThat from '#src/utils/assert-that.js'; import { WebAuthnVerification } from '../classes/verifications/web-authn-verification.js'; import { experienceRoutes } from '../const.js'; +import koaExperienceVerificationsAuditLog from '../middleware/koa-experience-verifications-audit-log.js'; import { type ExperienceInteractionRouterContext } from '../types.js'; export default function webAuthnVerificationRoute( @@ -31,6 +33,10 @@ export default function webAuthnVerificationRoute { const { experienceInteraction } = ctx; @@ -73,10 +79,21 @@ export default function webAuthnVerificationRoute { - const { experienceInteraction } = ctx; + const { experienceInteraction, verificationAuditLog } = ctx; const { verificationId, payload } = ctx.guard.body; + verificationAuditLog.append({ + payload: { + verificationId, + payload, + }, + }); + assertThat(experienceInteraction.identifiedUserId, 'session.identifier_not_found'); const webAuthnVerification = experienceInteraction.getVerificationRecordByTypeAndId( @@ -115,6 +132,10 @@ export default function webAuthnVerificationRoute { const { experienceInteraction } = ctx; @@ -156,10 +177,21 @@ export default function webAuthnVerificationRoute { - const { experienceInteraction } = ctx; + const { experienceInteraction, verificationAuditLog } = ctx; const { verificationId, payload } = ctx.guard.body; + verificationAuditLog.append({ + payload: { + verificationId, + payload, + }, + }); + assertThat(experienceInteraction.identifiedUserId, 'session.identifier_not_found'); const webAuthnVerification = experienceInteraction.getVerificationRecordByTypeAndId( diff --git a/packages/schemas/src/types/log/interaction.ts b/packages/schemas/src/types/log/interaction.ts index 6378aadfa..bcec59bb0 100644 --- a/packages/schemas/src/types/log/interaction.ts +++ b/packages/schemas/src/types/log/interaction.ts @@ -1,5 +1,5 @@ import { type MfaFactor } from '../../foundations/index.js'; -import type { InteractionEvent } from '../interactions.js'; +import type { InteractionEvent, VerificationType } from '../interactions.js'; export type Prefix = 'Interaction'; @@ -12,6 +12,7 @@ export enum Field { Profile = 'Profile', BindMfa = 'BindMfa', Mfa = 'Mfa', + Verification = 'Verification', } /** Method to verify the identifier */ @@ -74,7 +75,7 @@ export enum Action { */ export type LogKey = | `${Prefix}.${Action.Create | Action.End}` - | `${Prefix}.${InteractionEvent}.${Action.Update | Action.Submit}` + | `${Prefix}.${InteractionEvent}.${Action.Create | Action.Update | Action.Submit}` | `${Prefix}.${InteractionEvent}.${Field.Profile}.${ | Action.Update // PATCH profile | Action.Create // PUT profile @@ -93,4 +94,6 @@ export type LogKey = | `${Prefix}.${InteractionEvent}.${Field.BindMfa}.${MfaFactor}.${Action.Submit | Action.Create}` | `${Prefix}.${InteractionEvent.SignIn}.${Field.Mfa}.${MfaFactor}.${ | Action.Submit - | Action.Create}`; + | Action.Create}` + | `${Prefix}.${InteractionEvent}.${Field.Verification}.${VerificationType}.${Action}` + | `${Prefix}.${InteractionEvent}.${Field.Identifier}.${Action.Submit}`;