@invect/user-auth 0.0.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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/backend/index.cjs +1166 -0
- package/dist/backend/index.cjs.map +1 -0
- package/dist/backend/index.d.ts +42 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.mjs +1164 -0
- package/dist/backend/index.mjs.map +1 -0
- package/dist/backend/plugin.d.ts +62 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/types.d.ts +299 -0
- package/dist/backend/types.d.ts.map +1 -0
- package/dist/frontend/components/AuthGate.d.ts +17 -0
- package/dist/frontend/components/AuthGate.d.ts.map +1 -0
- package/dist/frontend/components/AuthenticatedInvect.d.ts +129 -0
- package/dist/frontend/components/AuthenticatedInvect.d.ts.map +1 -0
- package/dist/frontend/components/ProfilePage.d.ts +10 -0
- package/dist/frontend/components/ProfilePage.d.ts.map +1 -0
- package/dist/frontend/components/SidebarUserMenu.d.ts +12 -0
- package/dist/frontend/components/SidebarUserMenu.d.ts.map +1 -0
- package/dist/frontend/components/SignInForm.d.ts +14 -0
- package/dist/frontend/components/SignInForm.d.ts.map +1 -0
- package/dist/frontend/components/SignInPage.d.ts +19 -0
- package/dist/frontend/components/SignInPage.d.ts.map +1 -0
- package/dist/frontend/components/UserButton.d.ts +15 -0
- package/dist/frontend/components/UserButton.d.ts.map +1 -0
- package/dist/frontend/components/UserManagement.d.ts +19 -0
- package/dist/frontend/components/UserManagement.d.ts.map +1 -0
- package/dist/frontend/components/UserManagementPage.d.ts +9 -0
- package/dist/frontend/components/UserManagementPage.d.ts.map +1 -0
- package/dist/frontend/index.cjs +1262 -0
- package/dist/frontend/index.cjs.map +1 -0
- package/dist/frontend/index.d.ts +29 -0
- package/dist/frontend/index.d.ts.map +1 -0
- package/dist/frontend/index.mjs +1250 -0
- package/dist/frontend/index.mjs.map +1 -0
- package/dist/frontend/plugins/authFrontendPlugin.d.ts +14 -0
- package/dist/frontend/plugins/authFrontendPlugin.d.ts.map +1 -0
- package/dist/frontend/providers/AuthProvider.d.ts +46 -0
- package/dist/frontend/providers/AuthProvider.d.ts.map +1 -0
- package/dist/roles-BOY5N82v.cjs +74 -0
- package/dist/roles-BOY5N82v.cjs.map +1 -0
- package/dist/roles-CZuKFEpJ.mjs +33 -0
- package/dist/roles-CZuKFEpJ.mjs.map +1 -0
- package/dist/shared/roles.d.ts +11 -0
- package/dist/shared/roles.d.ts.map +1 -0
- package/dist/shared/types.cjs +0 -0
- package/dist/shared/types.d.ts +46 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.mjs +1 -0
- package/package.json +116 -0
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { InvectPlugin, InvectPluginSchema } from '@invect/core';
|
|
2
|
+
import type { BetterAuthPluginOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract schema for better-auth's database tables.
|
|
5
|
+
*
|
|
6
|
+
* These definitions allow the Invect CLI (`npx invect generate`) to include
|
|
7
|
+
* the better-auth tables when generating Drizzle/Prisma schema files.
|
|
8
|
+
*
|
|
9
|
+
* The shapes match better-auth's default table structure. If your better-auth
|
|
10
|
+
* config adds extra fields (e.g., via plugins like `twoFactor`, `organization`),
|
|
11
|
+
* you can extend these in your own config.
|
|
12
|
+
*/
|
|
13
|
+
export declare const BETTER_AUTH_SCHEMA: InvectPluginSchema;
|
|
14
|
+
/**
|
|
15
|
+
* Create an Invect plugin that wraps a better-auth instance.
|
|
16
|
+
*
|
|
17
|
+
* This plugin:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Proxies better-auth routes** — All of better-auth's HTTP endpoints
|
|
20
|
+
* (sign-in, sign-up, sign-out, OAuth callbacks, session, etc.) are mounted
|
|
21
|
+
* under the plugin endpoint space at `/plugins/auth/api/auth/*` (configurable).
|
|
22
|
+
*
|
|
23
|
+
* 2. **Resolves sessions → identities** — On every Invect API request, the
|
|
24
|
+
* `onRequest` hook reads the session cookie / bearer token via
|
|
25
|
+
* `auth.api.getSession()` and populates `InvectIdentity`.
|
|
26
|
+
*
|
|
27
|
+
* 3. **Handles authorization** — The `onAuthorize` hook lets better-auth's
|
|
28
|
+
* session decide whether a request is allowed.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* // Simple: let the plugin manage better-auth internally
|
|
33
|
+
* import { betterAuthPlugin } from '@invect/user-auth';
|
|
34
|
+
*
|
|
35
|
+
* app.use('/invect', createInvectRouter({
|
|
36
|
+
* databaseUrl: 'file:./dev.db',
|
|
37
|
+
* plugins: [betterAuthPlugin({
|
|
38
|
+
* globalAdmins: [{ email: 'admin@co.com', pw: 'secret' }],
|
|
39
|
+
* })],
|
|
40
|
+
* }));
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* // Advanced: provide your own better-auth instance
|
|
46
|
+
* import { betterAuth } from 'better-auth';
|
|
47
|
+
* import { betterAuthPlugin } from '@invect/user-auth';
|
|
48
|
+
*
|
|
49
|
+
* const auth = betterAuth({
|
|
50
|
+
* database: { ... },
|
|
51
|
+
* emailAndPassword: { enabled: true },
|
|
52
|
+
* // ... your better-auth config
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* app.use('/invect', createInvectRouter({
|
|
56
|
+
* databaseUrl: 'file:./dev.db',
|
|
57
|
+
* plugins: [betterAuthPlugin({ auth })],
|
|
58
|
+
* }));
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function betterAuthPlugin(options: BetterAuthPluginOptions): InvectPlugin;
|
|
62
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +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,uBAAuB,EAKxB,MAAM,SAAS,CAAC;AA8ZjB;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,kBA2EhC,CAAC;AAmNF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CAy1B/E"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { InvectIdentity, InvectRole } from '@invect/core';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal representation of a better-auth User object.
|
|
4
|
+
*/
|
|
5
|
+
export interface BetterAuthUser {
|
|
6
|
+
id: string;
|
|
7
|
+
name?: string | null;
|
|
8
|
+
email?: string | null;
|
|
9
|
+
image?: string | null;
|
|
10
|
+
role?: string | null;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Minimal representation of a better-auth Session object.
|
|
15
|
+
*/
|
|
16
|
+
export interface BetterAuthSession {
|
|
17
|
+
id: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
token: string;
|
|
20
|
+
expiresAt: Date | string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The shape returned by `auth.api.getSession()`.
|
|
25
|
+
*/
|
|
26
|
+
export interface BetterAuthSessionResult {
|
|
27
|
+
user: BetterAuthUser;
|
|
28
|
+
session: BetterAuthSession;
|
|
29
|
+
}
|
|
30
|
+
export interface BetterAuthInternalAdapter {
|
|
31
|
+
findUserByEmail: (email: string) => Promise<BetterAuthUser | {
|
|
32
|
+
user?: BetterAuthUser | null;
|
|
33
|
+
} | null>;
|
|
34
|
+
updateUser: (userId: string, data: Record<string, unknown>) => Promise<BetterAuthUser | null>;
|
|
35
|
+
}
|
|
36
|
+
export interface BetterAuthContext {
|
|
37
|
+
internalAdapter?: BetterAuthInternalAdapter;
|
|
38
|
+
}
|
|
39
|
+
export interface BetterAuthGlobalAdmin {
|
|
40
|
+
email?: string;
|
|
41
|
+
pw?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Minimal Auth instance type — what `betterAuth()` returns.
|
|
46
|
+
*
|
|
47
|
+
* We intentionally keep this narrow so we don't couple to better-auth's
|
|
48
|
+
* full internal types. The plugin only needs `handler` and `api.getSession`.
|
|
49
|
+
*/
|
|
50
|
+
export interface BetterAuthInstance {
|
|
51
|
+
/** Handles HTTP requests — the core request router. */
|
|
52
|
+
handler: (request: Request) => Promise<Response>;
|
|
53
|
+
/** Server-side API methods. */
|
|
54
|
+
api: {
|
|
55
|
+
getSession: (context: {
|
|
56
|
+
headers: Headers;
|
|
57
|
+
}) => Promise<BetterAuthSessionResult | null>;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
};
|
|
60
|
+
/** Auth options (used to read basePath). */
|
|
61
|
+
options?: {
|
|
62
|
+
basePath?: string;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
};
|
|
65
|
+
$context?: Promise<BetterAuthContext>;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cookie attribute options (subset of Better Auth's CookieOptions).
|
|
70
|
+
*/
|
|
71
|
+
export interface CookieAttributeOptions {
|
|
72
|
+
secure?: boolean;
|
|
73
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
74
|
+
path?: string;
|
|
75
|
+
domain?: string;
|
|
76
|
+
maxAge?: number;
|
|
77
|
+
httpOnly?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A reasonable subset of Better Auth's configuration that can be passed
|
|
81
|
+
* through when the plugin creates an internal better-auth instance.
|
|
82
|
+
*
|
|
83
|
+
* These are ignored when you provide your own `auth` instance.
|
|
84
|
+
*
|
|
85
|
+
* @see https://www.better-auth.com/docs/reference/auth
|
|
86
|
+
*/
|
|
87
|
+
export interface BetterAuthPassthroughOptions {
|
|
88
|
+
/** Email and password authentication settings. */
|
|
89
|
+
emailAndPassword?: {
|
|
90
|
+
enabled?: boolean;
|
|
91
|
+
disableSignUp?: boolean;
|
|
92
|
+
requireEmailVerification?: boolean;
|
|
93
|
+
minPasswordLength?: number;
|
|
94
|
+
maxPasswordLength?: number;
|
|
95
|
+
autoSignIn?: boolean;
|
|
96
|
+
revokeSessionsOnPasswordReset?: boolean;
|
|
97
|
+
};
|
|
98
|
+
/** Session configuration. */
|
|
99
|
+
session?: {
|
|
100
|
+
expiresIn?: number;
|
|
101
|
+
updateAge?: number;
|
|
102
|
+
disableSessionRefresh?: boolean;
|
|
103
|
+
freshAge?: number;
|
|
104
|
+
cookieCache?: {
|
|
105
|
+
enabled?: boolean;
|
|
106
|
+
maxAge?: number;
|
|
107
|
+
strategy?: 'compact' | 'jwt' | 'jwe';
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
/** Account linking configuration. */
|
|
111
|
+
account?: {
|
|
112
|
+
updateAccountOnSignIn?: boolean;
|
|
113
|
+
accountLinking?: {
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
disableImplicitLinking?: boolean;
|
|
116
|
+
allowDifferentEmails?: boolean;
|
|
117
|
+
allowUnlinkingAll?: boolean;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
/** Social / OAuth providers (passed directly to Better Auth). */
|
|
121
|
+
socialProviders?: Record<string, unknown>;
|
|
122
|
+
/** Rate limiting. */
|
|
123
|
+
rateLimit?: {
|
|
124
|
+
enabled?: boolean;
|
|
125
|
+
window?: number;
|
|
126
|
+
max?: number;
|
|
127
|
+
};
|
|
128
|
+
/** Advanced options — use with caution. */
|
|
129
|
+
advanced?: {
|
|
130
|
+
useSecureCookies?: boolean;
|
|
131
|
+
disableCSRFCheck?: boolean;
|
|
132
|
+
cookiePrefix?: string;
|
|
133
|
+
defaultCookieAttributes?: CookieAttributeOptions;
|
|
134
|
+
crossSubDomainCookies?: {
|
|
135
|
+
enabled: boolean;
|
|
136
|
+
additionalCookies?: string[];
|
|
137
|
+
domain?: string;
|
|
138
|
+
};
|
|
139
|
+
ipAddress?: {
|
|
140
|
+
ipAddressHeaders?: string[];
|
|
141
|
+
disableIpTracking?: boolean;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
/** Database hooks (lifecycle callbacks on core tables). */
|
|
145
|
+
databaseHooks?: Record<string, unknown>;
|
|
146
|
+
/** Lifecycle hooks (before/after request processing). */
|
|
147
|
+
hooks?: Record<string, unknown>;
|
|
148
|
+
/** Paths to disable (e.g. sign-up). */
|
|
149
|
+
disabledPaths?: string[];
|
|
150
|
+
/**
|
|
151
|
+
* Secret used for encryption, signing and hashing.
|
|
152
|
+
*
|
|
153
|
+
* Better Auth defaults to `BETTER_AUTH_SECRET` or `AUTH_SECRET` env vars.
|
|
154
|
+
* In production, this **must** be set or Better Auth will throw.
|
|
155
|
+
*
|
|
156
|
+
* Generate one with: `openssl rand -base64 32`
|
|
157
|
+
*/
|
|
158
|
+
secret?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Versioned secrets for non-destructive secret rotation.
|
|
161
|
+
*
|
|
162
|
+
* The first entry is the current key used for new encryption.
|
|
163
|
+
* Remaining entries are decryption-only (previous rotations).
|
|
164
|
+
*
|
|
165
|
+
* Can also be set via `BETTER_AUTH_SECRETS` env var:
|
|
166
|
+
* `BETTER_AUTH_SECRETS=2:base64secret,1:base64secret`
|
|
167
|
+
*/
|
|
168
|
+
secrets?: Array<{
|
|
169
|
+
version: number;
|
|
170
|
+
value: string;
|
|
171
|
+
}>;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Configuration for the Better Auth Invect plugin.
|
|
175
|
+
*/
|
|
176
|
+
export interface BetterAuthPluginOptions {
|
|
177
|
+
/**
|
|
178
|
+
* A configured better-auth instance (the return value of `betterAuth()`).
|
|
179
|
+
*
|
|
180
|
+
* When omitted, the plugin creates an internal better-auth instance
|
|
181
|
+
* automatically using Invect's database configuration. This is the
|
|
182
|
+
* recommended approach for simple setups — no separate `auth.ts` file needed.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* // Simple: let the plugin manage better-auth internally
|
|
187
|
+
* betterAuthPlugin({ globalAdmins: [{ email: 'admin@example.com', pw: 'secret' }] });
|
|
188
|
+
*
|
|
189
|
+
* // Advanced: provide your own instance for full control
|
|
190
|
+
* import { betterAuth } from 'better-auth';
|
|
191
|
+
* const auth = betterAuth({ ... });
|
|
192
|
+
* betterAuthPlugin({ auth });
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
auth?: BetterAuthInstance;
|
|
196
|
+
/**
|
|
197
|
+
* Database for the internal better-auth instance.
|
|
198
|
+
*
|
|
199
|
+
* Accepts anything that `betterAuth({ database })` accepts — e.g. a
|
|
200
|
+
* `better-sqlite3` instance, a `pg` Pool, etc.
|
|
201
|
+
*
|
|
202
|
+
* When omitted, the plugin creates a database client from Invect's
|
|
203
|
+
* `baseDatabaseConfig` (connection string + type).
|
|
204
|
+
*
|
|
205
|
+
* Only used when `auth` is **not** provided.
|
|
206
|
+
*/
|
|
207
|
+
database?: unknown;
|
|
208
|
+
/**
|
|
209
|
+
* Base URL for the auth server (used for cookies, CSRF tokens, etc.).
|
|
210
|
+
*
|
|
211
|
+
* Defaults to `BETTER_AUTH_URL` env var, or `http://localhost:PORT`.
|
|
212
|
+
*
|
|
213
|
+
* Only used when `auth` is **not** provided.
|
|
214
|
+
*/
|
|
215
|
+
baseURL?: string;
|
|
216
|
+
/**
|
|
217
|
+
* Origins trusted for CORS / cookie sharing.
|
|
218
|
+
*
|
|
219
|
+
* Defaults to common local development origins plus the `baseURL`.
|
|
220
|
+
*
|
|
221
|
+
* Only used when `auth` is **not** provided.
|
|
222
|
+
*/
|
|
223
|
+
trustedOrigins?: string[] | ((request: Request) => string[]);
|
|
224
|
+
/**
|
|
225
|
+
* URL path prefix where better-auth routes are mounted within Invect's
|
|
226
|
+
* plugin endpoint space.
|
|
227
|
+
*
|
|
228
|
+
* Plugin endpoints are served at `/plugins/<prefix>/...`.
|
|
229
|
+
* better-auth's own basePath (usually `/api/auth`) is mapped under this.
|
|
230
|
+
*
|
|
231
|
+
* @default 'auth'
|
|
232
|
+
*/
|
|
233
|
+
prefix?: string;
|
|
234
|
+
/**
|
|
235
|
+
* Map a better-auth user + session to an `InvectIdentity`.
|
|
236
|
+
*
|
|
237
|
+
* Override this to customise role mapping, team resolution, or resource
|
|
238
|
+
* access from your better-auth user model.
|
|
239
|
+
*
|
|
240
|
+
* @default — Uses `user.id`, `user.name`, and maps `user.role` to an Invect role.
|
|
241
|
+
*/
|
|
242
|
+
mapUser?: (user: BetterAuthUser, session: BetterAuthSession) => InvectIdentity | Promise<InvectIdentity>;
|
|
243
|
+
/**
|
|
244
|
+
* Map a better-auth user role string to an Invect role.
|
|
245
|
+
* Only used when `mapUser` is not provided.
|
|
246
|
+
*
|
|
247
|
+
* @default — Maps admin/RBAC roles directly, aliases readonly → viewer,
|
|
248
|
+
* and falls back to default for missing or unknown roles.
|
|
249
|
+
*/
|
|
250
|
+
mapRole?: (role: string | null | undefined) => InvectRole;
|
|
251
|
+
/**
|
|
252
|
+
* Paths (relative to the Invect mount point) that should be accessible
|
|
253
|
+
* without a valid session.
|
|
254
|
+
*
|
|
255
|
+
* The better-auth proxy routes (sign-in, sign-up, callback, etc.) are
|
|
256
|
+
* always public regardless of this setting.
|
|
257
|
+
*
|
|
258
|
+
* @default []
|
|
259
|
+
*/
|
|
260
|
+
publicPaths?: string[];
|
|
261
|
+
/**
|
|
262
|
+
* What to do when session resolution fails (network error, malformed token, etc.).
|
|
263
|
+
*
|
|
264
|
+
* - `'throw'` — Return 401 Unauthorized.
|
|
265
|
+
* - `'continue'` — Set identity to null and proceed (useful for mixed auth).
|
|
266
|
+
*
|
|
267
|
+
* @default 'throw'
|
|
268
|
+
*/
|
|
269
|
+
onSessionError?: 'throw' | 'continue';
|
|
270
|
+
/**
|
|
271
|
+
* Explicit list of global admin accounts to seed and/or promote on startup.
|
|
272
|
+
*
|
|
273
|
+
* Each configured admin is ensured to exist with the `admin` role.
|
|
274
|
+
* This is intentionally explicit; the plugin does not implicitly read
|
|
275
|
+
* admin credentials from environment variables.
|
|
276
|
+
*/
|
|
277
|
+
globalAdmins?: BetterAuthGlobalAdmin[];
|
|
278
|
+
/**
|
|
279
|
+
* Better Auth configuration options passed through to the internal instance.
|
|
280
|
+
*
|
|
281
|
+
* Use this to configure session behaviour, email/password settings,
|
|
282
|
+
* social providers, rate limiting, advanced cookie options, etc.
|
|
283
|
+
* without needing to create your own `betterAuth()` instance.
|
|
284
|
+
*
|
|
285
|
+
* Ignored when `auth` is provided (you already have full control).
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* betterAuthPlugin({
|
|
290
|
+
* betterAuthOptions: {
|
|
291
|
+
* session: { expiresIn: 60 * 60 * 24 * 30 }, // 30 days
|
|
292
|
+
* advanced: { useSecureCookies: true },
|
|
293
|
+
* },
|
|
294
|
+
* })
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
betterAuthOptions?: BetterAuthPassthroughOptions;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthGate — Conditionally renders children based on auth state.
|
|
3
|
+
*
|
|
4
|
+
* Useful for protecting routes or showing different content for
|
|
5
|
+
* authenticated vs unauthenticated users.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode } from 'react';
|
|
8
|
+
export interface AuthGateProps {
|
|
9
|
+
/** Content to show when authenticated */
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/** Content to show when NOT authenticated (defaults to null) */
|
|
12
|
+
fallback?: ReactNode;
|
|
13
|
+
/** Content to show while loading (defaults to null) */
|
|
14
|
+
loading?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
export declare function AuthGate({ children, fallback, loading }: AuthGateProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=AuthGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthGate.d.ts","sourceRoot":"","sources":["../../../src/frontend/components/AuthGate.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,QAAe,EAAE,OAAc,EAAE,EAAE,aAAa,2CAYpF"}
|