@invect/user-auth 0.0.1 → 0.1.1

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.
package/README.md CHANGED
@@ -14,7 +14,7 @@ pnpm add @invect/user-auth better-auth
14
14
 
15
15
  ```ts
16
16
  import { betterAuth } from 'better-auth';
17
- import { betterAuthPlugin } from '@invect/user-auth';
17
+ import { userAuth } from '@invect/user-auth';
18
18
  import { createInvectRouter } from '@invect/express';
19
19
 
20
20
  // 1. Configure better-auth
@@ -27,7 +27,7 @@ const auth = betterAuth({
27
27
  app.use('/invect', createInvectRouter({
28
28
  databaseUrl: 'file:./dev.db',
29
29
  plugins: [
30
- betterAuthPlugin({
30
+ userAuth({
31
31
  auth,
32
32
  globalAdmins: [
33
33
  {
@@ -77,7 +77,7 @@ function SignInPage() {
77
77
 
78
78
  | Entry Point | Import | Content |
79
79
  |-------------|--------|---------|
80
- | `@invect/user-auth` | `import { betterAuthPlugin } from '@invect/user-auth'` | Backend plugin (Node.js) |
80
+ | `@invect/user-auth` | `import { userAuth } from '@invect/user-auth'` | Backend plugin (Node.js) |
81
81
  | `@invect/user-auth/ui` | `import { AuthProvider, useAuth } from '@invect/user-auth/ui'` | Frontend components (Browser) |
82
82
  | `@invect/user-auth/types` | `import type { AuthUser } from '@invect/user-auth/types'` | Shared types |
83
83
 
@@ -216,14 +216,14 @@ function getErrorLogDetails(error) {
216
216
  /**
217
217
  * Abstract schema for better-auth's database tables.
218
218
  *
219
- * These definitions allow the Invect CLI (`npx invect generate`) to include
219
+ * These definitions allow the Invect CLI (`npx invect-cli generate`) to include
220
220
  * the better-auth tables when generating Drizzle/Prisma schema files.
221
221
  *
222
222
  * The shapes match better-auth's default table structure. If your better-auth
223
223
  * config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),
224
224
  * you can extend these in your own config.
225
225
  */
226
- const BETTER_AUTH_SCHEMA = {
226
+ const USER_AUTH_SCHEMA = {
227
227
  user: {
228
228
  tableName: "user",
229
229
  order: 1,
@@ -568,11 +568,11 @@ async function createMySQLPool(connectionString) {
568
568
  * @example
569
569
  * ```ts
570
570
  * // Simple: let the plugin manage better-auth internally
571
- * import { betterAuthPlugin } from '@invect/user-auth';
571
+ * import { userAuth } from '@invect/user-auth';
572
572
  *
573
573
  * app.use('/invect', createInvectRouter({
574
574
  * databaseUrl: 'file:./dev.db',
575
- * plugins: [betterAuthPlugin({
575
+ * plugins: [userAuth({
576
576
  * globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],
577
577
  * })],
578
578
  * }));
@@ -582,7 +582,7 @@ async function createMySQLPool(connectionString) {
582
582
  * ```ts
583
583
  * // Advanced: provide your own better-auth instance
584
584
  * import { betterAuth } from 'better-auth';
585
- * import { betterAuthPlugin } from '@invect/user-auth';
585
+ * import { userAuth } from '@invect/user-auth';
586
586
  *
587
587
  * const auth = betterAuth({
588
588
  * database: { ... },
@@ -592,11 +592,11 @@ async function createMySQLPool(connectionString) {
592
592
  *
593
593
  * app.use('/invect', createInvectRouter({
594
594
  * databaseUrl: 'file:./dev.db',
595
- * plugins: [betterAuthPlugin({ auth })],
595
+ * plugins: [userAuth({ auth })],
596
596
  * }));
597
597
  * ```
598
598
  */
599
- function betterAuthPlugin(options) {
599
+ function userAuth(options) {
600
600
  const { prefix = DEFAULT_PREFIX, mapUser: customMapUser, mapRole = defaultMapRole, publicPaths = [], onSessionError = "throw", globalAdmins = [] } = options;
601
601
  let auth = options.auth ?? null;
602
602
  let endpointLogger = console;
@@ -675,14 +675,14 @@ function betterAuthPlugin(options) {
675
675
  return {
676
676
  id: "better-auth",
677
677
  name: "Better Auth",
678
- schema: BETTER_AUTH_SCHEMA,
678
+ schema: USER_AUTH_SCHEMA,
679
679
  requiredTables: [
680
680
  "user",
681
681
  "session",
682
682
  "account",
683
683
  "verification"
684
684
  ],
685
- setupInstructions: "Run `npx invect generate` to add the better-auth tables to your schema, then `npx drizzle-kit push` (or `npx invect migrate`) to apply.",
685
+ setupInstructions: "Run `npx invect-cli generate` to add the better-auth tables to your schema, then `npx drizzle-kit push` (or `npx invect-cli migrate`) to apply.",
686
686
  endpoints: [
687
687
  {
688
688
  method: "GET",
@@ -1078,7 +1078,7 @@ function betterAuthPlugin(options) {
1078
1078
  betterAuthBasePath = auth.options?.basePath ?? "/api/auth";
1079
1079
  pluginContext.logger.info(`Better Auth plugin initialized (prefix: ${prefix}, basePath: ${betterAuthBasePath})`);
1080
1080
  if (globalAdmins.length === 0) {
1081
- pluginContext.logger.debug("No global admins configured. Pass `globalAdmins` to betterAuthPlugin(...) to seed admin access.");
1081
+ pluginContext.logger.debug("No global admins configured. Pass `globalAdmins` to userAuth(...) to seed admin access.");
1082
1082
  return;
1083
1083
  }
1084
1084
  for (const configuredAdmin of globalAdmins) {
@@ -1160,7 +1160,7 @@ function isBetterAuthRoute(path, prefix, basePath) {
1160
1160
  return path.startsWith(`/plugins/${prefix}${basePath}`);
1161
1161
  }
1162
1162
  //#endregion
1163
- exports.BETTER_AUTH_SCHEMA = BETTER_AUTH_SCHEMA;
1164
- exports.betterAuthPlugin = betterAuthPlugin;
1163
+ exports.USER_AUTH_SCHEMA = USER_AUTH_SCHEMA;
1164
+ exports.userAuth = userAuth;
1165
1165
 
1166
1166
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["AUTH_DEFAULT_ROLE","isAuthVisibleRole","AUTH_ADMIN_ROLE","AUTH_VISIBLE_ROLES","isAuthAssignableRole","AUTH_ASSIGNABLE_ROLES"],"sources":["../../src/backend/plugin.ts"],"sourcesContent":["import type {\n InvectPlugin,\n InvectIdentity,\n InvectRole,\n InvectPermission,\n InvectPluginSchema,\n} from '@invect/core';\nimport type {\n BetterAuthPluginOptions,\n BetterAuthContext,\n BetterAuthUser,\n BetterAuthSession,\n BetterAuthInstance,\n} from './types';\nimport {\n AUTH_ADMIN_ROLE,\n AUTH_ASSIGNABLE_ROLES,\n AUTH_DEFAULT_ROLE,\n AUTH_VISIBLE_ROLES,\n isAuthAssignableRole,\n isAuthVisibleRole,\n} from '../shared/roles';\n\ntype PluginLoggerLike = {\n error: (message: string, meta?: unknown) => void;\n info?: (message: string, meta?: unknown) => void;\n debug?: (message: string, meta?: unknown) => void;\n warn?: (message: string, meta?: unknown) => void;\n};\n\ntype BetterAuthApiUser = {\n id?: string;\n email?: string | null;\n name?: string | null;\n role?: string | null;\n [key: string]: unknown;\n};\n\ntype BetterAuthApiUserResult = {\n user?: BetterAuthApiUser | null;\n};\n\ntype BetterAuthHeadersQueryMethod<TResult> = (args: {\n headers: Headers;\n query: Record<string, string>;\n}) => Promise<TResult>;\n\ntype BetterAuthHeadersBodyMethod<TResult> = (args: {\n headers: Headers;\n body: Record<string, unknown>;\n}) => Promise<TResult>;\n\ntype BetterAuthHeadersBodyParamsMethod<TResult> = (args: {\n headers: Headers;\n body: Record<string, unknown>;\n params: Record<string, string>;\n}) => Promise<TResult>;\n\ntype BetterAuthBodyMethod<TResult> = (args: { body: Record<string, unknown> }) => Promise<TResult>;\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PREFIX = 'auth';\n\n/**\n * Default role mapping: keep admin/RBAC roles aligned and fall back to default.\n */\nfunction defaultMapRole(role: string | null | undefined): InvectRole {\n if (!role) {\n return AUTH_DEFAULT_ROLE;\n }\n if (role === 'viewer' || role === 'readonly') {\n return 'viewer';\n }\n if (isAuthVisibleRole(role)) {\n return role;\n }\n return AUTH_DEFAULT_ROLE;\n}\n\n/**\n * Default user → identity mapping.\n */\nfunction defaultMapUser(\n user: BetterAuthUser,\n _session: BetterAuthSession,\n mapRole: (role: string | null | undefined) => InvectRole,\n): InvectIdentity {\n const resolvedRole = mapRole(user.role);\n\n return {\n id: user.id,\n name: user.name ?? user.email ?? undefined,\n role: resolvedRole,\n permissions: resolvedRole === AUTH_ADMIN_ROLE ? ['admin:*'] : undefined,\n resourceAccess:\n resolvedRole === AUTH_ADMIN_ROLE\n ? {\n flows: '*',\n credentials: '*',\n }\n : undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Simple in-memory sliding-window rate limiter.\n *\n * Keyed by IP address (or a fallback identifier). Tracks request timestamps\n * per window and rejects requests that exceed the limit with HTTP 429.\n *\n * Only applied to authentication-sensitive endpoints (sign-in, sign-up,\n * password reset) to prevent brute-force attacks. Session reads (GET) are\n * not rate-limited.\n */\nclass RateLimiter {\n private windows = new Map<string, number[]>();\n private readonly maxRequests: number;\n private readonly windowMs: number;\n\n constructor(maxRequests = 10, windowMs = 60_000) {\n this.maxRequests = maxRequests;\n this.windowMs = windowMs;\n }\n\n /**\n * Returns `true` if the request should be rejected (over limit).\n */\n isRateLimited(key: string): { limited: boolean; retryAfterMs?: number } {\n const now = Date.now();\n const windowStart = now - this.windowMs;\n\n let timestamps = this.windows.get(key);\n if (!timestamps) {\n timestamps = [];\n this.windows.set(key, timestamps);\n }\n\n // Prune expired entries\n const valid = timestamps.filter((t) => t > windowStart);\n this.windows.set(key, valid);\n\n if (valid.length >= this.maxRequests) {\n const oldestInWindow = valid[0] ?? now;\n const retryAfterMs = oldestInWindow + this.windowMs - now;\n return { limited: true, retryAfterMs: Math.max(retryAfterMs, 1000) };\n }\n\n valid.push(now);\n return { limited: false };\n }\n\n /** Periodic cleanup of stale keys to prevent memory leaks. */\n cleanup(): void {\n const now = Date.now();\n for (const [key, timestamps] of this.windows) {\n const valid = timestamps.filter((t) => t > now - this.windowMs);\n if (valid.length === 0) {\n this.windows.delete(key);\n } else {\n this.windows.set(key, valid);\n }\n }\n }\n}\n\n/** Auth-sensitive path segments that should be rate-limited. */\nconst RATE_LIMITED_AUTH_PATHS = ['/sign-in/', '/sign-up/', '/forgot-password', '/reset-password'];\n\n/**\n * Convert a Node.js-style `IncomingHttpHeaders` record or a `Headers` instance\n * to a standard `Headers` object for passing to better-auth.\n */\nfunction toHeaders(raw: Record<string, string | undefined> | Headers): Headers {\n if (raw instanceof Headers) {\n return raw;\n }\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(raw)) {\n if (value !== undefined) {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * Resolve the session from a better-auth instance using request headers.\n */\nasync function resolveSession(\n auth: BetterAuthInstance | null,\n headers: Record<string, string | undefined> | Headers,\n): Promise<{ session: BetterAuthSession; user: BetterAuthUser } | null> {\n if (!auth) {\n return null;\n }\n\n const h = toHeaders(headers);\n\n try {\n // eslint-disable-next-line no-console\n console.log('[auth-debug] resolveSession: cookie header =', h.get('cookie')?.slice(0, 80));\n const result = await auth.api.getSession({\n headers: h,\n });\n // eslint-disable-next-line no-console\n console.log(\n '[auth-debug] resolveSession: result keys =',\n result ? Object.keys(result) : 'null',\n );\n if (result?.session && result?.user) {\n return {\n session: result.session,\n user: result.user,\n };\n }\n return null;\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[auth-debug] resolveSession threw:', (err as Error)?.message ?? err);\n return null;\n }\n}\n\nasync function callBetterAuthHandler(\n auth: BetterAuthInstance | null,\n request: Request,\n path: string,\n init?: {\n method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n query?: Record<string, string | undefined>;\n body?: Record<string, unknown>;\n },\n): Promise<{ status: number; body: unknown } | null> {\n if (!auth) {\n return null;\n }\n\n const basePath = auth.options?.basePath ?? '/api/auth';\n const targetUrl = new URL(`${basePath}${path}`, request.url);\n for (const [key, value] of Object.entries(init?.query ?? {})) {\n if (value !== undefined) {\n targetUrl.searchParams.set(key, value);\n }\n }\n\n const headers = new Headers(request.headers);\n const hasBody = init?.body !== undefined;\n if (hasBody && !headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n\n const authRequest = new Request(targetUrl.toString(), {\n method: init?.method ?? 'GET',\n headers,\n body: hasBody ? JSON.stringify(init?.body) : undefined,\n });\n\n const response = await auth.handler(authRequest);\n const text = await response.text();\n\n if (!text) {\n return { status: response.status, body: null };\n }\n\n try {\n return { status: response.status, body: JSON.parse(text) };\n } catch {\n return { status: response.status, body: text };\n }\n}\n\nasync function getAuthContext(auth: BetterAuthInstance | null): Promise<BetterAuthContext | null> {\n if (!auth) {\n return null;\n }\n try {\n return (await auth.$context) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction isBetterAuthUser(value: unknown): value is BetterAuthUser {\n return !!value && typeof value === 'object' && 'id' in value && typeof value.id === 'string';\n}\n\nfunction unwrapFoundUser(\n result: BetterAuthUser | { user?: BetterAuthUser | null } | null | undefined,\n): BetterAuthUser | null {\n if (!result) {\n return null;\n }\n\n if (typeof result === 'object' && 'user' in result) {\n const nestedUser = result.user;\n if (isBetterAuthUser(nestedUser)) {\n return nestedUser;\n }\n\n return null;\n }\n\n if (isBetterAuthUser(result)) {\n return result;\n }\n\n return null;\n}\n\nfunction toAuthApiErrorResponse(\n fallbackError: string,\n error: unknown,\n): { status: number; body: Record<string, unknown> } {\n if (error instanceof Response) {\n return {\n status: error.status || 500,\n body: { error: fallbackError, message: error.statusText || fallbackError },\n };\n }\n\n const status =\n error &&\n typeof error === 'object' &&\n 'status' in error &&\n typeof (error as { status?: unknown }).status === 'number'\n ? ((error as { status: number }).status ?? 500)\n : error &&\n typeof error === 'object' &&\n 'statusCode' in error &&\n typeof (error as { statusCode?: unknown }).statusCode === 'number'\n ? ((error as { statusCode: number }).statusCode ?? 500)\n : 500;\n\n const message =\n error &&\n typeof error === 'object' &&\n 'message' in error &&\n typeof (error as { message?: unknown }).message === 'string'\n ? (error as { message: string }).message || fallbackError\n : fallbackError;\n\n const code =\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n typeof (error as { code?: unknown }).code === 'string'\n ? (error as { code: string }).code\n : undefined;\n\n return {\n status,\n body: {\n error: fallbackError,\n message,\n ...(code ? { code } : {}),\n },\n };\n}\n\nfunction sanitizeForLogging(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeForLogging(item));\n }\n\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, nestedValue]) => {\n if (/password|token|secret/i.test(key)) {\n return [key, '[REDACTED]'];\n }\n\n return [key, sanitizeForLogging(nestedValue)];\n }),\n );\n }\n\n return value;\n}\n\nfunction getErrorLogDetails(error: unknown): Record<string, unknown> {\n if (error instanceof Response) {\n return {\n type: 'Response',\n status: error.status,\n statusText: error.statusText,\n };\n }\n\n if (error instanceof Error) {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n ...(error && typeof error === 'object' && 'cause' in error\n ? { cause: sanitizeForLogging((error as Error & { cause?: unknown }).cause) }\n : {}),\n ...(error && typeof error === 'object' && 'code' in error\n ? { code: (error as Error & { code?: unknown }).code }\n : {}),\n ...(error && typeof error === 'object' && 'status' in error\n ? { status: (error as Error & { status?: unknown }).status }\n : {}),\n ...(error && typeof error === 'object' && 'statusCode' in error\n ? { statusCode: (error as Error & { statusCode?: unknown }).statusCode }\n : {}),\n };\n }\n\n if (error && typeof error === 'object') {\n return sanitizeForLogging(error) as Record<string, unknown>;\n }\n\n return { value: error };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract schema — defines the tables better-auth requires\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract schema for better-auth's database tables.\n *\n * These definitions allow the Invect CLI (`npx invect generate`) to include\n * the better-auth tables when generating Drizzle/Prisma schema files.\n *\n * The shapes match better-auth's default table structure. If your better-auth\n * config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),\n * you can extend these in your own config.\n */\nexport const BETTER_AUTH_SCHEMA: InvectPluginSchema = {\n user: {\n tableName: 'user',\n order: 1,\n fields: {\n id: { type: 'string', primaryKey: true },\n name: { type: 'string', required: true },\n email: { type: 'string', required: true, unique: true },\n emailVerified: { type: 'boolean', required: true, defaultValue: false },\n image: { type: 'string', required: false },\n role: { type: 'string', required: false, defaultValue: AUTH_DEFAULT_ROLE },\n banned: { type: 'boolean', required: false, defaultValue: false },\n banReason: { type: 'string', required: false },\n banExpires: { type: 'date', required: false },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n },\n },\n\n session: {\n tableName: 'session',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n expiresAt: { type: 'date', required: true },\n token: { type: 'string', required: true, unique: true },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n ipAddress: { type: 'string', required: false },\n userAgent: { type: 'string', required: false },\n impersonatedBy: { type: 'string', required: false },\n userId: {\n type: 'string',\n required: true,\n references: { table: 'user', field: 'id', onDelete: 'cascade' },\n },\n },\n },\n\n account: {\n tableName: 'account',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n accountId: { type: 'string', required: true },\n providerId: { type: 'string', required: true },\n userId: {\n type: 'string',\n required: true,\n references: { table: 'user', field: 'id', onDelete: 'cascade' },\n },\n accessToken: { type: 'string', required: false },\n refreshToken: { type: 'string', required: false },\n idToken: { type: 'string', required: false },\n accessTokenExpiresAt: { type: 'date', required: false },\n refreshTokenExpiresAt: { type: 'date', required: false },\n scope: { type: 'string', required: false },\n password: { type: 'string', required: false },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n },\n },\n\n verification: {\n tableName: 'verification',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n identifier: { type: 'string', required: true },\n value: { type: 'string', required: true },\n expiresAt: { type: 'date', required: true },\n createdAt: { type: 'date', required: false },\n updatedAt: { type: 'date', required: false },\n },\n },\n};\n\n// ---------------------------------------------------------------------------\n// Internal better-auth instance creation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a better-auth instance internally using Invect's database config.\n *\n * Dynamically imports `better-auth` (a required peer dependency) and creates\n * a fully-configured instance with email/password auth, the admin plugin,\n * and session caching.\n *\n * Database resolution order:\n * 1. Explicit `options.database` (any value `betterAuth({ database })` accepts)\n * 2. Auto-created client from Invect's `baseDatabaseConfig.connectionString`\n */\nasync function createInternalBetterAuth(\n invectConfig: Record<string, unknown>,\n options: BetterAuthPluginOptions,\n logger: PluginLoggerLike,\n): Promise<BetterAuthInstance> {\n // 1. Dynamic-import better-auth (peer dependency)\n let betterAuthFn: (config: Record<string, unknown>) => unknown;\n let adminPlugin: (config?: Record<string, unknown>) => unknown;\n\n try {\n const betterAuthModule = await import('better-auth');\n betterAuthFn = betterAuthModule.betterAuth;\n } catch {\n throw new Error(\n 'Could not import \"better-auth\". It is a required peer dependency of @invect/user-auth. ' +\n 'Install it with: npm install better-auth',\n );\n }\n\n try {\n const pluginsModule = await import('better-auth/plugins');\n adminPlugin = pluginsModule.admin;\n } catch {\n throw new Error(\n 'Could not import \"better-auth/plugins\". Ensure better-auth is properly installed.',\n );\n }\n\n // 2. Resolve database\n let database: unknown = options.database;\n\n if (!database) {\n const dbConfig = invectConfig.baseDatabaseConfig as\n | { type?: string; connectionString?: string }\n | undefined;\n\n if (!dbConfig?.connectionString) {\n throw new Error(\n 'Cannot create internal better-auth instance: no database configuration found. ' +\n 'Either provide `auth` (a better-auth instance), `database`, or ensure ' +\n 'Invect baseDatabaseConfig has a connectionString.',\n );\n }\n\n const connStr = dbConfig.connectionString;\n const dbType = (dbConfig.type ?? 'sqlite').toLowerCase();\n\n if (dbType === 'sqlite') {\n database = await createSQLiteClient(connStr, logger);\n } else if (dbType === 'pg' || dbType === 'postgresql') {\n database = await createPostgresPool(connStr);\n } else if (dbType === 'mysql') {\n database = await createMySQLPool(connStr);\n } else {\n throw new Error(\n `Unsupported database type for internal better-auth: \"${dbType}\". ` +\n 'Supported: sqlite, pg, mysql. Alternatively, provide your own better-auth instance via `auth`.',\n );\n }\n }\n\n // 3. Resolve base URL\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n `http://localhost:${process.env.PORT ?? '3000'}`;\n\n // 4. Resolve trusted origins\n const configuredOrigins = options.trustedOrigins;\n const trustedOrigins =\n configuredOrigins ??\n ((request: Request) => {\n const trusted = new Set<string>([baseURL, 'http://localhost:5173', 'http://localhost:5174']);\n try {\n if (request) {\n trusted.add(new URL(request.url).origin);\n }\n } catch {\n // Ignore malformed URLs\n }\n return Array.from(trusted);\n });\n\n // 5. Build passthrough config from betterAuthOptions\n const passthrough = options.betterAuthOptions ?? {};\n\n const emailAndPassword = {\n enabled: true,\n ...passthrough.emailAndPassword,\n };\n\n const session = {\n cookieCache: { enabled: true, maxAge: 5 * 60 },\n ...passthrough.session,\n // Merge cookieCache if both exist\n ...(passthrough.session?.cookieCache\n ? {\n cookieCache: {\n enabled: true,\n maxAge: 5 * 60,\n ...passthrough.session.cookieCache,\n },\n }\n : {}),\n };\n\n // 6. Create the instance\n logger.info?.('Creating internal better-auth instance');\n\n const instance = betterAuthFn({\n baseURL,\n database,\n emailAndPassword,\n plugins: [adminPlugin({ defaultRole: AUTH_DEFAULT_ROLE, adminRoles: [AUTH_ADMIN_ROLE] })],\n session,\n trustedOrigins,\n // Spread optional passthrough fields\n ...(passthrough.socialProviders ? { socialProviders: passthrough.socialProviders } : {}),\n ...(passthrough.account ? { account: passthrough.account } : {}),\n ...(passthrough.rateLimit ? { rateLimit: passthrough.rateLimit } : {}),\n ...(passthrough.advanced ? { advanced: passthrough.advanced } : {}),\n ...(passthrough.databaseHooks ? { databaseHooks: passthrough.databaseHooks } : {}),\n ...(passthrough.hooks ? { hooks: passthrough.hooks } : {}),\n ...(passthrough.disabledPaths ? { disabledPaths: passthrough.disabledPaths } : {}),\n ...(passthrough.secret ? { secret: passthrough.secret } : {}),\n ...(passthrough.secrets ? { secrets: passthrough.secrets } : {}),\n });\n\n return instance as BetterAuthInstance;\n}\n\n/** Create a SQLite client using better-sqlite3. */\nasync function createSQLiteClient(connectionString: string, logger: PluginLoggerLike) {\n try {\n const { default: Database } = await import('better-sqlite3');\n const { Kysely, SqliteDialect, CamelCasePlugin } = await import('kysely');\n logger.debug?.(`Using better-sqlite3 for internal better-auth database`);\n // Strip file: prefix if present\n let dbPath = connectionString.replace(/^file:/, '');\n if (dbPath === '') {\n dbPath = ':memory:';\n }\n const sqliteDb = new Database(dbPath);\n // Return in Better Auth's { db, type } format so it uses our Kysely instance\n // directly, preserving CamelCasePlugin for snake_case column mapping.\n const kyselyDb = new Kysely({\n dialect: new SqliteDialect({ database: sqliteDb }),\n plugins: [new CamelCasePlugin()],\n });\n return { db: kyselyDb, type: 'sqlite' as const };\n } catch (err) {\n if (err instanceof Error && err.message.includes('better-sqlite3')) {\n throw new Error(\n 'Cannot create SQLite database for internal better-auth: ' +\n 'install better-sqlite3 (npm install better-sqlite3). ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n throw err;\n }\n}\n\n/** Create a PostgreSQL pool from a connection string. */\nasync function createPostgresPool(connectionString: string) {\n try {\n const { Pool } = await import('pg');\n return new Pool({ connectionString });\n } catch {\n throw new Error(\n 'Cannot create PostgreSQL pool for internal better-auth: ' +\n 'install the \"pg\" package. ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n}\n\n/** Create a MySQL pool from a connection string. */\nasync function createMySQLPool(connectionString: string) {\n try {\n const mysql = await import('mysql2/promise');\n return mysql.createPool(connectionString);\n } catch {\n throw new Error(\n 'Cannot create MySQL pool for internal better-auth: ' +\n 'install the \"mysql2\" package. ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an Invect plugin that wraps a better-auth instance.\n *\n * This plugin:\n *\n * 1. **Proxies better-auth routes** — All of better-auth's HTTP endpoints\n * (sign-in, sign-up, sign-out, OAuth callbacks, session, etc.) are mounted\n * under the plugin endpoint space at `/plugins/auth/api/auth/*` (configurable).\n *\n * 2. **Resolves sessions → identities** — On every Invect API request, the\n * `onRequest` hook reads the session cookie / bearer token via\n * `auth.api.getSession()` and populates `InvectIdentity`.\n *\n * 3. **Handles authorization** — The `onAuthorize` hook lets better-auth's\n * session decide whether a request is allowed.\n *\n * @example\n * ```ts\n * // Simple: let the plugin manage better-auth internally\n * import { betterAuthPlugin } from '@invect/user-auth';\n *\n * app.use('/invect', createInvectRouter({\n * databaseUrl: 'file:./dev.db',\n * plugins: [betterAuthPlugin({\n * globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],\n * })],\n * }));\n * ```\n *\n * @example\n * ```ts\n * // Advanced: provide your own better-auth instance\n * import { betterAuth } from 'better-auth';\n * import { betterAuthPlugin } from '@invect/user-auth';\n *\n * const auth = betterAuth({\n * database: { ... },\n * emailAndPassword: { enabled: true },\n * // ... your better-auth config\n * });\n *\n * app.use('/invect', createInvectRouter({\n * databaseUrl: 'file:./dev.db',\n * plugins: [betterAuthPlugin({ auth })],\n * }));\n * ```\n */\nexport function betterAuthPlugin(options: BetterAuthPluginOptions): InvectPlugin {\n const {\n prefix = DEFAULT_PREFIX,\n mapUser: customMapUser,\n mapRole = defaultMapRole,\n publicPaths = [],\n onSessionError = 'throw',\n globalAdmins = [],\n } = options;\n\n // Mutable — assigned in `init` when no external instance was provided.\n let auth: BetterAuthInstance | null = options.auth ?? null;\n\n let endpointLogger: PluginLoggerLike = console;\n\n // Determine better-auth's basePath (defaults to /api/auth).\n // Computed lazily since `auth` may not exist until `init`.\n let betterAuthBasePath = '/api/auth';\n\n /**\n * Resolve an identity from a request's headers.\n */\n async function getIdentityFromHeaders(\n headers: Record<string, string | undefined>,\n ): Promise<InvectIdentity | null> {\n if (!auth) {\n return null;\n }\n\n const result = await resolveSession(auth, headers);\n if (!result) {\n return null;\n }\n\n if (customMapUser) {\n return customMapUser(result.user, result.session);\n }\n\n return defaultMapUser(result.user, result.session, mapRole);\n }\n\n async function getIdentityFromRequest(request: Request): Promise<InvectIdentity | null> {\n if (!auth) {\n return null;\n }\n\n const result = await callBetterAuthHandler(auth, request, '/get-session');\n const body = result?.body as { session?: BetterAuthSession; user?: BetterAuthUser } | null;\n if (!body?.session || !body?.user) {\n return null;\n }\n\n if (customMapUser) {\n return customMapUser(body.user, body.session);\n }\n\n return defaultMapUser(body.user, body.session, mapRole);\n }\n\n async function resolveEndpointIdentity(ctx: {\n identity: InvectIdentity | null;\n request: Request;\n }): Promise<InvectIdentity | null> {\n if (ctx.identity) {\n return ctx.identity;\n }\n\n return getIdentityFromRequest(ctx.request);\n }\n\n // Rate limiter for auth-sensitive endpoints (sign-in, sign-up, password reset)\n // 10 attempts per IP per 60 seconds by default\n const authRateLimiter = new RateLimiter(10, 60_000);\n\n // Periodic cleanup every 5 minutes to prevent memory leaks\n const cleanupInterval = setInterval(() => authRateLimiter.cleanup(), 5 * 60_000);\n cleanupInterval.unref?.(); // Don't keep the process alive\n\n // ----- Build plugin endpoint list -----\n // We create a catch-all endpoint that proxies everything under the prefix\n // to better-auth's handler. This covers all auth routes: sign-in, sign-up,\n // OAuth callbacks, session management, etc.\n\n const proxyMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;\n\n const endpoints = proxyMethods.map((method) => ({\n method: method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: `/${prefix}/*` as const,\n isPublic: true, // Auth routes must be accessible without existing session\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n request: Request;\n }) => {\n // Build a new Request for better-auth's handler.\n // The incoming path is: /plugins/auth/api/auth/sign-in/email (for example)\n // We need to reconstruct the URL that better-auth expects.\n const incomingUrl = new URL(ctx.request.url);\n endpointLogger.debug?.(`[auth-proxy] ${method} ${incomingUrl.pathname}`);\n\n // Strip the /plugins/<prefix> prefix from the pathname to get\n // the better-auth-relative path\n const pluginPrefixPattern = `/plugins/${prefix}`;\n let authPath = incomingUrl.pathname;\n const prefixIdx = authPath.indexOf(pluginPrefixPattern);\n if (prefixIdx !== -1) {\n authPath = authPath.slice(prefixIdx + pluginPrefixPattern.length);\n }\n if (!authPath.startsWith('/')) {\n authPath = '/' + authPath;\n }\n\n // ── Rate-limit auth-sensitive POST endpoints ──────────\n if (method === 'POST' && RATE_LIMITED_AUTH_PATHS.some((p) => authPath.includes(p))) {\n const clientIp =\n ctx.headers['x-forwarded-for']?.split(',')[0]?.trim() ||\n ctx.headers['x-real-ip'] ||\n 'unknown';\n const { limited, retryAfterMs } = authRateLimiter.isRateLimited(clientIp);\n if (limited) {\n return new Response(\n JSON.stringify({\n error: 'Too Many Requests',\n message: 'Too many authentication attempts. Please try again later.',\n }),\n {\n status: 429,\n headers: {\n 'content-type': 'application/json',\n 'retry-after': String(Math.ceil((retryAfterMs ?? 60_000) / 1000)),\n },\n },\n );\n }\n }\n\n // Construct the URL that better-auth expects\n const authUrl = new URL(`${incomingUrl.origin}${authPath}${incomingUrl.search}`);\n endpointLogger.debug?.(\n `[auth-proxy] Forwarding to better-auth: ${method} ${authUrl.pathname}`,\n );\n\n // Clone/forward the request to better-auth\n const authRequest = new Request(authUrl.toString(), {\n method: ctx.request.method,\n headers: ctx.request.headers,\n body: method !== 'GET' && method !== 'DELETE' ? ctx.request.body : undefined,\n // @ts-expect-error - duplex is needed for streaming bodies\n duplex: method !== 'GET' && method !== 'DELETE' ? 'half' : undefined,\n });\n\n // Let better-auth handle it\n const response = await auth!.handler(authRequest);\n endpointLogger.debug?.(`[auth-proxy] Response: ${response.status} ${response.statusText}`, {\n setCookie: response.headers.get('set-cookie') ? 'present' : 'absent',\n contentType: response.headers.get('content-type'),\n });\n return response;\n },\n }));\n\n // ----- Build the plugin -----\n return {\n id: 'better-auth',\n name: 'Better Auth',\n\n // Abstract schema for better-auth tables — the CLI reads this to generate\n // Drizzle/Prisma schema files that include auth tables automatically.\n schema: BETTER_AUTH_SCHEMA,\n\n // Also declare requiredTables for the startup existence check.\n requiredTables: ['user', 'session', 'account', 'verification'],\n setupInstructions:\n 'Run `npx invect generate` to add the better-auth tables to your schema, ' +\n 'then `npx drizzle-kit push` (or `npx invect migrate`) to apply.',\n\n endpoints: [\n // ── Auth Info (specific routes must come BEFORE the catch-all proxy) ───\n\n {\n method: 'GET' as const,\n path: `/${prefix}/me`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n core: {\n getPermissions: (identity: InvectIdentity | null) => string[];\n getResolvedRole: (identity: InvectIdentity) => string | null;\n };\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n const permissions = ctx.core.getPermissions(identity);\n const resolvedRole = identity ? ctx.core.getResolvedRole(identity) : null;\n\n return {\n status: 200,\n body: {\n identity: identity\n ? {\n id: identity.id,\n name: identity.name,\n role: identity.role,\n resolvedRole,\n }\n : null,\n permissions,\n isAuthenticated: !!identity,\n },\n };\n },\n },\n\n {\n method: 'GET' as const,\n path: `/${prefix}/roles`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n core: { getAvailableRoles: () => unknown };\n }) => {\n const roles = ctx.core.getAvailableRoles() as Array<{\n role: string;\n permissions: string[];\n }>;\n const missingRoles = AUTH_VISIBLE_ROLES.filter(\n (role) => !roles.some((entry) => entry.role === role),\n ).map((role) => ({ role, permissions: [] }));\n return { status: 200, body: { roles: [...roles, ...missingRoles] } };\n },\n },\n\n // ── User Management (admin-only) ──────────────────────\n\n {\n method: 'GET' as const,\n path: `/${prefix}/users`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n // Try better-auth's admin listUsers API first\n if (typeof api.listUsers === 'function') {\n const listUsers = api.listUsers as BetterAuthHeadersQueryMethod<\n { users?: unknown[] } | unknown[] | null\n >;\n const result = await listUsers({\n headers,\n query: {\n limit: ctx.query.limit ?? '100',\n offset: ctx.query.offset ?? '0',\n },\n });\n const users = Array.isArray(result) ? result : (result?.users ?? []);\n return { status: 200, body: { users } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/list-users',\n {\n method: 'GET',\n query: {\n limit: ctx.query.limit ?? '100',\n offset: ctx.query.offset ?? '0',\n },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return {\n status: 200,\n body: fallbackResult.body as Record<string, unknown>,\n };\n }\n\n // Fallback: query the user table directly via an internal request\n // to better-auth's get-session endpoint for the current user\n return {\n status: 200,\n body: {\n users: [],\n message:\n 'User listing requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to list users', {\n identity: sanitizeForLogging(identity),\n query: sanitizeForLogging(ctx.query),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to list users', err);\n }\n },\n },\n\n {\n method: 'POST' as const,\n path: `/${prefix}/users`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { email, password, name, role } = ctx.body as {\n email?: string;\n password?: string;\n name?: string;\n role?: string;\n };\n\n if (!email || !password) {\n return {\n status: 400,\n body: { error: 'email and password are required' },\n };\n }\n\n if (role !== undefined && !isAuthAssignableRole(role)) {\n return {\n status: 400,\n body: {\n error: 'role must be one of: ' + AUTH_ASSIGNABLE_ROLES.join(', '),\n },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n let result: BetterAuthApiUserResult | null = null;\n\n if (typeof api.createUser === 'function') {\n const createUser =\n api.createUser as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await createUser({\n headers,\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n });\n } else if (typeof api.signUpEmail === 'function') {\n const signUpEmail =\n api.signUpEmail as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await signUpEmail({\n headers,\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n });\n } else {\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/create-user',\n {\n method: 'POST',\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n result = fallbackResult.body as BetterAuthApiUserResult;\n }\n }\n\n if (\n !result &&\n typeof api.createUser !== 'function' &&\n typeof api.signUpEmail !== 'function'\n ) {\n return {\n status: 500,\n body: { error: 'Auth API does not support user creation' },\n };\n }\n\n if (!result?.user) {\n return {\n status: 400,\n body: {\n error: 'Failed to create user',\n message: 'User may already exist',\n },\n };\n }\n\n return {\n status: 201,\n body: {\n user: {\n id: result.user.id,\n email: result.user.email,\n name: result.user.name,\n role: result.user.role,\n },\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to create user', {\n identity: sanitizeForLogging(identity),\n body: sanitizeForLogging({ email, name, role }),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to create user', err);\n }\n },\n },\n\n {\n method: 'PATCH' as const,\n path: `/${prefix}/users/:userId/role`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { userId } = ctx.params;\n const { role } = ctx.body as { role?: string };\n\n if (!isAuthAssignableRole(role)) {\n return {\n status: 400,\n body: {\n error: 'role must be one of: ' + AUTH_ASSIGNABLE_ROLES.join(', '),\n },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n // Try better-auth admin API for updating user\n if (typeof api.setRole === 'function') {\n const setRole = api.setRole as BetterAuthHeadersBodyMethod<unknown>;\n await setRole({\n headers,\n body: { userId, role },\n });\n return { status: 200, body: { success: true, userId, role } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/set-role',\n {\n method: 'POST',\n body: { userId, role },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return { status: 200, body: { success: true, userId, role } };\n }\n\n // Fallback: try updateUser API\n if (typeof api.updateUser === 'function') {\n const updateUser = api.updateUser as BetterAuthHeadersBodyParamsMethod<unknown>;\n await updateUser({\n headers,\n body: { role },\n params: { id: userId },\n });\n return { status: 200, body: { success: true, userId, role } };\n }\n\n return {\n status: 501,\n body: {\n error:\n 'Role update requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to update role', {\n identity: sanitizeForLogging(identity),\n params: sanitizeForLogging(ctx.params),\n body: sanitizeForLogging({ role }),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to update role', err);\n }\n },\n },\n\n {\n method: 'DELETE' as const,\n path: `/${prefix}/users/:userId`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { userId } = ctx.params;\n\n // Prevent deleting yourself\n if (identity.id === userId) {\n return {\n status: 400,\n body: { error: 'Cannot delete your own account' },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n if (typeof api.removeUser === 'function') {\n const removeUser = api.removeUser as BetterAuthHeadersBodyMethod<unknown>;\n await removeUser({\n headers,\n body: { userId },\n });\n return { status: 200, body: { success: true, userId } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/remove-user',\n {\n method: 'POST',\n body: { userId },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return { status: 200, body: { success: true, userId } };\n }\n\n // Try deleteUser\n if (typeof api.deleteUser === 'function') {\n const deleteUser = api.deleteUser as BetterAuthHeadersBodyMethod<unknown>;\n await deleteUser({\n headers,\n body: { userId },\n });\n return { status: 200, body: { success: true, userId } };\n }\n\n return {\n status: 501,\n body: {\n error:\n 'User deletion requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to delete user', {\n identity: sanitizeForLogging(identity),\n params: sanitizeForLogging(ctx.params),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to delete user', err);\n }\n },\n },\n\n // ── Better-Auth proxy catch-all (must come LAST so specific routes above win) ──\n ...endpoints,\n ],\n\n hooks: {\n /**\n * onRequest: Intercept incoming requests to resolve better-auth sessions.\n *\n * - Better-auth proxy routes are passed through untouched.\n * - For all other routes, we resolve the session. If no session exists\n * and `onSessionError === 'throw'`, we short-circuit with a 401.\n */\n onRequest: async (\n request: Request,\n context: { path: string; method: string; identity: InvectIdentity | null },\n ) => {\n // Skip session resolution for better-auth proxy routes\n if (isBetterAuthRoute(context.path, prefix, betterAuthBasePath)) {\n return; // Let the proxy endpoint handle it\n }\n\n // Skip for public paths\n if (publicPaths.some((p) => context.path.startsWith(p))) {\n return;\n }\n\n // Resolve session from request headers\n const headersRecord: Record<string, string | undefined> = {};\n request.headers.forEach((value, key) => {\n headersRecord[key] = value;\n });\n\n endpointLogger.debug?.(`[auth-onRequest] ${context.method} ${context.path}`, {\n hasCookie: !!headersRecord['cookie'],\n hasAuth: !!headersRecord['authorization'],\n });\n\n const identity = await getIdentityFromHeaders(headersRecord);\n\n endpointLogger.debug?.(`[auth-onRequest] Identity resolved:`, {\n authenticated: !!identity,\n userId: identity?.id,\n role: identity?.role,\n });\n\n // Write identity back so the framework adapter has it available.\n context.identity = identity;\n\n if (!identity && onSessionError === 'throw') {\n return {\n response: new Response(\n JSON.stringify({\n error: 'Unauthorized',\n message: 'Valid session required. Sign in via better-auth.',\n }),\n {\n status: 401,\n headers: { 'content-type': 'application/json' },\n },\n ),\n };\n }\n\n // Return void — identity is now on context.identity for the caller.\n return;\n },\n\n /**\n * onAuthorize: Baseline authorization guard using better-auth sessions.\n *\n * If the identity is already populated (from onRequest),\n * we defer to downstream authorization hooks. This hook is a fallback\n * for cases where the identity wasn't resolved upstream.\n */\n onAuthorize: async (context: {\n identity: InvectIdentity | null;\n action: InvectPermission;\n resource?: { type: string; id?: string };\n }) => {\n // If an identity is already attached, let downstream authorization proceed\n if (context.identity) {\n return;\n }\n\n // No identity — deny access\n return { allowed: false, reason: 'No valid better-auth session' };\n },\n },\n\n init: async (pluginContext) => {\n endpointLogger = pluginContext.logger;\n\n // ── Create internal better-auth instance if none was provided ────────\n if (!auth) {\n auth = await createInternalBetterAuth(pluginContext.config, options, pluginContext.logger);\n }\n\n betterAuthBasePath = auth.options?.basePath ?? '/api/auth';\n\n pluginContext.logger.info(\n `Better Auth plugin initialized (prefix: ${prefix}, basePath: ${betterAuthBasePath})`,\n );\n\n if (globalAdmins.length === 0) {\n pluginContext.logger.debug(\n 'No global admins configured. Pass `globalAdmins` to betterAuthPlugin(...) to seed admin access.',\n );\n return;\n }\n\n for (const configuredAdmin of globalAdmins) {\n const adminEmail = configuredAdmin.email?.trim();\n const adminPassword = configuredAdmin.pw;\n const adminName = configuredAdmin.name?.trim() || 'Admin';\n\n if (!adminEmail || !adminPassword) {\n pluginContext.logger.debug(\n 'Skipping invalid global admin config: both email and pw are required.',\n );\n continue;\n }\n\n try {\n const authContext = await getAuthContext(auth);\n const existingAdminUser = unwrapFoundUser(\n await authContext?.internalAdapter?.findUserByEmail(adminEmail),\n );\n\n if (existingAdminUser) {\n if (existingAdminUser.role !== 'admin') {\n await authContext?.internalAdapter?.updateUser(existingAdminUser.id, {\n role: 'admin',\n });\n pluginContext.logger.info(`Admin user promoted: ${adminEmail}`);\n } else {\n pluginContext.logger.debug(`Admin user already configured: ${adminEmail}`);\n }\n\n continue;\n }\n\n const api = auth!.api as Record<string, unknown>;\n let result: BetterAuthApiUserResult | null = null;\n\n if (typeof api.createUser === 'function') {\n const createUser =\n api.createUser as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await createUser({\n headers: new Headers(),\n body: {\n email: adminEmail,\n password: adminPassword,\n name: adminName,\n role: 'admin',\n },\n }).catch((err: unknown) => {\n pluginContext.logger.error?.(\n `createUser failed for ${adminEmail}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n });\n } else if (typeof api.signUpEmail === 'function') {\n const signUpEmail = api.signUpEmail as BetterAuthBodyMethod<BetterAuthApiUserResult>;\n result = await signUpEmail({\n body: {\n email: adminEmail,\n password: adminPassword,\n name: adminName,\n },\n }).catch((err: unknown) => {\n pluginContext.logger.error?.(\n `signUpEmail failed for ${adminEmail}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n });\n } else {\n pluginContext.logger.debug(\n `Could not create global admin ${adminEmail}: auth.api.createUser/signUpEmail are unavailable.`,\n );\n continue;\n }\n\n if (result?.user) {\n // Promote the newly created user to admin via internal adapter\n const createdAuthContext = authContext ?? (await getAuthContext(auth));\n const createdAdminUser =\n unwrapFoundUser(\n await createdAuthContext?.internalAdapter?.findUserByEmail(adminEmail),\n ) ?? result.user;\n\n if (createdAdminUser?.id && createdAdminUser.role !== 'admin') {\n await createdAuthContext?.internalAdapter?.updateUser(createdAdminUser.id, {\n role: 'admin',\n });\n }\n\n pluginContext.logger.info(`Admin user created: ${adminEmail}`);\n } else {\n pluginContext.logger.debug(\n `Admin user already exists or could not be created: ${adminEmail}`,\n );\n }\n } catch (seedErr) {\n pluginContext.logger.debug(\n `Could not seed admin user (tables may not exist yet): ${adminEmail} — ${seedErr instanceof Error ? seedErr.message : String(seedErr)}`,\n );\n }\n }\n },\n\n $ERROR_CODES: {\n 'auth:session_expired': {\n message: 'Session has expired. Please sign in again.',\n status: 401,\n },\n 'auth:session_not_found': {\n message: 'No valid session found.',\n status: 401,\n },\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a path is a better-auth proxy route (should skip session checks).\n * Only matches the actual better-auth API proxy routes, not custom plugin endpoints.\n */\nfunction isBetterAuthRoute(path: string, prefix: string, basePath: string): boolean {\n return path.startsWith(`/plugins/${prefix}${basePath}`);\n}\n"],"mappings":";;;AAgEA,MAAM,iBAAiB;;;;AAKvB,SAAS,eAAe,MAA6C;AACnE,KAAI,CAAC,KACH,QAAOA,cAAAA;AAET,KAAI,SAAS,YAAY,SAAS,WAChC,QAAO;AAET,KAAIC,cAAAA,kBAAkB,KAAK,CACzB,QAAO;AAET,QAAOD,cAAAA;;;;;AAMT,SAAS,eACP,MACA,UACA,SACgB;CAChB,MAAM,eAAe,QAAQ,KAAK,KAAK;AAEvC,QAAO;EACL,IAAI,KAAK;EACT,MAAM,KAAK,QAAQ,KAAK,SAAS,KAAA;EACjC,MAAM;EACN,aAAa,iBAAA,UAAmC,CAAC,UAAU,GAAG,KAAA;EAC9D,gBACE,iBAAA,UACI;GACE,OAAO;GACP,aAAa;GACd,GACD,KAAA;EACP;;;;;;;;;;;;AAiBH,IAAM,cAAN,MAAkB;CAChB,0BAAkB,IAAI,KAAuB;CAC7C;CACA;CAEA,YAAY,cAAc,IAAI,WAAW,KAAQ;AAC/C,OAAK,cAAc;AACnB,OAAK,WAAW;;;;;CAMlB,cAAc,KAA0D;EACtE,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,cAAc,MAAM,KAAK;EAE/B,IAAI,aAAa,KAAK,QAAQ,IAAI,IAAI;AACtC,MAAI,CAAC,YAAY;AACf,gBAAa,EAAE;AACf,QAAK,QAAQ,IAAI,KAAK,WAAW;;EAInC,MAAM,QAAQ,WAAW,QAAQ,MAAM,IAAI,YAAY;AACvD,OAAK,QAAQ,IAAI,KAAK,MAAM;AAE5B,MAAI,MAAM,UAAU,KAAK,aAAa;GAEpC,MAAM,gBADiB,MAAM,MAAM,OACG,KAAK,WAAW;AACtD,UAAO;IAAE,SAAS;IAAM,cAAc,KAAK,IAAI,cAAc,IAAK;IAAE;;AAGtE,QAAM,KAAK,IAAI;AACf,SAAO,EAAE,SAAS,OAAO;;;CAI3B,UAAgB;EACd,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,CAAC,KAAK,eAAe,KAAK,SAAS;GAC5C,MAAM,QAAQ,WAAW,QAAQ,MAAM,IAAI,MAAM,KAAK,SAAS;AAC/D,OAAI,MAAM,WAAW,EACnB,MAAK,QAAQ,OAAO,IAAI;OAExB,MAAK,QAAQ,IAAI,KAAK,MAAM;;;;;AAOpC,MAAM,0BAA0B;CAAC;CAAa;CAAa;CAAoB;CAAkB;;;;;AAMjG,SAAS,UAAU,KAA4D;AAC7E,KAAI,eAAe,QACjB,QAAO;CAGT,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,CAC5C,KAAI,UAAU,KAAA,EACZ,SAAQ,IAAI,KAAK,MAAM;AAG3B,QAAO;;;;;AAMT,eAAe,eACb,MACA,SACsE;AACtE,KAAI,CAAC,KACH,QAAO;CAGT,MAAM,IAAI,UAAU,QAAQ;AAE5B,KAAI;AAEF,UAAQ,IAAI,gDAAgD,EAAE,IAAI,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;EAC1F,MAAM,SAAS,MAAM,KAAK,IAAI,WAAW,EACvC,SAAS,GACV,CAAC;AAEF,UAAQ,IACN,8CACA,SAAS,OAAO,KAAK,OAAO,GAAG,OAChC;AACD,MAAI,QAAQ,WAAW,QAAQ,KAC7B,QAAO;GACL,SAAS,OAAO;GAChB,MAAM,OAAO;GACd;AAEH,SAAO;UACA,KAAK;AAEZ,UAAQ,MAAM,sCAAuC,KAAe,WAAW,IAAI;AACnF,SAAO;;;AAIX,eAAe,sBACb,MACA,SACA,MACA,MAKmD;AACnD,KAAI,CAAC,KACH,QAAO;CAGT,MAAM,WAAW,KAAK,SAAS,YAAY;CAC3C,MAAM,YAAY,IAAI,IAAI,GAAG,WAAW,QAAQ,QAAQ,IAAI;AAC5D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,SAAS,EAAE,CAAC,CAC1D,KAAI,UAAU,KAAA,EACZ,WAAU,aAAa,IAAI,KAAK,MAAM;CAI1C,MAAM,UAAU,IAAI,QAAQ,QAAQ,QAAQ;CAC5C,MAAM,UAAU,MAAM,SAAS,KAAA;AAC/B,KAAI,WAAW,CAAC,QAAQ,IAAI,eAAe,CACzC,SAAQ,IAAI,gBAAgB,mBAAmB;CAGjD,MAAM,cAAc,IAAI,QAAQ,UAAU,UAAU,EAAE;EACpD,QAAQ,MAAM,UAAU;EACxB;EACA,MAAM,UAAU,KAAK,UAAU,MAAM,KAAK,GAAG,KAAA;EAC9C,CAAC;CAEF,MAAM,WAAW,MAAM,KAAK,QAAQ,YAAY;CAChD,MAAM,OAAO,MAAM,SAAS,MAAM;AAElC,KAAI,CAAC,KACH,QAAO;EAAE,QAAQ,SAAS;EAAQ,MAAM;EAAM;AAGhD,KAAI;AACF,SAAO;GAAE,QAAQ,SAAS;GAAQ,MAAM,KAAK,MAAM,KAAK;GAAE;SACpD;AACN,SAAO;GAAE,QAAQ,SAAS;GAAQ,MAAM;GAAM;;;AAIlD,eAAe,eAAe,MAAoE;AAChG,KAAI,CAAC,KACH,QAAO;AAET,KAAI;AACF,SAAQ,MAAM,KAAK,YAAa;SAC1B;AACN,SAAO;;;AAIX,SAAS,iBAAiB,OAAyC;AACjE,QAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,QAAQ,SAAS,OAAO,MAAM,OAAO;;AAGtF,SAAS,gBACP,QACuB;AACvB,KAAI,CAAC,OACH,QAAO;AAGT,KAAI,OAAO,WAAW,YAAY,UAAU,QAAQ;EAClD,MAAM,aAAa,OAAO;AAC1B,MAAI,iBAAiB,WAAW,CAC9B,QAAO;AAGT,SAAO;;AAGT,KAAI,iBAAiB,OAAO,CAC1B,QAAO;AAGT,QAAO;;AAGT,SAAS,uBACP,eACA,OACmD;AACnD,KAAI,iBAAiB,SACnB,QAAO;EACL,QAAQ,MAAM,UAAU;EACxB,MAAM;GAAE,OAAO;GAAe,SAAS,MAAM,cAAc;GAAe;EAC3E;CAGH,MAAM,SACJ,SACA,OAAO,UAAU,YACjB,YAAY,SACZ,OAAQ,MAA+B,WAAW,WAC5C,MAA6B,UAAU,MACzC,SACE,OAAO,UAAU,YACjB,gBAAgB,SAChB,OAAQ,MAAmC,eAAe,WACxD,MAAiC,cAAc,MACjD;CAER,MAAM,UACJ,SACA,OAAO,UAAU,YACjB,aAAa,SACb,OAAQ,MAAgC,YAAY,WAC/C,MAA8B,WAAW,gBAC1C;CAEN,MAAM,OACJ,SACA,OAAO,UAAU,YACjB,UAAU,SACV,OAAQ,MAA6B,SAAS,WACzC,MAA2B,OAC5B,KAAA;AAEN,QAAO;EACL;EACA,MAAM;GACJ,OAAO;GACP;GACA,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;GACzB;EACF;;AAGH,SAAS,mBAAmB,OAAyB;AACnD,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,SAAS,mBAAmB,KAAK,CAAC;AAGtD,KAAI,SAAS,OAAO,UAAU,SAC5B,QAAO,OAAO,YACZ,OAAO,QAAQ,MAAM,CAAC,KAAK,CAAC,KAAK,iBAAiB;AAChD,MAAI,yBAAyB,KAAK,IAAI,CACpC,QAAO,CAAC,KAAK,aAAa;AAG5B,SAAO,CAAC,KAAK,mBAAmB,YAAY,CAAC;GAC7C,CACH;AAGH,QAAO;;AAGT,SAAS,mBAAmB,OAAyC;AACnE,KAAI,iBAAiB,SACnB,QAAO;EACL,MAAM;EACN,QAAQ,MAAM;EACd,YAAY,MAAM;EACnB;AAGH,KAAI,iBAAiB,MACnB,QAAO;EACL,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,MAAM;EACb,GAAI,SAAS,OAAO,UAAU,YAAY,WAAW,QACjD,EAAE,OAAO,mBAAoB,MAAsC,MAAM,EAAE,GAC3E,EAAE;EACN,GAAI,SAAS,OAAO,UAAU,YAAY,UAAU,QAChD,EAAE,MAAO,MAAqC,MAAM,GACpD,EAAE;EACN,GAAI,SAAS,OAAO,UAAU,YAAY,YAAY,QAClD,EAAE,QAAS,MAAuC,QAAQ,GAC1D,EAAE;EACN,GAAI,SAAS,OAAO,UAAU,YAAY,gBAAgB,QACtD,EAAE,YAAa,MAA2C,YAAY,GACtE,EAAE;EACP;AAGH,KAAI,SAAS,OAAO,UAAU,SAC5B,QAAO,mBAAmB,MAAM;AAGlC,QAAO,EAAE,OAAO,OAAO;;;;;;;;;;;;AAiBzB,MAAa,qBAAyC;CACpD,MAAM;EACJ,WAAW;EACX,OAAO;EACP,QAAQ;GACN,IAAI;IAAE,MAAM;IAAU,YAAY;IAAM;GACxC,MAAM;IAAE,MAAM;IAAU,UAAU;IAAM;GACxC,OAAO;IAAE,MAAM;IAAU,UAAU;IAAM,QAAQ;IAAM;GACvD,eAAe;IAAE,MAAM;IAAW,UAAU;IAAM,cAAc;IAAO;GACvE,OAAO;IAAE,MAAM;IAAU,UAAU;IAAO;GAC1C,MAAM;IAAE,MAAM;IAAU,UAAU;IAAO,cAAcA,cAAAA;IAAmB;GAC1E,QAAQ;IAAE,MAAM;IAAW,UAAU;IAAO,cAAc;IAAO;GACjE,WAAW;IAAE,MAAM;IAAU,UAAU;IAAO;GAC9C,YAAY;IAAE,MAAM;IAAQ,UAAU;IAAO;GAC7C,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GAClE,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GACnE;EACF;CAED,SAAS;EACP,WAAW;EACX,OAAO;EACP,QAAQ;GACN,IAAI;IAAE,MAAM;IAAU,YAAY;IAAM;GACxC,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM;GAC3C,OAAO;IAAE,MAAM;IAAU,UAAU;IAAM,QAAQ;IAAM;GACvD,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GAClE,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GAClE,WAAW;IAAE,MAAM;IAAU,UAAU;IAAO;GAC9C,WAAW;IAAE,MAAM;IAAU,UAAU;IAAO;GAC9C,gBAAgB;IAAE,MAAM;IAAU,UAAU;IAAO;GACnD,QAAQ;IACN,MAAM;IACN,UAAU;IACV,YAAY;KAAE,OAAO;KAAQ,OAAO;KAAM,UAAU;KAAW;IAChE;GACF;EACF;CAED,SAAS;EACP,WAAW;EACX,OAAO;EACP,QAAQ;GACN,IAAI;IAAE,MAAM;IAAU,YAAY;IAAM;GACxC,WAAW;IAAE,MAAM;IAAU,UAAU;IAAM;GAC7C,YAAY;IAAE,MAAM;IAAU,UAAU;IAAM;GAC9C,QAAQ;IACN,MAAM;IACN,UAAU;IACV,YAAY;KAAE,OAAO;KAAQ,OAAO;KAAM,UAAU;KAAW;IAChE;GACD,aAAa;IAAE,MAAM;IAAU,UAAU;IAAO;GAChD,cAAc;IAAE,MAAM;IAAU,UAAU;IAAO;GACjD,SAAS;IAAE,MAAM;IAAU,UAAU;IAAO;GAC5C,sBAAsB;IAAE,MAAM;IAAQ,UAAU;IAAO;GACvD,uBAAuB;IAAE,MAAM;IAAQ,UAAU;IAAO;GACxD,OAAO;IAAE,MAAM;IAAU,UAAU;IAAO;GAC1C,UAAU;IAAE,MAAM;IAAU,UAAU;IAAO;GAC7C,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GAClE,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM,cAAc;IAAS;GACnE;EACF;CAED,cAAc;EACZ,WAAW;EACX,OAAO;EACP,QAAQ;GACN,IAAI;IAAE,MAAM;IAAU,YAAY;IAAM;GACxC,YAAY;IAAE,MAAM;IAAU,UAAU;IAAM;GAC9C,OAAO;IAAE,MAAM;IAAU,UAAU;IAAM;GACzC,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAM;GAC3C,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAO;GAC5C,WAAW;IAAE,MAAM;IAAQ,UAAU;IAAO;GAC7C;EACF;CACF;;;;;;;;;;;;AAiBD,eAAe,yBACb,cACA,SACA,QAC6B;CAE7B,IAAI;CACJ,IAAI;AAEJ,KAAI;AAEF,kBADyB,MAAM,OAAO,gBACN;SAC1B;AACN,QAAM,IAAI,MACR,oIAED;;AAGH,KAAI;AAEF,iBADsB,MAAM,OAAO,wBACP;SACtB;AACN,QAAM,IAAI,MACR,sFACD;;CAIH,IAAI,WAAoB,QAAQ;AAEhC,KAAI,CAAC,UAAU;EACb,MAAM,WAAW,aAAa;AAI9B,MAAI,CAAC,UAAU,iBACb,OAAM,IAAI,MACR,wMAGD;EAGH,MAAM,UAAU,SAAS;EACzB,MAAM,UAAU,SAAS,QAAQ,UAAU,aAAa;AAExD,MAAI,WAAW,SACb,YAAW,MAAM,mBAAmB,SAAS,OAAO;WAC3C,WAAW,QAAQ,WAAW,aACvC,YAAW,MAAM,mBAAmB,QAAQ;WACnC,WAAW,QACpB,YAAW,MAAM,gBAAgB,QAAQ;MAEzC,OAAM,IAAI,MACR,wDAAwD,OAAO,qGAEhE;;CAKL,MAAM,UACJ,QAAQ,WACR,QAAQ,IAAI,mBACZ,oBAAoB,QAAQ,IAAI,QAAQ;CAI1C,MAAM,iBADoB,QAAQ,oBAG9B,YAAqB;EACrB,MAAM,UAAU,IAAI,IAAY;GAAC;GAAS;GAAyB;GAAwB,CAAC;AAC5F,MAAI;AACF,OAAI,QACF,SAAQ,IAAI,IAAI,IAAI,QAAQ,IAAI,CAAC,OAAO;UAEpC;AAGR,SAAO,MAAM,KAAK,QAAQ;;CAI9B,MAAM,cAAc,QAAQ,qBAAqB,EAAE;CAEnD,MAAM,mBAAmB;EACvB,SAAS;EACT,GAAG,YAAY;EAChB;CAED,MAAM,UAAU;EACd,aAAa;GAAE,SAAS;GAAM,QAAQ;GAAQ;EAC9C,GAAG,YAAY;EAEf,GAAI,YAAY,SAAS,cACrB,EACE,aAAa;GACX,SAAS;GACT,QAAQ;GACR,GAAG,YAAY,QAAQ;GACxB,EACF,GACD,EAAE;EACP;AAGD,QAAO,OAAO,yCAAyC;AAqBvD,QAnBiB,aAAa;EAC5B;EACA;EACA;EACA,SAAS,CAAC,YAAY;GAAE,aAAaA,cAAAA;GAAmB,YAAY,CAACE,cAAAA,gBAAgB;GAAE,CAAC,CAAC;EACzF;EACA;EAEA,GAAI,YAAY,kBAAkB,EAAE,iBAAiB,YAAY,iBAAiB,GAAG,EAAE;EACvF,GAAI,YAAY,UAAU,EAAE,SAAS,YAAY,SAAS,GAAG,EAAE;EAC/D,GAAI,YAAY,YAAY,EAAE,WAAW,YAAY,WAAW,GAAG,EAAE;EACrE,GAAI,YAAY,WAAW,EAAE,UAAU,YAAY,UAAU,GAAG,EAAE;EAClE,GAAI,YAAY,gBAAgB,EAAE,eAAe,YAAY,eAAe,GAAG,EAAE;EACjF,GAAI,YAAY,QAAQ,EAAE,OAAO,YAAY,OAAO,GAAG,EAAE;EACzD,GAAI,YAAY,gBAAgB,EAAE,eAAe,YAAY,eAAe,GAAG,EAAE;EACjF,GAAI,YAAY,SAAS,EAAE,QAAQ,YAAY,QAAQ,GAAG,EAAE;EAC5D,GAAI,YAAY,UAAU,EAAE,SAAS,YAAY,SAAS,GAAG,EAAE;EAChE,CAAC;;;AAMJ,eAAe,mBAAmB,kBAA0B,QAA0B;AACpF,KAAI;EACF,MAAM,EAAE,SAAS,aAAa,MAAM,OAAO;EAC3C,MAAM,EAAE,QAAQ,eAAe,oBAAoB,MAAM,OAAO;AAChE,SAAO,QAAQ,yDAAyD;EAExE,IAAI,SAAS,iBAAiB,QAAQ,UAAU,GAAG;AACnD,MAAI,WAAW,GACb,UAAS;AASX,SAAO;GAAE,IAJQ,IAAI,OAAO;IAC1B,SAAS,IAAI,cAAc,EAAE,UAJd,IAAI,SAAS,OAAO,EAIc,CAAC;IAClD,SAAS,CAAC,IAAI,iBAAiB,CAAC;IACjC,CAAC;GACqB,MAAM;GAAmB;UACzC,KAAK;AACZ,MAAI,eAAe,SAAS,IAAI,QAAQ,SAAS,iBAAiB,CAChE,OAAM,IAAI,MACR,2LAGD;AAEH,QAAM;;;;AAKV,eAAe,mBAAmB,kBAA0B;AAC1D,KAAI;EACF,MAAM,EAAE,SAAS,MAAM,OAAO;AAC9B,SAAO,IAAI,KAAK,EAAE,kBAAkB,CAAC;SAC/B;AACN,QAAM,IAAI,MACR,kKAGD;;;;AAKL,eAAe,gBAAgB,kBAA0B;AACvD,KAAI;AAEF,UADc,MAAM,OAAO,mBACd,WAAW,iBAAiB;SACnC;AACN,QAAM,IAAI,MACR,iKAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDL,SAAgB,iBAAiB,SAAgD;CAC/E,MAAM,EACJ,SAAS,gBACT,SAAS,eACT,UAAU,gBACV,cAAc,EAAE,EAChB,iBAAiB,SACjB,eAAe,EAAE,KACf;CAGJ,IAAI,OAAkC,QAAQ,QAAQ;CAEtD,IAAI,iBAAmC;CAIvC,IAAI,qBAAqB;;;;CAKzB,eAAe,uBACb,SACgC;AAChC,MAAI,CAAC,KACH,QAAO;EAGT,MAAM,SAAS,MAAM,eAAe,MAAM,QAAQ;AAClD,MAAI,CAAC,OACH,QAAO;AAGT,MAAI,cACF,QAAO,cAAc,OAAO,MAAM,OAAO,QAAQ;AAGnD,SAAO,eAAe,OAAO,MAAM,OAAO,SAAS,QAAQ;;CAG7D,eAAe,uBAAuB,SAAkD;AACtF,MAAI,CAAC,KACH,QAAO;EAIT,MAAM,QADS,MAAM,sBAAsB,MAAM,SAAS,eAAe,GACpD;AACrB,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,KAC3B,QAAO;AAGT,MAAI,cACF,QAAO,cAAc,KAAK,MAAM,KAAK,QAAQ;AAG/C,SAAO,eAAe,KAAK,MAAM,KAAK,SAAS,QAAQ;;CAGzD,eAAe,wBAAwB,KAGJ;AACjC,MAAI,IAAI,SACN,QAAO,IAAI;AAGb,SAAO,uBAAuB,IAAI,QAAQ;;CAK5C,MAAM,kBAAkB,IAAI,YAAY,IAAI,IAAO;AAG3B,mBAAkB,gBAAgB,SAAS,EAAE,IAAI,IAAO,CAChE,SAAS;CASzB,MAAM,YAFe;EAAC;EAAO;EAAQ;EAAO;EAAS;EAAS,CAE/B,KAAK,YAAY;EACtC;EACR,MAAM,IAAI,OAAO;EACjB,UAAU;EACV,SAAS,OAAO,QAMV;GAIJ,MAAM,cAAc,IAAI,IAAI,IAAI,QAAQ,IAAI;AAC5C,kBAAe,QAAQ,gBAAgB,OAAO,GAAG,YAAY,WAAW;GAIxE,MAAM,sBAAsB,YAAY;GACxC,IAAI,WAAW,YAAY;GAC3B,MAAM,YAAY,SAAS,QAAQ,oBAAoB;AACvD,OAAI,cAAc,GAChB,YAAW,SAAS,MAAM,YAAY,oBAAoB,OAAO;AAEnE,OAAI,CAAC,SAAS,WAAW,IAAI,CAC3B,YAAW,MAAM;AAInB,OAAI,WAAW,UAAU,wBAAwB,MAAM,MAAM,SAAS,SAAS,EAAE,CAAC,EAAE;IAClF,MAAM,WACJ,IAAI,QAAQ,oBAAoB,MAAM,IAAI,CAAC,IAAI,MAAM,IACrD,IAAI,QAAQ,gBACZ;IACF,MAAM,EAAE,SAAS,iBAAiB,gBAAgB,cAAc,SAAS;AACzE,QAAI,QACF,QAAO,IAAI,SACT,KAAK,UAAU;KACb,OAAO;KACP,SAAS;KACV,CAAC,EACF;KACE,QAAQ;KACR,SAAS;MACP,gBAAgB;MAChB,eAAe,OAAO,KAAK,MAAM,gBAAgB,OAAU,IAAK,CAAC;MAClE;KACF,CACF;;GAKL,MAAM,UAAU,IAAI,IAAI,GAAG,YAAY,SAAS,WAAW,YAAY,SAAS;AAChF,kBAAe,QACb,2CAA2C,OAAO,GAAG,QAAQ,WAC9D;GAGD,MAAM,cAAc,IAAI,QAAQ,QAAQ,UAAU,EAAE;IAClD,QAAQ,IAAI,QAAQ;IACpB,SAAS,IAAI,QAAQ;IACrB,MAAM,WAAW,SAAS,WAAW,WAAW,IAAI,QAAQ,OAAO,KAAA;IAEnE,QAAQ,WAAW,SAAS,WAAW,WAAW,SAAS,KAAA;IAC5D,CAAC;GAGF,MAAM,WAAW,MAAM,KAAM,QAAQ,YAAY;AACjD,kBAAe,QAAQ,0BAA0B,SAAS,OAAO,GAAG,SAAS,cAAc;IACzF,WAAW,SAAS,QAAQ,IAAI,aAAa,GAAG,YAAY;IAC5D,aAAa,SAAS,QAAQ,IAAI,eAAe;IAClD,CAAC;AACF,UAAO;;EAEV,EAAE;AAGH,QAAO;EACL,IAAI;EACJ,MAAM;EAIN,QAAQ;EAGR,gBAAgB;GAAC;GAAQ;GAAW;GAAW;GAAe;EAC9D,mBACE;EAGF,WAAW;GAGT;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAWV;KACJ,MAAM,WAAW,MAAM,wBAAwB,IAAI;KACnD,MAAM,cAAc,IAAI,KAAK,eAAe,SAAS;KACrD,MAAM,eAAe,WAAW,IAAI,KAAK,gBAAgB,SAAS,GAAG;AAErE,YAAO;MACL,QAAQ;MACR,MAAM;OACJ,UAAU,WACN;QACE,IAAI,SAAS;QACb,MAAM,SAAS;QACf,MAAM,SAAS;QACf;QACD,GACD;OACJ;OACA,iBAAiB,CAAC,CAAC;OACpB;MACF;;IAEJ;GAED;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAQV;KACJ,MAAM,QAAQ,IAAI,KAAK,mBAAmB;KAI1C,MAAM,eAAeC,cAAAA,mBAAmB,QACrC,SAAS,CAAC,MAAM,MAAM,UAAU,MAAM,SAAS,KAAK,CACtD,CAAC,KAAK,UAAU;MAAE;MAAM,aAAa,EAAE;MAAE,EAAE;AAC5C,YAAO;MAAE,QAAQ;MAAK,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,aAAa,EAAE;MAAE;;IAEvE;GAID;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAOV;KACJ,MAAM,WAAW,MAAM,wBAAwB,IAAI;AACnD,SAAI,CAAC,YAAY,SAAS,SAAS,QACjC,QAAO;MACL,QAAQ;MACR,MAAM;OAAE,OAAO;OAAa,SAAS;OAAyB;MAC/D;AAGH,SAAI;MACF,MAAM,MAAM,KAAM;MAClB,MAAM,UAAU,UAAU,IAAI,QAAQ;AAGtC,UAAI,OAAO,IAAI,cAAc,YAAY;OACvC,MAAM,YAAY,IAAI;OAGtB,MAAM,SAAS,MAAM,UAAU;QAC7B;QACA,OAAO;SACL,OAAO,IAAI,MAAM,SAAS;SAC1B,QAAQ,IAAI,MAAM,UAAU;SAC7B;QACF,CAAC;AAEF,cAAO;QAAE,QAAQ;QAAK,MAAM,EAAE,OADhB,MAAM,QAAQ,OAAO,GAAG,SAAU,QAAQ,SAAS,EAAE,EAC9B;QAAE;;MAGzC,MAAM,iBAAiB,MAAM,sBAC3B,MACA,IAAI,SACJ,qBACA;OACE,QAAQ;OACR,OAAO;QACL,OAAO,IAAI,MAAM,SAAS;QAC1B,QAAQ,IAAI,MAAM,UAAU;QAC7B;OACF,CACF;AACD,UAAI,kBAAkB,eAAe,UAAU,OAAO,eAAe,SAAS,IAC5E,QAAO;OACL,QAAQ;OACR,MAAM,eAAe;OACtB;AAKH,aAAO;OACL,QAAQ;OACR,MAAM;QACJ,OAAO,EAAE;QACT,SACE;QAEH;OACF;cACM,KAAK;AACZ,qBAAe,MAAM,wBAAwB;OAC3C,UAAU,mBAAmB,SAAS;OACtC,OAAO,mBAAmB,IAAI,MAAM;OACpC,OAAO,mBAAmB,IAAI;OAC/B,CAAC;AACF,aAAO,uBAAuB,wBAAwB,IAAI;;;IAG/D;GAED;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAOV;KACJ,MAAM,WAAW,MAAM,wBAAwB,IAAI;AACnD,SAAI,CAAC,YAAY,SAAS,SAAS,QACjC,QAAO;MACL,QAAQ;MACR,MAAM;OAAE,OAAO;OAAa,SAAS;OAAyB;MAC/D;KAGH,MAAM,EAAE,OAAO,UAAU,MAAM,SAAS,IAAI;AAO5C,SAAI,CAAC,SAAS,CAAC,SACb,QAAO;MACL,QAAQ;MACR,MAAM,EAAE,OAAO,mCAAmC;MACnD;AAGH,SAAI,SAAS,KAAA,KAAa,CAACC,cAAAA,qBAAqB,KAAK,CACnD,QAAO;MACL,QAAQ;MACR,MAAM,EACJ,OAAO,0BAA0BC,cAAAA,sBAAsB,KAAK,KAAK,EAClE;MACF;AAGH,SAAI;MACF,MAAM,MAAM,KAAM;MAClB,MAAM,UAAU,UAAU,IAAI,QAAQ;MACtC,IAAI,SAAyC;AAE7C,UAAI,OAAO,IAAI,eAAe,YAAY;OACxC,MAAM,aACJ,IAAI;AACN,gBAAS,MAAM,WAAW;QACxB;QACA,MAAM;SACJ;SACA;SACA,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC;SAC/B,MAAM,QAAA;SACP;QACF,CAAC;iBACO,OAAO,IAAI,gBAAgB,YAAY;OAChD,MAAM,cACJ,IAAI;AACN,gBAAS,MAAM,YAAY;QACzB;QACA,MAAM;SACJ;SACA;SACA,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC;SAC/B,MAAM,QAAA;SACP;QACF,CAAC;aACG;OACL,MAAM,iBAAiB,MAAM,sBAC3B,MACA,IAAI,SACJ,sBACA;QACE,QAAQ;QACR,MAAM;SACJ;SACA;SACA,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC;SAC/B,MAAM,QAAA;SACP;QACF,CACF;AACD,WAAI,kBAAkB,eAAe,UAAU,OAAO,eAAe,SAAS,IAC5E,UAAS,eAAe;;AAI5B,UACE,CAAC,UACD,OAAO,IAAI,eAAe,cAC1B,OAAO,IAAI,gBAAgB,WAE3B,QAAO;OACL,QAAQ;OACR,MAAM,EAAE,OAAO,2CAA2C;OAC3D;AAGH,UAAI,CAAC,QAAQ,KACX,QAAO;OACL,QAAQ;OACR,MAAM;QACJ,OAAO;QACP,SAAS;QACV;OACF;AAGH,aAAO;OACL,QAAQ;OACR,MAAM,EACJ,MAAM;QACJ,IAAI,OAAO,KAAK;QAChB,OAAO,OAAO,KAAK;QACnB,MAAM,OAAO,KAAK;QAClB,MAAM,OAAO,KAAK;QACnB,EACF;OACF;cACM,KAAK;AACZ,qBAAe,MAAM,yBAAyB;OAC5C,UAAU,mBAAmB,SAAS;OACtC,MAAM,mBAAmB;QAAE;QAAO;QAAM;QAAM,CAAC;OAC/C,OAAO,mBAAmB,IAAI;OAC/B,CAAC;AACF,aAAO,uBAAuB,yBAAyB,IAAI;;;IAGhE;GAED;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAOV;KACJ,MAAM,WAAW,MAAM,wBAAwB,IAAI;AACnD,SAAI,CAAC,YAAY,SAAS,SAAS,QACjC,QAAO;MACL,QAAQ;MACR,MAAM;OAAE,OAAO;OAAa,SAAS;OAAyB;MAC/D;KAGH,MAAM,EAAE,WAAW,IAAI;KACvB,MAAM,EAAE,SAAS,IAAI;AAErB,SAAI,CAACD,cAAAA,qBAAqB,KAAK,CAC7B,QAAO;MACL,QAAQ;MACR,MAAM,EACJ,OAAO,0BAA0BC,cAAAA,sBAAsB,KAAK,KAAK,EAClE;MACF;AAGH,SAAI;MACF,MAAM,MAAM,KAAM;MAClB,MAAM,UAAU,UAAU,IAAI,QAAQ;AAGtC,UAAI,OAAO,IAAI,YAAY,YAAY;OACrC,MAAM,UAAU,IAAI;AACpB,aAAM,QAAQ;QACZ;QACA,MAAM;SAAE;SAAQ;SAAM;QACvB,CAAC;AACF,cAAO;QAAE,QAAQ;QAAK,MAAM;SAAE,SAAS;SAAM;SAAQ;SAAM;QAAE;;MAG/D,MAAM,iBAAiB,MAAM,sBAC3B,MACA,IAAI,SACJ,mBACA;OACE,QAAQ;OACR,MAAM;QAAE;QAAQ;QAAM;OACvB,CACF;AACD,UAAI,kBAAkB,eAAe,UAAU,OAAO,eAAe,SAAS,IAC5E,QAAO;OAAE,QAAQ;OAAK,MAAM;QAAE,SAAS;QAAM;QAAQ;QAAM;OAAE;AAI/D,UAAI,OAAO,IAAI,eAAe,YAAY;OACxC,MAAM,aAAa,IAAI;AACvB,aAAM,WAAW;QACf;QACA,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,IAAI,QAAQ;QACvB,CAAC;AACF,cAAO;QAAE,QAAQ;QAAK,MAAM;SAAE,SAAS;SAAM;SAAQ;SAAM;QAAE;;AAG/D,aAAO;OACL,QAAQ;OACR,MAAM,EACJ,OACE,wGAEH;OACF;cACM,KAAK;AACZ,qBAAe,MAAM,yBAAyB;OAC5C,UAAU,mBAAmB,SAAS;OACtC,QAAQ,mBAAmB,IAAI,OAAO;OACtC,MAAM,mBAAmB,EAAE,MAAM,CAAC;OAClC,OAAO,mBAAmB,IAAI;OAC/B,CAAC;AACF,aAAO,uBAAuB,yBAAyB,IAAI;;;IAGhE;GAED;IACE,QAAQ;IACR,MAAM,IAAI,OAAO;IACjB,UAAU;IACV,SAAS,OAAO,QAOV;KACJ,MAAM,WAAW,MAAM,wBAAwB,IAAI;AACnD,SAAI,CAAC,YAAY,SAAS,SAAS,QACjC,QAAO;MACL,QAAQ;MACR,MAAM;OAAE,OAAO;OAAa,SAAS;OAAyB;MAC/D;KAGH,MAAM,EAAE,WAAW,IAAI;AAGvB,SAAI,SAAS,OAAO,OAClB,QAAO;MACL,QAAQ;MACR,MAAM,EAAE,OAAO,kCAAkC;MAClD;AAGH,SAAI;MACF,MAAM,MAAM,KAAM;MAClB,MAAM,UAAU,UAAU,IAAI,QAAQ;AAEtC,UAAI,OAAO,IAAI,eAAe,YAAY;OACxC,MAAM,aAAa,IAAI;AACvB,aAAM,WAAW;QACf;QACA,MAAM,EAAE,QAAQ;QACjB,CAAC;AACF,cAAO;QAAE,QAAQ;QAAK,MAAM;SAAE,SAAS;SAAM;SAAQ;QAAE;;MAGzD,MAAM,iBAAiB,MAAM,sBAC3B,MACA,IAAI,SACJ,sBACA;OACE,QAAQ;OACR,MAAM,EAAE,QAAQ;OACjB,CACF;AACD,UAAI,kBAAkB,eAAe,UAAU,OAAO,eAAe,SAAS,IAC5E,QAAO;OAAE,QAAQ;OAAK,MAAM;QAAE,SAAS;QAAM;QAAQ;OAAE;AAIzD,UAAI,OAAO,IAAI,eAAe,YAAY;OACxC,MAAM,aAAa,IAAI;AACvB,aAAM,WAAW;QACf;QACA,MAAM,EAAE,QAAQ;QACjB,CAAC;AACF,cAAO;QAAE,QAAQ;QAAK,MAAM;SAAE,SAAS;SAAM;SAAQ;QAAE;;AAGzD,aAAO;OACL,QAAQ;OACR,MAAM,EACJ,OACE,0GAEH;OACF;cACM,KAAK;AACZ,qBAAe,MAAM,yBAAyB;OAC5C,UAAU,mBAAmB,SAAS;OACtC,QAAQ,mBAAmB,IAAI,OAAO;OACtC,OAAO,mBAAmB,IAAI;OAC/B,CAAC;AACF,aAAO,uBAAuB,yBAAyB,IAAI;;;IAGhE;GAGD,GAAG;GACJ;EAED,OAAO;GAQL,WAAW,OACT,SACA,YACG;AAEH,QAAI,kBAAkB,QAAQ,MAAM,QAAQ,mBAAmB,CAC7D;AAIF,QAAI,YAAY,MAAM,MAAM,QAAQ,KAAK,WAAW,EAAE,CAAC,CACrD;IAIF,MAAM,gBAAoD,EAAE;AAC5D,YAAQ,QAAQ,SAAS,OAAO,QAAQ;AACtC,mBAAc,OAAO;MACrB;AAEF,mBAAe,QAAQ,oBAAoB,QAAQ,OAAO,GAAG,QAAQ,QAAQ;KAC3E,WAAW,CAAC,CAAC,cAAc;KAC3B,SAAS,CAAC,CAAC,cAAc;KAC1B,CAAC;IAEF,MAAM,WAAW,MAAM,uBAAuB,cAAc;AAE5D,mBAAe,QAAQ,uCAAuC;KAC5D,eAAe,CAAC,CAAC;KACjB,QAAQ,UAAU;KAClB,MAAM,UAAU;KACjB,CAAC;AAGF,YAAQ,WAAW;AAEnB,QAAI,CAAC,YAAY,mBAAmB,QAClC,QAAO,EACL,UAAU,IAAI,SACZ,KAAK,UAAU;KACb,OAAO;KACP,SAAS;KACV,CAAC,EACF;KACE,QAAQ;KACR,SAAS,EAAE,gBAAgB,oBAAoB;KAChD,CACF,EACF;;GAcL,aAAa,OAAO,YAId;AAEJ,QAAI,QAAQ,SACV;AAIF,WAAO;KAAE,SAAS;KAAO,QAAQ;KAAgC;;GAEpE;EAED,MAAM,OAAO,kBAAkB;AAC7B,oBAAiB,cAAc;AAG/B,OAAI,CAAC,KACH,QAAO,MAAM,yBAAyB,cAAc,QAAQ,SAAS,cAAc,OAAO;AAG5F,wBAAqB,KAAK,SAAS,YAAY;AAE/C,iBAAc,OAAO,KACnB,2CAA2C,OAAO,cAAc,mBAAmB,GACpF;AAED,OAAI,aAAa,WAAW,GAAG;AAC7B,kBAAc,OAAO,MACnB,kGACD;AACD;;AAGF,QAAK,MAAM,mBAAmB,cAAc;IAC1C,MAAM,aAAa,gBAAgB,OAAO,MAAM;IAChD,MAAM,gBAAgB,gBAAgB;IACtC,MAAM,YAAY,gBAAgB,MAAM,MAAM,IAAI;AAElD,QAAI,CAAC,cAAc,CAAC,eAAe;AACjC,mBAAc,OAAO,MACnB,wEACD;AACD;;AAGF,QAAI;KACF,MAAM,cAAc,MAAM,eAAe,KAAK;KAC9C,MAAM,oBAAoB,gBACxB,MAAM,aAAa,iBAAiB,gBAAgB,WAAW,CAChE;AAED,SAAI,mBAAmB;AACrB,UAAI,kBAAkB,SAAS,SAAS;AACtC,aAAM,aAAa,iBAAiB,WAAW,kBAAkB,IAAI,EACnE,MAAM,SACP,CAAC;AACF,qBAAc,OAAO,KAAK,wBAAwB,aAAa;YAE/D,eAAc,OAAO,MAAM,kCAAkC,aAAa;AAG5E;;KAGF,MAAM,MAAM,KAAM;KAClB,IAAI,SAAyC;AAE7C,SAAI,OAAO,IAAI,eAAe,YAAY;MACxC,MAAM,aACJ,IAAI;AACN,eAAS,MAAM,WAAW;OACxB,SAAS,IAAI,SAAS;OACtB,MAAM;QACJ,OAAO;QACP,UAAU;QACV,MAAM;QACN,MAAM;QACP;OACF,CAAC,CAAC,OAAO,QAAiB;AACzB,qBAAc,OAAO,QACnB,yBAAyB,WAAW,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACzF;AACD,cAAO;QACP;gBACO,OAAO,IAAI,gBAAgB,YAAY;MAChD,MAAM,cAAc,IAAI;AACxB,eAAS,MAAM,YAAY,EACzB,MAAM;OACJ,OAAO;OACP,UAAU;OACV,MAAM;OACP,EACF,CAAC,CAAC,OAAO,QAAiB;AACzB,qBAAc,OAAO,QACnB,0BAA0B,WAAW,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1F;AACD,cAAO;QACP;YACG;AACL,oBAAc,OAAO,MACnB,iCAAiC,WAAW,oDAC7C;AACD;;AAGF,SAAI,QAAQ,MAAM;MAEhB,MAAM,qBAAqB,eAAgB,MAAM,eAAe,KAAK;MACrE,MAAM,mBACJ,gBACE,MAAM,oBAAoB,iBAAiB,gBAAgB,WAAW,CACvE,IAAI,OAAO;AAEd,UAAI,kBAAkB,MAAM,iBAAiB,SAAS,QACpD,OAAM,oBAAoB,iBAAiB,WAAW,iBAAiB,IAAI,EACzE,MAAM,SACP,CAAC;AAGJ,oBAAc,OAAO,KAAK,uBAAuB,aAAa;WAE9D,eAAc,OAAO,MACnB,sDAAsD,aACvD;aAEI,SAAS;AAChB,mBAAc,OAAO,MACnB,yDAAyD,WAAW,KAAK,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ,GACtI;;;;EAKP,cAAc;GACZ,wBAAwB;IACtB,SAAS;IACT,QAAQ;IACT;GACD,0BAA0B;IACxB,SAAS;IACT,QAAQ;IACT;GACF;EACF;;;;;;AAWH,SAAS,kBAAkB,MAAc,QAAgB,UAA2B;AAClF,QAAO,KAAK,WAAW,YAAY,SAAS,WAAW"}
1
+ {"version":3,"file":"index.cjs","names":["AUTH_DEFAULT_ROLE","isAuthVisibleRole","AUTH_ADMIN_ROLE","AUTH_VISIBLE_ROLES","isAuthAssignableRole","AUTH_ASSIGNABLE_ROLES"],"sources":["../../src/backend/plugin.ts"],"sourcesContent":["import type {\n InvectPlugin,\n InvectIdentity,\n InvectRole,\n InvectPermission,\n InvectPluginSchema,\n} from '@invect/core';\nimport type {\n UserAuthPluginOptions,\n BetterAuthContext,\n BetterAuthUser,\n BetterAuthSession,\n BetterAuthInstance,\n} from './types';\nimport {\n AUTH_ADMIN_ROLE,\n AUTH_ASSIGNABLE_ROLES,\n AUTH_DEFAULT_ROLE,\n AUTH_VISIBLE_ROLES,\n isAuthAssignableRole,\n isAuthVisibleRole,\n} from '../shared/roles';\n\ntype PluginLoggerLike = {\n error: (message: string, meta?: unknown) => void;\n info?: (message: string, meta?: unknown) => void;\n debug?: (message: string, meta?: unknown) => void;\n warn?: (message: string, meta?: unknown) => void;\n};\n\ntype BetterAuthApiUser = {\n id?: string;\n email?: string | null;\n name?: string | null;\n role?: string | null;\n [key: string]: unknown;\n};\n\ntype BetterAuthApiUserResult = {\n user?: BetterAuthApiUser | null;\n};\n\ntype BetterAuthHeadersQueryMethod<TResult> = (args: {\n headers: Headers;\n query: Record<string, string>;\n}) => Promise<TResult>;\n\ntype BetterAuthHeadersBodyMethod<TResult> = (args: {\n headers: Headers;\n body: Record<string, unknown>;\n}) => Promise<TResult>;\n\ntype BetterAuthHeadersBodyParamsMethod<TResult> = (args: {\n headers: Headers;\n body: Record<string, unknown>;\n params: Record<string, string>;\n}) => Promise<TResult>;\n\ntype BetterAuthBodyMethod<TResult> = (args: { body: Record<string, unknown> }) => Promise<TResult>;\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PREFIX = 'auth';\n\n/**\n * Default role mapping: keep admin/RBAC roles aligned and fall back to default.\n */\nfunction defaultMapRole(role: string | null | undefined): InvectRole {\n if (!role) {\n return AUTH_DEFAULT_ROLE;\n }\n if (role === 'viewer' || role === 'readonly') {\n return 'viewer';\n }\n if (isAuthVisibleRole(role)) {\n return role;\n }\n return AUTH_DEFAULT_ROLE;\n}\n\n/**\n * Default user → identity mapping.\n */\nfunction defaultMapUser(\n user: BetterAuthUser,\n _session: BetterAuthSession,\n mapRole: (role: string | null | undefined) => InvectRole,\n): InvectIdentity {\n const resolvedRole = mapRole(user.role);\n\n return {\n id: user.id,\n name: user.name ?? user.email ?? undefined,\n role: resolvedRole,\n permissions: resolvedRole === AUTH_ADMIN_ROLE ? ['admin:*'] : undefined,\n resourceAccess:\n resolvedRole === AUTH_ADMIN_ROLE\n ? {\n flows: '*',\n credentials: '*',\n }\n : undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Simple in-memory sliding-window rate limiter.\n *\n * Keyed by IP address (or a fallback identifier). Tracks request timestamps\n * per window and rejects requests that exceed the limit with HTTP 429.\n *\n * Only applied to authentication-sensitive endpoints (sign-in, sign-up,\n * password reset) to prevent brute-force attacks. Session reads (GET) are\n * not rate-limited.\n */\nclass RateLimiter {\n private windows = new Map<string, number[]>();\n private readonly maxRequests: number;\n private readonly windowMs: number;\n\n constructor(maxRequests = 10, windowMs = 60_000) {\n this.maxRequests = maxRequests;\n this.windowMs = windowMs;\n }\n\n /**\n * Returns `true` if the request should be rejected (over limit).\n */\n isRateLimited(key: string): { limited: boolean; retryAfterMs?: number } {\n const now = Date.now();\n const windowStart = now - this.windowMs;\n\n let timestamps = this.windows.get(key);\n if (!timestamps) {\n timestamps = [];\n this.windows.set(key, timestamps);\n }\n\n // Prune expired entries\n const valid = timestamps.filter((t) => t > windowStart);\n this.windows.set(key, valid);\n\n if (valid.length >= this.maxRequests) {\n const oldestInWindow = valid[0] ?? now;\n const retryAfterMs = oldestInWindow + this.windowMs - now;\n return { limited: true, retryAfterMs: Math.max(retryAfterMs, 1000) };\n }\n\n valid.push(now);\n return { limited: false };\n }\n\n /** Periodic cleanup of stale keys to prevent memory leaks. */\n cleanup(): void {\n const now = Date.now();\n for (const [key, timestamps] of this.windows) {\n const valid = timestamps.filter((t) => t > now - this.windowMs);\n if (valid.length === 0) {\n this.windows.delete(key);\n } else {\n this.windows.set(key, valid);\n }\n }\n }\n}\n\n/** Auth-sensitive path segments that should be rate-limited. */\nconst RATE_LIMITED_AUTH_PATHS = ['/sign-in/', '/sign-up/', '/forgot-password', '/reset-password'];\n\n/**\n * Convert a Node.js-style `IncomingHttpHeaders` record or a `Headers` instance\n * to a standard `Headers` object for passing to better-auth.\n */\nfunction toHeaders(raw: Record<string, string | undefined> | Headers): Headers {\n if (raw instanceof Headers) {\n return raw;\n }\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(raw)) {\n if (value !== undefined) {\n headers.set(key, value);\n }\n }\n return headers;\n}\n\n/**\n * Resolve the session from a better-auth instance using request headers.\n */\nasync function resolveSession(\n auth: BetterAuthInstance | null,\n headers: Record<string, string | undefined> | Headers,\n): Promise<{ session: BetterAuthSession; user: BetterAuthUser } | null> {\n if (!auth) {\n return null;\n }\n\n const h = toHeaders(headers);\n\n try {\n // eslint-disable-next-line no-console\n console.log('[auth-debug] resolveSession: cookie header =', h.get('cookie')?.slice(0, 80));\n const result = await auth.api.getSession({\n headers: h,\n });\n // eslint-disable-next-line no-console\n console.log(\n '[auth-debug] resolveSession: result keys =',\n result ? Object.keys(result) : 'null',\n );\n if (result?.session && result?.user) {\n return {\n session: result.session,\n user: result.user,\n };\n }\n return null;\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[auth-debug] resolveSession threw:', (err as Error)?.message ?? err);\n return null;\n }\n}\n\nasync function callBetterAuthHandler(\n auth: BetterAuthInstance | null,\n request: Request,\n path: string,\n init?: {\n method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n query?: Record<string, string | undefined>;\n body?: Record<string, unknown>;\n },\n): Promise<{ status: number; body: unknown } | null> {\n if (!auth) {\n return null;\n }\n\n const basePath = auth.options?.basePath ?? '/api/auth';\n const targetUrl = new URL(`${basePath}${path}`, request.url);\n for (const [key, value] of Object.entries(init?.query ?? {})) {\n if (value !== undefined) {\n targetUrl.searchParams.set(key, value);\n }\n }\n\n const headers = new Headers(request.headers);\n const hasBody = init?.body !== undefined;\n if (hasBody && !headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n\n const authRequest = new Request(targetUrl.toString(), {\n method: init?.method ?? 'GET',\n headers,\n body: hasBody ? JSON.stringify(init?.body) : undefined,\n });\n\n const response = await auth.handler(authRequest);\n const text = await response.text();\n\n if (!text) {\n return { status: response.status, body: null };\n }\n\n try {\n return { status: response.status, body: JSON.parse(text) };\n } catch {\n return { status: response.status, body: text };\n }\n}\n\nasync function getAuthContext(auth: BetterAuthInstance | null): Promise<BetterAuthContext | null> {\n if (!auth) {\n return null;\n }\n try {\n return (await auth.$context) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction isBetterAuthUser(value: unknown): value is BetterAuthUser {\n return !!value && typeof value === 'object' && 'id' in value && typeof value.id === 'string';\n}\n\nfunction unwrapFoundUser(\n result: BetterAuthUser | { user?: BetterAuthUser | null } | null | undefined,\n): BetterAuthUser | null {\n if (!result) {\n return null;\n }\n\n if (typeof result === 'object' && 'user' in result) {\n const nestedUser = result.user;\n if (isBetterAuthUser(nestedUser)) {\n return nestedUser;\n }\n\n return null;\n }\n\n if (isBetterAuthUser(result)) {\n return result;\n }\n\n return null;\n}\n\nfunction toAuthApiErrorResponse(\n fallbackError: string,\n error: unknown,\n): { status: number; body: Record<string, unknown> } {\n if (error instanceof Response) {\n return {\n status: error.status || 500,\n body: { error: fallbackError, message: error.statusText || fallbackError },\n };\n }\n\n const status =\n error &&\n typeof error === 'object' &&\n 'status' in error &&\n typeof (error as { status?: unknown }).status === 'number'\n ? ((error as { status: number }).status ?? 500)\n : error &&\n typeof error === 'object' &&\n 'statusCode' in error &&\n typeof (error as { statusCode?: unknown }).statusCode === 'number'\n ? ((error as { statusCode: number }).statusCode ?? 500)\n : 500;\n\n const message =\n error &&\n typeof error === 'object' &&\n 'message' in error &&\n typeof (error as { message?: unknown }).message === 'string'\n ? (error as { message: string }).message || fallbackError\n : fallbackError;\n\n const code =\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n typeof (error as { code?: unknown }).code === 'string'\n ? (error as { code: string }).code\n : undefined;\n\n return {\n status,\n body: {\n error: fallbackError,\n message,\n ...(code ? { code } : {}),\n },\n };\n}\n\nfunction sanitizeForLogging(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeForLogging(item));\n }\n\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, nestedValue]) => {\n if (/password|token|secret/i.test(key)) {\n return [key, '[REDACTED]'];\n }\n\n return [key, sanitizeForLogging(nestedValue)];\n }),\n );\n }\n\n return value;\n}\n\nfunction getErrorLogDetails(error: unknown): Record<string, unknown> {\n if (error instanceof Response) {\n return {\n type: 'Response',\n status: error.status,\n statusText: error.statusText,\n };\n }\n\n if (error instanceof Error) {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n ...(error && typeof error === 'object' && 'cause' in error\n ? { cause: sanitizeForLogging((error as Error & { cause?: unknown }).cause) }\n : {}),\n ...(error && typeof error === 'object' && 'code' in error\n ? { code: (error as Error & { code?: unknown }).code }\n : {}),\n ...(error && typeof error === 'object' && 'status' in error\n ? { status: (error as Error & { status?: unknown }).status }\n : {}),\n ...(error && typeof error === 'object' && 'statusCode' in error\n ? { statusCode: (error as Error & { statusCode?: unknown }).statusCode }\n : {}),\n };\n }\n\n if (error && typeof error === 'object') {\n return sanitizeForLogging(error) as Record<string, unknown>;\n }\n\n return { value: error };\n}\n\n// ---------------------------------------------------------------------------\n// Abstract schema — defines the tables better-auth requires\n// ---------------------------------------------------------------------------\n\n/**\n * Abstract schema for better-auth's database tables.\n *\n * These definitions allow the Invect CLI (`npx invect-cli generate`) to include\n * the better-auth tables when generating Drizzle/Prisma schema files.\n *\n * The shapes match better-auth's default table structure. If your better-auth\n * config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),\n * you can extend these in your own config.\n */\nexport const USER_AUTH_SCHEMA: InvectPluginSchema = {\n user: {\n tableName: 'user',\n order: 1,\n fields: {\n id: { type: 'string', primaryKey: true },\n name: { type: 'string', required: true },\n email: { type: 'string', required: true, unique: true },\n emailVerified: { type: 'boolean', required: true, defaultValue: false },\n image: { type: 'string', required: false },\n role: { type: 'string', required: false, defaultValue: AUTH_DEFAULT_ROLE },\n banned: { type: 'boolean', required: false, defaultValue: false },\n banReason: { type: 'string', required: false },\n banExpires: { type: 'date', required: false },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n },\n },\n\n session: {\n tableName: 'session',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n expiresAt: { type: 'date', required: true },\n token: { type: 'string', required: true, unique: true },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n ipAddress: { type: 'string', required: false },\n userAgent: { type: 'string', required: false },\n impersonatedBy: { type: 'string', required: false },\n userId: {\n type: 'string',\n required: true,\n references: { table: 'user', field: 'id', onDelete: 'cascade' },\n },\n },\n },\n\n account: {\n tableName: 'account',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n accountId: { type: 'string', required: true },\n providerId: { type: 'string', required: true },\n userId: {\n type: 'string',\n required: true,\n references: { table: 'user', field: 'id', onDelete: 'cascade' },\n },\n accessToken: { type: 'string', required: false },\n refreshToken: { type: 'string', required: false },\n idToken: { type: 'string', required: false },\n accessTokenExpiresAt: { type: 'date', required: false },\n refreshTokenExpiresAt: { type: 'date', required: false },\n scope: { type: 'string', required: false },\n password: { type: 'string', required: false },\n createdAt: { type: 'date', required: true, defaultValue: 'now()' },\n updatedAt: { type: 'date', required: true, defaultValue: 'now()' },\n },\n },\n\n verification: {\n tableName: 'verification',\n order: 2,\n fields: {\n id: { type: 'string', primaryKey: true },\n identifier: { type: 'string', required: true },\n value: { type: 'string', required: true },\n expiresAt: { type: 'date', required: true },\n createdAt: { type: 'date', required: false },\n updatedAt: { type: 'date', required: false },\n },\n },\n};\n\n// ---------------------------------------------------------------------------\n// Internal better-auth instance creation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a better-auth instance internally using Invect's database config.\n *\n * Dynamically imports `better-auth` (a required peer dependency) and creates\n * a fully-configured instance with email/password auth, the admin plugin,\n * and session caching.\n *\n * Database resolution order:\n * 1. Explicit `options.database` (any value `betterAuth({ database })` accepts)\n * 2. Auto-created client from Invect's `baseDatabaseConfig.connectionString`\n */\nasync function createInternalBetterAuth(\n invectConfig: Record<string, unknown>,\n options: UserAuthPluginOptions,\n logger: PluginLoggerLike,\n): Promise<BetterAuthInstance> {\n // 1. Dynamic-import better-auth (peer dependency)\n let betterAuthFn: (config: Record<string, unknown>) => unknown;\n let adminPlugin: (config?: Record<string, unknown>) => unknown;\n\n try {\n const betterAuthModule = await import('better-auth');\n betterAuthFn = betterAuthModule.betterAuth;\n } catch {\n throw new Error(\n 'Could not import \"better-auth\". It is a required peer dependency of @invect/user-auth. ' +\n 'Install it with: npm install better-auth',\n );\n }\n\n try {\n const pluginsModule = await import('better-auth/plugins');\n adminPlugin = pluginsModule.admin;\n } catch {\n throw new Error(\n 'Could not import \"better-auth/plugins\". Ensure better-auth is properly installed.',\n );\n }\n\n // 2. Resolve database\n let database: unknown = options.database;\n\n if (!database) {\n const dbConfig = invectConfig.baseDatabaseConfig as\n | { type?: string; connectionString?: string }\n | undefined;\n\n if (!dbConfig?.connectionString) {\n throw new Error(\n 'Cannot create internal better-auth instance: no database configuration found. ' +\n 'Either provide `auth` (a better-auth instance), `database`, or ensure ' +\n 'Invect baseDatabaseConfig has a connectionString.',\n );\n }\n\n const connStr = dbConfig.connectionString;\n const dbType = (dbConfig.type ?? 'sqlite').toLowerCase();\n\n if (dbType === 'sqlite') {\n database = await createSQLiteClient(connStr, logger);\n } else if (dbType === 'pg' || dbType === 'postgresql') {\n database = await createPostgresPool(connStr);\n } else if (dbType === 'mysql') {\n database = await createMySQLPool(connStr);\n } else {\n throw new Error(\n `Unsupported database type for internal better-auth: \"${dbType}\". ` +\n 'Supported: sqlite, pg, mysql. Alternatively, provide your own better-auth instance via `auth`.',\n );\n }\n }\n\n // 3. Resolve base URL\n const baseURL =\n options.baseURL ??\n process.env.BETTER_AUTH_URL ??\n `http://localhost:${process.env.PORT ?? '3000'}`;\n\n // 4. Resolve trusted origins\n const configuredOrigins = options.trustedOrigins;\n const trustedOrigins =\n configuredOrigins ??\n ((request: Request) => {\n const trusted = new Set<string>([baseURL, 'http://localhost:5173', 'http://localhost:5174']);\n try {\n if (request) {\n trusted.add(new URL(request.url).origin);\n }\n } catch {\n // Ignore malformed URLs\n }\n return Array.from(trusted);\n });\n\n // 5. Build passthrough config from betterAuthOptions\n const passthrough = options.betterAuthOptions ?? {};\n\n const emailAndPassword = {\n enabled: true,\n ...passthrough.emailAndPassword,\n };\n\n const session = {\n cookieCache: { enabled: true, maxAge: 5 * 60 },\n ...passthrough.session,\n // Merge cookieCache if both exist\n ...(passthrough.session?.cookieCache\n ? {\n cookieCache: {\n enabled: true,\n maxAge: 5 * 60,\n ...passthrough.session.cookieCache,\n },\n }\n : {}),\n };\n\n // 6. Create the instance\n logger.info?.('Creating internal better-auth instance');\n\n const instance = betterAuthFn({\n baseURL,\n database,\n emailAndPassword,\n plugins: [adminPlugin({ defaultRole: AUTH_DEFAULT_ROLE, adminRoles: [AUTH_ADMIN_ROLE] })],\n session,\n trustedOrigins,\n // Spread optional passthrough fields\n ...(passthrough.socialProviders ? { socialProviders: passthrough.socialProviders } : {}),\n ...(passthrough.account ? { account: passthrough.account } : {}),\n ...(passthrough.rateLimit ? { rateLimit: passthrough.rateLimit } : {}),\n ...(passthrough.advanced ? { advanced: passthrough.advanced } : {}),\n ...(passthrough.databaseHooks ? { databaseHooks: passthrough.databaseHooks } : {}),\n ...(passthrough.hooks ? { hooks: passthrough.hooks } : {}),\n ...(passthrough.disabledPaths ? { disabledPaths: passthrough.disabledPaths } : {}),\n ...(passthrough.secret ? { secret: passthrough.secret } : {}),\n ...(passthrough.secrets ? { secrets: passthrough.secrets } : {}),\n });\n\n return instance as BetterAuthInstance;\n}\n\n/** Create a SQLite client using better-sqlite3. */\nasync function createSQLiteClient(connectionString: string, logger: PluginLoggerLike) {\n try {\n const { default: Database } = await import('better-sqlite3');\n const { Kysely, SqliteDialect, CamelCasePlugin } = await import('kysely');\n logger.debug?.(`Using better-sqlite3 for internal better-auth database`);\n // Strip file: prefix if present\n let dbPath = connectionString.replace(/^file:/, '');\n if (dbPath === '') {\n dbPath = ':memory:';\n }\n const sqliteDb = new Database(dbPath);\n // Return in Better Auth's { db, type } format so it uses our Kysely instance\n // directly, preserving CamelCasePlugin for snake_case column mapping.\n const kyselyDb = new Kysely({\n dialect: new SqliteDialect({ database: sqliteDb }),\n plugins: [new CamelCasePlugin()],\n });\n return { db: kyselyDb, type: 'sqlite' as const };\n } catch (err) {\n if (err instanceof Error && err.message.includes('better-sqlite3')) {\n throw new Error(\n 'Cannot create SQLite database for internal better-auth: ' +\n 'install better-sqlite3 (npm install better-sqlite3). ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n throw err;\n }\n}\n\n/** Create a PostgreSQL pool from a connection string. */\nasync function createPostgresPool(connectionString: string) {\n try {\n const { Pool } = await import('pg');\n return new Pool({ connectionString });\n } catch {\n throw new Error(\n 'Cannot create PostgreSQL pool for internal better-auth: ' +\n 'install the \"pg\" package. ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n}\n\n/** Create a MySQL pool from a connection string. */\nasync function createMySQLPool(connectionString: string) {\n try {\n const mysql = await import('mysql2/promise');\n return mysql.createPool(connectionString);\n } catch {\n throw new Error(\n 'Cannot create MySQL pool for internal better-auth: ' +\n 'install the \"mysql2\" package. ' +\n 'Alternatively, provide your own better-auth instance via the `auth` option.',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an Invect plugin that wraps a better-auth instance.\n *\n * This plugin:\n *\n * 1. **Proxies better-auth routes** — All of better-auth's HTTP endpoints\n * (sign-in, sign-up, sign-out, OAuth callbacks, session, etc.) are mounted\n * under the plugin endpoint space at `/plugins/auth/api/auth/*` (configurable).\n *\n * 2. **Resolves sessions → identities** — On every Invect API request, the\n * `onRequest` hook reads the session cookie / bearer token via\n * `auth.api.getSession()` and populates `InvectIdentity`.\n *\n * 3. **Handles authorization** — The `onAuthorize` hook lets better-auth's\n * session decide whether a request is allowed.\n *\n * @example\n * ```ts\n * // Simple: let the plugin manage better-auth internally\n * import { userAuth } from '@invect/user-auth';\n *\n * app.use('/invect', createInvectRouter({\n * databaseUrl: 'file:./dev.db',\n * plugins: [userAuth({\n * globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],\n * })],\n * }));\n * ```\n *\n * @example\n * ```ts\n * // Advanced: provide your own better-auth instance\n * import { betterAuth } from 'better-auth';\n * import { userAuth } from '@invect/user-auth';\n *\n * const auth = betterAuth({\n * database: { ... },\n * emailAndPassword: { enabled: true },\n * // ... your better-auth config\n * });\n *\n * app.use('/invect', createInvectRouter({\n * databaseUrl: 'file:./dev.db',\n * plugins: [userAuth({ auth })],\n * }));\n * ```\n */\nexport function userAuth(options: UserAuthPluginOptions): InvectPlugin {\n const {\n prefix = DEFAULT_PREFIX,\n mapUser: customMapUser,\n mapRole = defaultMapRole,\n publicPaths = [],\n onSessionError = 'throw',\n globalAdmins = [],\n } = options;\n\n // Mutable — assigned in `init` when no external instance was provided.\n let auth: BetterAuthInstance | null = options.auth ?? null;\n\n let endpointLogger: PluginLoggerLike = console;\n\n // Determine better-auth's basePath (defaults to /api/auth).\n // Computed lazily since `auth` may not exist until `init`.\n let betterAuthBasePath = '/api/auth';\n\n /**\n * Resolve an identity from a request's headers.\n */\n async function getIdentityFromHeaders(\n headers: Record<string, string | undefined>,\n ): Promise<InvectIdentity | null> {\n if (!auth) {\n return null;\n }\n\n const result = await resolveSession(auth, headers);\n if (!result) {\n return null;\n }\n\n if (customMapUser) {\n return customMapUser(result.user, result.session);\n }\n\n return defaultMapUser(result.user, result.session, mapRole);\n }\n\n async function getIdentityFromRequest(request: Request): Promise<InvectIdentity | null> {\n if (!auth) {\n return null;\n }\n\n const result = await callBetterAuthHandler(auth, request, '/get-session');\n const body = result?.body as { session?: BetterAuthSession; user?: BetterAuthUser } | null;\n if (!body?.session || !body?.user) {\n return null;\n }\n\n if (customMapUser) {\n return customMapUser(body.user, body.session);\n }\n\n return defaultMapUser(body.user, body.session, mapRole);\n }\n\n async function resolveEndpointIdentity(ctx: {\n identity: InvectIdentity | null;\n request: Request;\n }): Promise<InvectIdentity | null> {\n if (ctx.identity) {\n return ctx.identity;\n }\n\n return getIdentityFromRequest(ctx.request);\n }\n\n // Rate limiter for auth-sensitive endpoints (sign-in, sign-up, password reset)\n // 10 attempts per IP per 60 seconds by default\n const authRateLimiter = new RateLimiter(10, 60_000);\n\n // Periodic cleanup every 5 minutes to prevent memory leaks\n const cleanupInterval = setInterval(() => authRateLimiter.cleanup(), 5 * 60_000);\n cleanupInterval.unref?.(); // Don't keep the process alive\n\n // ----- Build plugin endpoint list -----\n // We create a catch-all endpoint that proxies everything under the prefix\n // to better-auth's handler. This covers all auth routes: sign-in, sign-up,\n // OAuth callbacks, session management, etc.\n\n const proxyMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;\n\n const endpoints = proxyMethods.map((method) => ({\n method: method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: `/${prefix}/*` as const,\n isPublic: true, // Auth routes must be accessible without existing session\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n request: Request;\n }) => {\n // Build a new Request for better-auth's handler.\n // The incoming path is: /plugins/auth/api/auth/sign-in/email (for example)\n // We need to reconstruct the URL that better-auth expects.\n const incomingUrl = new URL(ctx.request.url);\n endpointLogger.debug?.(`[auth-proxy] ${method} ${incomingUrl.pathname}`);\n\n // Strip the /plugins/<prefix> prefix from the pathname to get\n // the better-auth-relative path\n const pluginPrefixPattern = `/plugins/${prefix}`;\n let authPath = incomingUrl.pathname;\n const prefixIdx = authPath.indexOf(pluginPrefixPattern);\n if (prefixIdx !== -1) {\n authPath = authPath.slice(prefixIdx + pluginPrefixPattern.length);\n }\n if (!authPath.startsWith('/')) {\n authPath = '/' + authPath;\n }\n\n // ── Rate-limit auth-sensitive POST endpoints ──────────\n if (method === 'POST' && RATE_LIMITED_AUTH_PATHS.some((p) => authPath.includes(p))) {\n const clientIp =\n ctx.headers['x-forwarded-for']?.split(',')[0]?.trim() ||\n ctx.headers['x-real-ip'] ||\n 'unknown';\n const { limited, retryAfterMs } = authRateLimiter.isRateLimited(clientIp);\n if (limited) {\n return new Response(\n JSON.stringify({\n error: 'Too Many Requests',\n message: 'Too many authentication attempts. Please try again later.',\n }),\n {\n status: 429,\n headers: {\n 'content-type': 'application/json',\n 'retry-after': String(Math.ceil((retryAfterMs ?? 60_000) / 1000)),\n },\n },\n );\n }\n }\n\n // Construct the URL that better-auth expects\n const authUrl = new URL(`${incomingUrl.origin}${authPath}${incomingUrl.search}`);\n endpointLogger.debug?.(\n `[auth-proxy] Forwarding to better-auth: ${method} ${authUrl.pathname}`,\n );\n\n // Clone/forward the request to better-auth\n const authRequest = new Request(authUrl.toString(), {\n method: ctx.request.method,\n headers: ctx.request.headers,\n body: method !== 'GET' && method !== 'DELETE' ? ctx.request.body : undefined,\n // @ts-expect-error - duplex is needed for streaming bodies\n duplex: method !== 'GET' && method !== 'DELETE' ? 'half' : undefined,\n });\n\n // Let better-auth handle it\n const response = await auth!.handler(authRequest);\n endpointLogger.debug?.(`[auth-proxy] Response: ${response.status} ${response.statusText}`, {\n setCookie: response.headers.get('set-cookie') ? 'present' : 'absent',\n contentType: response.headers.get('content-type'),\n });\n return response;\n },\n }));\n\n // ----- Build the plugin -----\n return {\n id: 'better-auth',\n name: 'Better Auth',\n\n // Abstract schema for better-auth tables — the CLI reads this to generate\n // Drizzle/Prisma schema files that include auth tables automatically.\n schema: USER_AUTH_SCHEMA,\n\n // Also declare requiredTables for the startup existence check.\n requiredTables: ['user', 'session', 'account', 'verification'],\n setupInstructions:\n 'Run `npx invect-cli generate` to add the better-auth tables to your schema, ' +\n 'then `npx drizzle-kit push` (or `npx invect-cli migrate`) to apply.',\n\n endpoints: [\n // ── Auth Info (specific routes must come BEFORE the catch-all proxy) ───\n\n {\n method: 'GET' as const,\n path: `/${prefix}/me`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n core: {\n getPermissions: (identity: InvectIdentity | null) => string[];\n getResolvedRole: (identity: InvectIdentity) => string | null;\n };\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n const permissions = ctx.core.getPermissions(identity);\n const resolvedRole = identity ? ctx.core.getResolvedRole(identity) : null;\n\n return {\n status: 200,\n body: {\n identity: identity\n ? {\n id: identity.id,\n name: identity.name,\n role: identity.role,\n resolvedRole,\n }\n : null,\n permissions,\n isAuthenticated: !!identity,\n },\n };\n },\n },\n\n {\n method: 'GET' as const,\n path: `/${prefix}/roles`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n core: { getAvailableRoles: () => unknown };\n }) => {\n const roles = ctx.core.getAvailableRoles() as Array<{\n role: string;\n permissions: string[];\n }>;\n const missingRoles = AUTH_VISIBLE_ROLES.filter(\n (role) => !roles.some((entry) => entry.role === role),\n ).map((role) => ({ role, permissions: [] }));\n return { status: 200, body: { roles: [...roles, ...missingRoles] } };\n },\n },\n\n // ── User Management (admin-only) ──────────────────────\n\n {\n method: 'GET' as const,\n path: `/${prefix}/users`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n // Try better-auth's admin listUsers API first\n if (typeof api.listUsers === 'function') {\n const listUsers = api.listUsers as BetterAuthHeadersQueryMethod<\n { users?: unknown[] } | unknown[] | null\n >;\n const result = await listUsers({\n headers,\n query: {\n limit: ctx.query.limit ?? '100',\n offset: ctx.query.offset ?? '0',\n },\n });\n const users = Array.isArray(result) ? result : (result?.users ?? []);\n return { status: 200, body: { users } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/list-users',\n {\n method: 'GET',\n query: {\n limit: ctx.query.limit ?? '100',\n offset: ctx.query.offset ?? '0',\n },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return {\n status: 200,\n body: fallbackResult.body as Record<string, unknown>,\n };\n }\n\n // Fallback: query the user table directly via an internal request\n // to better-auth's get-session endpoint for the current user\n return {\n status: 200,\n body: {\n users: [],\n message:\n 'User listing requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to list users', {\n identity: sanitizeForLogging(identity),\n query: sanitizeForLogging(ctx.query),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to list users', err);\n }\n },\n },\n\n {\n method: 'POST' as const,\n path: `/${prefix}/users`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { email, password, name, role } = ctx.body as {\n email?: string;\n password?: string;\n name?: string;\n role?: string;\n };\n\n if (!email || !password) {\n return {\n status: 400,\n body: { error: 'email and password are required' },\n };\n }\n\n if (role !== undefined && !isAuthAssignableRole(role)) {\n return {\n status: 400,\n body: {\n error: 'role must be one of: ' + AUTH_ASSIGNABLE_ROLES.join(', '),\n },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n let result: BetterAuthApiUserResult | null = null;\n\n if (typeof api.createUser === 'function') {\n const createUser =\n api.createUser as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await createUser({\n headers,\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n });\n } else if (typeof api.signUpEmail === 'function') {\n const signUpEmail =\n api.signUpEmail as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await signUpEmail({\n headers,\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n });\n } else {\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/create-user',\n {\n method: 'POST',\n body: {\n email,\n password,\n name: name ?? email.split('@')[0],\n role: role ?? AUTH_DEFAULT_ROLE,\n },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n result = fallbackResult.body as BetterAuthApiUserResult;\n }\n }\n\n if (\n !result &&\n typeof api.createUser !== 'function' &&\n typeof api.signUpEmail !== 'function'\n ) {\n return {\n status: 500,\n body: { error: 'Auth API does not support user creation' },\n };\n }\n\n if (!result?.user) {\n return {\n status: 400,\n body: {\n error: 'Failed to create user',\n message: 'User may already exist',\n },\n };\n }\n\n return {\n status: 201,\n body: {\n user: {\n id: result.user.id,\n email: result.user.email,\n name: result.user.name,\n role: result.user.role,\n },\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to create user', {\n identity: sanitizeForLogging(identity),\n body: sanitizeForLogging({ email, name, role }),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to create user', err);\n }\n },\n },\n\n {\n method: 'PATCH' as const,\n path: `/${prefix}/users/:userId/role`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { userId } = ctx.params;\n const { role } = ctx.body as { role?: string };\n\n if (!isAuthAssignableRole(role)) {\n return {\n status: 400,\n body: {\n error: 'role must be one of: ' + AUTH_ASSIGNABLE_ROLES.join(', '),\n },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n // Try better-auth admin API for updating user\n if (typeof api.setRole === 'function') {\n const setRole = api.setRole as BetterAuthHeadersBodyMethod<unknown>;\n await setRole({\n headers,\n body: { userId, role },\n });\n return { status: 200, body: { success: true, userId, role } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/set-role',\n {\n method: 'POST',\n body: { userId, role },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return { status: 200, body: { success: true, userId, role } };\n }\n\n // Fallback: try updateUser API\n if (typeof api.updateUser === 'function') {\n const updateUser = api.updateUser as BetterAuthHeadersBodyParamsMethod<unknown>;\n await updateUser({\n headers,\n body: { role },\n params: { id: userId },\n });\n return { status: 200, body: { success: true, userId, role } };\n }\n\n return {\n status: 501,\n body: {\n error:\n 'Role update requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to update role', {\n identity: sanitizeForLogging(identity),\n params: sanitizeForLogging(ctx.params),\n body: sanitizeForLogging({ role }),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to update role', err);\n }\n },\n },\n\n {\n method: 'DELETE' as const,\n path: `/${prefix}/users/:userId`,\n isPublic: false,\n handler: async (ctx: {\n body: Record<string, unknown>;\n params: Record<string, string>;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n identity: InvectIdentity | null;\n request: Request;\n }) => {\n const identity = await resolveEndpointIdentity(ctx);\n if (!identity || identity.role !== 'admin') {\n return {\n status: 403,\n body: { error: 'Forbidden', message: 'Admin access required' },\n };\n }\n\n const { userId } = ctx.params;\n\n // Prevent deleting yourself\n if (identity.id === userId) {\n return {\n status: 400,\n body: { error: 'Cannot delete your own account' },\n };\n }\n\n try {\n const api = auth!.api as Record<string, unknown>;\n const headers = toHeaders(ctx.headers);\n\n if (typeof api.removeUser === 'function') {\n const removeUser = api.removeUser as BetterAuthHeadersBodyMethod<unknown>;\n await removeUser({\n headers,\n body: { userId },\n });\n return { status: 200, body: { success: true, userId } };\n }\n\n const fallbackResult = await callBetterAuthHandler(\n auth,\n ctx.request,\n '/admin/remove-user',\n {\n method: 'POST',\n body: { userId },\n },\n );\n if (fallbackResult && fallbackResult.status >= 200 && fallbackResult.status < 300) {\n return { status: 200, body: { success: true, userId } };\n }\n\n // Try deleteUser\n if (typeof api.deleteUser === 'function') {\n const deleteUser = api.deleteUser as BetterAuthHeadersBodyMethod<unknown>;\n await deleteUser({\n headers,\n body: { userId },\n });\n return { status: 200, body: { success: true, userId } };\n }\n\n return {\n status: 501,\n body: {\n error:\n 'User deletion requires the better-auth admin plugin. ' +\n 'Add `admin()` to your better-auth plugins config.',\n },\n };\n } catch (err) {\n endpointLogger.error('Failed to delete user', {\n identity: sanitizeForLogging(identity),\n params: sanitizeForLogging(ctx.params),\n error: getErrorLogDetails(err),\n });\n return toAuthApiErrorResponse('Failed to delete user', err);\n }\n },\n },\n\n // ── Better-Auth proxy catch-all (must come LAST so specific routes above win) ──\n ...endpoints,\n ],\n\n hooks: {\n /**\n * onRequest: Intercept incoming requests to resolve better-auth sessions.\n *\n * - Better-auth proxy routes are passed through untouched.\n * - For all other routes, we resolve the session. If no session exists\n * and `onSessionError === 'throw'`, we short-circuit with a 401.\n */\n onRequest: async (\n request: Request,\n context: { path: string; method: string; identity: InvectIdentity | null },\n ) => {\n // Skip session resolution for better-auth proxy routes\n if (isBetterAuthRoute(context.path, prefix, betterAuthBasePath)) {\n return; // Let the proxy endpoint handle it\n }\n\n // Skip for public paths\n if (publicPaths.some((p) => context.path.startsWith(p))) {\n return;\n }\n\n // Resolve session from request headers\n const headersRecord: Record<string, string | undefined> = {};\n request.headers.forEach((value, key) => {\n headersRecord[key] = value;\n });\n\n endpointLogger.debug?.(`[auth-onRequest] ${context.method} ${context.path}`, {\n hasCookie: !!headersRecord['cookie'],\n hasAuth: !!headersRecord['authorization'],\n });\n\n const identity = await getIdentityFromHeaders(headersRecord);\n\n endpointLogger.debug?.(`[auth-onRequest] Identity resolved:`, {\n authenticated: !!identity,\n userId: identity?.id,\n role: identity?.role,\n });\n\n // Write identity back so the framework adapter has it available.\n context.identity = identity;\n\n if (!identity && onSessionError === 'throw') {\n return {\n response: new Response(\n JSON.stringify({\n error: 'Unauthorized',\n message: 'Valid session required. Sign in via better-auth.',\n }),\n {\n status: 401,\n headers: { 'content-type': 'application/json' },\n },\n ),\n };\n }\n\n // Return void — identity is now on context.identity for the caller.\n return;\n },\n\n /**\n * onAuthorize: Baseline authorization guard using better-auth sessions.\n *\n * If the identity is already populated (from onRequest),\n * we defer to downstream authorization hooks. This hook is a fallback\n * for cases where the identity wasn't resolved upstream.\n */\n onAuthorize: async (context: {\n identity: InvectIdentity | null;\n action: InvectPermission;\n resource?: { type: string; id?: string };\n }) => {\n // If an identity is already attached, let downstream authorization proceed\n if (context.identity) {\n return;\n }\n\n // No identity — deny access\n return { allowed: false, reason: 'No valid better-auth session' };\n },\n },\n\n init: async (pluginContext) => {\n endpointLogger = pluginContext.logger;\n\n // ── Create internal better-auth instance if none was provided ────────\n if (!auth) {\n auth = await createInternalBetterAuth(pluginContext.config, options, pluginContext.logger);\n }\n\n betterAuthBasePath = auth.options?.basePath ?? '/api/auth';\n\n pluginContext.logger.info(\n `Better Auth plugin initialized (prefix: ${prefix}, basePath: ${betterAuthBasePath})`,\n );\n\n if (globalAdmins.length === 0) {\n pluginContext.logger.debug(\n 'No global admins configured. Pass `globalAdmins` to userAuth(...) to seed admin access.',\n );\n return;\n }\n\n for (const configuredAdmin of globalAdmins) {\n const adminEmail = configuredAdmin.email?.trim();\n const adminPassword = configuredAdmin.pw;\n const adminName = configuredAdmin.name?.trim() || 'Admin';\n\n if (!adminEmail || !adminPassword) {\n pluginContext.logger.debug(\n 'Skipping invalid global admin config: both email and pw are required.',\n );\n continue;\n }\n\n try {\n const authContext = await getAuthContext(auth);\n const existingAdminUser = unwrapFoundUser(\n await authContext?.internalAdapter?.findUserByEmail(adminEmail),\n );\n\n if (existingAdminUser) {\n if (existingAdminUser.role !== 'admin') {\n await authContext?.internalAdapter?.updateUser(existingAdminUser.id, {\n role: 'admin',\n });\n pluginContext.logger.info(`Admin user promoted: ${adminEmail}`);\n } else {\n pluginContext.logger.debug(`Admin user already configured: ${adminEmail}`);\n }\n\n continue;\n }\n\n const api = auth!.api as Record<string, unknown>;\n let result: BetterAuthApiUserResult | null = null;\n\n if (typeof api.createUser === 'function') {\n const createUser =\n api.createUser as BetterAuthHeadersBodyMethod<BetterAuthApiUserResult>;\n result = await createUser({\n headers: new Headers(),\n body: {\n email: adminEmail,\n password: adminPassword,\n name: adminName,\n role: 'admin',\n },\n }).catch((err: unknown) => {\n pluginContext.logger.error?.(\n `createUser failed for ${adminEmail}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n });\n } else if (typeof api.signUpEmail === 'function') {\n const signUpEmail = api.signUpEmail as BetterAuthBodyMethod<BetterAuthApiUserResult>;\n result = await signUpEmail({\n body: {\n email: adminEmail,\n password: adminPassword,\n name: adminName,\n },\n }).catch((err: unknown) => {\n pluginContext.logger.error?.(\n `signUpEmail failed for ${adminEmail}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return null;\n });\n } else {\n pluginContext.logger.debug(\n `Could not create global admin ${adminEmail}: auth.api.createUser/signUpEmail are unavailable.`,\n );\n continue;\n }\n\n if (result?.user) {\n // Promote the newly created user to admin via internal adapter\n const createdAuthContext = authContext ?? (await getAuthContext(auth));\n const createdAdminUser =\n unwrapFoundUser(\n await createdAuthContext?.internalAdapter?.findUserByEmail(adminEmail),\n ) ?? result.user;\n\n if (createdAdminUser?.id && createdAdminUser.role !== 'admin') {\n await createdAuthContext?.internalAdapter?.updateUser(createdAdminUser.id, {\n role: 'admin',\n });\n }\n\n pluginContext.logger.info(`Admin user created: ${adminEmail}`);\n } else {\n pluginContext.logger.debug(\n `Admin user already exists or could not be created: ${adminEmail}`,\n );\n }\n } catch (seedErr) {\n pluginContext.logger.debug(\n `Could not seed admin user (tables may not exist yet): ${adminEmail} — ${seedErr instanceof Error ? seedErr.message : String(seedErr)}`,\n );\n }\n }\n },\n\n $ERROR_CODES: {\n 'auth:session_expired': {\n message: 'Session has expired. Please sign in again.',\n status: 401,\n },\n 'auth:session_not_found': {\n message: 'No valid session found.',\n status: 401,\n },\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a path is a better-auth proxy route (should skip session checks).\n * Only matches the actual better-auth API proxy routes, not custom plugin endpoints.\n */\nfunction isBetterAuthRoute(path: string, prefix: string, basePath: string): boolean {\n return path.startsWith(`/plugins/${prefix}${basePath}`);\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @invect/user-auth — Backend Entry Point
3
3
  *
4
- * Wraps a better-auth instance as an Invect plugin, providing:
4
+ * Wraps a [better-auth](https://better-auth.com) instance as an Invect plugin,
5
+ * providing:
5
6
  * - Session-based identity resolution
6
7
  * - Proxied auth routes (sign-in, sign-up, OAuth, etc.)
7
8
  * - Authorization hook integration
@@ -13,30 +14,30 @@
13
14
  * @example
14
15
  * ```ts
15
16
  * // Simple — no separate auth setup needed:
16
- * import { betterAuthPlugin } from '@invect/user-auth';
17
+ * import { userAuth } from '@invect/user-auth';
17
18
  *
18
19
  * createInvectRouter({
19
20
  * databaseUrl: 'file:./dev.db',
20
- * plugins: [betterAuthPlugin()],
21
+ * plugins: [userAuth()],
21
22
  * });
22
23
  * ```
23
24
  *
24
25
  * @example
25
26
  * ```ts
26
- * // Advanced — provide your own instance:
27
+ * // Advanced — provide your own better-auth instance:
27
28
  * import { betterAuth } from 'better-auth';
28
- * import { betterAuthPlugin } from '@invect/user-auth';
29
+ * import { userAuth } from '@invect/user-auth';
29
30
  *
30
31
  * const auth = betterAuth({ ... });
31
32
  *
32
33
  * createInvectRouter({
33
34
  * databaseUrl: 'file:./dev.db',
34
- * plugins: [betterAuthPlugin({ auth })],
35
+ * plugins: [userAuth({ auth })],
35
36
  * });
36
37
  * ```
37
38
  *
38
39
  * @packageDocumentation
39
40
  */
40
- export { betterAuthPlugin, BETTER_AUTH_SCHEMA } from './plugin';
41
- export type { BetterAuthPluginOptions, BetterAuthPassthroughOptions, BetterAuthInstance, BetterAuthUser, BetterAuthSession, BetterAuthSessionResult, } from './types';
41
+ export { userAuth, USER_AUTH_SCHEMA } from './plugin';
42
+ export type { UserAuthPluginOptions, BetterAuthPassthroughOptions, BetterAuthInstance, BetterAuthUser, BetterAuthSession, BetterAuthSessionResult, } from './types';
42
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EACV,uBAAuB,EACvB,4BAA4B,EAC5B,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtD,YAAY,EACV,qBAAqB,EACrB,4BAA4B,EAC5B,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,SAAS,CAAC"}