From 644261d5a66b39b38fbe5b4d38257a5bc2bb31e6 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 26 May 2026 22:06:00 -0700 Subject: [PATCH 1/5] Define SimpleWebAuthnLogger interface --- packages/server/src/helpers/logging.ts | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/server/src/helpers/logging.ts b/packages/server/src/helpers/logging.ts index b06fdcb2..873f7f9a 100644 --- a/packages/server/src/helpers/logging.ts +++ b/packages/server/src/helpers/logging.ts @@ -1,20 +1,22 @@ -// const defaultLogger = debug('SimpleWebAuthn'); - /** - * Generate an instance of a `debug` logger that extends off of the "simplewebauthn" namespace for - * consistent naming. + * A basic logging interface that enables projects to capture logging output from SimpleWebAuthn + * using whatever logging method is appropriate for the project. * - * See https://www.npmjs.com/package/debug for information on how to control logging output when - * using @simplewebauthn/server + * For example, a project using `console` statements to capture logs can use the following + * implementation of this interface: * - * Example: - * - * ``` - * const log = getLogger('mds'); - * log('hello'); // simplewebauthn:mds hello +0ms + * ```ts + * const ConsoleLogger: SimpleWebAuthnLogger = { + * debug(message: string, ...args: unknown[]) { console.debug(message, ...args); }, + * info(message: string, ...args: unknown[]) { console.info(message, ...args); }, + * warn(message: string, ...args: unknown[]) { console.warn(message, ...args); }, + * error(message: string, ...args: unknown[]) { console.error(message, ...args); }, + * }; * ``` */ -export function getLogger(_name: string): (message: string, ..._rest: unknown[]) => void { - // This is a noop for now while I search for a better debug logger technique - return (_message, ..._rest) => {}; +export interface SimpleWebAuthnLogger { + debug: (message: string, ...args: unknown[]) => void; + info: (message: string, ...args: unknown[]) => void; + warn: (message: string, ...args: unknown[]) => void; + error: (message: string, ...args: unknown[]) => void; } From 915b554d305bc1160bbc154c870de27f39bf4811 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 26 May 2026 22:06:26 -0700 Subject: [PATCH 2/5] Create DefaultNoopLogger instance --- packages/server/src/helpers/logging.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/server/src/helpers/logging.ts b/packages/server/src/helpers/logging.ts index 873f7f9a..b3b9a77a 100644 --- a/packages/server/src/helpers/logging.ts +++ b/packages/server/src/helpers/logging.ts @@ -20,3 +20,14 @@ export interface SimpleWebAuthnLogger { warn: (message: string, ...args: unknown[]) => void; error: (message: string, ...args: unknown[]) => void; } + +/** + * A logger instance that doesn't do anything. Useful as a default argument when no custom instance + * of the `SimpleWebAuthnLogger` interface is specified. + */ +export const DefaultNoopLogger: SimpleWebAuthnLogger = { + debug() {}, + info() {}, + warn() {}, + error() {}, +}; From 9bdb8d65f8277f1ef8f7a68080555f9ca2b69b13 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 26 May 2026 22:06:42 -0700 Subject: [PATCH 3/5] Update MetadataService to accept and use logger --- .../server/src/services/metadataService.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/server/src/services/metadataService.ts b/packages/server/src/services/metadataService.ts index e6d60778..078750d0 100644 --- a/packages/server/src/services/metadataService.ts +++ b/packages/server/src/services/metadataService.ts @@ -1,7 +1,7 @@ import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString.ts'; import type { MetadataBLOBPayloadEntry, MetadataStatement } from '../metadata/mdsTypes.ts'; import { verifyMDSBlob } from '../metadata/verifyMDSBlob.ts'; -import { getLogger } from '../helpers/logging.ts'; +import { DefaultNoopLogger, type SimpleWebAuthnLogger } from '../helpers/logging.ts'; import { fetch } from '../helpers/fetch.ts'; import type { Uint8Array_ } from '../types/index.ts'; @@ -44,8 +44,6 @@ enum SERVICE_STATE { */ export type VerificationMode = 'permissive' | 'strict'; -const log = getLogger('MetadataService'); - interface MetadataService { /** * Prepare the service to handle remote MDS servers and/or cache local metadata statements. @@ -65,6 +63,7 @@ interface MetadataService { mdsServers?: string[]; statements?: MetadataStatement[]; verificationMode?: VerificationMode; + logger?: SimpleWebAuthnLogger; }): Promise; /** * Get a metadata statement for a given AAGUID. @@ -86,18 +85,27 @@ export class BaseMetadataService implements MetadataService { private statementCache: { [aaguid: string]: CachedBLOBEntry } = {}; private state: SERVICE_STATE = SERVICE_STATE.DISABLED; private verificationMode: VerificationMode = 'strict'; + private logger: SimpleWebAuthnLogger = DefaultNoopLogger; async initialize( opts: { mdsServers?: string[]; statements?: MetadataStatement[]; verificationMode?: VerificationMode; + logger?: SimpleWebAuthnLogger; } = {}, ): Promise { // Reset statement cache this.statementCache = {}; - const { mdsServers = [defaultURLMDS], statements, verificationMode } = opts; + const { + mdsServers = [defaultURLMDS], + statements, + verificationMode, + logger = DefaultNoopLogger, + } = opts; + + this.logger = logger; this.setState(SERVICE_STATE.REFRESHING); @@ -124,7 +132,7 @@ export class BaseMetadataService implements MetadataService { } }); - log(`Cached ${statementsAdded} local statements`); + this.logger.info(`Cached ${statementsAdded} local statements`); } /** @@ -149,7 +157,7 @@ export class BaseMetadataService implements MetadataService { await this.verifyBlob(blob, cachedMDS); } catch (err) { // Notify of the error and move on - log(`Could not download BLOB from ${url}:`, err); + this.logger.error(`Could not download BLOB from ${url}:`, err); numServers -= 1; } } @@ -157,7 +165,7 @@ export class BaseMetadataService implements MetadataService { // Calculate the difference to get the total number of new statements we successfully added const newCacheCount = Object.keys(this.statementCache).length; const cacheDiff = newCacheCount - currentCacheCount; - log( + this.logger.info( `Cached ${cacheDiff} statements from ${numServers} metadata server(s)`, ); } @@ -288,7 +296,7 @@ export class BaseMetadataService implements MetadataService { // TODO (Feb 2026): It'd be more actionable for devs if a specific error was raised here, // then this message was logged higher up when it can include the array index of the stale // blob. - log( + this.logger.warn( `⚠️ This MDS blob (serial: ${payload.no}) contains stale data as of ${parsedNextUpdate.toISOString()}. Please consider re-initializing MetadataService with a newer MDS blob.`, ); } @@ -337,11 +345,11 @@ export class BaseMetadataService implements MetadataService { this.state = newState; if (newState === SERVICE_STATE.DISABLED) { - log('MetadataService is DISABLED'); + this.logger.debug('MetadataService is DISABLED'); } else if (newState === SERVICE_STATE.REFRESHING) { - log('MetadataService is REFRESHING'); + this.logger.debug('MetadataService is REFRESHING'); } else if (newState === SERVICE_STATE.READY) { - log('MetadataService is READY'); + this.logger.debug('MetadataService is READY'); } } } From b4422e2b983f2ae764aecb978ef949826aab948c Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 27 May 2026 09:17:32 -0700 Subject: [PATCH 4/5] Allow logging levels to be defined independently --- packages/server/src/helpers/index.ts | 1 + packages/server/src/helpers/logging.ts | 43 +++++++++++++++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/server/src/helpers/index.ts b/packages/server/src/helpers/index.ts index 2e20d519..6c1c0e2a 100644 --- a/packages/server/src/helpers/index.ts +++ b/packages/server/src/helpers/index.ts @@ -15,3 +15,4 @@ export * from './verifySignature.ts'; export * from './iso/index.ts'; export * from '../metadata/verifyMDSBlob.ts'; export * as cose from './cose.ts'; +export { type SimpleWebAuthnLogger } from './logging.ts'; diff --git a/packages/server/src/helpers/logging.ts b/packages/server/src/helpers/logging.ts index b3b9a77a..3067fb95 100644 --- a/packages/server/src/helpers/logging.ts +++ b/packages/server/src/helpers/logging.ts @@ -1,13 +1,14 @@ /** * A basic logging interface that enables projects to capture logging output from SimpleWebAuthn - * using whatever logging method is appropriate for the project. + * using whatever logging method is appropriate for the project. Logging levels can be defined + * independently to only capture desired levels. * * For example, a project using `console` statements to capture logs can use the following * implementation of this interface: * * ```ts * const ConsoleLogger: SimpleWebAuthnLogger = { - * debug(message: string, ...args: unknown[]) { console.debug(message, ...args); }, + * // debug(message: string, ...args: unknown[]) { console.debug(message, ...args); }, * info(message: string, ...args: unknown[]) { console.info(message, ...args); }, * warn(message: string, ...args: unknown[]) { console.warn(message, ...args); }, * error(message: string, ...args: unknown[]) { console.error(message, ...args); }, @@ -15,19 +16,47 @@ * ``` */ export interface SimpleWebAuthnLogger { - debug: (message: string, ...args: unknown[]) => void; - info: (message: string, ...args: unknown[]) => void; - warn: (message: string, ...args: unknown[]) => void; - error: (message: string, ...args: unknown[]) => void; + debug?: (message: string, ...args: unknown[]) => void; + info?: (message: string, ...args: unknown[]) => void; + warn?: (message: string, ...args: unknown[]) => void; + error?: (message: string, ...args: unknown[]) => void; } /** * A logger instance that doesn't do anything. Useful as a default argument when no custom instance * of the `SimpleWebAuthnLogger` interface is specified. */ -export const DefaultNoopLogger: SimpleWebAuthnLogger = { +export const DefaultNoopLogger: Required = { debug() {}, info() {}, warn() {}, error() {}, }; + +/** + * Generate an instance of SimpleWebAuthnLogger that defines all methods. Any logging method not + * defined on `logger` will be a no-op. + */ +export function buildLoggerAllMethods( + logger: SimpleWebAuthnLogger, +): Required { + const toReturn: Required = { ...DefaultNoopLogger }; + + if (logger.debug) { + toReturn.debug = logger.debug; + } + + if (logger.info) { + toReturn.info = logger.info; + } + + if (logger.warn) { + toReturn.warn = logger.warn; + } + + if (logger.error) { + toReturn.error = logger.error; + } + + return toReturn; +} From 25928cd8abf74918188ea7935c1e6b6b49a01a22 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Wed, 27 May 2026 09:17:47 -0700 Subject: [PATCH 5/5] Update MetadataService --- packages/server/src/services/metadataService.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/server/src/services/metadataService.ts b/packages/server/src/services/metadataService.ts index 078750d0..4c6add66 100644 --- a/packages/server/src/services/metadataService.ts +++ b/packages/server/src/services/metadataService.ts @@ -1,7 +1,11 @@ import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString.ts'; import type { MetadataBLOBPayloadEntry, MetadataStatement } from '../metadata/mdsTypes.ts'; import { verifyMDSBlob } from '../metadata/verifyMDSBlob.ts'; -import { DefaultNoopLogger, type SimpleWebAuthnLogger } from '../helpers/logging.ts'; +import { + buildLoggerAllMethods, + DefaultNoopLogger, + type SimpleWebAuthnLogger, +} from '../helpers/logging.ts'; import { fetch } from '../helpers/fetch.ts'; import type { Uint8Array_ } from '../types/index.ts'; @@ -85,7 +89,7 @@ export class BaseMetadataService implements MetadataService { private statementCache: { [aaguid: string]: CachedBLOBEntry } = {}; private state: SERVICE_STATE = SERVICE_STATE.DISABLED; private verificationMode: VerificationMode = 'strict'; - private logger: SimpleWebAuthnLogger = DefaultNoopLogger; + private logger: Required = DefaultNoopLogger; async initialize( opts: { @@ -105,7 +109,7 @@ export class BaseMetadataService implements MetadataService { logger = DefaultNoopLogger, } = opts; - this.logger = logger; + this.logger = buildLoggerAllMethods(logger); this.setState(SERVICE_STATE.REFRESHING);