@open-mercato/enterprise 0.6.8-develop.6976.1.9b79145ac4 → 0.6.8-develop.6977.1.5041da373e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import {
5
5
  verifyAuthenticationResponse,
6
6
  verifyRegistrationResponse
7
7
  } from "@simplewebauthn/server";
8
+ import { createLogger } from "@open-mercato/shared/lib/logger";
8
9
  import { z } from "zod";
9
10
  import {
10
11
  readSecurityModuleConfig,
@@ -12,6 +13,7 @@ import {
12
13
  resolveSecurityModuleConfigForRequest
13
14
  } from "../security-config.js";
14
15
  const SETUP_TOKEN_VERSION = "v1";
16
+ const logger = createLogger("security").child({ component: "passkey-provider" });
15
17
  const setupPayloadSchema = z.object({
16
18
  label: z.string().min(1).max(100).optional(),
17
19
  userName: z.string().min(1).max(255).optional(),
@@ -30,15 +32,9 @@ const setupConfirmationPayloadSchema = z.union([
30
32
  label: z.string().min(1).max(100).optional()
31
33
  })
32
34
  ]);
33
- const verifyPayloadSchema = z.union([
34
- z.object({
35
- response: z.record(z.string(), z.unknown())
36
- }),
37
- z.object({
38
- credentialId: z.string().min(1),
39
- challenge: z.string().min(1)
40
- })
41
- ]);
35
+ const verifyPayloadSchema = z.object({
36
+ response: z.record(z.string(), z.unknown())
37
+ });
42
38
  const setupTokenSchema = z.object({
43
39
  version: z.string().min(1),
44
40
  userId: z.string().min(1),
@@ -198,59 +194,51 @@ class PasskeyProvider {
198
194
  };
199
195
  }
200
196
  async verify(userId, method, payload, context, runtimeContext) {
201
- const parsed = verifyPayloadSchema.parse(payload);
197
+ const parsed = verifyPayloadSchema.safeParse(payload);
198
+ if (!parsed.success) return false;
202
199
  if (method.userId !== userId) return false;
203
200
  const parsedContext = verifyContextSchema.safeParse(context);
201
+ if (!parsedContext.success) return false;
204
202
  const securityConfig = this.resolveSecurityConfig(runtimeContext);
205
- if (!parsedContext.success) {
206
- if ("credentialId" in parsed) {
207
- const metadataCredentialId2 = method.providerMetadata?.credentialId;
208
- return typeof metadataCredentialId2 === "string" && metadataCredentialId2 === parsed.credentialId;
209
- }
210
- return false;
211
- }
212
203
  const pending = parsedContext.data.challenge;
213
204
  if (Date.now() - pending.createdAt > securityConfig.webauthn.challengeTtlMs) {
214
205
  return false;
215
206
  }
216
- if ("response" in parsed) {
217
- const metadata = method.providerMetadata ?? {};
218
- const credentialId = typeof metadata.credentialId === "string" ? metadata.credentialId : null;
219
- const credentialPublicKey = typeof metadata.credentialPublicKey === "string" ? metadata.credentialPublicKey : typeof metadata.publicKey === "string" ? metadata.publicKey : null;
220
- const counter = typeof metadata.counter === "number" && Number.isFinite(metadata.counter) ? metadata.counter : 0;
221
- if (!credentialId || !credentialPublicKey) {
222
- return false;
207
+ const metadata = method.providerMetadata ?? {};
208
+ const credentialId = typeof metadata.credentialId === "string" ? metadata.credentialId : null;
209
+ const credentialPublicKey = typeof metadata.credentialPublicKey === "string" ? metadata.credentialPublicKey : typeof metadata.publicKey === "string" ? metadata.publicKey : null;
210
+ const counter = typeof metadata.counter === "number" && Number.isFinite(metadata.counter) ? metadata.counter : 0;
211
+ if (!credentialId || !credentialPublicKey) {
212
+ return false;
213
+ }
214
+ const verification = await verifyAuthenticationResponse({
215
+ response: parsed.data.response,
216
+ expectedChallenge: pending.challenge,
217
+ expectedOrigin: this.getExpectedOrigins(securityConfig),
218
+ expectedRPID: this.getRpId(securityConfig),
219
+ requireUserVerification: false,
220
+ credential: {
221
+ id: credentialId,
222
+ publicKey: this.base64UrlToBytes(credentialPublicKey),
223
+ counter,
224
+ transports: this.normalizeTransports(metadata.transports)
223
225
  }
224
- const verification = await verifyAuthenticationResponse({
225
- response: parsed.response,
226
- expectedChallenge: pending.challenge,
227
- expectedOrigin: this.getExpectedOrigins(securityConfig),
228
- expectedRPID: this.getRpId(securityConfig),
229
- requireUserVerification: false,
230
- credential: {
231
- id: credentialId,
232
- publicKey: this.base64UrlToBytes(credentialPublicKey),
233
- counter,
234
- transports: this.normalizeTransports(metadata.transports)
235
- }
226
+ }).catch((error) => {
227
+ logger.warn("Passkey assertion could not be verified", {
228
+ methodId: method.id,
229
+ rpId: this.getRpId(securityConfig),
230
+ err: error
236
231
  });
237
- if (!verification.verified) {
238
- return false;
239
- }
240
- const nextCounter = verification.authenticationInfo?.newCounter;
241
- if (typeof nextCounter === "number" && Number.isFinite(nextCounter)) {
242
- const providerMetadata = method.providerMetadata ?? {};
243
- providerMetadata.counter = nextCounter;
244
- method.providerMetadata = providerMetadata;
245
- }
246
- return true;
247
- }
248
- const metadataCredentialId = method.providerMetadata?.credentialId;
249
- if (typeof metadataCredentialId !== "string" || metadataCredentialId !== parsed.credentialId) {
232
+ return null;
233
+ });
234
+ if (!verification?.verified) {
250
235
  return false;
251
236
  }
252
- if (pending.challenge !== parsed.challenge) {
253
- return false;
237
+ const nextCounter = verification.authenticationInfo?.newCounter;
238
+ if (typeof nextCounter === "number" && Number.isFinite(nextCounter)) {
239
+ const providerMetadata = method.providerMetadata ?? {};
240
+ providerMetadata.counter = nextCounter;
241
+ method.providerMetadata = providerMetadata;
254
242
  }
255
243
  return true;
256
244
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/security/lib/providers/PasskeyProvider.ts"],
4
- "sourcesContent": ["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\nimport {\n generateAuthenticationOptions,\n generateRegistrationOptions,\n verifyAuthenticationResponse,\n verifyRegistrationResponse,\n} from '@simplewebauthn/server'\nimport type { AuthenticatorTransportFuture } from '@simplewebauthn/types'\nimport { z } from 'zod'\nimport type {\n MfaMethodRecord,\n MfaProviderConfirmResult,\n MfaProviderInterface,\n MfaProviderRuntimeContext,\n MfaProviderUser,\n MfaVerifyContext,\n} from '../mfa-provider-interface'\nimport type { SecurityModuleConfig } from '../security-config'\nimport {\n readSecurityModuleConfig,\n readSecuritySetupTokenSecret,\n resolveSecurityModuleConfigForRequest,\n} from '../security-config'\n\nconst SETUP_TOKEN_VERSION = 'v1'\n\nconst setupPayloadSchema = z.object({\n label: z.string().min(1).max(100).optional(),\n userName: z.string().min(1).max(255).optional(),\n authenticatorAttachment: z.enum(['platform', 'cross-platform']).optional(),\n})\n\nconst setupConfirmationPayloadSchema = z.union([\n z.object({\n response: z.record(z.string(), z.unknown()),\n label: z.string().min(1).max(100).optional(),\n }),\n z.object({\n credentialId: z.string().min(1),\n publicKey: z.string().min(1),\n challenge: z.string().min(1),\n transports: z.array(z.string().min(1)).optional(),\n label: z.string().min(1).max(100).optional(),\n }),\n])\n\nconst verifyPayloadSchema = z.union([\n z.object({\n response: z.record(z.string(), z.unknown()),\n }),\n z.object({\n credentialId: z.string().min(1),\n challenge: z.string().min(1),\n }),\n])\n\nconst setupTokenSchema = z.object({\n version: z.string().min(1),\n userId: z.string().min(1),\n challenge: z.string().min(1),\n label: z.string().min(1).max(100).optional(),\n createdAt: z.number().int().nonnegative(),\n nonce: z.string().min(1),\n})\n\ntype PendingSetup = {\n version: string\n userId: string\n challenge: string\n label?: string\n createdAt: number\n nonce: string\n}\n\nconst verifyContextSchema = z.object({\n challenge: z.object({\n challenge: z.string().min(1),\n createdAt: z.number().int().nonnegative(),\n }),\n})\n\nconst AUTHENTICATOR_TRANSPORTS: AuthenticatorTransportFuture[] = [\n 'ble',\n 'cable',\n 'hybrid',\n 'internal',\n 'nfc',\n 'smart-card',\n 'usb',\n]\n\nexport class PasskeyProvider implements MfaProviderInterface {\n readonly type = 'passkey'\n readonly label = 'Passkey'\n readonly icon = 'Key'\n readonly allowMultiple = true\n readonly setupSchema = setupPayloadSchema\n readonly verifySchema = verifyPayloadSchema\n\n constructor(\n private readonly securityConfig: SecurityModuleConfig = readSecurityModuleConfig(),\n private readonly setupTokenSecret: string = readSecuritySetupTokenSecret(),\n ) {}\n\n resolveSetupPayload(user: MfaProviderUser, payload: unknown): unknown {\n const parsed = setupPayloadSchema.parse(payload ?? {})\n const email = typeof user.email === 'string' ? user.email.trim() : ''\n\n return {\n ...parsed,\n ...(parsed.label || email.length === 0 ? {} : { label: email }),\n ...(parsed.userName || email.length === 0 ? {} : { userName: email }),\n }\n }\n\n async setup(\n userId: string,\n payload: unknown,\n context?: MfaProviderRuntimeContext,\n ): Promise<{ setupId: string; clientData: Record<string, unknown> }> {\n const parsed = setupPayloadSchema.parse(payload ?? {})\n const userName = parsed.userName ?? parsed.label ?? userId\n const userDisplayName = parsed.label ?? parsed.userName ?? userId\n const securityConfig = this.resolveSecurityConfig(context)\n const options = await generateRegistrationOptions({\n rpName: this.getRpName(securityConfig),\n rpID: this.getRpId(securityConfig),\n userID: this.toWebAuthnUserId(userId),\n userName,\n userDisplayName,\n timeout: securityConfig.webauthn.setupTtlMs,\n attestationType: 'none',\n authenticatorSelection: {\n residentKey: 'required',\n userVerification: 'preferred',\n ...(parsed.authenticatorAttachment ? { authenticatorAttachment: parsed.authenticatorAttachment } : {}),\n },\n })\n\n const now = Date.now()\n const setupId = this.createSetupToken({\n version: SETUP_TOKEN_VERSION,\n userId,\n challenge: options.challenge,\n label: parsed.label,\n createdAt: now,\n nonce: randomBytes(16).toString('hex'),\n })\n\n return {\n setupId,\n clientData: options as unknown as Record<string, unknown>,\n }\n }\n\n async confirmSetup(\n userId: string,\n setupId: string,\n payload: unknown,\n context?: MfaProviderRuntimeContext,\n ): Promise<MfaProviderConfirmResult> {\n const parsed = setupConfirmationPayloadSchema.parse(payload)\n const pending = this.readSetupToken(setupId)\n const securityConfig = this.resolveSecurityConfig(context)\n if (!pending || pending.version !== SETUP_TOKEN_VERSION || pending.userId !== userId) {\n throw new Error('Passkey setup session not found')\n }\n if (Date.now() - pending.createdAt > securityConfig.webauthn.setupTtlMs) {\n throw new Error('Passkey setup session expired')\n }\n\n if ('response' in parsed) {\n const verification = await verifyRegistrationResponse({\n response: parsed.response as never,\n expectedChallenge: pending.challenge,\n expectedOrigin: this.getExpectedOrigins(securityConfig),\n expectedRPID: this.getRpId(securityConfig),\n requireUserVerification: false,\n })\n\n if (!verification.verified || !verification.registrationInfo) {\n throw new Error('Passkey registration verification failed')\n }\n\n const registrationInfo = verification.registrationInfo as {\n credential?: {\n id: string\n publicKey: Uint8Array\n counter: number\n transports?: string[]\n }\n credentialID?: string\n credentialPublicKey?: Uint8Array\n counter?: number\n }\n\n const credentialId = registrationInfo.credential?.id ?? registrationInfo.credentialID\n const publicKeyBytes = registrationInfo.credential?.publicKey ?? registrationInfo.credentialPublicKey\n const counter = registrationInfo.credential?.counter ?? registrationInfo.counter ?? 0\n const transports = this.normalizeTransports(registrationInfo.credential?.transports)\n\n if (!credentialId || !publicKeyBytes) {\n throw new Error('Passkey registration did not return credential data')\n }\n\n return {\n metadata: {\n credentialId,\n credentialPublicKey: this.bytesToBase64Url(publicKeyBytes),\n counter,\n transports,\n label: parsed.label ?? pending.label ?? 'Passkey',\n },\n }\n }\n\n if (parsed.challenge !== pending.challenge) {\n throw new Error('Invalid passkey setup challenge')\n }\n\n return {\n metadata: {\n credentialId: parsed.credentialId,\n credentialPublicKey: parsed.publicKey,\n counter: 0,\n transports: parsed.transports ?? [],\n label: parsed.label ?? pending.label ?? 'Passkey',\n },\n }\n }\n\n async prepareChallenge(\n userId: string,\n method: MfaMethodRecord,\n context?: MfaProviderRuntimeContext,\n ): Promise<{ clientData?: Record<string, unknown>; verifyContext?: MfaVerifyContext }> {\n if (method.userId !== userId) {\n throw new Error('MFA method does not belong to user')\n }\n\n const metadata = method.providerMetadata ?? {}\n const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null\n if (!credentialId) {\n throw new Error('Passkey credential is not configured')\n }\n\n const securityConfig = this.resolveSecurityConfig(context)\n const options = await generateAuthenticationOptions({\n rpID: this.getRpId(securityConfig),\n userVerification: 'preferred',\n timeout: securityConfig.webauthn.challengeTtlMs,\n allowCredentials: [{\n id: credentialId,\n transports: this.normalizeTransports(metadata.transports),\n }],\n })\n\n const now = Date.now()\n\n return {\n clientData: options as unknown as Record<string, unknown>,\n verifyContext: {\n challenge: {\n challenge: options.challenge,\n createdAt: now,\n },\n },\n }\n }\n\n async verify(\n userId: string,\n method: MfaMethodRecord,\n payload: unknown,\n context?: MfaVerifyContext,\n runtimeContext?: MfaProviderRuntimeContext,\n ): Promise<boolean> {\n const parsed = verifyPayloadSchema.parse(payload)\n if (method.userId !== userId) return false\n\n const parsedContext = verifyContextSchema.safeParse(context)\n const securityConfig = this.resolveSecurityConfig(runtimeContext)\n if (!parsedContext.success) {\n if ('credentialId' in parsed) {\n const metadataCredentialId = method.providerMetadata?.credentialId\n return typeof metadataCredentialId === 'string' && metadataCredentialId === parsed.credentialId\n }\n return false\n }\n const pending = parsedContext.data.challenge\n if (Date.now() - pending.createdAt > securityConfig.webauthn.challengeTtlMs) {\n return false\n }\n\n if ('response' in parsed) {\n const metadata = method.providerMetadata ?? {}\n const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null\n const credentialPublicKey = typeof metadata.credentialPublicKey === 'string'\n ? metadata.credentialPublicKey\n : typeof metadata.publicKey === 'string'\n ? metadata.publicKey\n : null\n const counter = typeof metadata.counter === 'number' && Number.isFinite(metadata.counter)\n ? metadata.counter\n : 0\n\n if (!credentialId || !credentialPublicKey) {\n return false\n }\n\n const verification = await verifyAuthenticationResponse({\n response: parsed.response as never,\n expectedChallenge: pending.challenge,\n expectedOrigin: this.getExpectedOrigins(securityConfig),\n expectedRPID: this.getRpId(securityConfig),\n requireUserVerification: false,\n credential: {\n id: credentialId,\n publicKey: this.base64UrlToBytes(credentialPublicKey),\n counter,\n transports: this.normalizeTransports(metadata.transports),\n },\n })\n\n if (!verification.verified) {\n return false\n }\n\n const nextCounter = verification.authenticationInfo?.newCounter\n if (typeof nextCounter === 'number' && Number.isFinite(nextCounter)) {\n const providerMetadata = method.providerMetadata ?? {}\n providerMetadata.counter = nextCounter\n method.providerMetadata = providerMetadata\n }\n\n return true\n }\n\n const metadataCredentialId = method.providerMetadata?.credentialId\n if (typeof metadataCredentialId !== 'string' || metadataCredentialId !== parsed.credentialId) {\n return false\n }\n if (pending.challenge !== parsed.challenge) {\n return false\n }\n\n return true\n }\n\n private normalizeTransports(raw: unknown): AuthenticatorTransportFuture[] {\n if (!Array.isArray(raw)) return []\n return raw.filter((value: unknown): value is AuthenticatorTransportFuture =>\n typeof value === 'string' && AUTHENTICATOR_TRANSPORTS.includes(value as AuthenticatorTransportFuture),\n )\n }\n\n private bytesToBase64Url(value: Uint8Array): string {\n return Buffer.from(value).toString('base64url')\n }\n\n private base64UrlToBytes(value: string) {\n return new Uint8Array(Buffer.from(value, 'base64url')).slice()\n }\n\n private createSetupToken(setup: PendingSetup): string {\n const encodedPayload = Buffer.from(JSON.stringify(setup), 'utf8').toString('base64url')\n const signature = this.signSetupPayload(encodedPayload)\n return `${encodedPayload}.${signature}`\n }\n\n private readSetupToken(token: string): PendingSetup | null {\n const [encodedPayload, signature, ...extra] = token.split('.')\n if (!encodedPayload || !signature || extra.length > 0) {\n return null\n }\n\n const expectedSignature = this.signSetupPayload(encodedPayload)\n const providedBuffer = Buffer.from(signature, 'base64url')\n const expectedBuffer = Buffer.from(expectedSignature, 'base64url')\n if (providedBuffer.length !== expectedBuffer.length || !timingSafeEqual(providedBuffer, expectedBuffer)) {\n return null\n }\n\n try {\n const raw = Buffer.from(encodedPayload, 'base64url').toString('utf8')\n const parsed = JSON.parse(raw)\n const result = setupTokenSchema.safeParse(parsed)\n return result.success ? result.data : null\n } catch {\n return null\n }\n }\n\n private signSetupPayload(encodedPayload: string): string {\n return createHmac('sha256', this.getSetupTokenSecret()).update(encodedPayload).digest('base64url')\n }\n\n private getSetupTokenSecret(): string {\n return this.setupTokenSecret\n }\n\n private getRpName(securityConfig: SecurityModuleConfig): string {\n return securityConfig.webauthn.rpName\n }\n\n private getRpId(securityConfig: SecurityModuleConfig): string {\n return securityConfig.webauthn.rpId\n }\n\n private getExpectedOrigins(securityConfig: SecurityModuleConfig): string[] {\n return [...securityConfig.webauthn.expectedOrigins]\n }\n\n private resolveSecurityConfig(context?: MfaProviderRuntimeContext): SecurityModuleConfig {\n return resolveSecurityModuleConfigForRequest(this.securityConfig, context?.request)\n }\n\n private toWebAuthnUserId(userId: string) {\n return new TextEncoder().encode(userId).slice()\n }\n}\n\nexport default PasskeyProvider\n"],
5
- "mappings": "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",
6
- "names": ["metadataCredentialId"]
4
+ "sourcesContent": ["import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\nimport {\n generateAuthenticationOptions,\n generateRegistrationOptions,\n verifyAuthenticationResponse,\n verifyRegistrationResponse,\n} from '@simplewebauthn/server'\nimport type { AuthenticatorTransportFuture } from '@simplewebauthn/types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { z } from 'zod'\nimport type {\n MfaMethodRecord,\n MfaProviderConfirmResult,\n MfaProviderInterface,\n MfaProviderRuntimeContext,\n MfaProviderUser,\n MfaVerifyContext,\n} from '../mfa-provider-interface'\nimport type { SecurityModuleConfig } from '../security-config'\nimport {\n readSecurityModuleConfig,\n readSecuritySetupTokenSecret,\n resolveSecurityModuleConfigForRequest,\n} from '../security-config'\n\nconst SETUP_TOKEN_VERSION = 'v1'\n\nconst logger = createLogger('security').child({ component: 'passkey-provider' })\n\nconst setupPayloadSchema = z.object({\n label: z.string().min(1).max(100).optional(),\n userName: z.string().min(1).max(255).optional(),\n authenticatorAttachment: z.enum(['platform', 'cross-platform']).optional(),\n})\n\nconst setupConfirmationPayloadSchema = z.union([\n z.object({\n response: z.record(z.string(), z.unknown()),\n label: z.string().min(1).max(100).optional(),\n }),\n z.object({\n credentialId: z.string().min(1),\n publicKey: z.string().min(1),\n challenge: z.string().min(1),\n transports: z.array(z.string().min(1)).optional(),\n label: z.string().min(1).max(100).optional(),\n }),\n])\n\nconst verifyPayloadSchema = z.object({\n response: z.record(z.string(), z.unknown()),\n})\n\nconst setupTokenSchema = z.object({\n version: z.string().min(1),\n userId: z.string().min(1),\n challenge: z.string().min(1),\n label: z.string().min(1).max(100).optional(),\n createdAt: z.number().int().nonnegative(),\n nonce: z.string().min(1),\n})\n\ntype PendingSetup = {\n version: string\n userId: string\n challenge: string\n label?: string\n createdAt: number\n nonce: string\n}\n\nconst verifyContextSchema = z.object({\n challenge: z.object({\n challenge: z.string().min(1),\n createdAt: z.number().int().nonnegative(),\n }),\n})\n\nconst AUTHENTICATOR_TRANSPORTS: AuthenticatorTransportFuture[] = [\n 'ble',\n 'cable',\n 'hybrid',\n 'internal',\n 'nfc',\n 'smart-card',\n 'usb',\n]\n\nexport class PasskeyProvider implements MfaProviderInterface {\n readonly type = 'passkey'\n readonly label = 'Passkey'\n readonly icon = 'Key'\n readonly allowMultiple = true\n readonly setupSchema = setupPayloadSchema\n readonly verifySchema = verifyPayloadSchema\n\n constructor(\n private readonly securityConfig: SecurityModuleConfig = readSecurityModuleConfig(),\n private readonly setupTokenSecret: string = readSecuritySetupTokenSecret(),\n ) {}\n\n resolveSetupPayload(user: MfaProviderUser, payload: unknown): unknown {\n const parsed = setupPayloadSchema.parse(payload ?? {})\n const email = typeof user.email === 'string' ? user.email.trim() : ''\n\n return {\n ...parsed,\n ...(parsed.label || email.length === 0 ? {} : { label: email }),\n ...(parsed.userName || email.length === 0 ? {} : { userName: email }),\n }\n }\n\n async setup(\n userId: string,\n payload: unknown,\n context?: MfaProviderRuntimeContext,\n ): Promise<{ setupId: string; clientData: Record<string, unknown> }> {\n const parsed = setupPayloadSchema.parse(payload ?? {})\n const userName = parsed.userName ?? parsed.label ?? userId\n const userDisplayName = parsed.label ?? parsed.userName ?? userId\n const securityConfig = this.resolveSecurityConfig(context)\n const options = await generateRegistrationOptions({\n rpName: this.getRpName(securityConfig),\n rpID: this.getRpId(securityConfig),\n userID: this.toWebAuthnUserId(userId),\n userName,\n userDisplayName,\n timeout: securityConfig.webauthn.setupTtlMs,\n attestationType: 'none',\n authenticatorSelection: {\n residentKey: 'required',\n userVerification: 'preferred',\n ...(parsed.authenticatorAttachment ? { authenticatorAttachment: parsed.authenticatorAttachment } : {}),\n },\n })\n\n const now = Date.now()\n const setupId = this.createSetupToken({\n version: SETUP_TOKEN_VERSION,\n userId,\n challenge: options.challenge,\n label: parsed.label,\n createdAt: now,\n nonce: randomBytes(16).toString('hex'),\n })\n\n return {\n setupId,\n clientData: options as unknown as Record<string, unknown>,\n }\n }\n\n async confirmSetup(\n userId: string,\n setupId: string,\n payload: unknown,\n context?: MfaProviderRuntimeContext,\n ): Promise<MfaProviderConfirmResult> {\n const parsed = setupConfirmationPayloadSchema.parse(payload)\n const pending = this.readSetupToken(setupId)\n const securityConfig = this.resolveSecurityConfig(context)\n if (!pending || pending.version !== SETUP_TOKEN_VERSION || pending.userId !== userId) {\n throw new Error('Passkey setup session not found')\n }\n if (Date.now() - pending.createdAt > securityConfig.webauthn.setupTtlMs) {\n throw new Error('Passkey setup session expired')\n }\n\n if ('response' in parsed) {\n const verification = await verifyRegistrationResponse({\n response: parsed.response as never,\n expectedChallenge: pending.challenge,\n expectedOrigin: this.getExpectedOrigins(securityConfig),\n expectedRPID: this.getRpId(securityConfig),\n requireUserVerification: false,\n })\n\n if (!verification.verified || !verification.registrationInfo) {\n throw new Error('Passkey registration verification failed')\n }\n\n const registrationInfo = verification.registrationInfo as {\n credential?: {\n id: string\n publicKey: Uint8Array\n counter: number\n transports?: string[]\n }\n credentialID?: string\n credentialPublicKey?: Uint8Array\n counter?: number\n }\n\n const credentialId = registrationInfo.credential?.id ?? registrationInfo.credentialID\n const publicKeyBytes = registrationInfo.credential?.publicKey ?? registrationInfo.credentialPublicKey\n const counter = registrationInfo.credential?.counter ?? registrationInfo.counter ?? 0\n const transports = this.normalizeTransports(registrationInfo.credential?.transports)\n\n if (!credentialId || !publicKeyBytes) {\n throw new Error('Passkey registration did not return credential data')\n }\n\n return {\n metadata: {\n credentialId,\n credentialPublicKey: this.bytesToBase64Url(publicKeyBytes),\n counter,\n transports,\n label: parsed.label ?? pending.label ?? 'Passkey',\n },\n }\n }\n\n if (parsed.challenge !== pending.challenge) {\n throw new Error('Invalid passkey setup challenge')\n }\n\n return {\n metadata: {\n credentialId: parsed.credentialId,\n credentialPublicKey: parsed.publicKey,\n counter: 0,\n transports: parsed.transports ?? [],\n label: parsed.label ?? pending.label ?? 'Passkey',\n },\n }\n }\n\n async prepareChallenge(\n userId: string,\n method: MfaMethodRecord,\n context?: MfaProviderRuntimeContext,\n ): Promise<{ clientData?: Record<string, unknown>; verifyContext?: MfaVerifyContext }> {\n if (method.userId !== userId) {\n throw new Error('MFA method does not belong to user')\n }\n\n const metadata = method.providerMetadata ?? {}\n const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null\n if (!credentialId) {\n throw new Error('Passkey credential is not configured')\n }\n\n const securityConfig = this.resolveSecurityConfig(context)\n const options = await generateAuthenticationOptions({\n rpID: this.getRpId(securityConfig),\n userVerification: 'preferred',\n timeout: securityConfig.webauthn.challengeTtlMs,\n allowCredentials: [{\n id: credentialId,\n transports: this.normalizeTransports(metadata.transports),\n }],\n })\n\n const now = Date.now()\n\n return {\n clientData: options as unknown as Record<string, unknown>,\n verifyContext: {\n challenge: {\n challenge: options.challenge,\n createdAt: now,\n },\n },\n }\n }\n\n async verify(\n userId: string,\n method: MfaMethodRecord,\n payload: unknown,\n context?: MfaVerifyContext,\n runtimeContext?: MfaProviderRuntimeContext,\n ): Promise<boolean> {\n const parsed = verifyPayloadSchema.safeParse(payload)\n if (!parsed.success) return false\n if (method.userId !== userId) return false\n\n const parsedContext = verifyContextSchema.safeParse(context)\n if (!parsedContext.success) return false\n\n const securityConfig = this.resolveSecurityConfig(runtimeContext)\n const pending = parsedContext.data.challenge\n if (Date.now() - pending.createdAt > securityConfig.webauthn.challengeTtlMs) {\n return false\n }\n\n const metadata = method.providerMetadata ?? {}\n const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null\n const credentialPublicKey = typeof metadata.credentialPublicKey === 'string'\n ? metadata.credentialPublicKey\n : typeof metadata.publicKey === 'string'\n ? metadata.publicKey\n : null\n const counter = typeof metadata.counter === 'number' && Number.isFinite(metadata.counter)\n ? metadata.counter\n : 0\n\n if (!credentialId || !credentialPublicKey) {\n return false\n }\n\n const verification = await verifyAuthenticationResponse({\n response: parsed.data.response as never,\n expectedChallenge: pending.challenge,\n expectedOrigin: this.getExpectedOrigins(securityConfig),\n expectedRPID: this.getRpId(securityConfig),\n requireUserVerification: false,\n credential: {\n id: credentialId,\n publicKey: this.base64UrlToBytes(credentialPublicKey),\n counter,\n transports: this.normalizeTransports(metadata.transports),\n },\n }).catch((error: unknown) => {\n logger.warn('Passkey assertion could not be verified', {\n methodId: method.id,\n rpId: this.getRpId(securityConfig),\n err: error,\n })\n return null\n })\n\n if (!verification?.verified) {\n return false\n }\n\n const nextCounter = verification.authenticationInfo?.newCounter\n if (typeof nextCounter === 'number' && Number.isFinite(nextCounter)) {\n const providerMetadata = method.providerMetadata ?? {}\n providerMetadata.counter = nextCounter\n method.providerMetadata = providerMetadata\n }\n\n return true\n }\n\n private normalizeTransports(raw: unknown): AuthenticatorTransportFuture[] {\n if (!Array.isArray(raw)) return []\n return raw.filter((value: unknown): value is AuthenticatorTransportFuture =>\n typeof value === 'string' && AUTHENTICATOR_TRANSPORTS.includes(value as AuthenticatorTransportFuture),\n )\n }\n\n private bytesToBase64Url(value: Uint8Array): string {\n return Buffer.from(value).toString('base64url')\n }\n\n private base64UrlToBytes(value: string) {\n return new Uint8Array(Buffer.from(value, 'base64url')).slice()\n }\n\n private createSetupToken(setup: PendingSetup): string {\n const encodedPayload = Buffer.from(JSON.stringify(setup), 'utf8').toString('base64url')\n const signature = this.signSetupPayload(encodedPayload)\n return `${encodedPayload}.${signature}`\n }\n\n private readSetupToken(token: string): PendingSetup | null {\n const [encodedPayload, signature, ...extra] = token.split('.')\n if (!encodedPayload || !signature || extra.length > 0) {\n return null\n }\n\n const expectedSignature = this.signSetupPayload(encodedPayload)\n const providedBuffer = Buffer.from(signature, 'base64url')\n const expectedBuffer = Buffer.from(expectedSignature, 'base64url')\n if (providedBuffer.length !== expectedBuffer.length || !timingSafeEqual(providedBuffer, expectedBuffer)) {\n return null\n }\n\n try {\n const raw = Buffer.from(encodedPayload, 'base64url').toString('utf8')\n const parsed = JSON.parse(raw)\n const result = setupTokenSchema.safeParse(parsed)\n return result.success ? result.data : null\n } catch {\n return null\n }\n }\n\n private signSetupPayload(encodedPayload: string): string {\n return createHmac('sha256', this.getSetupTokenSecret()).update(encodedPayload).digest('base64url')\n }\n\n private getSetupTokenSecret(): string {\n return this.setupTokenSecret\n }\n\n private getRpName(securityConfig: SecurityModuleConfig): string {\n return securityConfig.webauthn.rpName\n }\n\n private getRpId(securityConfig: SecurityModuleConfig): string {\n return securityConfig.webauthn.rpId\n }\n\n private getExpectedOrigins(securityConfig: SecurityModuleConfig): string[] {\n return [...securityConfig.webauthn.expectedOrigins]\n }\n\n private resolveSecurityConfig(context?: MfaProviderRuntimeContext): SecurityModuleConfig {\n return resolveSecurityModuleConfigForRequest(this.securityConfig, context?.request)\n }\n\n private toWebAuthnUserId(userId: string) {\n return new TextEncoder().encode(userId).slice()\n }\n}\n\nexport default PasskeyProvider\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.8-develop.6976.1.9b79145ac4",
3
+ "version": "0.6.8-develop.6977.1.5041da373e",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@open-mercato/core": "0.6.8-develop.6976.1.9b79145ac4",
69
- "@open-mercato/ui": "0.6.8-develop.6976.1.9b79145ac4",
68
+ "@open-mercato/core": "0.6.8-develop.6977.1.5041da373e",
69
+ "@open-mercato/ui": "0.6.8-develop.6977.1.5041da373e",
70
70
  "@simplewebauthn/browser": "^13.3.0",
71
71
  "@simplewebauthn/server": "^13.3.2",
72
72
  "@simplewebauthn/types": "^12.0.0",
@@ -76,12 +76,12 @@
76
76
  "qrcode": "^1.5.4"
77
77
  },
78
78
  "peerDependencies": {
79
- "@open-mercato/shared": "0.6.8-develop.6976.1.9b79145ac4",
79
+ "@open-mercato/shared": "0.6.8-develop.6977.1.5041da373e",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.6.8-develop.6976.1.9b79145ac4",
84
+ "@open-mercato/shared": "0.6.8-develop.6977.1.5041da373e",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.17",
87
87
  "@types/react-dom": "^19.2.3",
@@ -6,6 +6,7 @@ import {
6
6
  verifyRegistrationResponse,
7
7
  } from '@simplewebauthn/server'
8
8
  import type { AuthenticatorTransportFuture } from '@simplewebauthn/types'
9
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
10
  import { z } from 'zod'
10
11
  import type {
11
12
  MfaMethodRecord,
@@ -24,6 +25,8 @@ import {
24
25
 
25
26
  const SETUP_TOKEN_VERSION = 'v1'
26
27
 
28
+ const logger = createLogger('security').child({ component: 'passkey-provider' })
29
+
27
30
  const setupPayloadSchema = z.object({
28
31
  label: z.string().min(1).max(100).optional(),
29
32
  userName: z.string().min(1).max(255).optional(),
@@ -44,15 +47,9 @@ const setupConfirmationPayloadSchema = z.union([
44
47
  }),
45
48
  ])
46
49
 
47
- const verifyPayloadSchema = z.union([
48
- z.object({
49
- response: z.record(z.string(), z.unknown()),
50
- }),
51
- z.object({
52
- credentialId: z.string().min(1),
53
- challenge: z.string().min(1),
54
- }),
55
- ])
50
+ const verifyPayloadSchema = z.object({
51
+ response: z.record(z.string(), z.unknown()),
52
+ })
56
53
 
57
54
  const setupTokenSchema = z.object({
58
55
  version: z.string().min(1),
@@ -275,73 +272,64 @@ export class PasskeyProvider implements MfaProviderInterface {
275
272
  context?: MfaVerifyContext,
276
273
  runtimeContext?: MfaProviderRuntimeContext,
277
274
  ): Promise<boolean> {
278
- const parsed = verifyPayloadSchema.parse(payload)
275
+ const parsed = verifyPayloadSchema.safeParse(payload)
276
+ if (!parsed.success) return false
279
277
  if (method.userId !== userId) return false
280
278
 
281
279
  const parsedContext = verifyContextSchema.safeParse(context)
280
+ if (!parsedContext.success) return false
281
+
282
282
  const securityConfig = this.resolveSecurityConfig(runtimeContext)
283
- if (!parsedContext.success) {
284
- if ('credentialId' in parsed) {
285
- const metadataCredentialId = method.providerMetadata?.credentialId
286
- return typeof metadataCredentialId === 'string' && metadataCredentialId === parsed.credentialId
287
- }
288
- return false
289
- }
290
283
  const pending = parsedContext.data.challenge
291
284
  if (Date.now() - pending.createdAt > securityConfig.webauthn.challengeTtlMs) {
292
285
  return false
293
286
  }
294
287
 
295
- if ('response' in parsed) {
296
- const metadata = method.providerMetadata ?? {}
297
- const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null
298
- const credentialPublicKey = typeof metadata.credentialPublicKey === 'string'
299
- ? metadata.credentialPublicKey
300
- : typeof metadata.publicKey === 'string'
301
- ? metadata.publicKey
302
- : null
303
- const counter = typeof metadata.counter === 'number' && Number.isFinite(metadata.counter)
304
- ? metadata.counter
305
- : 0
306
-
307
- if (!credentialId || !credentialPublicKey) {
308
- return false
309
- }
288
+ const metadata = method.providerMetadata ?? {}
289
+ const credentialId = typeof metadata.credentialId === 'string' ? metadata.credentialId : null
290
+ const credentialPublicKey = typeof metadata.credentialPublicKey === 'string'
291
+ ? metadata.credentialPublicKey
292
+ : typeof metadata.publicKey === 'string'
293
+ ? metadata.publicKey
294
+ : null
295
+ const counter = typeof metadata.counter === 'number' && Number.isFinite(metadata.counter)
296
+ ? metadata.counter
297
+ : 0
298
+
299
+ if (!credentialId || !credentialPublicKey) {
300
+ return false
301
+ }
310
302
 
311
- const verification = await verifyAuthenticationResponse({
312
- response: parsed.response as never,
313
- expectedChallenge: pending.challenge,
314
- expectedOrigin: this.getExpectedOrigins(securityConfig),
315
- expectedRPID: this.getRpId(securityConfig),
316
- requireUserVerification: false,
317
- credential: {
318
- id: credentialId,
319
- publicKey: this.base64UrlToBytes(credentialPublicKey),
320
- counter,
321
- transports: this.normalizeTransports(metadata.transports),
322
- },
303
+ const verification = await verifyAuthenticationResponse({
304
+ response: parsed.data.response as never,
305
+ expectedChallenge: pending.challenge,
306
+ expectedOrigin: this.getExpectedOrigins(securityConfig),
307
+ expectedRPID: this.getRpId(securityConfig),
308
+ requireUserVerification: false,
309
+ credential: {
310
+ id: credentialId,
311
+ publicKey: this.base64UrlToBytes(credentialPublicKey),
312
+ counter,
313
+ transports: this.normalizeTransports(metadata.transports),
314
+ },
315
+ }).catch((error: unknown) => {
316
+ logger.warn('Passkey assertion could not be verified', {
317
+ methodId: method.id,
318
+ rpId: this.getRpId(securityConfig),
319
+ err: error,
323
320
  })
321
+ return null
322
+ })
324
323
 
325
- if (!verification.verified) {
326
- return false
327
- }
328
-
329
- const nextCounter = verification.authenticationInfo?.newCounter
330
- if (typeof nextCounter === 'number' && Number.isFinite(nextCounter)) {
331
- const providerMetadata = method.providerMetadata ?? {}
332
- providerMetadata.counter = nextCounter
333
- method.providerMetadata = providerMetadata
334
- }
335
-
336
- return true
337
- }
338
-
339
- const metadataCredentialId = method.providerMetadata?.credentialId
340
- if (typeof metadataCredentialId !== 'string' || metadataCredentialId !== parsed.credentialId) {
324
+ if (!verification?.verified) {
341
325
  return false
342
326
  }
343
- if (pending.challenge !== parsed.challenge) {
344
- return false
327
+
328
+ const nextCounter = verification.authenticationInfo?.newCounter
329
+ if (typeof nextCounter === 'number' && Number.isFinite(nextCounter)) {
330
+ const providerMetadata = method.providerMetadata ?? {}
331
+ providerMetadata.counter = nextCounter
332
+ method.providerMetadata = providerMetadata
345
333
  }
346
334
 
347
335
  return true