Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@
"engines": {
"node": ">=22.12.0"
}
}
}
102 changes: 50 additions & 52 deletions apps/tanstack-start/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FileRouteTypes>()
export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
import type { getRouter } from "./router.tsx"
import type { createStart } from "@tanstack/react-start"
declare module "@tanstack/react-start" {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@
"path-to-regexp": "^1.9.0",
"serialize-javascript": "^7.0.3"
}
}
}
4 changes: 4 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
halvaradop marked this conversation as resolved.
}

export type AuthClient = ReturnType<typeof createAuthInstance>["handlers"]
Expand Down Expand Up @@ -435,3 +435,10 @@ export type FunctionAPIContext<Options extends object> = {
export type SignInReturn<Redirect extends boolean = boolean> = Redirect extends true
? Response
: { redirect: false; signInURL: string }

export type InternalContext = RouterGlobalContext & {
cookieConfig: {
secure: CookieStoreConfig
standard: CookieStoreConfig
}
}
10 changes: 1 addition & 9 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
33 changes: 24 additions & 9 deletions packages/core/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ const logLevelToSeverity: Record<LogLevel, string[]> = {
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<string, number> = {
emergency: 0,
Expand All @@ -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<Logger>): InternalLogger | undefined => {
if (!logger) return undefined
const level = logger.level
const allowedSeverities = logLevelToSeverity[level] ?? []

const internalLogger: InternalLogger = {
return {
level,
log<T extends keyof typeof logMessages>(key: T, overrides?: Partial<SyslogOptions>) {
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",
})
}
Comment thread
halvaradop marked this conversation as resolved.
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
}
87 changes: 87 additions & 0 deletions packages/core/test/context.test.ts
Original file line number Diff line number Diff line change
@@ -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,
})
)
})
})
1 change: 1 addition & 0 deletions packages/core/test/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ export const {
api,
} = createAuth({
oauth: [oauthCustomService, oauthCustomServiceProfile],
logger: true,
})
1 change: 0 additions & 1 deletion packages/core/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading