@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 +3 -3
- package/dist/backend/index.cjs +12 -12
- package/dist/backend/index.cjs.map +1 -1
- package/dist/backend/index.d.ts +9 -8
- package/dist/backend/index.d.ts.map +1 -1
- package/dist/backend/index.mjs +11 -11
- package/dist/backend/index.mjs.map +1 -1
- package/dist/backend/plugin.d.ts +8 -8
- package/dist/backend/plugin.d.ts.map +1 -1
- package/dist/backend/types.d.ts +7 -5
- package/dist/backend/types.d.ts.map +1 -1
- package/dist/frontend/components/AuthenticatedInvect.d.ts +1 -1
- package/dist/frontend/index.cjs.map +1 -1
- package/dist/frontend/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/backend/index.mjs
CHANGED
|
@@ -215,14 +215,14 @@ function getErrorLogDetails(error) {
|
|
|
215
215
|
/**
|
|
216
216
|
* Abstract schema for better-auth's database tables.
|
|
217
217
|
*
|
|
218
|
-
* These definitions allow the Invect CLI (`npx invect generate`) to include
|
|
218
|
+
* These definitions allow the Invect CLI (`npx invect-cli generate`) to include
|
|
219
219
|
* the better-auth tables when generating Drizzle/Prisma schema files.
|
|
220
220
|
*
|
|
221
221
|
* The shapes match better-auth's default table structure. If your better-auth
|
|
222
222
|
* config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),
|
|
223
223
|
* you can extend these in your own config.
|
|
224
224
|
*/
|
|
225
|
-
const
|
|
225
|
+
const USER_AUTH_SCHEMA = {
|
|
226
226
|
user: {
|
|
227
227
|
tableName: "user",
|
|
228
228
|
order: 1,
|
|
@@ -567,11 +567,11 @@ async function createMySQLPool(connectionString) {
|
|
|
567
567
|
* @example
|
|
568
568
|
* ```ts
|
|
569
569
|
* // Simple: let the plugin manage better-auth internally
|
|
570
|
-
* import {
|
|
570
|
+
* import { userAuth } from '@invect/user-auth';
|
|
571
571
|
*
|
|
572
572
|
* app.use('/invect', createInvectRouter({
|
|
573
573
|
* databaseUrl: 'file:./dev.db',
|
|
574
|
-
* plugins: [
|
|
574
|
+
* plugins: [userAuth({
|
|
575
575
|
* globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],
|
|
576
576
|
* })],
|
|
577
577
|
* }));
|
|
@@ -581,7 +581,7 @@ async function createMySQLPool(connectionString) {
|
|
|
581
581
|
* ```ts
|
|
582
582
|
* // Advanced: provide your own better-auth instance
|
|
583
583
|
* import { betterAuth } from 'better-auth';
|
|
584
|
-
* import {
|
|
584
|
+
* import { userAuth } from '@invect/user-auth';
|
|
585
585
|
*
|
|
586
586
|
* const auth = betterAuth({
|
|
587
587
|
* database: { ... },
|
|
@@ -591,11 +591,11 @@ async function createMySQLPool(connectionString) {
|
|
|
591
591
|
*
|
|
592
592
|
* app.use('/invect', createInvectRouter({
|
|
593
593
|
* databaseUrl: 'file:./dev.db',
|
|
594
|
-
* plugins: [
|
|
594
|
+
* plugins: [userAuth({ auth })],
|
|
595
595
|
* }));
|
|
596
596
|
* ```
|
|
597
597
|
*/
|
|
598
|
-
function
|
|
598
|
+
function userAuth(options) {
|
|
599
599
|
const { prefix = DEFAULT_PREFIX, mapUser: customMapUser, mapRole = defaultMapRole, publicPaths = [], onSessionError = "throw", globalAdmins = [] } = options;
|
|
600
600
|
let auth = options.auth ?? null;
|
|
601
601
|
let endpointLogger = console;
|
|
@@ -674,14 +674,14 @@ function betterAuthPlugin(options) {
|
|
|
674
674
|
return {
|
|
675
675
|
id: "better-auth",
|
|
676
676
|
name: "Better Auth",
|
|
677
|
-
schema:
|
|
677
|
+
schema: USER_AUTH_SCHEMA,
|
|
678
678
|
requiredTables: [
|
|
679
679
|
"user",
|
|
680
680
|
"session",
|
|
681
681
|
"account",
|
|
682
682
|
"verification"
|
|
683
683
|
],
|
|
684
|
-
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.",
|
|
684
|
+
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.",
|
|
685
685
|
endpoints: [
|
|
686
686
|
{
|
|
687
687
|
method: "GET",
|
|
@@ -1077,7 +1077,7 @@ function betterAuthPlugin(options) {
|
|
|
1077
1077
|
betterAuthBasePath = auth.options?.basePath ?? "/api/auth";
|
|
1078
1078
|
pluginContext.logger.info(`Better Auth plugin initialized (prefix: ${prefix}, basePath: ${betterAuthBasePath})`);
|
|
1079
1079
|
if (globalAdmins.length === 0) {
|
|
1080
|
-
pluginContext.logger.debug("No global admins configured. Pass `globalAdmins` to
|
|
1080
|
+
pluginContext.logger.debug("No global admins configured. Pass `globalAdmins` to userAuth(...) to seed admin access.");
|
|
1081
1081
|
return;
|
|
1082
1082
|
}
|
|
1083
1083
|
for (const configuredAdmin of globalAdmins) {
|
|
@@ -1159,6 +1159,6 @@ function isBetterAuthRoute(path, prefix, basePath) {
|
|
|
1159
1159
|
return path.startsWith(`/plugins/${prefix}${basePath}`);
|
|
1160
1160
|
}
|
|
1161
1161
|
//#endregion
|
|
1162
|
-
export {
|
|
1162
|
+
export { USER_AUTH_SCHEMA, userAuth };
|
|
1163
1163
|
|
|
1164
1164
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"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":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"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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"}
|
package/dist/backend/plugin.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { InvectPlugin, InvectPluginSchema } from '@invect/core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { UserAuthPluginOptions } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Abstract schema for better-auth's database tables.
|
|
5
5
|
*
|
|
6
|
-
* These definitions allow the Invect CLI (`npx invect generate`) to include
|
|
6
|
+
* These definitions allow the Invect CLI (`npx invect-cli generate`) to include
|
|
7
7
|
* the better-auth tables when generating Drizzle/Prisma schema files.
|
|
8
8
|
*
|
|
9
9
|
* The shapes match better-auth's default table structure. If your better-auth
|
|
10
10
|
* config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),
|
|
11
11
|
* you can extend these in your own config.
|
|
12
12
|
*/
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const USER_AUTH_SCHEMA: InvectPluginSchema;
|
|
14
14
|
/**
|
|
15
15
|
* Create an Invect plugin that wraps a better-auth instance.
|
|
16
16
|
*
|
|
@@ -30,11 +30,11 @@ export declare const BETTER_AUTH_SCHEMA: InvectPluginSchema;
|
|
|
30
30
|
* @example
|
|
31
31
|
* ```ts
|
|
32
32
|
* // Simple: let the plugin manage better-auth internally
|
|
33
|
-
* import {
|
|
33
|
+
* import { userAuth } from '@invect/user-auth';
|
|
34
34
|
*
|
|
35
35
|
* app.use('/invect', createInvectRouter({
|
|
36
36
|
* databaseUrl: 'file:./dev.db',
|
|
37
|
-
* plugins: [
|
|
37
|
+
* plugins: [userAuth({
|
|
38
38
|
* globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],
|
|
39
39
|
* })],
|
|
40
40
|
* }));
|
|
@@ -44,7 +44,7 @@ export declare const BETTER_AUTH_SCHEMA: InvectPluginSchema;
|
|
|
44
44
|
* ```ts
|
|
45
45
|
* // Advanced: provide your own better-auth instance
|
|
46
46
|
* import { betterAuth } from 'better-auth';
|
|
47
|
-
* import {
|
|
47
|
+
* import { userAuth } from '@invect/user-auth';
|
|
48
48
|
*
|
|
49
49
|
* const auth = betterAuth({
|
|
50
50
|
* database: { ... },
|
|
@@ -54,9 +54,9 @@ export declare const BETTER_AUTH_SCHEMA: InvectPluginSchema;
|
|
|
54
54
|
*
|
|
55
55
|
* app.use('/invect', createInvectRouter({
|
|
56
56
|
* databaseUrl: 'file:./dev.db',
|
|
57
|
-
* plugins: [
|
|
57
|
+
* plugins: [userAuth({ auth })],
|
|
58
58
|
* }));
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
|
-
export declare function
|
|
61
|
+
export declare function userAuth(options: UserAuthPluginOptions): InvectPlugin;
|
|
62
62
|
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAIZ,kBAAkB,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAIZ,kBAAkB,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,qBAAqB,EAKtB,MAAM,SAAS,CAAC;AA8ZjB;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,kBA2E9B,CAAC;AAmNF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,qBAAqB,GAAG,YAAY,CAy1BrE"}
|
package/dist/backend/types.d.ts
CHANGED
|
@@ -171,9 +171,11 @@ export interface BetterAuthPassthroughOptions {
|
|
|
171
171
|
}>;
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
* Configuration for the
|
|
174
|
+
* Configuration for the User Auth Invect plugin.
|
|
175
|
+
*
|
|
176
|
+
* Wraps a [better-auth](https://better-auth.com) instance as an Invect plugin.
|
|
175
177
|
*/
|
|
176
|
-
export interface
|
|
178
|
+
export interface UserAuthPluginOptions {
|
|
177
179
|
/**
|
|
178
180
|
* A configured better-auth instance (the return value of `betterAuth()`).
|
|
179
181
|
*
|
|
@@ -184,12 +186,12 @@ export interface BetterAuthPluginOptions {
|
|
|
184
186
|
* @example
|
|
185
187
|
* ```ts
|
|
186
188
|
* // Simple: let the plugin manage better-auth internally
|
|
187
|
-
*
|
|
189
|
+
* userAuth({ globalAdmins: [{ email: 'admin@example.com', pw: 'secret' }] });
|
|
188
190
|
*
|
|
189
191
|
* // Advanced: provide your own instance for full control
|
|
190
192
|
* import { betterAuth } from 'better-auth';
|
|
191
193
|
* const auth = betterAuth({ ... });
|
|
192
|
-
*
|
|
194
|
+
* userAuth({ auth });
|
|
193
195
|
* ```
|
|
194
196
|
*/
|
|
195
197
|
auth?: BetterAuthInstance;
|
|
@@ -286,7 +288,7 @@ export interface BetterAuthPluginOptions {
|
|
|
286
288
|
*
|
|
287
289
|
* @example
|
|
288
290
|
* ```ts
|
|
289
|
-
*
|
|
291
|
+
* userAuth({
|
|
290
292
|
* betterAuthOptions: {
|
|
291
293
|
* session: { expiresIn: 60 * 60 * 24 * 30 }, // 30 days
|
|
292
294
|
* advanced: { useSecureCookies: true },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAW/D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,eAAe,EAAE,CACf,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,cAAc,GAAG;QAAE,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACvE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC/F;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,yBAAyB,CAAC;CAC7C;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEjD,+BAA+B;IAC/B,GAAG,EAAE;QACH,UAAU,EAAE,CAAC,OAAO,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;QACvF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IAEF,4CAA4C;IAC5C,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IAEF,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAEtC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAMD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,4BAA4B;IAC3C,kDAAkD;IAClD,gBAAgB,CAAC,EAAE;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,wBAAwB,CAAC,EAAE,OAAO,CAAC;QACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,6BAA6B,CAAC,EAAE,OAAO,CAAC;KACzC,CAAC;IAEF,6BAA6B;IAC7B,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE;YACZ,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,QAAQ,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;SACtC,CAAC;KACH,CAAC;IAEF,qCAAqC;IACrC,OAAO,CAAC,EAAE;QACR,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,cAAc,CAAC,EAAE;YACf,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,sBAAsB,CAAC,EAAE,OAAO,CAAC;YACjC,oBAAoB,CAAC,EAAE,OAAO,CAAC;YAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;SAC7B,CAAC;KACH,CAAC;IAEF,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1C,qBAAqB;IACrB,SAAS,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IAEF,2CAA2C;IAC3C,QAAQ,CAAC,EAAE;QACT,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,uBAAuB,CAAC,EAAE,sBAAsB,CAAC;QACjD,qBAAqB,CAAC,EAAE;YACtB,OAAO,EAAE,OAAO,CAAC;YACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;YAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;SACjB,CAAC;QACF,SAAS,CAAC,EAAE;YACV,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;YAC5B,iBAAiB,CAAC,EAAE,OAAO,CAAC;SAC7B,CAAC;KACH,CAAC;IAEF,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAExC,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAMD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAE1B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;IAE7D;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,iBAAiB,KACvB,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAE9C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,UAAU,CAAC;IAE1D;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAEtC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAEvC;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* When authenticated, renders the full Invect UI.
|
|
10
10
|
*
|
|
11
11
|
* Sign-up is disabled — initial admin users are configured explicitly via
|
|
12
|
-
* `
|
|
12
|
+
* `userAuth({ globalAdmins: [...] })`, and subsequent users are
|
|
13
13
|
* created by the admin through the User Management panel.
|
|
14
14
|
*
|
|
15
15
|
* @example
|