diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index aef7a3b6ca..7660947c41 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -53,6 +53,7 @@ import type { Adapter, AdapterSession, AdapterUser } from "./adapters.js" import type { Account, AuthAction, + AuthConfigInternal, Awaitable, CookiesOptions, DefaultSession, @@ -102,19 +103,22 @@ export async function Auth( request: Request, config: AuthConfig ): Promise { - const logger = setLogger(config) + const configWithLogger: AuthConfigInternal = { + ...config, + logger: setLogger(config), + } - const internalRequest = await toInternalRequest(request, config) + const internalRequest = await toInternalRequest(request, configWithLogger) // There was an error parsing the request if (!internalRequest) return Response.json(`Bad request.`, { status: 400 }) const warningsOrError = assertConfig(internalRequest, config) if (Array.isArray(warningsOrError)) { - warningsOrError.forEach(logger.warn) + warningsOrError.forEach(configWithLogger.logger.warn) } else if (warningsOrError) { // If there's an error in the user config, bail out early - logger.error(warningsOrError) + configWithLogger.logger.error(warningsOrError) const htmlPages = new Set([ "signin", "signout", @@ -143,7 +147,7 @@ export async function Auth( // Either there was no error page configured or the configured one contains infinite redirects if (!pages?.error || authOnErrorPage) { if (authOnErrorPage) { - logger.error( + configWithLogger.logger.error( new ErrorPageLoop( `The error page ${pages?.error} should not require authentication` ) @@ -161,7 +165,10 @@ export async function Auth( const isRedirect = request.headers?.has("X-Auth-Return-Redirect") const isRaw = config.raw === raw try { - const internalResponse = await AuthInternal(internalRequest, config) + const internalResponse = await AuthInternal( + internalRequest, + configWithLogger + ) if (isRaw) return internalResponse const response = toResponse(internalResponse) @@ -172,7 +179,7 @@ export async function Auth( return Response.json({ url }, { headers: response.headers }) } catch (e) { const error = e as Error - logger.error(error) + configWithLogger.logger.error(error) const isAuthError = error instanceof AuthError if (isAuthError && isRaw && !isRedirect) throw error diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts index 00ee0027c1..937e98d6f3 100644 --- a/packages/core/src/lib/index.ts +++ b/packages/core/src/lib/index.ts @@ -4,9 +4,11 @@ import { init } from "./init.js" import renderPage from "./pages/index.js" import * as actions from "./actions/index.js" import { validateCSRF } from "./actions/callback/oauth/csrf-token.js" - -import type { RequestInternal, ResponseInternal } from "../types.js" -import type { AuthConfig } from "../index.js" +import type { + AuthConfigInternal, + RequestInternal, + ResponseInternal, +} from "../types.js" import { skipCSRFCheck } from "./symbols.js" export { customFetch, raw, skipCSRFCheck } from "./symbols.js" @@ -14,7 +16,7 @@ export { customFetch, raw, skipCSRFCheck } from "./symbols.js" /** @internal */ export async function AuthInternal( request: RequestInternal, - authOptions: AuthConfig + authOptions: AuthConfigInternal ): Promise { const { action, providerId, error, method } = request diff --git a/packages/core/src/lib/init.ts b/packages/core/src/lib/init.ts index 9e8ca122f6..d61272919a 100644 --- a/packages/core/src/lib/init.ts +++ b/packages/core/src/lib/init.ts @@ -5,15 +5,19 @@ import { createCSRFToken } from "./actions/callback/oauth/csrf-token.js" import { AdapterError, EventError } from "../errors.js" import parseProviders from "./utils/providers.js" -import { setLogger, type LoggerInstance } from "./utils/logger.js" +import type { LoggerInstance } from "./utils/logger.js" import { merge } from "./utils/merge.js" -import type { InternalOptions, RequestInternal } from "../types.js" +import type { + AuthConfigInternal, + InternalOptions, + RequestInternal, +} from "../types.js" import type { AuthConfig } from "../index.js" interface InitParams { url: URL - authOptions: AuthConfig + authOptions: AuthConfigInternal providerId?: string action: InternalOptions["action"] /** Callback URL value extracted from the incoming request. */ @@ -65,7 +69,6 @@ export async function init({ options: InternalOptions cookies: cookie.Cookie[] }> { - const logger = setLogger(config) const { providers, provider } = parseProviders({ url, providerId, config }) const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default @@ -129,11 +132,11 @@ export async function init({ ...config.jwt, }, // Event messages - events: eventsErrorHandler(config.events ?? {}, logger), - adapter: adapterErrorHandler(config.adapter, logger), + events: eventsErrorHandler(config.events ?? {}, config.logger), + adapter: adapterErrorHandler(config.adapter, config.logger), // Callback functions callbacks: { ...defaultCallbacks, ...config.callbacks }, - logger, + logger: config.logger, callbackUrl: url.origin, isOnRedirectProxy, experimental: { diff --git a/packages/core/src/lib/utils/logger.ts b/packages/core/src/lib/utils/logger.ts index e9e5c85858..9941318eef 100644 --- a/packages/core/src/lib/utils/logger.ts +++ b/packages/core/src/lib/utils/logger.ts @@ -63,12 +63,13 @@ export function setLogger( ...defaultLogger, } - // Turn off debug logging if `debug` isn't set to `true` - if (!config.debug) newLogger.debug = () => {} - if (config.logger?.error) newLogger.error = config.logger.error if (config.logger?.warn) newLogger.warn = config.logger.warn - if (config.logger?.debug) newLogger.debug = config.logger.debug + + // Disable debug logging if `debug` is not explicitly set to `true`, even if a custom logger is provided + if (!config.debug) newLogger.debug = () => {} + if (config.debug && config.logger?.debug) + newLogger.debug = config.logger.debug config.logger ??= newLogger return newLogger diff --git a/packages/core/src/lib/utils/web.ts b/packages/core/src/lib/utils/web.ts index fdbafbcadc..8c4a036b2c 100644 --- a/packages/core/src/lib/utils/web.ts +++ b/packages/core/src/lib/utils/web.ts @@ -1,14 +1,12 @@ import * as cookie from "../vendored/cookie.js" import { UnknownAction } from "../../errors.js" -import { setLogger } from "./logger.js" - import type { AuthAction, + AuthConfigInternal, RequestInternal, ResponseInternal, } from "../../types.js" import { isAuthAction } from "./actions.js" -import type { AuthConfig } from "../../index.js" const { parse: parseCookie, serialize: serializeCookie } = cookie @@ -26,7 +24,7 @@ async function getBody(req: Request): Promise | undefined> { export async function toInternalRequest( req: Request, - config: AuthConfig + config: AuthConfigInternal ): Promise { try { if (req.method !== "GET" && req.method !== "POST") @@ -54,9 +52,8 @@ export async function toInternalRequest( query: Object.fromEntries(url.searchParams), } } catch (e) { - const logger = setLogger(config) - logger.error(e as Error) - logger.debug("request", req) + config.logger.error(e as Error) + config.logger.debug("request", req) } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b6653ffe04..1b91325312 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -428,3 +428,7 @@ export interface InternalOptions { experimental: NonNullable basePath: string } + +export type AuthConfigInternal = Omit & { + logger: LoggerInstance +} diff --git a/packages/core/test/logger.test.ts b/packages/core/test/logger.test.ts new file mode 100644 index 0000000000..198aad23af --- /dev/null +++ b/packages/core/test/logger.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect, vi } from "vitest" +import { setLogger } from "../src/lib/utils/logger" +import type { AuthConfig } from "../src/types.ts" + +describe("setLogger", () => { + it("should return default logger if no custom logger is provided", () => { + const logger = setLogger({}) + expect(logger.error).toBeInstanceOf(Function) + expect(logger.debug).toBeInstanceOf(Function) + expect(logger.warn).toBeInstanceOf(Function) + }) + + it("should override error and warn with custom logger", () => { + const customError = vi.fn() + const customWarn = vi.fn() + const logger = setLogger({ + logger: { + error: customError, + warn: customWarn, + }, + }) + expect(logger.error).toBe(customError) + expect(logger.warn).toBe(customWarn) + expect(logger.debug).toBeInstanceOf(Function) + }) + + it("should override debug only if debug is true", () => { + const customDebug = vi.fn() + const logger1 = setLogger({ + logger: { + debug: customDebug, + }, + debug: true, + }) + expect(logger1.debug).toBe(customDebug) + expect(logger1.error).toBeInstanceOf(Function) + expect(logger1.warn).toBeInstanceOf(Function) + + const logger2 = setLogger({ + logger: { + debug: customDebug, + }, + debug: false, + }) + expect(logger2.debug).not.toBe(customDebug) + expect(logger2.debug).toBeInstanceOf(Function) + expect(logger2.error).toBeInstanceOf(Function) + expect(logger2.warn).toBeInstanceOf(Function) + }) + + it("should assign the new logger to config.logger if not present", () => { + const config: Partial = {} + const logger = setLogger(config) + expect(config.logger).toBe(logger) + }) +})