diff --git a/apps/astro/package.json b/apps/astro/package.json index 111fd92b..9c198562 100644 --- a/apps/astro/package.json +++ b/apps/astro/package.json @@ -33,4 +33,4 @@ "engines": { "node": ">=22.12.0" } -} \ No newline at end of file +} diff --git a/apps/tanstack-start/src/routeTree.gen.ts b/apps/tanstack-start/src/routeTree.gen.ts index f72ee3f1..c6110736 100644 --- a/apps/tanstack-start/src/routeTree.gen.ts +++ b/apps/tanstack-start/src/routeTree.gen.ts @@ -8,79 +8,77 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from './routes/__root' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$' +import { Route as rootRouteImport } from "./routes/__root" +import { Route as IndexRouteImport } from "./routes/index" +import { Route as ApiAuthSplatRouteImport } from "./routes/api/auth.$" const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, + id: "/", + path: "/", + getParentRoute: () => rootRouteImport, } as any) const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ - id: '/api/auth/$', - path: '/api/auth/$', - getParentRoute: () => rootRouteImport, + id: "/api/auth/$", + path: "/api/auth/$", + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute + "/": typeof IndexRoute + "/api/auth/$": typeof ApiAuthSplatRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute + "/": typeof IndexRoute + "/api/auth/$": typeof ApiAuthSplatRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/api/auth/$': typeof ApiAuthSplatRoute + __root__: typeof rootRouteImport + "/": typeof IndexRoute + "/api/auth/$": typeof ApiAuthSplatRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/api/auth/$' - fileRoutesByTo: FileRoutesByTo - to: '/' | '/api/auth/$' - id: '__root__' | '/' | '/api/auth/$' - fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: "/" | "/api/auth/$" + fileRoutesByTo: FileRoutesByTo + to: "/" | "/api/auth/$" + id: "__root__" | "/" | "/api/auth/$" + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - ApiAuthSplatRoute: typeof ApiAuthSplatRoute + IndexRoute: typeof IndexRoute + ApiAuthSplatRoute: typeof ApiAuthSplatRoute } -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport +declare module "@tanstack/react-router" { + interface FileRoutesByPath { + "/": { + id: "/" + path: "/" + fullPath: "/" + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + "/api/auth/$": { + id: "/api/auth/$" + path: "/api/auth/$" + fullPath: "/api/auth/$" + preLoaderRoute: typeof ApiAuthSplatRouteImport + parentRoute: typeof rootRouteImport + } } - '/api/auth/$': { - id: '/api/auth/$' - path: '/api/auth/$' - fullPath: '/api/auth/$' - preLoaderRoute: typeof ApiAuthSplatRouteImport - parentRoute: typeof rootRouteImport - } - } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - ApiAuthSplatRoute: ApiAuthSplatRoute, + IndexRoute: IndexRoute, + ApiAuthSplatRoute: ApiAuthSplatRoute, } -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() +export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes() -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } +import type { getRouter } from "./router.tsx" +import type { createStart } from "@tanstack/react-start" +declare module "@tanstack/react-start" { + interface Register { + ssr: true + router: Awaited> + } } diff --git a/package.json b/package.json index 7c0ee54c..b5a5a87b 100644 --- a/package.json +++ b/package.json @@ -101,4 +101,4 @@ "path-to-regexp": "^1.9.0", "serialize-javascript": "^7.0.3" } -} \ No newline at end of file +} diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0756e220..a27f6ebf 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -32,6 +32,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Added `timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99) +### Changed + +- Updated logger configuration priority in `createAuth`: direct logger configuration is applied first, followed by `LOG_LEVEL`, and finally the `DEBUG` environment variable. [#120](https://github.com/aura-stack-ts/auth/pull/120) + --- ## [0.4.0] - 2026-02-16 diff --git a/packages/core/src/@types/index.ts b/packages/core/src/@types/index.ts index b7d419d7..336d39e3 100644 --- a/packages/core/src/@types/index.ts +++ b/packages/core/src/@types/index.ts @@ -389,9 +389,9 @@ export type SyslogOptions = { * Logger function interface for structured logging. * Called when errors or warnings occur during authentication flows. */ -export type Logger = { - level: LogLevel - log: (args: SyslogOptions) => void +export interface Logger { + level?: LogLevel + log?: (args: SyslogOptions) => void } export type AuthClient = ReturnType["handlers"] @@ -435,3 +435,10 @@ export type FunctionAPIContext = { export type SignInReturn = Redirect extends true ? Response : { redirect: false; signInURL: string } + +export type InternalContext = RouterGlobalContext & { + cookieConfig: { + secure: CookieStoreConfig + standard: CookieStoreConfig + } +} diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index ac05cee2..78c9c9ec 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -3,15 +3,7 @@ import { createProxyLogger } from "@/logger.ts" import { createCookieStore } from "@/cookie.ts" import { getEnv, getEnvArray, getEnvBoolean } from "@/env.ts" import { createBuiltInOAuthProviders } from "@/oauth/index.ts" -import type { AuthConfig, CookieStoreConfig } from "@/@types/index.ts" -import type { GlobalContext } from "@aura-stack/router" - -export type InternalContext = GlobalContext & { - cookieConfig: { - secure: CookieStoreConfig - standard: CookieStoreConfig - } -} +import type { AuthConfig, InternalContext } from "@/@types/index.ts" export const createContext = (config?: AuthConfig): InternalContext => { const trustedProxyHeadersEnv = getEnv("TRUSTED_PROXY_HEADERS") diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 05e7290c..7cf2453c 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -285,6 +285,10 @@ const logLevelToSeverity: Record = { error: ["error", "critical", "alert", "emergency"], } +const isValidLogLevel = (value: string | undefined): value is LogLevel => { + return value === "debug" || value === "info" || value === "warn" || value === "error" +} + const getSeverityLevel = (severity: string): number => { const severities: Record = { emergency: 0, @@ -306,38 +310,49 @@ export const createSyslogMessage = (options: SyslogOptions): string => { return `<${pri}>1 ${timestamp} ${hostname} ${appName} ${procId} ${msgId} ${structuredDataStr} ${message}` } -export const createLogger = (logger?: Logger): InternalLogger | undefined => { +export const createLogger = (logger?: Required): InternalLogger | undefined => { if (!logger) return undefined const level = logger.level const allowedSeverities = logLevelToSeverity[level] ?? [] - const internalLogger: InternalLogger = { + return { level, log(key: T, overrides?: Partial) { const entry = createLogEntry(key, overrides) if (!allowedSeverities.includes(entry.severity)) return entry - logger.log({ timestamp: entry.timestamp, appName: entry.appName ?? "aura-auth", hostname: entry.hostname ?? "aura-auth", ...entry, }) - return entry }, } - return internalLogger } +/** + * Creates the logger instance based on the provided configuration and environment variables. + * Priority: config.logger, LOG_LEVEL env, DEBUG env and defaults to undefined if logging is not enabled. + * + */ export const createProxyLogger = (config?: AuthConfig) => { const level = getEnv("LOG_LEVEL") const debug = getEnvBoolean("DEBUG") - if (debug || config?.logger === true) { + if (typeof config?.logger === "object") { + return createLogger({ + log: config.logger?.log || createSyslogMessage, + level: isValidLogLevel(config.logger?.level) ? config.logger?.level : isValidLogLevel(level) ? level : "error", + }) + } + if (debug || config?.logger === true || level) { return createLogger({ - level: (level as LogLevel) ?? "debug", - log: createSyslogMessage, + level: isValidLogLevel(level) ? level : "debug", + log: (options) => { + const message = createSyslogMessage(options) + console.log(message) + }, }) } - return typeof config?.logger === "object" ? createLogger(config.logger) : undefined + return undefined } diff --git a/packages/core/test/context.test.ts b/packages/core/test/context.test.ts new file mode 100644 index 00000000..d9a87cc0 --- /dev/null +++ b/packages/core/test/context.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, vi, test } from "vitest" +import { createProxyLogger } from "@/logger.ts" +import type { Logger } from "@/@types/index.ts" + +describe("createProxyLogger", () => { + test("proxyLogger with disabled logger", () => { + const logger = createProxyLogger() + expect(logger).toBeUndefined() + }) + + test("proxyLogger with enabled logger", () => { + const logger = createProxyLogger({ oauth: [], logger: true }) + expect(logger).toMatchObject({ level: "debug", log: expect.any(Function) }) + }) + + test("proxyLogger with enabled logger and DEBUG env var set to false", () => { + vi.stubEnv("DEBUG", "false") + const logger = createProxyLogger({ oauth: [], logger: true }) + expect(logger).toMatchObject({ level: "debug", log: expect.any(Function) }) + }) + + test("proxyLogger with DEBUG env var set to true", () => { + vi.stubEnv("DEBUG", "true") + const logger = createProxyLogger() + expect(logger).toMatchObject({ level: "debug", log: expect.any(Function) }) + }) + + test("proxyLogger with LOG_LEVEL env var set to info", () => { + vi.stubEnv("LOG_LEVEL", "info") + const logger = createProxyLogger({ oauth: [], logger: true }) + expect(logger?.level).toBe("info") + }) + + test("proxyLogger with DEBUG and LOG_LEVEL env vars set", () => { + vi.stubEnv("DEBUG", "true") + vi.stubEnv("LOG_LEVEL", "error") + const logger = createProxyLogger({ oauth: [], logger: true }) + expect(logger?.level).toBe("error") + }) + + test("proxyLogger with enabled logger and DEBUG and LOG_LEVEL env vars set", () => { + vi.stubEnv("DEBUG", "true") + vi.stubEnv("LOG_LEVEL", "error") + const logger = createProxyLogger({ oauth: [], logger: { level: "warn", log: () => {} } }) + expect(logger?.level).toBe("warn") + }) + + test("proxyLogger should fallback to debug when LOG_LEVEL env var is invalid", () => { + vi.stubEnv("LOG_LEVEL", "verbose") + const logger = createProxyLogger({ oauth: [], logger: true }) + expect(logger?.level).toBe("debug") + }) + + test("proxyLogger with LOG_LEVEL env var and without logger enabled", () => { + vi.stubEnv("LOG_LEVEL", "warn") + const logger = createProxyLogger() + expect(logger).toMatchObject({ level: "warn", log: expect.any(Function) }) + }) + + test("proxyLogger with empty config", () => { + const logger = createProxyLogger({ oauth: [], logger: {} }) + expect(logger?.level).toBe("error") + }) + + test("proxyLogger with custom logger", () => { + const log = vi.fn() + const customLogger: Logger = { + level: "error", + log, + } + const logger = createProxyLogger({ oauth: [], logger: customLogger }) + expect(logger).toMatchObject({ level: "error", log: expect.any(Function) }) + + logger?.log("AUTH_SESSION_INVALID") + expect(log).not.toHaveBeenCalled() + + const timestamp = new Date().toISOString() + logger?.log("SERVER_ERROR", { timestamp }) + expect(log).toHaveBeenCalledWith( + expect.objectContaining({ + msgId: "SERVER_ERROR", + severity: "error", + timestamp, + }) + ) + }) +}) diff --git a/packages/core/test/presets.ts b/packages/core/test/presets.ts index fa75ec7c..41a53609 100644 --- a/packages/core/test/presets.ts +++ b/packages/core/test/presets.ts @@ -43,4 +43,5 @@ export const { api, } = createAuth({ oauth: [oauthCustomService, oauthCustomServiceProfile], + logger: true, }) diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index f9de1e41..afc5ac81 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -26,7 +26,6 @@ export default defineConfig({ "AURA_AUTH_OAUTH-PROVIDER_CLIENT_SECRET": "oauth_client_secret", "AURA_AUTH_OAUTH-PROFILE_CLIENT_ID": "oauth_profile_client_id", "AURA_AUTH_OAUTH-PROFILE_CLIENT_SECRET": "oauth_profile_client_secret", - AURA_AUTH_DEBUG: "1", }, }, resolve: {