@objectstack/plugin-auth 3.2.4 → 3.2.5
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +8 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/auth-manager.test.ts +39 -0
- package/src/auth-manager.ts +9 -1
- package/src/auth-plugin.ts +5 -22
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/plugin-auth@3.2.
|
|
2
|
+
> @objectstack/plugin-auth@3.2.5 build /home/runner/work/spec/spec/packages/plugins/plugin-auth
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m26.88 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m65.75 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 63ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m29.59 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m66.40 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 72ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m421.
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m421.
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 7688ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m421.97 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m421.97 KB[39m
|
package/CHANGELOG.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -91,6 +91,13 @@ interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
|
91
91
|
* Required for database operations using ObjectQL instead of third-party ORMs
|
|
92
92
|
*/
|
|
93
93
|
dataEngine?: IDataEngine;
|
|
94
|
+
/**
|
|
95
|
+
* Base path for auth routes
|
|
96
|
+
* Forwarded to better-auth's basePath option so it can match incoming
|
|
97
|
+
* request URLs without manual path rewriting.
|
|
98
|
+
* @default '/api/v1/auth'
|
|
99
|
+
*/
|
|
100
|
+
basePath?: string;
|
|
94
101
|
}
|
|
95
102
|
/**
|
|
96
103
|
* Authentication Manager
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,13 @@ interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
|
91
91
|
* Required for database operations using ObjectQL instead of third-party ORMs
|
|
92
92
|
*/
|
|
93
93
|
dataEngine?: IDataEngine;
|
|
94
|
+
/**
|
|
95
|
+
* Base path for auth routes
|
|
96
|
+
* Forwarded to better-auth's basePath option so it can match incoming
|
|
97
|
+
* request URLs without manual path rewriting.
|
|
98
|
+
* @default '/api/v1/auth'
|
|
99
|
+
*/
|
|
100
|
+
basePath?: string;
|
|
94
101
|
}
|
|
95
102
|
/**
|
|
96
103
|
* Authentication Manager
|
package/dist/index.js
CHANGED
|
@@ -366,8 +366,7 @@ var AuthManager = class {
|
|
|
366
366
|
// Base configuration
|
|
367
367
|
secret: this.config.secret || this.generateSecret(),
|
|
368
368
|
baseURL: this.config.baseUrl || "http://localhost:3000",
|
|
369
|
-
basePath: "/",
|
|
370
|
-
// ← 关键修复!告诉 better-auth 路径已被剥离
|
|
369
|
+
basePath: this.config.basePath || "/api/v1/auth",
|
|
371
370
|
// Database adapter configuration
|
|
372
371
|
database: this.createDatabaseConfig(),
|
|
373
372
|
// Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)
|
|
@@ -601,19 +600,7 @@ var AuthPlugin = class {
|
|
|
601
600
|
const rawApp = httpServer.getRawApp();
|
|
602
601
|
rawApp.all(`${basePath}/*`, async (c) => {
|
|
603
602
|
try {
|
|
604
|
-
const
|
|
605
|
-
const url = new URL(request.url);
|
|
606
|
-
const authPath = url.pathname.replace(basePath, "");
|
|
607
|
-
const rewrittenUrl = new URL(authPath || "/", url.origin);
|
|
608
|
-
rewrittenUrl.search = url.search;
|
|
609
|
-
const rewrittenRequest = new Request(rewrittenUrl, {
|
|
610
|
-
method: request.method,
|
|
611
|
-
headers: request.headers,
|
|
612
|
-
body: request.body,
|
|
613
|
-
duplex: "half"
|
|
614
|
-
// Required for Request with body
|
|
615
|
-
});
|
|
616
|
-
const response = await this.authManager.handleRequest(rewrittenRequest);
|
|
603
|
+
const response = await this.authManager.handleRequest(c.req.raw);
|
|
617
604
|
if (response.status >= 500) {
|
|
618
605
|
try {
|
|
619
606
|
const body = await response.clone().text();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/auth-manager.ts","../src/objectql-adapter.ts","../src/auth-schema-config.ts","../src/auth-plugin.ts","../src/objects/auth-user.object.ts","../src/objects/auth-session.object.ts","../src/objects/auth-account.object.ts","../src/objects/auth-verification.object.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-auth\n * \n * Authentication & Identity Plugin for ObjectStack\n * Powered by better-auth for robust, secure authentication\n * Uses ObjectQL for data persistence (no third-party ORM required)\n */\n\nexport * from './auth-plugin.js';\nexport * from './auth-manager.js';\nexport * from './objectql-adapter.js';\nexport * from './auth-schema-config.js';\nexport * from './objects/index.js';\nexport type { AuthConfig, AuthProviderConfig, AuthPluginConfig } from '@objectstack/spec/system';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { betterAuth } from 'better-auth';\nimport type { Auth, BetterAuthOptions } from 'better-auth';\nimport { organization } from 'better-auth/plugins/organization';\nimport { twoFactor } from 'better-auth/plugins/two-factor';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { AuthConfig } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/core';\nimport { createObjectQLAdapterFactory } from './objectql-adapter.js';\nimport {\n AUTH_USER_CONFIG,\n AUTH_SESSION_CONFIG,\n AUTH_ACCOUNT_CONFIG,\n AUTH_VERIFICATION_CONFIG,\n buildOrganizationPluginSchema,\n buildTwoFactorPluginSchema,\n} from './auth-schema-config.js';\n\n/**\n * Extended options for AuthManager\n */\nexport interface AuthManagerOptions extends Partial<AuthConfig> {\n /**\n * Better-Auth instance (for advanced use cases)\n * If not provided, one will be created from config\n */\n authInstance?: Auth<any>;\n\n /**\n * ObjectQL Data Engine instance\n * Required for database operations using ObjectQL instead of third-party ORMs\n */\n dataEngine?: IDataEngine;\n}\n\n/**\n * Authentication Manager\n *\n * Wraps better-auth and provides authentication services for ObjectStack.\n * Supports multiple authentication methods:\n * - Email/password\n * - OAuth providers (Google, GitHub, etc.)\n * - Magic links\n * - Two-factor authentication\n * - Passkeys\n * - Organization/teams\n */\nexport class AuthManager {\n private auth: Auth<any> | null = null;\n private config: AuthManagerOptions;\n\n constructor(config: AuthManagerOptions) {\n this.config = config;\n\n // Use provided auth instance\n if (config.authInstance) {\n this.auth = config.authInstance;\n }\n // Don't create auth instance automatically to avoid database initialization errors\n // It will be created lazily when needed\n }\n\n /**\n * Get or create the better-auth instance (lazy initialization)\n */\n private getOrCreateAuth(): Auth<any> {\n if (!this.auth) {\n this.auth = this.createAuthInstance();\n }\n return this.auth;\n }\n\n /**\n * Create a better-auth instance from configuration\n */\n private createAuthInstance(): Auth<any> {\n const betterAuthConfig: BetterAuthOptions = {\n // Base configuration\n secret: this.config.secret || this.generateSecret(),\n baseURL: this.config.baseUrl || 'http://localhost:3000',\n basePath: '/', // ← 关键修复!告诉 better-auth 路径已被剥离\n\n // Database adapter configuration\n database: this.createDatabaseConfig(),\n\n // Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)\n // These declarations tell better-auth the actual table/column names used\n // by ObjectStack's protocol layer, enabling automatic transformation via\n // createAdapterFactory.\n user: {\n ...AUTH_USER_CONFIG,\n },\n account: {\n ...AUTH_ACCOUNT_CONFIG,\n },\n verification: {\n ...AUTH_VERIFICATION_CONFIG,\n },\n\n // Email configuration\n emailAndPassword: {\n enabled: true,\n },\n\n // Session configuration\n session: {\n ...AUTH_SESSION_CONFIG,\n expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days default\n updateAge: this.config.session?.updateAge || 60 * 60 * 24, // 1 day default\n },\n\n // better-auth plugins — registered based on AuthPluginConfig flags\n plugins: this.buildPluginList(),\n };\n\n return betterAuth(betterAuthConfig);\n }\n\n /**\n * Build the list of better-auth plugins based on AuthPluginConfig flags.\n *\n * Each plugin that introduces its own database tables is configured with\n * a `schema` option containing the appropriate snake_case field mappings,\n * so that `createAdapterFactory` transforms them automatically.\n */\n private buildPluginList(): any[] {\n const pluginConfig = this.config.plugins;\n const plugins: any[] = [];\n\n if (pluginConfig?.organization) {\n plugins.push(organization({\n schema: buildOrganizationPluginSchema(),\n }));\n }\n\n if (pluginConfig?.twoFactor) {\n plugins.push(twoFactor({\n schema: buildTwoFactorPluginSchema(),\n }));\n }\n\n if (pluginConfig?.magicLink) {\n // magic-link reuses the `verification` table — no extra schema mapping needed.\n // The sendMagicLink callback must be provided by the application at a higher level.\n // Here we provide a no-op default that logs a warning; real applications should\n // override this via AuthManagerOptions or a config extension point.\n plugins.push(magicLink({\n sendMagicLink: async ({ email, url }) => {\n console.warn(\n `[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`,\n );\n },\n }));\n }\n\n return plugins;\n }\n\n /**\n * Create database configuration using ObjectQL adapter\n *\n * better-auth resolves the `database` option as follows:\n * - `undefined` → in-memory adapter\n * - `typeof fn === \"function\"` → treated as `DBAdapterInstance`, called with `(options)`\n * - otherwise → forwarded to Kysely adapter factory (pool/dialect)\n *\n * A raw `CustomAdapter` object would fall into the third branch and fail\n * silently. We therefore wrap the ObjectQL adapter in a factory function\n * so it is correctly recognised as a `DBAdapterInstance`.\n */\n private createDatabaseConfig(): any {\n // Use ObjectQL adapter factory if dataEngine is provided\n if (this.config.dataEngine) {\n // createObjectQLAdapterFactory returns an AdapterFactory\n // (options => DBAdapter) which better-auth invokes via getBaseAdapter().\n // The factory is created by better-auth's createAdapterFactory and\n // automatically applies modelName/fields transformations declared in\n // the betterAuth config above.\n return createObjectQLAdapterFactory(this.config.dataEngine);\n }\n\n // Fallback warning if no dataEngine is provided\n console.warn(\n '⚠️ WARNING: No dataEngine provided to AuthManager! ' +\n 'Using in-memory storage. This is NOT suitable for production. ' +\n 'Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions.'\n );\n\n // Return a minimal in-memory configuration as fallback\n // This allows the system to work in development/testing without a real database\n return undefined; // better-auth will use its default in-memory adapter\n }\n\n /**\n * Generate a secure secret if not provided\n */\n private generateSecret(): string {\n const envSecret = process.env.AUTH_SECRET;\n\n if (!envSecret) {\n // In production, a secret MUST be provided\n // For development/testing, we'll use a fallback but warn about it\n const fallbackSecret = 'dev-secret-' + Date.now();\n\n console.warn(\n '⚠️ WARNING: No AUTH_SECRET environment variable set! ' +\n 'Using a temporary development secret. ' +\n 'This is NOT secure for production use. ' +\n 'Please set AUTH_SECRET in your environment variables.'\n );\n\n return fallbackSecret;\n }\n\n return envSecret;\n }\n\n /**\n * Get the underlying better-auth instance\n * Useful for advanced use cases\n */\n getAuthInstance(): Auth<any> {\n return this.getOrCreateAuth();\n }\n\n /**\n * Handle an authentication request\n * Forwards the request directly to better-auth's universal handler\n *\n * better-auth catches internal errors (database / adapter / ORM) and\n * returns a 500 Response instead of throwing. We therefore inspect the\n * response status and log server errors so they are not silently swallowed.\n *\n * @param request - Web standard Request object\n * @returns Web standard Response object\n */\n async handleRequest(request: Request): Promise<Response> {\n const auth = this.getOrCreateAuth();\n const response = await auth.handler(request);\n\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n console.error('[AuthManager] better-auth returned error:', response.status, body);\n } catch {\n console.error('[AuthManager] better-auth returned error:', response.status, '(unable to read body)');\n }\n }\n\n return response;\n }\n\n /**\n * Get the better-auth API for programmatic access\n * Use this for server-side operations (e.g., creating users, checking sessions)\n */\n get api() {\n return this.getOrCreateAuth().api;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/core';\nimport { createAdapterFactory } from 'better-auth/adapters';\nimport type { CleanedWhere } from 'better-auth/adapters';\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * Mapping from better-auth model names to ObjectStack protocol object names.\n *\n * better-auth uses hardcoded model names ('user', 'session', 'account', 'verification')\n * while ObjectStack's protocol layer uses `sys_` prefixed names. This map bridges the two.\n */\nexport const AUTH_MODEL_TO_PROTOCOL: Record<string, string> = {\n user: SystemObjectName.USER,\n session: SystemObjectName.SESSION,\n account: SystemObjectName.ACCOUNT,\n verification: SystemObjectName.VERIFICATION,\n};\n\n/**\n * Resolve a better-auth model name to the ObjectStack protocol object name.\n * Falls back to the original model name for custom / non-core models.\n */\nexport function resolveProtocolName(model: string): string {\n return AUTH_MODEL_TO_PROTOCOL[model] ?? model;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert better-auth where clause to ObjectQL query format.\n *\n * Field names in the incoming {@link CleanedWhere} are expected to already be\n * in snake_case (transformed by `createAdapterFactory`).\n */\nfunction convertWhere(where: CleanedWhere[]): Record<string, any> {\n const filter: Record<string, any> = {};\n\n for (const condition of where) {\n const fieldName = condition.field;\n\n if (condition.operator === 'eq') {\n filter[fieldName] = condition.value;\n } else if (condition.operator === 'ne') {\n filter[fieldName] = { $ne: condition.value };\n } else if (condition.operator === 'in') {\n filter[fieldName] = { $in: condition.value };\n } else if (condition.operator === 'gt') {\n filter[fieldName] = { $gt: condition.value };\n } else if (condition.operator === 'gte') {\n filter[fieldName] = { $gte: condition.value };\n } else if (condition.operator === 'lt') {\n filter[fieldName] = { $lt: condition.value };\n } else if (condition.operator === 'lte') {\n filter[fieldName] = { $lte: condition.value };\n } else if (condition.operator === 'contains') {\n filter[fieldName] = { $regex: condition.value };\n }\n }\n\n return filter;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an ObjectQL adapter **factory** for better-auth.\n *\n * Uses better-auth's official `createAdapterFactory` so that model-name and\n * field-name transformations (declared via `modelName` / `fields` in the\n * betterAuth config) are applied **automatically** before any data reaches\n * ObjectQL. This eliminates the need for manual camelCase ↔ snake_case\n * conversion inside the adapter.\n *\n * The returned value is an `AdapterFactory` – a function of type\n * `(options: BetterAuthOptions) => DBAdapter` – which is the shape expected\n * by `betterAuth({ database: … })`.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth AdapterFactory\n */\nexport function createObjectQLAdapterFactory(dataEngine: IDataEngine) {\n return createAdapterFactory({\n config: {\n adapterId: 'objectql',\n // ObjectQL natively supports these types — no extra conversion needed\n supportsBooleans: true,\n supportsDates: true,\n supportsJSON: true,\n },\n adapter: () => ({\n create: async <T extends Record<string, any>>(\n { model, data, select: _select }: { model: string; data: T; select?: string[] },\n ): Promise<T> => {\n const result = await dataEngine.insert(model, data);\n return result as T;\n },\n\n findOne: async <T>(\n { model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n const result = await dataEngine.findOne(model, { filter, select });\n\n return result ? (result as T) : null;\n },\n\n findMany: async <T>(\n { model, where, limit, offset, sortBy, join: _join }: {\n model: string; where?: CleanedWhere[]; limit: number;\n offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any;\n },\n ): Promise<T[]> => {\n const filter = where ? convertWhere(where) : {};\n\n const sort = sortBy\n ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }]\n : undefined;\n\n const results = await dataEngine.find(model, {\n filter,\n limit: limit || 100,\n skip: offset,\n sort,\n });\n\n return results as T[];\n },\n\n count: async (\n { model, where }: { model: string; where?: CleanedWhere[] },\n ): Promise<number> => {\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(model, { filter });\n },\n\n update: async <T>(\n { model, where, update }: { model: string; where: CleanedWhere[]; update: T },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n // ObjectQL requires an ID for updates – find the record first\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return null;\n\n const result = await dataEngine.update(model, { ...(update as any), id: record.id });\n return result ? (result as T) : null;\n },\n\n updateMany: async (\n { model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n // Sequential updates: ObjectQL requires an ID per update\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.update(model, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<void> => {\n const filter = convertWhere(where);\n\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return;\n\n await dataEngine.delete(model, { filter: { id: record.id } });\n },\n\n deleteMany: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.delete(model, { filter: { id: record.id } });\n }\n return records.length;\n },\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Legacy adapter (kept for backward compatibility)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a raw ObjectQL adapter for better-auth (without factory wrapping).\n *\n * > **Prefer {@link createObjectQLAdapterFactory}** for production use.\n * > The factory version leverages `createAdapterFactory` and automatically\n * > handles model-name + field-name transformations declared in the\n * > better-auth config.\n *\n * This function is retained for direct / low-level usage where callers\n * manage field-name conversion themselves.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth CustomAdapter (raw, without factory wrapping)\n */\nexport function createObjectQLAdapter(dataEngine: IDataEngine) {\n return {\n create: async <T extends Record<string, any>>({ model, data, select: _select }: { model: string; data: T; select?: string[] }): Promise<T> => {\n const objectName = resolveProtocolName(model);\n const result = await dataEngine.insert(objectName, data);\n return result as T;\n },\n\n findOne: async <T>({ model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const result = await dataEngine.findOne(objectName, { filter, select });\n return result ? result as T : null;\n },\n\n findMany: async <T>({ model, where, limit, offset, sortBy, join: _join }: { model: string; where?: CleanedWhere[]; limit: number; offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any }): Promise<T[]> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n const sort = sortBy ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }] : undefined;\n const results = await dataEngine.find(objectName, { filter, limit: limit || 100, skip: offset, sort });\n return results as T[];\n },\n\n count: async ({ model, where }: { model: string; where?: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(objectName, { filter });\n },\n\n update: async <T>({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return null;\n const result = await dataEngine.update(objectName, { ...update, id: record.id });\n return result ? result as T : null;\n },\n\n updateMany: async ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.update(objectName, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<void> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return;\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n },\n\n deleteMany: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n }\n return records.length;\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * better-auth ↔ ObjectStack Schema Mapping\n *\n * better-auth uses camelCase field names internally (e.g. `emailVerified`, `userId`)\n * while ObjectStack's protocol layer uses snake_case (e.g. `email_verified`, `user_id`).\n *\n * These constants declare the `modelName` and `fields` mappings for each core auth\n * model, following better-auth's official schema customisation API\n * ({@link https://www.better-auth.com/docs/concepts/database}).\n *\n * The mappings serve two purposes:\n * 1. `modelName` — maps the default model name to the ObjectStack protocol name\n * (e.g. `user` → `sys_user`).\n * 2. `fields` — maps camelCase field names to their snake_case database column\n * equivalents. Only fields whose names differ need to be listed; fields that\n * are already identical (e.g. `email`, `name`, `token`) are omitted.\n *\n * These mappings are consumed by:\n * - The `betterAuth()` configuration in {@link AuthManager} so that\n * `getAuthTables()` builds the correct schema.\n * - The ObjectQL adapter factory (via `createAdapterFactory`) which uses the\n * schema to transform data and where-clauses automatically.\n */\n\n// ---------------------------------------------------------------------------\n// User model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `user` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | emailVerified | email_verified |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_USER_CONFIG = {\n modelName: SystemObjectName.USER, // 'sys_user'\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Session model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `session` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | userId | user_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n * | ipAddress | ip_address |\n * | userAgent | user_agent |\n */\nexport const AUTH_SESSION_CONFIG = {\n modelName: SystemObjectName.SESSION, // 'sys_session'\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Account model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `account` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:--------------------------|:-------------------------------|\n * | userId | user_id |\n * | providerId | provider_id |\n * | accountId | account_id |\n * | accessToken | access_token |\n * | refreshToken | refresh_token |\n * | idToken | id_token |\n * | accessTokenExpiresAt | access_token_expires_at |\n * | refreshTokenExpiresAt | refresh_token_expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ACCOUNT_CONFIG = {\n modelName: SystemObjectName.ACCOUNT, // 'sys_account'\n fields: {\n userId: 'user_id',\n providerId: 'provider_id',\n accountId: 'account_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Verification model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `verification` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_VERIFICATION_CONFIG = {\n modelName: SystemObjectName.VERIFICATION, // 'sys_verification'\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ===========================================================================\n// Plugin Table Mappings\n// ===========================================================================\n//\n// better-auth plugins (organization, two-factor, etc.) introduce additional\n// tables with their own camelCase field names. The mappings below are passed\n// to the plugin's `schema` option so that `createAdapterFactory` transforms\n// them to snake_case automatically, just like the core models above.\n// ===========================================================================\n\n// ---------------------------------------------------------------------------\n// Organization plugin – organization table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `organization` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ORGANIZATION_SCHEMA = {\n modelName: 'sys_organization',\n fields: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – member table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `member` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_MEMBER_SCHEMA = {\n modelName: 'sys_member',\n fields: {\n organizationId: 'organization_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – invitation table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `invitation` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | inviterId | inviter_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | teamId | team_id |\n */\nexport const AUTH_INVITATION_SCHEMA = {\n modelName: 'sys_invitation',\n fields: {\n organizationId: 'organization_id',\n inviterId: 'inviter_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n teamId: 'team_id',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – session additional fields\n// ---------------------------------------------------------------------------\n\n/**\n * Organization plugin adds `activeOrganizationId` (and optionally\n * `activeTeamId`) to the session model. These field mappings are\n * injected via the organization plugin's `schema.session.fields`.\n */\nexport const AUTH_ORG_SESSION_FIELDS = {\n activeOrganizationId: 'active_organization_id',\n activeTeamId: 'active_team_id',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – team table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `team` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_TEAM_SCHEMA = {\n modelName: 'sys_team',\n fields: {\n organizationId: 'organization_id',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – teamMember table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `teamMember` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | teamId | team_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_TEAM_MEMBER_SCHEMA = {\n modelName: 'sys_team_member',\n fields: {\n teamId: 'team_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Two-Factor plugin – twoFactor table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Two-Factor plugin `twoFactor` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | backupCodes | backup_codes |\n * | userId | user_id |\n */\nexport const AUTH_TWO_FACTOR_SCHEMA = {\n modelName: 'sys_two_factor',\n fields: {\n backupCodes: 'backup_codes',\n userId: 'user_id',\n },\n} as const;\n\n/**\n * Two-Factor plugin adds a `twoFactorEnabled` field to the user model.\n */\nexport const AUTH_TWO_FACTOR_USER_FIELDS = {\n twoFactorEnabled: 'two_factor_enabled',\n} as const;\n\n/**\n * Builds the `schema` option for better-auth's `twoFactor()` plugin.\n *\n * @returns An object suitable for `twoFactor({ schema: … })`\n */\nexport function buildTwoFactorPluginSchema() {\n return {\n twoFactor: AUTH_TWO_FACTOR_SCHEMA,\n user: {\n fields: AUTH_TWO_FACTOR_USER_FIELDS,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build organization plugin schema option\n// ---------------------------------------------------------------------------\n\n/**\n * Builds the `schema` option for better-auth's `organization()` plugin.\n *\n * The organization plugin accepts a `schema` sub-option that allows\n * customising model names and field names for each table it manages.\n * This helper assembles the correct snake_case mappings from the\n * individual `AUTH_*_SCHEMA` constants above.\n *\n * @returns An object suitable for `organization({ schema: … })`\n */\nexport function buildOrganizationPluginSchema() {\n return {\n organization: AUTH_ORGANIZATION_SCHEMA,\n member: AUTH_MEMBER_SCHEMA,\n invitation: AUTH_INVITATION_SCHEMA,\n team: AUTH_TEAM_SCHEMA,\n teamMember: AUTH_TEAM_MEMBER_SCHEMA,\n session: {\n fields: AUTH_ORG_SESSION_FIELDS,\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { AuthConfig } from '@objectstack/spec/system';\nimport { AuthManager } from './auth-manager.js';\n\n/**\n * Auth Plugin Options\n * Extends AuthConfig from spec with additional runtime options\n */\nexport interface AuthPluginOptions extends Partial<AuthConfig> {\n /**\n * Whether to automatically register auth routes\n * @default true\n */\n registerRoutes?: boolean;\n \n /**\n * Base path for auth routes\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Plugin\n * \n * Provides authentication and identity services for ObjectStack applications.\n * \n * **Dual-Mode Operation:**\n * - **Server mode** (HonoServerPlugin active): Registers HTTP routes at basePath,\n * forwarding all auth requests to better-auth's universal handler.\n * - **MSW/Mock mode** (no HTTP server): Gracefully skips route registration but\n * still registers the `auth` service, allowing HttpDispatcher.handleAuth() to\n * simulate auth flows (sign-up, sign-in, etc.) for development and testing.\n * \n * Features:\n * - Session management\n * - User registration/login\n * - OAuth providers (Google, GitHub, etc.)\n * - Organization/team support\n * - 2FA, passkeys, magic links\n * \n * This plugin registers:\n * - `auth` service (auth manager instance) — always\n * - HTTP routes for authentication endpoints — only when HTTP server is available\n * \n * Integrates with better-auth library to provide comprehensive\n * authentication capabilities including email/password, OAuth, 2FA,\n * magic links, passkeys, and organization support.\n */\nexport class AuthPlugin implements Plugin {\n name = 'com.objectstack.auth';\n type = 'standard';\n version = '1.0.0';\n dependencies: string[] = []; // HTTP server is optional; routes are registered only when available\n \n private options: AuthPluginOptions;\n private authManager: AuthManager | null = null;\n\n constructor(options: AuthPluginOptions = {}) {\n this.options = {\n registerRoutes: true,\n basePath: '/api/v1/auth',\n ...options\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Initializing Auth Plugin...');\n\n // Validate required configuration\n if (!this.options.secret) {\n throw new Error('AuthPlugin: secret is required');\n }\n\n // Get data engine service for database operations\n const dataEngine = ctx.getService<any>('data');\n if (!dataEngine) {\n ctx.logger.warn('No data engine service found - auth will use in-memory storage');\n }\n\n // Initialize auth manager with data engine\n this.authManager = new AuthManager({\n ...this.options,\n dataEngine,\n });\n\n // Register auth service\n ctx.registerService('auth', this.authManager);\n \n ctx.logger.info('Auth Plugin initialized successfully');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Starting Auth Plugin...');\n\n if (!this.authManager) {\n throw new Error('Auth manager not initialized');\n }\n\n // Defer HTTP route registration to kernel:ready hook.\n // This ensures all plugins (including HonoServerPlugin) have completed\n // their init and start phases before we attempt to look up the\n // http-server service — making AuthPlugin resilient to plugin\n // loading order.\n if (this.options.registerRoutes) {\n ctx.hook('kernel:ready', async () => {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // Service not found — expected in MSW/mock mode\n }\n\n if (httpServer) {\n // Route registration errors should propagate (server misconfiguration)\n this.registerAuthRoutes(httpServer, ctx);\n ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);\n } else {\n ctx.logger.warn(\n 'No HTTP server available — auth routes not registered. ' +\n 'Auth service is still available for MSW/mock environments via HttpDispatcher.'\n );\n }\n });\n }\n\n // Register auth middleware on ObjectQL engine (if available)\n try {\n const ql = ctx.getService<any>('objectql');\n if (ql && typeof ql.registerMiddleware === 'function') {\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n // If context already has userId or isSystem, skip auth resolution\n if (opCtx.context?.userId || opCtx.context?.isSystem) {\n return next();\n }\n // Future: resolve session from AsyncLocalStorage or request context\n await next();\n });\n ctx.logger.info('Auth middleware registered on ObjectQL engine');\n }\n } catch (_e) {\n ctx.logger.debug('ObjectQL engine not available, skipping auth middleware registration');\n }\n\n ctx.logger.info('Auth Plugin started successfully');\n }\n\n async destroy(): Promise<void> {\n // Cleanup if needed\n this.authManager = null;\n }\n\n /**\n * Register authentication routes with HTTP server\n * \n * Uses better-auth's universal handler for all authentication requests.\n * This forwards all requests under basePath to better-auth, which handles:\n * - Email/password authentication\n * - OAuth providers (Google, GitHub, etc.)\n * - Session management\n * - Password reset\n * - Email verification\n * - 2FA, passkeys, magic links (if enabled)\n */\n private registerAuthRoutes(httpServer: IHttpServer, ctx: PluginContext): void {\n if (!this.authManager) return;\n\n const basePath = this.options.basePath || '/api/v1/auth';\n\n // Get raw Hono app to use native wildcard routing\n // Type assertion is safe here because we explicitly require Hono server as a dependency\n if (!('getRawApp' in httpServer) || typeof (httpServer as any).getRawApp !== 'function') {\n ctx.logger.error('HTTP server does not support getRawApp() - wildcard routing requires Hono server');\n throw new Error(\n 'AuthPlugin requires HonoServerPlugin for wildcard routing support. ' +\n 'Please ensure HonoServerPlugin is loaded before AuthPlugin.'\n );\n }\n\n const rawApp = (httpServer as any).getRawApp();\n\n // Register wildcard route to forward all auth requests to better-auth\n // Better-auth expects requests at its baseURL, so we need to preserve the full path\n rawApp.all(`${basePath}/*`, async (c: any) => {\n try {\n // Get the Web standard Request from Hono context\n const request = c.req.raw as Request;\n \n // Create a new Request with the path rewritten to match better-auth's expectations\n // Better-auth expects paths like /sign-in/email, /sign-up/email, etc.\n // We need to strip our basePath prefix\n const url = new URL(request.url);\n const authPath = url.pathname.replace(basePath, '');\n const rewrittenUrl = new URL(authPath || '/', url.origin);\n rewrittenUrl.search = url.search; // Preserve query params\n \n const rewrittenRequest = new Request(rewrittenUrl, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n duplex: 'half' as any, // Required for Request with body\n });\n\n // Forward to better-auth handler\n const response = await this.authManager!.handleRequest(rewrittenRequest);\n\n // better-auth catches internal errors and returns error Responses\n // without throwing, so the catch block below would never trigger.\n // We proactively log server errors here for observability.\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: ${body}`));\n } catch {\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: (unable to read body)`));\n }\n }\n \n return response;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n ctx.logger.error('Auth request error:', err);\n \n // Return error response\n return new Response(\n JSON.stringify({\n success: false,\n error: err.message,\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n });\n\n ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);\n }\n}\n\n\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth User Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - email: string (unique, lowercase)\n * - email_verified: boolean\n * - name: string\n * - image: string | null\n */\nexport const AuthUser = ObjectSchema.create({\n name: 'sys_user',\n label: 'User',\n pluralLabel: 'Users',\n icon: 'user',\n description: 'User accounts for authentication',\n titleFormat: '{name} ({email})',\n compactLayout: ['name', 'email', 'email_verified'],\n \n fields: {\n // ID is auto-generated by ObjectQL\n id: Field.text({\n label: 'User ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n email: Field.email({\n label: 'Email',\n required: true,\n searchable: true,\n }),\n \n email_verified: Field.boolean({\n label: 'Email Verified',\n defaultValue: false,\n }),\n \n name: Field.text({\n label: 'Name',\n required: true,\n searchable: true,\n maxLength: 255,\n }),\n \n image: Field.url({\n label: 'Profile Image',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['email'], unique: true },\n { fields: ['created_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: true,\n searchable: true,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: true,\n },\n \n // Validation Rules\n validations: [\n {\n name: 'email_unique',\n type: 'unique',\n severity: 'error',\n message: 'Email must be unique',\n fields: ['email'],\n caseSensitive: false,\n },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Session Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - user_id: string\n * - expires_at: Date\n * - token: string\n * - ip_address: string | null\n * - user_agent: string | null\n */\nexport const AuthSession = ObjectSchema.create({\n name: 'sys_session',\n label: 'Session',\n pluralLabel: 'Sessions',\n icon: 'key',\n description: 'Active user sessions',\n titleFormat: 'Session {token}',\n compactLayout: ['user_id', 'expires_at', 'ip_address'],\n \n fields: {\n id: Field.text({\n label: 'Session ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n token: Field.text({\n label: 'Session Token',\n required: true,\n }),\n \n ip_address: Field.text({\n label: 'IP Address',\n required: false,\n maxLength: 45, // Support IPv6\n }),\n \n user_agent: Field.textarea({\n label: 'User Agent',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['token'], unique: true },\n { fields: ['user_id'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false, // Sessions don't need history tracking\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'delete'], // No update for sessions\n trash: false, // Sessions should be hard deleted\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Account Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - provider_id: string (e.g., 'google', 'github')\n * - account_id: string (provider's user ID)\n * - user_id: string (link to user table)\n * - access_token: string | null\n * - refresh_token: string | null\n * - id_token: string | null\n * - access_token_expires_at: Date | null\n * - refresh_token_expires_at: Date | null\n * - scope: string | null\n * - password: string | null (for email/password provider)\n */\nexport const AuthAccount = ObjectSchema.create({\n name: 'sys_account',\n label: 'Account',\n pluralLabel: 'Accounts',\n icon: 'link',\n description: 'OAuth and authentication provider accounts',\n titleFormat: '{provider_id} - {account_id}',\n compactLayout: ['provider_id', 'user_id', 'account_id'],\n \n fields: {\n id: Field.text({\n label: 'Account ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n provider_id: Field.text({\n label: 'Provider ID',\n required: true,\n description: 'OAuth provider identifier (google, github, etc.)',\n }),\n \n account_id: Field.text({\n label: 'Provider Account ID',\n required: true,\n description: \"User's ID in the provider's system\",\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n description: 'Link to user table',\n }),\n \n access_token: Field.textarea({\n label: 'Access Token',\n required: false,\n }),\n \n refresh_token: Field.textarea({\n label: 'Refresh Token',\n required: false,\n }),\n \n id_token: Field.textarea({\n label: 'ID Token',\n required: false,\n }),\n \n access_token_expires_at: Field.datetime({\n label: 'Access Token Expires At',\n required: false,\n }),\n \n refresh_token_expires_at: Field.datetime({\n label: 'Refresh Token Expires At',\n required: false,\n }),\n \n scope: Field.text({\n label: 'OAuth Scope',\n required: false,\n }),\n \n password: Field.text({\n label: 'Password Hash',\n required: false,\n description: 'Hashed password for email/password provider',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['provider_id', 'account_id'], unique: true },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Verification Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - value: string (verification token/code)\n * - expires_at: Date\n * - identifier: string (email or phone number)\n */\nexport const AuthVerification = ObjectSchema.create({\n name: 'sys_verification',\n label: 'Verification',\n pluralLabel: 'Verifications',\n icon: 'shield-check',\n description: 'Email and phone verification tokens',\n titleFormat: 'Verification for {identifier}',\n compactLayout: ['identifier', 'expires_at', 'created_at'],\n \n fields: {\n id: Field.text({\n label: 'Verification ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n value: Field.text({\n label: 'Verification Token',\n required: true,\n description: 'Token or code for verification',\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n identifier: Field.text({\n label: 'Identifier',\n required: true,\n description: 'Email address or phone number',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['value'], unique: true },\n { fields: ['identifier'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'create', 'delete'], // No list or update\n trash: false, // Hard delete expired tokens\n mru: false,\n },\n});\n"],"mappings":"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","names":["import_system","import_data","import_data","import_data"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth-manager.ts","../src/objectql-adapter.ts","../src/auth-schema-config.ts","../src/auth-plugin.ts","../src/objects/auth-user.object.ts","../src/objects/auth-session.object.ts","../src/objects/auth-account.object.ts","../src/objects/auth-verification.object.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-auth\n * \n * Authentication & Identity Plugin for ObjectStack\n * Powered by better-auth for robust, secure authentication\n * Uses ObjectQL for data persistence (no third-party ORM required)\n */\n\nexport * from './auth-plugin.js';\nexport * from './auth-manager.js';\nexport * from './objectql-adapter.js';\nexport * from './auth-schema-config.js';\nexport * from './objects/index.js';\nexport type { AuthConfig, AuthProviderConfig, AuthPluginConfig } from '@objectstack/spec/system';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { betterAuth } from 'better-auth';\nimport type { Auth, BetterAuthOptions } from 'better-auth';\nimport { organization } from 'better-auth/plugins/organization';\nimport { twoFactor } from 'better-auth/plugins/two-factor';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { AuthConfig } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/core';\nimport { createObjectQLAdapterFactory } from './objectql-adapter.js';\nimport {\n AUTH_USER_CONFIG,\n AUTH_SESSION_CONFIG,\n AUTH_ACCOUNT_CONFIG,\n AUTH_VERIFICATION_CONFIG,\n buildOrganizationPluginSchema,\n buildTwoFactorPluginSchema,\n} from './auth-schema-config.js';\n\n/**\n * Extended options for AuthManager\n */\nexport interface AuthManagerOptions extends Partial<AuthConfig> {\n /**\n * Better-Auth instance (for advanced use cases)\n * If not provided, one will be created from config\n */\n authInstance?: Auth<any>;\n\n /**\n * ObjectQL Data Engine instance\n * Required for database operations using ObjectQL instead of third-party ORMs\n */\n dataEngine?: IDataEngine;\n\n /**\n * Base path for auth routes\n * Forwarded to better-auth's basePath option so it can match incoming\n * request URLs without manual path rewriting.\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Manager\n *\n * Wraps better-auth and provides authentication services for ObjectStack.\n * Supports multiple authentication methods:\n * - Email/password\n * - OAuth providers (Google, GitHub, etc.)\n * - Magic links\n * - Two-factor authentication\n * - Passkeys\n * - Organization/teams\n */\nexport class AuthManager {\n private auth: Auth<any> | null = null;\n private config: AuthManagerOptions;\n\n constructor(config: AuthManagerOptions) {\n this.config = config;\n\n // Use provided auth instance\n if (config.authInstance) {\n this.auth = config.authInstance;\n }\n // Don't create auth instance automatically to avoid database initialization errors\n // It will be created lazily when needed\n }\n\n /**\n * Get or create the better-auth instance (lazy initialization)\n */\n private getOrCreateAuth(): Auth<any> {\n if (!this.auth) {\n this.auth = this.createAuthInstance();\n }\n return this.auth;\n }\n\n /**\n * Create a better-auth instance from configuration\n */\n private createAuthInstance(): Auth<any> {\n const betterAuthConfig: BetterAuthOptions = {\n // Base configuration\n secret: this.config.secret || this.generateSecret(),\n baseURL: this.config.baseUrl || 'http://localhost:3000',\n basePath: this.config.basePath || '/api/v1/auth',\n\n // Database adapter configuration\n database: this.createDatabaseConfig(),\n\n // Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)\n // These declarations tell better-auth the actual table/column names used\n // by ObjectStack's protocol layer, enabling automatic transformation via\n // createAdapterFactory.\n user: {\n ...AUTH_USER_CONFIG,\n },\n account: {\n ...AUTH_ACCOUNT_CONFIG,\n },\n verification: {\n ...AUTH_VERIFICATION_CONFIG,\n },\n\n // Email configuration\n emailAndPassword: {\n enabled: true,\n },\n\n // Session configuration\n session: {\n ...AUTH_SESSION_CONFIG,\n expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days default\n updateAge: this.config.session?.updateAge || 60 * 60 * 24, // 1 day default\n },\n\n // better-auth plugins — registered based on AuthPluginConfig flags\n plugins: this.buildPluginList(),\n };\n\n return betterAuth(betterAuthConfig);\n }\n\n /**\n * Build the list of better-auth plugins based on AuthPluginConfig flags.\n *\n * Each plugin that introduces its own database tables is configured with\n * a `schema` option containing the appropriate snake_case field mappings,\n * so that `createAdapterFactory` transforms them automatically.\n */\n private buildPluginList(): any[] {\n const pluginConfig = this.config.plugins;\n const plugins: any[] = [];\n\n if (pluginConfig?.organization) {\n plugins.push(organization({\n schema: buildOrganizationPluginSchema(),\n }));\n }\n\n if (pluginConfig?.twoFactor) {\n plugins.push(twoFactor({\n schema: buildTwoFactorPluginSchema(),\n }));\n }\n\n if (pluginConfig?.magicLink) {\n // magic-link reuses the `verification` table — no extra schema mapping needed.\n // The sendMagicLink callback must be provided by the application at a higher level.\n // Here we provide a no-op default that logs a warning; real applications should\n // override this via AuthManagerOptions or a config extension point.\n plugins.push(magicLink({\n sendMagicLink: async ({ email, url }) => {\n console.warn(\n `[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`,\n );\n },\n }));\n }\n\n return plugins;\n }\n\n /**\n * Create database configuration using ObjectQL adapter\n *\n * better-auth resolves the `database` option as follows:\n * - `undefined` → in-memory adapter\n * - `typeof fn === \"function\"` → treated as `DBAdapterInstance`, called with `(options)`\n * - otherwise → forwarded to Kysely adapter factory (pool/dialect)\n *\n * A raw `CustomAdapter` object would fall into the third branch and fail\n * silently. We therefore wrap the ObjectQL adapter in a factory function\n * so it is correctly recognised as a `DBAdapterInstance`.\n */\n private createDatabaseConfig(): any {\n // Use ObjectQL adapter factory if dataEngine is provided\n if (this.config.dataEngine) {\n // createObjectQLAdapterFactory returns an AdapterFactory\n // (options => DBAdapter) which better-auth invokes via getBaseAdapter().\n // The factory is created by better-auth's createAdapterFactory and\n // automatically applies modelName/fields transformations declared in\n // the betterAuth config above.\n return createObjectQLAdapterFactory(this.config.dataEngine);\n }\n\n // Fallback warning if no dataEngine is provided\n console.warn(\n '⚠️ WARNING: No dataEngine provided to AuthManager! ' +\n 'Using in-memory storage. This is NOT suitable for production. ' +\n 'Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions.'\n );\n\n // Return a minimal in-memory configuration as fallback\n // This allows the system to work in development/testing without a real database\n return undefined; // better-auth will use its default in-memory adapter\n }\n\n /**\n * Generate a secure secret if not provided\n */\n private generateSecret(): string {\n const envSecret = process.env.AUTH_SECRET;\n\n if (!envSecret) {\n // In production, a secret MUST be provided\n // For development/testing, we'll use a fallback but warn about it\n const fallbackSecret = 'dev-secret-' + Date.now();\n\n console.warn(\n '⚠️ WARNING: No AUTH_SECRET environment variable set! ' +\n 'Using a temporary development secret. ' +\n 'This is NOT secure for production use. ' +\n 'Please set AUTH_SECRET in your environment variables.'\n );\n\n return fallbackSecret;\n }\n\n return envSecret;\n }\n\n /**\n * Get the underlying better-auth instance\n * Useful for advanced use cases\n */\n getAuthInstance(): Auth<any> {\n return this.getOrCreateAuth();\n }\n\n /**\n * Handle an authentication request\n * Forwards the request directly to better-auth's universal handler\n *\n * better-auth catches internal errors (database / adapter / ORM) and\n * returns a 500 Response instead of throwing. We therefore inspect the\n * response status and log server errors so they are not silently swallowed.\n *\n * @param request - Web standard Request object\n * @returns Web standard Response object\n */\n async handleRequest(request: Request): Promise<Response> {\n const auth = this.getOrCreateAuth();\n const response = await auth.handler(request);\n\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n console.error('[AuthManager] better-auth returned error:', response.status, body);\n } catch {\n console.error('[AuthManager] better-auth returned error:', response.status, '(unable to read body)');\n }\n }\n\n return response;\n }\n\n /**\n * Get the better-auth API for programmatic access\n * Use this for server-side operations (e.g., creating users, checking sessions)\n */\n get api() {\n return this.getOrCreateAuth().api;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/core';\nimport { createAdapterFactory } from 'better-auth/adapters';\nimport type { CleanedWhere } from 'better-auth/adapters';\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * Mapping from better-auth model names to ObjectStack protocol object names.\n *\n * better-auth uses hardcoded model names ('user', 'session', 'account', 'verification')\n * while ObjectStack's protocol layer uses `sys_` prefixed names. This map bridges the two.\n */\nexport const AUTH_MODEL_TO_PROTOCOL: Record<string, string> = {\n user: SystemObjectName.USER,\n session: SystemObjectName.SESSION,\n account: SystemObjectName.ACCOUNT,\n verification: SystemObjectName.VERIFICATION,\n};\n\n/**\n * Resolve a better-auth model name to the ObjectStack protocol object name.\n * Falls back to the original model name for custom / non-core models.\n */\nexport function resolveProtocolName(model: string): string {\n return AUTH_MODEL_TO_PROTOCOL[model] ?? model;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert better-auth where clause to ObjectQL query format.\n *\n * Field names in the incoming {@link CleanedWhere} are expected to already be\n * in snake_case (transformed by `createAdapterFactory`).\n */\nfunction convertWhere(where: CleanedWhere[]): Record<string, any> {\n const filter: Record<string, any> = {};\n\n for (const condition of where) {\n const fieldName = condition.field;\n\n if (condition.operator === 'eq') {\n filter[fieldName] = condition.value;\n } else if (condition.operator === 'ne') {\n filter[fieldName] = { $ne: condition.value };\n } else if (condition.operator === 'in') {\n filter[fieldName] = { $in: condition.value };\n } else if (condition.operator === 'gt') {\n filter[fieldName] = { $gt: condition.value };\n } else if (condition.operator === 'gte') {\n filter[fieldName] = { $gte: condition.value };\n } else if (condition.operator === 'lt') {\n filter[fieldName] = { $lt: condition.value };\n } else if (condition.operator === 'lte') {\n filter[fieldName] = { $lte: condition.value };\n } else if (condition.operator === 'contains') {\n filter[fieldName] = { $regex: condition.value };\n }\n }\n\n return filter;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an ObjectQL adapter **factory** for better-auth.\n *\n * Uses better-auth's official `createAdapterFactory` so that model-name and\n * field-name transformations (declared via `modelName` / `fields` in the\n * betterAuth config) are applied **automatically** before any data reaches\n * ObjectQL. This eliminates the need for manual camelCase ↔ snake_case\n * conversion inside the adapter.\n *\n * The returned value is an `AdapterFactory` – a function of type\n * `(options: BetterAuthOptions) => DBAdapter` – which is the shape expected\n * by `betterAuth({ database: … })`.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth AdapterFactory\n */\nexport function createObjectQLAdapterFactory(dataEngine: IDataEngine) {\n return createAdapterFactory({\n config: {\n adapterId: 'objectql',\n // ObjectQL natively supports these types — no extra conversion needed\n supportsBooleans: true,\n supportsDates: true,\n supportsJSON: true,\n },\n adapter: () => ({\n create: async <T extends Record<string, any>>(\n { model, data, select: _select }: { model: string; data: T; select?: string[] },\n ): Promise<T> => {\n const result = await dataEngine.insert(model, data);\n return result as T;\n },\n\n findOne: async <T>(\n { model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n const result = await dataEngine.findOne(model, { filter, select });\n\n return result ? (result as T) : null;\n },\n\n findMany: async <T>(\n { model, where, limit, offset, sortBy, join: _join }: {\n model: string; where?: CleanedWhere[]; limit: number;\n offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any;\n },\n ): Promise<T[]> => {\n const filter = where ? convertWhere(where) : {};\n\n const sort = sortBy\n ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }]\n : undefined;\n\n const results = await dataEngine.find(model, {\n filter,\n limit: limit || 100,\n skip: offset,\n sort,\n });\n\n return results as T[];\n },\n\n count: async (\n { model, where }: { model: string; where?: CleanedWhere[] },\n ): Promise<number> => {\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(model, { filter });\n },\n\n update: async <T>(\n { model, where, update }: { model: string; where: CleanedWhere[]; update: T },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n // ObjectQL requires an ID for updates – find the record first\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return null;\n\n const result = await dataEngine.update(model, { ...(update as any), id: record.id });\n return result ? (result as T) : null;\n },\n\n updateMany: async (\n { model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n // Sequential updates: ObjectQL requires an ID per update\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.update(model, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<void> => {\n const filter = convertWhere(where);\n\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return;\n\n await dataEngine.delete(model, { filter: { id: record.id } });\n },\n\n deleteMany: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.delete(model, { filter: { id: record.id } });\n }\n return records.length;\n },\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Legacy adapter (kept for backward compatibility)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a raw ObjectQL adapter for better-auth (without factory wrapping).\n *\n * > **Prefer {@link createObjectQLAdapterFactory}** for production use.\n * > The factory version leverages `createAdapterFactory` and automatically\n * > handles model-name + field-name transformations declared in the\n * > better-auth config.\n *\n * This function is retained for direct / low-level usage where callers\n * manage field-name conversion themselves.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth CustomAdapter (raw, without factory wrapping)\n */\nexport function createObjectQLAdapter(dataEngine: IDataEngine) {\n return {\n create: async <T extends Record<string, any>>({ model, data, select: _select }: { model: string; data: T; select?: string[] }): Promise<T> => {\n const objectName = resolveProtocolName(model);\n const result = await dataEngine.insert(objectName, data);\n return result as T;\n },\n\n findOne: async <T>({ model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const result = await dataEngine.findOne(objectName, { filter, select });\n return result ? result as T : null;\n },\n\n findMany: async <T>({ model, where, limit, offset, sortBy, join: _join }: { model: string; where?: CleanedWhere[]; limit: number; offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any }): Promise<T[]> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n const sort = sortBy ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }] : undefined;\n const results = await dataEngine.find(objectName, { filter, limit: limit || 100, skip: offset, sort });\n return results as T[];\n },\n\n count: async ({ model, where }: { model: string; where?: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(objectName, { filter });\n },\n\n update: async <T>({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return null;\n const result = await dataEngine.update(objectName, { ...update, id: record.id });\n return result ? result as T : null;\n },\n\n updateMany: async ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.update(objectName, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<void> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return;\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n },\n\n deleteMany: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n }\n return records.length;\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * better-auth ↔ ObjectStack Schema Mapping\n *\n * better-auth uses camelCase field names internally (e.g. `emailVerified`, `userId`)\n * while ObjectStack's protocol layer uses snake_case (e.g. `email_verified`, `user_id`).\n *\n * These constants declare the `modelName` and `fields` mappings for each core auth\n * model, following better-auth's official schema customisation API\n * ({@link https://www.better-auth.com/docs/concepts/database}).\n *\n * The mappings serve two purposes:\n * 1. `modelName` — maps the default model name to the ObjectStack protocol name\n * (e.g. `user` → `sys_user`).\n * 2. `fields` — maps camelCase field names to their snake_case database column\n * equivalents. Only fields whose names differ need to be listed; fields that\n * are already identical (e.g. `email`, `name`, `token`) are omitted.\n *\n * These mappings are consumed by:\n * - The `betterAuth()` configuration in {@link AuthManager} so that\n * `getAuthTables()` builds the correct schema.\n * - The ObjectQL adapter factory (via `createAdapterFactory`) which uses the\n * schema to transform data and where-clauses automatically.\n */\n\n// ---------------------------------------------------------------------------\n// User model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `user` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | emailVerified | email_verified |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_USER_CONFIG = {\n modelName: SystemObjectName.USER, // 'sys_user'\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Session model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `session` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | userId | user_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n * | ipAddress | ip_address |\n * | userAgent | user_agent |\n */\nexport const AUTH_SESSION_CONFIG = {\n modelName: SystemObjectName.SESSION, // 'sys_session'\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Account model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `account` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:--------------------------|:-------------------------------|\n * | userId | user_id |\n * | providerId | provider_id |\n * | accountId | account_id |\n * | accessToken | access_token |\n * | refreshToken | refresh_token |\n * | idToken | id_token |\n * | accessTokenExpiresAt | access_token_expires_at |\n * | refreshTokenExpiresAt | refresh_token_expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ACCOUNT_CONFIG = {\n modelName: SystemObjectName.ACCOUNT, // 'sys_account'\n fields: {\n userId: 'user_id',\n providerId: 'provider_id',\n accountId: 'account_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Verification model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `verification` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_VERIFICATION_CONFIG = {\n modelName: SystemObjectName.VERIFICATION, // 'sys_verification'\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ===========================================================================\n// Plugin Table Mappings\n// ===========================================================================\n//\n// better-auth plugins (organization, two-factor, etc.) introduce additional\n// tables with their own camelCase field names. The mappings below are passed\n// to the plugin's `schema` option so that `createAdapterFactory` transforms\n// them to snake_case automatically, just like the core models above.\n// ===========================================================================\n\n// ---------------------------------------------------------------------------\n// Organization plugin – organization table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `organization` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ORGANIZATION_SCHEMA = {\n modelName: 'sys_organization',\n fields: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – member table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `member` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_MEMBER_SCHEMA = {\n modelName: 'sys_member',\n fields: {\n organizationId: 'organization_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – invitation table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `invitation` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | inviterId | inviter_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | teamId | team_id |\n */\nexport const AUTH_INVITATION_SCHEMA = {\n modelName: 'sys_invitation',\n fields: {\n organizationId: 'organization_id',\n inviterId: 'inviter_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n teamId: 'team_id',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – session additional fields\n// ---------------------------------------------------------------------------\n\n/**\n * Organization plugin adds `activeOrganizationId` (and optionally\n * `activeTeamId`) to the session model. These field mappings are\n * injected via the organization plugin's `schema.session.fields`.\n */\nexport const AUTH_ORG_SESSION_FIELDS = {\n activeOrganizationId: 'active_organization_id',\n activeTeamId: 'active_team_id',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – team table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `team` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_TEAM_SCHEMA = {\n modelName: 'sys_team',\n fields: {\n organizationId: 'organization_id',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – teamMember table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `teamMember` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | teamId | team_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_TEAM_MEMBER_SCHEMA = {\n modelName: 'sys_team_member',\n fields: {\n teamId: 'team_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Two-Factor plugin – twoFactor table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Two-Factor plugin `twoFactor` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | backupCodes | backup_codes |\n * | userId | user_id |\n */\nexport const AUTH_TWO_FACTOR_SCHEMA = {\n modelName: 'sys_two_factor',\n fields: {\n backupCodes: 'backup_codes',\n userId: 'user_id',\n },\n} as const;\n\n/**\n * Two-Factor plugin adds a `twoFactorEnabled` field to the user model.\n */\nexport const AUTH_TWO_FACTOR_USER_FIELDS = {\n twoFactorEnabled: 'two_factor_enabled',\n} as const;\n\n/**\n * Builds the `schema` option for better-auth's `twoFactor()` plugin.\n *\n * @returns An object suitable for `twoFactor({ schema: … })`\n */\nexport function buildTwoFactorPluginSchema() {\n return {\n twoFactor: AUTH_TWO_FACTOR_SCHEMA,\n user: {\n fields: AUTH_TWO_FACTOR_USER_FIELDS,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build organization plugin schema option\n// ---------------------------------------------------------------------------\n\n/**\n * Builds the `schema` option for better-auth's `organization()` plugin.\n *\n * The organization plugin accepts a `schema` sub-option that allows\n * customising model names and field names for each table it manages.\n * This helper assembles the correct snake_case mappings from the\n * individual `AUTH_*_SCHEMA` constants above.\n *\n * @returns An object suitable for `organization({ schema: … })`\n */\nexport function buildOrganizationPluginSchema() {\n return {\n organization: AUTH_ORGANIZATION_SCHEMA,\n member: AUTH_MEMBER_SCHEMA,\n invitation: AUTH_INVITATION_SCHEMA,\n team: AUTH_TEAM_SCHEMA,\n teamMember: AUTH_TEAM_MEMBER_SCHEMA,\n session: {\n fields: AUTH_ORG_SESSION_FIELDS,\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { AuthConfig } from '@objectstack/spec/system';\nimport { AuthManager } from './auth-manager.js';\n\n/**\n * Auth Plugin Options\n * Extends AuthConfig from spec with additional runtime options\n */\nexport interface AuthPluginOptions extends Partial<AuthConfig> {\n /**\n * Whether to automatically register auth routes\n * @default true\n */\n registerRoutes?: boolean;\n \n /**\n * Base path for auth routes\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Plugin\n * \n * Provides authentication and identity services for ObjectStack applications.\n * \n * **Dual-Mode Operation:**\n * - **Server mode** (HonoServerPlugin active): Registers HTTP routes at basePath,\n * forwarding all auth requests to better-auth's universal handler.\n * - **MSW/Mock mode** (no HTTP server): Gracefully skips route registration but\n * still registers the `auth` service, allowing HttpDispatcher.handleAuth() to\n * simulate auth flows (sign-up, sign-in, etc.) for development and testing.\n * \n * Features:\n * - Session management\n * - User registration/login\n * - OAuth providers (Google, GitHub, etc.)\n * - Organization/team support\n * - 2FA, passkeys, magic links\n * \n * This plugin registers:\n * - `auth` service (auth manager instance) — always\n * - HTTP routes for authentication endpoints — only when HTTP server is available\n * \n * Integrates with better-auth library to provide comprehensive\n * authentication capabilities including email/password, OAuth, 2FA,\n * magic links, passkeys, and organization support.\n */\nexport class AuthPlugin implements Plugin {\n name = 'com.objectstack.auth';\n type = 'standard';\n version = '1.0.0';\n dependencies: string[] = []; // HTTP server is optional; routes are registered only when available\n \n private options: AuthPluginOptions;\n private authManager: AuthManager | null = null;\n\n constructor(options: AuthPluginOptions = {}) {\n this.options = {\n registerRoutes: true,\n basePath: '/api/v1/auth',\n ...options\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Initializing Auth Plugin...');\n\n // Validate required configuration\n if (!this.options.secret) {\n throw new Error('AuthPlugin: secret is required');\n }\n\n // Get data engine service for database operations\n const dataEngine = ctx.getService<any>('data');\n if (!dataEngine) {\n ctx.logger.warn('No data engine service found - auth will use in-memory storage');\n }\n\n // Initialize auth manager with data engine\n this.authManager = new AuthManager({\n ...this.options,\n dataEngine,\n });\n\n // Register auth service\n ctx.registerService('auth', this.authManager);\n \n ctx.logger.info('Auth Plugin initialized successfully');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Starting Auth Plugin...');\n\n if (!this.authManager) {\n throw new Error('Auth manager not initialized');\n }\n\n // Defer HTTP route registration to kernel:ready hook.\n // This ensures all plugins (including HonoServerPlugin) have completed\n // their init and start phases before we attempt to look up the\n // http-server service — making AuthPlugin resilient to plugin\n // loading order.\n if (this.options.registerRoutes) {\n ctx.hook('kernel:ready', async () => {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // Service not found — expected in MSW/mock mode\n }\n\n if (httpServer) {\n // Route registration errors should propagate (server misconfiguration)\n this.registerAuthRoutes(httpServer, ctx);\n ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);\n } else {\n ctx.logger.warn(\n 'No HTTP server available — auth routes not registered. ' +\n 'Auth service is still available for MSW/mock environments via HttpDispatcher.'\n );\n }\n });\n }\n\n // Register auth middleware on ObjectQL engine (if available)\n try {\n const ql = ctx.getService<any>('objectql');\n if (ql && typeof ql.registerMiddleware === 'function') {\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n // If context already has userId or isSystem, skip auth resolution\n if (opCtx.context?.userId || opCtx.context?.isSystem) {\n return next();\n }\n // Future: resolve session from AsyncLocalStorage or request context\n await next();\n });\n ctx.logger.info('Auth middleware registered on ObjectQL engine');\n }\n } catch (_e) {\n ctx.logger.debug('ObjectQL engine not available, skipping auth middleware registration');\n }\n\n ctx.logger.info('Auth Plugin started successfully');\n }\n\n async destroy(): Promise<void> {\n // Cleanup if needed\n this.authManager = null;\n }\n\n /**\n * Register authentication routes with HTTP server\n * \n * Uses better-auth's universal handler for all authentication requests.\n * This forwards all requests under basePath to better-auth, which handles:\n * - Email/password authentication\n * - OAuth providers (Google, GitHub, etc.)\n * - Session management\n * - Password reset\n * - Email verification\n * - 2FA, passkeys, magic links (if enabled)\n */\n private registerAuthRoutes(httpServer: IHttpServer, ctx: PluginContext): void {\n if (!this.authManager) return;\n\n const basePath = this.options.basePath || '/api/v1/auth';\n\n // Get raw Hono app to use native wildcard routing\n // Type assertion is safe here because we explicitly require Hono server as a dependency\n if (!('getRawApp' in httpServer) || typeof (httpServer as any).getRawApp !== 'function') {\n ctx.logger.error('HTTP server does not support getRawApp() - wildcard routing requires Hono server');\n throw new Error(\n 'AuthPlugin requires HonoServerPlugin for wildcard routing support. ' +\n 'Please ensure HonoServerPlugin is loaded before AuthPlugin.'\n );\n }\n\n const rawApp = (httpServer as any).getRawApp();\n\n // Register wildcard route to forward all auth requests to better-auth.\n // better-auth is configured with basePath matching our route prefix, so we\n // forward the original request directly — no path rewriting needed.\n rawApp.all(`${basePath}/*`, async (c: any) => {\n try {\n // Forward the original request to better-auth handler\n const response = await this.authManager!.handleRequest(c.req.raw);\n\n // better-auth catches internal errors and returns error Responses\n // without throwing, so the catch block below would never trigger.\n // We proactively log server errors here for observability.\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: ${body}`));\n } catch {\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: (unable to read body)`));\n }\n }\n \n return response;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n ctx.logger.error('Auth request error:', err);\n \n // Return error response\n return new Response(\n JSON.stringify({\n success: false,\n error: err.message,\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n });\n\n ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);\n }\n}\n\n\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth User Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - email: string (unique, lowercase)\n * - email_verified: boolean\n * - name: string\n * - image: string | null\n */\nexport const AuthUser = ObjectSchema.create({\n name: 'sys_user',\n label: 'User',\n pluralLabel: 'Users',\n icon: 'user',\n description: 'User accounts for authentication',\n titleFormat: '{name} ({email})',\n compactLayout: ['name', 'email', 'email_verified'],\n \n fields: {\n // ID is auto-generated by ObjectQL\n id: Field.text({\n label: 'User ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n email: Field.email({\n label: 'Email',\n required: true,\n searchable: true,\n }),\n \n email_verified: Field.boolean({\n label: 'Email Verified',\n defaultValue: false,\n }),\n \n name: Field.text({\n label: 'Name',\n required: true,\n searchable: true,\n maxLength: 255,\n }),\n \n image: Field.url({\n label: 'Profile Image',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['email'], unique: true },\n { fields: ['created_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: true,\n searchable: true,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: true,\n },\n \n // Validation Rules\n validations: [\n {\n name: 'email_unique',\n type: 'unique',\n severity: 'error',\n message: 'Email must be unique',\n fields: ['email'],\n caseSensitive: false,\n },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Session Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - user_id: string\n * - expires_at: Date\n * - token: string\n * - ip_address: string | null\n * - user_agent: string | null\n */\nexport const AuthSession = ObjectSchema.create({\n name: 'sys_session',\n label: 'Session',\n pluralLabel: 'Sessions',\n icon: 'key',\n description: 'Active user sessions',\n titleFormat: 'Session {token}',\n compactLayout: ['user_id', 'expires_at', 'ip_address'],\n \n fields: {\n id: Field.text({\n label: 'Session ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n token: Field.text({\n label: 'Session Token',\n required: true,\n }),\n \n ip_address: Field.text({\n label: 'IP Address',\n required: false,\n maxLength: 45, // Support IPv6\n }),\n \n user_agent: Field.textarea({\n label: 'User Agent',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['token'], unique: true },\n { fields: ['user_id'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false, // Sessions don't need history tracking\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'delete'], // No update for sessions\n trash: false, // Sessions should be hard deleted\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Account Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - provider_id: string (e.g., 'google', 'github')\n * - account_id: string (provider's user ID)\n * - user_id: string (link to user table)\n * - access_token: string | null\n * - refresh_token: string | null\n * - id_token: string | null\n * - access_token_expires_at: Date | null\n * - refresh_token_expires_at: Date | null\n * - scope: string | null\n * - password: string | null (for email/password provider)\n */\nexport const AuthAccount = ObjectSchema.create({\n name: 'sys_account',\n label: 'Account',\n pluralLabel: 'Accounts',\n icon: 'link',\n description: 'OAuth and authentication provider accounts',\n titleFormat: '{provider_id} - {account_id}',\n compactLayout: ['provider_id', 'user_id', 'account_id'],\n \n fields: {\n id: Field.text({\n label: 'Account ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n provider_id: Field.text({\n label: 'Provider ID',\n required: true,\n description: 'OAuth provider identifier (google, github, etc.)',\n }),\n \n account_id: Field.text({\n label: 'Provider Account ID',\n required: true,\n description: \"User's ID in the provider's system\",\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n description: 'Link to user table',\n }),\n \n access_token: Field.textarea({\n label: 'Access Token',\n required: false,\n }),\n \n refresh_token: Field.textarea({\n label: 'Refresh Token',\n required: false,\n }),\n \n id_token: Field.textarea({\n label: 'ID Token',\n required: false,\n }),\n \n access_token_expires_at: Field.datetime({\n label: 'Access Token Expires At',\n required: false,\n }),\n \n refresh_token_expires_at: Field.datetime({\n label: 'Refresh Token Expires At',\n required: false,\n }),\n \n scope: Field.text({\n label: 'OAuth Scope',\n required: false,\n }),\n \n password: Field.text({\n label: 'Password Hash',\n required: false,\n description: 'Hashed password for email/password provider',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['provider_id', 'account_id'], unique: true },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Verification Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - value: string (verification token/code)\n * - expires_at: Date\n * - identifier: string (email or phone number)\n */\nexport const AuthVerification = ObjectSchema.create({\n name: 'sys_verification',\n label: 'Verification',\n pluralLabel: 'Verifications',\n icon: 'shield-check',\n description: 'Email and phone verification tokens',\n titleFormat: 'Verification for {identifier}',\n compactLayout: ['identifier', 'expires_at', 'created_at'],\n \n fields: {\n id: Field.text({\n label: 'Verification ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n value: Field.text({\n label: 'Verification Token',\n required: true,\n description: 'Token or code for verification',\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n identifier: Field.text({\n label: 'Identifier',\n required: true,\n description: 'Email address or phone number',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['value'], unique: true },\n { fields: ['identifier'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'create', 'delete'], // No list or update\n trash: false, // Hard delete expired tokens\n mru: false,\n },\n});\n"],"mappings":"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","names":["import_system","import_data","import_data","import_data"]}
|
package/dist/index.mjs
CHANGED
|
@@ -317,8 +317,7 @@ var AuthManager = class {
|
|
|
317
317
|
// Base configuration
|
|
318
318
|
secret: this.config.secret || this.generateSecret(),
|
|
319
319
|
baseURL: this.config.baseUrl || "http://localhost:3000",
|
|
320
|
-
basePath: "/",
|
|
321
|
-
// ← 关键修复!告诉 better-auth 路径已被剥离
|
|
320
|
+
basePath: this.config.basePath || "/api/v1/auth",
|
|
322
321
|
// Database adapter configuration
|
|
323
322
|
database: this.createDatabaseConfig(),
|
|
324
323
|
// Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)
|
|
@@ -552,19 +551,7 @@ var AuthPlugin = class {
|
|
|
552
551
|
const rawApp = httpServer.getRawApp();
|
|
553
552
|
rawApp.all(`${basePath}/*`, async (c) => {
|
|
554
553
|
try {
|
|
555
|
-
const
|
|
556
|
-
const url = new URL(request.url);
|
|
557
|
-
const authPath = url.pathname.replace(basePath, "");
|
|
558
|
-
const rewrittenUrl = new URL(authPath || "/", url.origin);
|
|
559
|
-
rewrittenUrl.search = url.search;
|
|
560
|
-
const rewrittenRequest = new Request(rewrittenUrl, {
|
|
561
|
-
method: request.method,
|
|
562
|
-
headers: request.headers,
|
|
563
|
-
body: request.body,
|
|
564
|
-
duplex: "half"
|
|
565
|
-
// Required for Request with body
|
|
566
|
-
});
|
|
567
|
-
const response = await this.authManager.handleRequest(rewrittenRequest);
|
|
554
|
+
const response = await this.authManager.handleRequest(c.req.raw);
|
|
568
555
|
if (response.status >= 500) {
|
|
569
556
|
try {
|
|
570
557
|
const body = await response.clone().text();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth-manager.ts","../src/objectql-adapter.ts","../src/auth-schema-config.ts","../src/auth-plugin.ts","../src/objects/auth-user.object.ts","../src/objects/auth-session.object.ts","../src/objects/auth-account.object.ts","../src/objects/auth-verification.object.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { betterAuth } from 'better-auth';\nimport type { Auth, BetterAuthOptions } from 'better-auth';\nimport { organization } from 'better-auth/plugins/organization';\nimport { twoFactor } from 'better-auth/plugins/two-factor';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { AuthConfig } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/core';\nimport { createObjectQLAdapterFactory } from './objectql-adapter.js';\nimport {\n AUTH_USER_CONFIG,\n AUTH_SESSION_CONFIG,\n AUTH_ACCOUNT_CONFIG,\n AUTH_VERIFICATION_CONFIG,\n buildOrganizationPluginSchema,\n buildTwoFactorPluginSchema,\n} from './auth-schema-config.js';\n\n/**\n * Extended options for AuthManager\n */\nexport interface AuthManagerOptions extends Partial<AuthConfig> {\n /**\n * Better-Auth instance (for advanced use cases)\n * If not provided, one will be created from config\n */\n authInstance?: Auth<any>;\n\n /**\n * ObjectQL Data Engine instance\n * Required for database operations using ObjectQL instead of third-party ORMs\n */\n dataEngine?: IDataEngine;\n}\n\n/**\n * Authentication Manager\n *\n * Wraps better-auth and provides authentication services for ObjectStack.\n * Supports multiple authentication methods:\n * - Email/password\n * - OAuth providers (Google, GitHub, etc.)\n * - Magic links\n * - Two-factor authentication\n * - Passkeys\n * - Organization/teams\n */\nexport class AuthManager {\n private auth: Auth<any> | null = null;\n private config: AuthManagerOptions;\n\n constructor(config: AuthManagerOptions) {\n this.config = config;\n\n // Use provided auth instance\n if (config.authInstance) {\n this.auth = config.authInstance;\n }\n // Don't create auth instance automatically to avoid database initialization errors\n // It will be created lazily when needed\n }\n\n /**\n * Get or create the better-auth instance (lazy initialization)\n */\n private getOrCreateAuth(): Auth<any> {\n if (!this.auth) {\n this.auth = this.createAuthInstance();\n }\n return this.auth;\n }\n\n /**\n * Create a better-auth instance from configuration\n */\n private createAuthInstance(): Auth<any> {\n const betterAuthConfig: BetterAuthOptions = {\n // Base configuration\n secret: this.config.secret || this.generateSecret(),\n baseURL: this.config.baseUrl || 'http://localhost:3000',\n basePath: '/', // ← 关键修复!告诉 better-auth 路径已被剥离\n\n // Database adapter configuration\n database: this.createDatabaseConfig(),\n\n // Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)\n // These declarations tell better-auth the actual table/column names used\n // by ObjectStack's protocol layer, enabling automatic transformation via\n // createAdapterFactory.\n user: {\n ...AUTH_USER_CONFIG,\n },\n account: {\n ...AUTH_ACCOUNT_CONFIG,\n },\n verification: {\n ...AUTH_VERIFICATION_CONFIG,\n },\n\n // Email configuration\n emailAndPassword: {\n enabled: true,\n },\n\n // Session configuration\n session: {\n ...AUTH_SESSION_CONFIG,\n expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days default\n updateAge: this.config.session?.updateAge || 60 * 60 * 24, // 1 day default\n },\n\n // better-auth plugins — registered based on AuthPluginConfig flags\n plugins: this.buildPluginList(),\n };\n\n return betterAuth(betterAuthConfig);\n }\n\n /**\n * Build the list of better-auth plugins based on AuthPluginConfig flags.\n *\n * Each plugin that introduces its own database tables is configured with\n * a `schema` option containing the appropriate snake_case field mappings,\n * so that `createAdapterFactory` transforms them automatically.\n */\n private buildPluginList(): any[] {\n const pluginConfig = this.config.plugins;\n const plugins: any[] = [];\n\n if (pluginConfig?.organization) {\n plugins.push(organization({\n schema: buildOrganizationPluginSchema(),\n }));\n }\n\n if (pluginConfig?.twoFactor) {\n plugins.push(twoFactor({\n schema: buildTwoFactorPluginSchema(),\n }));\n }\n\n if (pluginConfig?.magicLink) {\n // magic-link reuses the `verification` table — no extra schema mapping needed.\n // The sendMagicLink callback must be provided by the application at a higher level.\n // Here we provide a no-op default that logs a warning; real applications should\n // override this via AuthManagerOptions or a config extension point.\n plugins.push(magicLink({\n sendMagicLink: async ({ email, url }) => {\n console.warn(\n `[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`,\n );\n },\n }));\n }\n\n return plugins;\n }\n\n /**\n * Create database configuration using ObjectQL adapter\n *\n * better-auth resolves the `database` option as follows:\n * - `undefined` → in-memory adapter\n * - `typeof fn === \"function\"` → treated as `DBAdapterInstance`, called with `(options)`\n * - otherwise → forwarded to Kysely adapter factory (pool/dialect)\n *\n * A raw `CustomAdapter` object would fall into the third branch and fail\n * silently. We therefore wrap the ObjectQL adapter in a factory function\n * so it is correctly recognised as a `DBAdapterInstance`.\n */\n private createDatabaseConfig(): any {\n // Use ObjectQL adapter factory if dataEngine is provided\n if (this.config.dataEngine) {\n // createObjectQLAdapterFactory returns an AdapterFactory\n // (options => DBAdapter) which better-auth invokes via getBaseAdapter().\n // The factory is created by better-auth's createAdapterFactory and\n // automatically applies modelName/fields transformations declared in\n // the betterAuth config above.\n return createObjectQLAdapterFactory(this.config.dataEngine);\n }\n\n // Fallback warning if no dataEngine is provided\n console.warn(\n '⚠️ WARNING: No dataEngine provided to AuthManager! ' +\n 'Using in-memory storage. This is NOT suitable for production. ' +\n 'Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions.'\n );\n\n // Return a minimal in-memory configuration as fallback\n // This allows the system to work in development/testing without a real database\n return undefined; // better-auth will use its default in-memory adapter\n }\n\n /**\n * Generate a secure secret if not provided\n */\n private generateSecret(): string {\n const envSecret = process.env.AUTH_SECRET;\n\n if (!envSecret) {\n // In production, a secret MUST be provided\n // For development/testing, we'll use a fallback but warn about it\n const fallbackSecret = 'dev-secret-' + Date.now();\n\n console.warn(\n '⚠️ WARNING: No AUTH_SECRET environment variable set! ' +\n 'Using a temporary development secret. ' +\n 'This is NOT secure for production use. ' +\n 'Please set AUTH_SECRET in your environment variables.'\n );\n\n return fallbackSecret;\n }\n\n return envSecret;\n }\n\n /**\n * Get the underlying better-auth instance\n * Useful for advanced use cases\n */\n getAuthInstance(): Auth<any> {\n return this.getOrCreateAuth();\n }\n\n /**\n * Handle an authentication request\n * Forwards the request directly to better-auth's universal handler\n *\n * better-auth catches internal errors (database / adapter / ORM) and\n * returns a 500 Response instead of throwing. We therefore inspect the\n * response status and log server errors so they are not silently swallowed.\n *\n * @param request - Web standard Request object\n * @returns Web standard Response object\n */\n async handleRequest(request: Request): Promise<Response> {\n const auth = this.getOrCreateAuth();\n const response = await auth.handler(request);\n\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n console.error('[AuthManager] better-auth returned error:', response.status, body);\n } catch {\n console.error('[AuthManager] better-auth returned error:', response.status, '(unable to read body)');\n }\n }\n\n return response;\n }\n\n /**\n * Get the better-auth API for programmatic access\n * Use this for server-side operations (e.g., creating users, checking sessions)\n */\n get api() {\n return this.getOrCreateAuth().api;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/core';\nimport { createAdapterFactory } from 'better-auth/adapters';\nimport type { CleanedWhere } from 'better-auth/adapters';\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * Mapping from better-auth model names to ObjectStack protocol object names.\n *\n * better-auth uses hardcoded model names ('user', 'session', 'account', 'verification')\n * while ObjectStack's protocol layer uses `sys_` prefixed names. This map bridges the two.\n */\nexport const AUTH_MODEL_TO_PROTOCOL: Record<string, string> = {\n user: SystemObjectName.USER,\n session: SystemObjectName.SESSION,\n account: SystemObjectName.ACCOUNT,\n verification: SystemObjectName.VERIFICATION,\n};\n\n/**\n * Resolve a better-auth model name to the ObjectStack protocol object name.\n * Falls back to the original model name for custom / non-core models.\n */\nexport function resolveProtocolName(model: string): string {\n return AUTH_MODEL_TO_PROTOCOL[model] ?? model;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert better-auth where clause to ObjectQL query format.\n *\n * Field names in the incoming {@link CleanedWhere} are expected to already be\n * in snake_case (transformed by `createAdapterFactory`).\n */\nfunction convertWhere(where: CleanedWhere[]): Record<string, any> {\n const filter: Record<string, any> = {};\n\n for (const condition of where) {\n const fieldName = condition.field;\n\n if (condition.operator === 'eq') {\n filter[fieldName] = condition.value;\n } else if (condition.operator === 'ne') {\n filter[fieldName] = { $ne: condition.value };\n } else if (condition.operator === 'in') {\n filter[fieldName] = { $in: condition.value };\n } else if (condition.operator === 'gt') {\n filter[fieldName] = { $gt: condition.value };\n } else if (condition.operator === 'gte') {\n filter[fieldName] = { $gte: condition.value };\n } else if (condition.operator === 'lt') {\n filter[fieldName] = { $lt: condition.value };\n } else if (condition.operator === 'lte') {\n filter[fieldName] = { $lte: condition.value };\n } else if (condition.operator === 'contains') {\n filter[fieldName] = { $regex: condition.value };\n }\n }\n\n return filter;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an ObjectQL adapter **factory** for better-auth.\n *\n * Uses better-auth's official `createAdapterFactory` so that model-name and\n * field-name transformations (declared via `modelName` / `fields` in the\n * betterAuth config) are applied **automatically** before any data reaches\n * ObjectQL. This eliminates the need for manual camelCase ↔ snake_case\n * conversion inside the adapter.\n *\n * The returned value is an `AdapterFactory` – a function of type\n * `(options: BetterAuthOptions) => DBAdapter` – which is the shape expected\n * by `betterAuth({ database: … })`.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth AdapterFactory\n */\nexport function createObjectQLAdapterFactory(dataEngine: IDataEngine) {\n return createAdapterFactory({\n config: {\n adapterId: 'objectql',\n // ObjectQL natively supports these types — no extra conversion needed\n supportsBooleans: true,\n supportsDates: true,\n supportsJSON: true,\n },\n adapter: () => ({\n create: async <T extends Record<string, any>>(\n { model, data, select: _select }: { model: string; data: T; select?: string[] },\n ): Promise<T> => {\n const result = await dataEngine.insert(model, data);\n return result as T;\n },\n\n findOne: async <T>(\n { model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n const result = await dataEngine.findOne(model, { filter, select });\n\n return result ? (result as T) : null;\n },\n\n findMany: async <T>(\n { model, where, limit, offset, sortBy, join: _join }: {\n model: string; where?: CleanedWhere[]; limit: number;\n offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any;\n },\n ): Promise<T[]> => {\n const filter = where ? convertWhere(where) : {};\n\n const sort = sortBy\n ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }]\n : undefined;\n\n const results = await dataEngine.find(model, {\n filter,\n limit: limit || 100,\n skip: offset,\n sort,\n });\n\n return results as T[];\n },\n\n count: async (\n { model, where }: { model: string; where?: CleanedWhere[] },\n ): Promise<number> => {\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(model, { filter });\n },\n\n update: async <T>(\n { model, where, update }: { model: string; where: CleanedWhere[]; update: T },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n // ObjectQL requires an ID for updates – find the record first\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return null;\n\n const result = await dataEngine.update(model, { ...(update as any), id: record.id });\n return result ? (result as T) : null;\n },\n\n updateMany: async (\n { model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n // Sequential updates: ObjectQL requires an ID per update\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.update(model, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<void> => {\n const filter = convertWhere(where);\n\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return;\n\n await dataEngine.delete(model, { filter: { id: record.id } });\n },\n\n deleteMany: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.delete(model, { filter: { id: record.id } });\n }\n return records.length;\n },\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Legacy adapter (kept for backward compatibility)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a raw ObjectQL adapter for better-auth (without factory wrapping).\n *\n * > **Prefer {@link createObjectQLAdapterFactory}** for production use.\n * > The factory version leverages `createAdapterFactory` and automatically\n * > handles model-name + field-name transformations declared in the\n * > better-auth config.\n *\n * This function is retained for direct / low-level usage where callers\n * manage field-name conversion themselves.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth CustomAdapter (raw, without factory wrapping)\n */\nexport function createObjectQLAdapter(dataEngine: IDataEngine) {\n return {\n create: async <T extends Record<string, any>>({ model, data, select: _select }: { model: string; data: T; select?: string[] }): Promise<T> => {\n const objectName = resolveProtocolName(model);\n const result = await dataEngine.insert(objectName, data);\n return result as T;\n },\n\n findOne: async <T>({ model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const result = await dataEngine.findOne(objectName, { filter, select });\n return result ? result as T : null;\n },\n\n findMany: async <T>({ model, where, limit, offset, sortBy, join: _join }: { model: string; where?: CleanedWhere[]; limit: number; offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any }): Promise<T[]> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n const sort = sortBy ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }] : undefined;\n const results = await dataEngine.find(objectName, { filter, limit: limit || 100, skip: offset, sort });\n return results as T[];\n },\n\n count: async ({ model, where }: { model: string; where?: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(objectName, { filter });\n },\n\n update: async <T>({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return null;\n const result = await dataEngine.update(objectName, { ...update, id: record.id });\n return result ? result as T : null;\n },\n\n updateMany: async ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.update(objectName, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<void> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return;\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n },\n\n deleteMany: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n }\n return records.length;\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * better-auth ↔ ObjectStack Schema Mapping\n *\n * better-auth uses camelCase field names internally (e.g. `emailVerified`, `userId`)\n * while ObjectStack's protocol layer uses snake_case (e.g. `email_verified`, `user_id`).\n *\n * These constants declare the `modelName` and `fields` mappings for each core auth\n * model, following better-auth's official schema customisation API\n * ({@link https://www.better-auth.com/docs/concepts/database}).\n *\n * The mappings serve two purposes:\n * 1. `modelName` — maps the default model name to the ObjectStack protocol name\n * (e.g. `user` → `sys_user`).\n * 2. `fields` — maps camelCase field names to their snake_case database column\n * equivalents. Only fields whose names differ need to be listed; fields that\n * are already identical (e.g. `email`, `name`, `token`) are omitted.\n *\n * These mappings are consumed by:\n * - The `betterAuth()` configuration in {@link AuthManager} so that\n * `getAuthTables()` builds the correct schema.\n * - The ObjectQL adapter factory (via `createAdapterFactory`) which uses the\n * schema to transform data and where-clauses automatically.\n */\n\n// ---------------------------------------------------------------------------\n// User model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `user` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | emailVerified | email_verified |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_USER_CONFIG = {\n modelName: SystemObjectName.USER, // 'sys_user'\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Session model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `session` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | userId | user_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n * | ipAddress | ip_address |\n * | userAgent | user_agent |\n */\nexport const AUTH_SESSION_CONFIG = {\n modelName: SystemObjectName.SESSION, // 'sys_session'\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Account model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `account` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:--------------------------|:-------------------------------|\n * | userId | user_id |\n * | providerId | provider_id |\n * | accountId | account_id |\n * | accessToken | access_token |\n * | refreshToken | refresh_token |\n * | idToken | id_token |\n * | accessTokenExpiresAt | access_token_expires_at |\n * | refreshTokenExpiresAt | refresh_token_expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ACCOUNT_CONFIG = {\n modelName: SystemObjectName.ACCOUNT, // 'sys_account'\n fields: {\n userId: 'user_id',\n providerId: 'provider_id',\n accountId: 'account_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Verification model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `verification` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_VERIFICATION_CONFIG = {\n modelName: SystemObjectName.VERIFICATION, // 'sys_verification'\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ===========================================================================\n// Plugin Table Mappings\n// ===========================================================================\n//\n// better-auth plugins (organization, two-factor, etc.) introduce additional\n// tables with their own camelCase field names. The mappings below are passed\n// to the plugin's `schema` option so that `createAdapterFactory` transforms\n// them to snake_case automatically, just like the core models above.\n// ===========================================================================\n\n// ---------------------------------------------------------------------------\n// Organization plugin – organization table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `organization` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ORGANIZATION_SCHEMA = {\n modelName: 'sys_organization',\n fields: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – member table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `member` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_MEMBER_SCHEMA = {\n modelName: 'sys_member',\n fields: {\n organizationId: 'organization_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – invitation table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `invitation` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | inviterId | inviter_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | teamId | team_id |\n */\nexport const AUTH_INVITATION_SCHEMA = {\n modelName: 'sys_invitation',\n fields: {\n organizationId: 'organization_id',\n inviterId: 'inviter_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n teamId: 'team_id',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – session additional fields\n// ---------------------------------------------------------------------------\n\n/**\n * Organization plugin adds `activeOrganizationId` (and optionally\n * `activeTeamId`) to the session model. These field mappings are\n * injected via the organization plugin's `schema.session.fields`.\n */\nexport const AUTH_ORG_SESSION_FIELDS = {\n activeOrganizationId: 'active_organization_id',\n activeTeamId: 'active_team_id',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – team table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `team` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_TEAM_SCHEMA = {\n modelName: 'sys_team',\n fields: {\n organizationId: 'organization_id',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – teamMember table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `teamMember` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | teamId | team_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_TEAM_MEMBER_SCHEMA = {\n modelName: 'sys_team_member',\n fields: {\n teamId: 'team_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Two-Factor plugin – twoFactor table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Two-Factor plugin `twoFactor` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | backupCodes | backup_codes |\n * | userId | user_id |\n */\nexport const AUTH_TWO_FACTOR_SCHEMA = {\n modelName: 'sys_two_factor',\n fields: {\n backupCodes: 'backup_codes',\n userId: 'user_id',\n },\n} as const;\n\n/**\n * Two-Factor plugin adds a `twoFactorEnabled` field to the user model.\n */\nexport const AUTH_TWO_FACTOR_USER_FIELDS = {\n twoFactorEnabled: 'two_factor_enabled',\n} as const;\n\n/**\n * Builds the `schema` option for better-auth's `twoFactor()` plugin.\n *\n * @returns An object suitable for `twoFactor({ schema: … })`\n */\nexport function buildTwoFactorPluginSchema() {\n return {\n twoFactor: AUTH_TWO_FACTOR_SCHEMA,\n user: {\n fields: AUTH_TWO_FACTOR_USER_FIELDS,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build organization plugin schema option\n// ---------------------------------------------------------------------------\n\n/**\n * Builds the `schema` option for better-auth's `organization()` plugin.\n *\n * The organization plugin accepts a `schema` sub-option that allows\n * customising model names and field names for each table it manages.\n * This helper assembles the correct snake_case mappings from the\n * individual `AUTH_*_SCHEMA` constants above.\n *\n * @returns An object suitable for `organization({ schema: … })`\n */\nexport function buildOrganizationPluginSchema() {\n return {\n organization: AUTH_ORGANIZATION_SCHEMA,\n member: AUTH_MEMBER_SCHEMA,\n invitation: AUTH_INVITATION_SCHEMA,\n team: AUTH_TEAM_SCHEMA,\n teamMember: AUTH_TEAM_MEMBER_SCHEMA,\n session: {\n fields: AUTH_ORG_SESSION_FIELDS,\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { AuthConfig } from '@objectstack/spec/system';\nimport { AuthManager } from './auth-manager.js';\n\n/**\n * Auth Plugin Options\n * Extends AuthConfig from spec with additional runtime options\n */\nexport interface AuthPluginOptions extends Partial<AuthConfig> {\n /**\n * Whether to automatically register auth routes\n * @default true\n */\n registerRoutes?: boolean;\n \n /**\n * Base path for auth routes\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Plugin\n * \n * Provides authentication and identity services for ObjectStack applications.\n * \n * **Dual-Mode Operation:**\n * - **Server mode** (HonoServerPlugin active): Registers HTTP routes at basePath,\n * forwarding all auth requests to better-auth's universal handler.\n * - **MSW/Mock mode** (no HTTP server): Gracefully skips route registration but\n * still registers the `auth` service, allowing HttpDispatcher.handleAuth() to\n * simulate auth flows (sign-up, sign-in, etc.) for development and testing.\n * \n * Features:\n * - Session management\n * - User registration/login\n * - OAuth providers (Google, GitHub, etc.)\n * - Organization/team support\n * - 2FA, passkeys, magic links\n * \n * This plugin registers:\n * - `auth` service (auth manager instance) — always\n * - HTTP routes for authentication endpoints — only when HTTP server is available\n * \n * Integrates with better-auth library to provide comprehensive\n * authentication capabilities including email/password, OAuth, 2FA,\n * magic links, passkeys, and organization support.\n */\nexport class AuthPlugin implements Plugin {\n name = 'com.objectstack.auth';\n type = 'standard';\n version = '1.0.0';\n dependencies: string[] = []; // HTTP server is optional; routes are registered only when available\n \n private options: AuthPluginOptions;\n private authManager: AuthManager | null = null;\n\n constructor(options: AuthPluginOptions = {}) {\n this.options = {\n registerRoutes: true,\n basePath: '/api/v1/auth',\n ...options\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Initializing Auth Plugin...');\n\n // Validate required configuration\n if (!this.options.secret) {\n throw new Error('AuthPlugin: secret is required');\n }\n\n // Get data engine service for database operations\n const dataEngine = ctx.getService<any>('data');\n if (!dataEngine) {\n ctx.logger.warn('No data engine service found - auth will use in-memory storage');\n }\n\n // Initialize auth manager with data engine\n this.authManager = new AuthManager({\n ...this.options,\n dataEngine,\n });\n\n // Register auth service\n ctx.registerService('auth', this.authManager);\n \n ctx.logger.info('Auth Plugin initialized successfully');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Starting Auth Plugin...');\n\n if (!this.authManager) {\n throw new Error('Auth manager not initialized');\n }\n\n // Defer HTTP route registration to kernel:ready hook.\n // This ensures all plugins (including HonoServerPlugin) have completed\n // their init and start phases before we attempt to look up the\n // http-server service — making AuthPlugin resilient to plugin\n // loading order.\n if (this.options.registerRoutes) {\n ctx.hook('kernel:ready', async () => {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // Service not found — expected in MSW/mock mode\n }\n\n if (httpServer) {\n // Route registration errors should propagate (server misconfiguration)\n this.registerAuthRoutes(httpServer, ctx);\n ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);\n } else {\n ctx.logger.warn(\n 'No HTTP server available — auth routes not registered. ' +\n 'Auth service is still available for MSW/mock environments via HttpDispatcher.'\n );\n }\n });\n }\n\n // Register auth middleware on ObjectQL engine (if available)\n try {\n const ql = ctx.getService<any>('objectql');\n if (ql && typeof ql.registerMiddleware === 'function') {\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n // If context already has userId or isSystem, skip auth resolution\n if (opCtx.context?.userId || opCtx.context?.isSystem) {\n return next();\n }\n // Future: resolve session from AsyncLocalStorage or request context\n await next();\n });\n ctx.logger.info('Auth middleware registered on ObjectQL engine');\n }\n } catch (_e) {\n ctx.logger.debug('ObjectQL engine not available, skipping auth middleware registration');\n }\n\n ctx.logger.info('Auth Plugin started successfully');\n }\n\n async destroy(): Promise<void> {\n // Cleanup if needed\n this.authManager = null;\n }\n\n /**\n * Register authentication routes with HTTP server\n * \n * Uses better-auth's universal handler for all authentication requests.\n * This forwards all requests under basePath to better-auth, which handles:\n * - Email/password authentication\n * - OAuth providers (Google, GitHub, etc.)\n * - Session management\n * - Password reset\n * - Email verification\n * - 2FA, passkeys, magic links (if enabled)\n */\n private registerAuthRoutes(httpServer: IHttpServer, ctx: PluginContext): void {\n if (!this.authManager) return;\n\n const basePath = this.options.basePath || '/api/v1/auth';\n\n // Get raw Hono app to use native wildcard routing\n // Type assertion is safe here because we explicitly require Hono server as a dependency\n if (!('getRawApp' in httpServer) || typeof (httpServer as any).getRawApp !== 'function') {\n ctx.logger.error('HTTP server does not support getRawApp() - wildcard routing requires Hono server');\n throw new Error(\n 'AuthPlugin requires HonoServerPlugin for wildcard routing support. ' +\n 'Please ensure HonoServerPlugin is loaded before AuthPlugin.'\n );\n }\n\n const rawApp = (httpServer as any).getRawApp();\n\n // Register wildcard route to forward all auth requests to better-auth\n // Better-auth expects requests at its baseURL, so we need to preserve the full path\n rawApp.all(`${basePath}/*`, async (c: any) => {\n try {\n // Get the Web standard Request from Hono context\n const request = c.req.raw as Request;\n \n // Create a new Request with the path rewritten to match better-auth's expectations\n // Better-auth expects paths like /sign-in/email, /sign-up/email, etc.\n // We need to strip our basePath prefix\n const url = new URL(request.url);\n const authPath = url.pathname.replace(basePath, '');\n const rewrittenUrl = new URL(authPath || '/', url.origin);\n rewrittenUrl.search = url.search; // Preserve query params\n \n const rewrittenRequest = new Request(rewrittenUrl, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n duplex: 'half' as any, // Required for Request with body\n });\n\n // Forward to better-auth handler\n const response = await this.authManager!.handleRequest(rewrittenRequest);\n\n // better-auth catches internal errors and returns error Responses\n // without throwing, so the catch block below would never trigger.\n // We proactively log server errors here for observability.\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: ${body}`));\n } catch {\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: (unable to read body)`));\n }\n }\n \n return response;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n ctx.logger.error('Auth request error:', err);\n \n // Return error response\n return new Response(\n JSON.stringify({\n success: false,\n error: err.message,\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n });\n\n ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);\n }\n}\n\n\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth User Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - email: string (unique, lowercase)\n * - email_verified: boolean\n * - name: string\n * - image: string | null\n */\nexport const AuthUser = ObjectSchema.create({\n name: 'sys_user',\n label: 'User',\n pluralLabel: 'Users',\n icon: 'user',\n description: 'User accounts for authentication',\n titleFormat: '{name} ({email})',\n compactLayout: ['name', 'email', 'email_verified'],\n \n fields: {\n // ID is auto-generated by ObjectQL\n id: Field.text({\n label: 'User ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n email: Field.email({\n label: 'Email',\n required: true,\n searchable: true,\n }),\n \n email_verified: Field.boolean({\n label: 'Email Verified',\n defaultValue: false,\n }),\n \n name: Field.text({\n label: 'Name',\n required: true,\n searchable: true,\n maxLength: 255,\n }),\n \n image: Field.url({\n label: 'Profile Image',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['email'], unique: true },\n { fields: ['created_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: true,\n searchable: true,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: true,\n },\n \n // Validation Rules\n validations: [\n {\n name: 'email_unique',\n type: 'unique',\n severity: 'error',\n message: 'Email must be unique',\n fields: ['email'],\n caseSensitive: false,\n },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Session Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - user_id: string\n * - expires_at: Date\n * - token: string\n * - ip_address: string | null\n * - user_agent: string | null\n */\nexport const AuthSession = ObjectSchema.create({\n name: 'sys_session',\n label: 'Session',\n pluralLabel: 'Sessions',\n icon: 'key',\n description: 'Active user sessions',\n titleFormat: 'Session {token}',\n compactLayout: ['user_id', 'expires_at', 'ip_address'],\n \n fields: {\n id: Field.text({\n label: 'Session ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n token: Field.text({\n label: 'Session Token',\n required: true,\n }),\n \n ip_address: Field.text({\n label: 'IP Address',\n required: false,\n maxLength: 45, // Support IPv6\n }),\n \n user_agent: Field.textarea({\n label: 'User Agent',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['token'], unique: true },\n { fields: ['user_id'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false, // Sessions don't need history tracking\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'delete'], // No update for sessions\n trash: false, // Sessions should be hard deleted\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Account Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - provider_id: string (e.g., 'google', 'github')\n * - account_id: string (provider's user ID)\n * - user_id: string (link to user table)\n * - access_token: string | null\n * - refresh_token: string | null\n * - id_token: string | null\n * - access_token_expires_at: Date | null\n * - refresh_token_expires_at: Date | null\n * - scope: string | null\n * - password: string | null (for email/password provider)\n */\nexport const AuthAccount = ObjectSchema.create({\n name: 'sys_account',\n label: 'Account',\n pluralLabel: 'Accounts',\n icon: 'link',\n description: 'OAuth and authentication provider accounts',\n titleFormat: '{provider_id} - {account_id}',\n compactLayout: ['provider_id', 'user_id', 'account_id'],\n \n fields: {\n id: Field.text({\n label: 'Account ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n provider_id: Field.text({\n label: 'Provider ID',\n required: true,\n description: 'OAuth provider identifier (google, github, etc.)',\n }),\n \n account_id: Field.text({\n label: 'Provider Account ID',\n required: true,\n description: \"User's ID in the provider's system\",\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n description: 'Link to user table',\n }),\n \n access_token: Field.textarea({\n label: 'Access Token',\n required: false,\n }),\n \n refresh_token: Field.textarea({\n label: 'Refresh Token',\n required: false,\n }),\n \n id_token: Field.textarea({\n label: 'ID Token',\n required: false,\n }),\n \n access_token_expires_at: Field.datetime({\n label: 'Access Token Expires At',\n required: false,\n }),\n \n refresh_token_expires_at: Field.datetime({\n label: 'Refresh Token Expires At',\n required: false,\n }),\n \n scope: Field.text({\n label: 'OAuth Scope',\n required: false,\n }),\n \n password: Field.text({\n label: 'Password Hash',\n required: false,\n description: 'Hashed password for email/password provider',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['provider_id', 'account_id'], unique: true },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Verification Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - value: string (verification token/code)\n * - expires_at: Date\n * - identifier: string (email or phone number)\n */\nexport const AuthVerification = ObjectSchema.create({\n name: 'sys_verification',\n label: 'Verification',\n pluralLabel: 'Verifications',\n icon: 'shield-check',\n description: 'Email and phone verification tokens',\n titleFormat: 'Verification for {identifier}',\n compactLayout: ['identifier', 'expires_at', 'created_at'],\n \n fields: {\n id: Field.text({\n label: 'Verification ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n value: Field.text({\n label: 'Verification Token',\n required: true,\n description: 'Token or code for verification',\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n identifier: Field.text({\n label: 'Identifier',\n required: true,\n description: 'Email address or phone number',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['value'], unique: true },\n { fields: ['identifier'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'create', 'delete'], // No list or update\n trash: false, // Hard delete expired tokens\n mru: false,\n },\n});\n"],"mappings":"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","names":["SystemObjectName","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth-manager.ts","../src/objectql-adapter.ts","../src/auth-schema-config.ts","../src/auth-plugin.ts","../src/objects/auth-user.object.ts","../src/objects/auth-session.object.ts","../src/objects/auth-account.object.ts","../src/objects/auth-verification.object.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { betterAuth } from 'better-auth';\nimport type { Auth, BetterAuthOptions } from 'better-auth';\nimport { organization } from 'better-auth/plugins/organization';\nimport { twoFactor } from 'better-auth/plugins/two-factor';\nimport { magicLink } from 'better-auth/plugins/magic-link';\nimport type { AuthConfig } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/core';\nimport { createObjectQLAdapterFactory } from './objectql-adapter.js';\nimport {\n AUTH_USER_CONFIG,\n AUTH_SESSION_CONFIG,\n AUTH_ACCOUNT_CONFIG,\n AUTH_VERIFICATION_CONFIG,\n buildOrganizationPluginSchema,\n buildTwoFactorPluginSchema,\n} from './auth-schema-config.js';\n\n/**\n * Extended options for AuthManager\n */\nexport interface AuthManagerOptions extends Partial<AuthConfig> {\n /**\n * Better-Auth instance (for advanced use cases)\n * If not provided, one will be created from config\n */\n authInstance?: Auth<any>;\n\n /**\n * ObjectQL Data Engine instance\n * Required for database operations using ObjectQL instead of third-party ORMs\n */\n dataEngine?: IDataEngine;\n\n /**\n * Base path for auth routes\n * Forwarded to better-auth's basePath option so it can match incoming\n * request URLs without manual path rewriting.\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Manager\n *\n * Wraps better-auth and provides authentication services for ObjectStack.\n * Supports multiple authentication methods:\n * - Email/password\n * - OAuth providers (Google, GitHub, etc.)\n * - Magic links\n * - Two-factor authentication\n * - Passkeys\n * - Organization/teams\n */\nexport class AuthManager {\n private auth: Auth<any> | null = null;\n private config: AuthManagerOptions;\n\n constructor(config: AuthManagerOptions) {\n this.config = config;\n\n // Use provided auth instance\n if (config.authInstance) {\n this.auth = config.authInstance;\n }\n // Don't create auth instance automatically to avoid database initialization errors\n // It will be created lazily when needed\n }\n\n /**\n * Get or create the better-auth instance (lazy initialization)\n */\n private getOrCreateAuth(): Auth<any> {\n if (!this.auth) {\n this.auth = this.createAuthInstance();\n }\n return this.auth;\n }\n\n /**\n * Create a better-auth instance from configuration\n */\n private createAuthInstance(): Auth<any> {\n const betterAuthConfig: BetterAuthOptions = {\n // Base configuration\n secret: this.config.secret || this.generateSecret(),\n baseURL: this.config.baseUrl || 'http://localhost:3000',\n basePath: this.config.basePath || '/api/v1/auth',\n\n // Database adapter configuration\n database: this.createDatabaseConfig(),\n\n // Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)\n // These declarations tell better-auth the actual table/column names used\n // by ObjectStack's protocol layer, enabling automatic transformation via\n // createAdapterFactory.\n user: {\n ...AUTH_USER_CONFIG,\n },\n account: {\n ...AUTH_ACCOUNT_CONFIG,\n },\n verification: {\n ...AUTH_VERIFICATION_CONFIG,\n },\n\n // Email configuration\n emailAndPassword: {\n enabled: true,\n },\n\n // Session configuration\n session: {\n ...AUTH_SESSION_CONFIG,\n expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days default\n updateAge: this.config.session?.updateAge || 60 * 60 * 24, // 1 day default\n },\n\n // better-auth plugins — registered based on AuthPluginConfig flags\n plugins: this.buildPluginList(),\n };\n\n return betterAuth(betterAuthConfig);\n }\n\n /**\n * Build the list of better-auth plugins based on AuthPluginConfig flags.\n *\n * Each plugin that introduces its own database tables is configured with\n * a `schema` option containing the appropriate snake_case field mappings,\n * so that `createAdapterFactory` transforms them automatically.\n */\n private buildPluginList(): any[] {\n const pluginConfig = this.config.plugins;\n const plugins: any[] = [];\n\n if (pluginConfig?.organization) {\n plugins.push(organization({\n schema: buildOrganizationPluginSchema(),\n }));\n }\n\n if (pluginConfig?.twoFactor) {\n plugins.push(twoFactor({\n schema: buildTwoFactorPluginSchema(),\n }));\n }\n\n if (pluginConfig?.magicLink) {\n // magic-link reuses the `verification` table — no extra schema mapping needed.\n // The sendMagicLink callback must be provided by the application at a higher level.\n // Here we provide a no-op default that logs a warning; real applications should\n // override this via AuthManagerOptions or a config extension point.\n plugins.push(magicLink({\n sendMagicLink: async ({ email, url }) => {\n console.warn(\n `[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`,\n );\n },\n }));\n }\n\n return plugins;\n }\n\n /**\n * Create database configuration using ObjectQL adapter\n *\n * better-auth resolves the `database` option as follows:\n * - `undefined` → in-memory adapter\n * - `typeof fn === \"function\"` → treated as `DBAdapterInstance`, called with `(options)`\n * - otherwise → forwarded to Kysely adapter factory (pool/dialect)\n *\n * A raw `CustomAdapter` object would fall into the third branch and fail\n * silently. We therefore wrap the ObjectQL adapter in a factory function\n * so it is correctly recognised as a `DBAdapterInstance`.\n */\n private createDatabaseConfig(): any {\n // Use ObjectQL adapter factory if dataEngine is provided\n if (this.config.dataEngine) {\n // createObjectQLAdapterFactory returns an AdapterFactory\n // (options => DBAdapter) which better-auth invokes via getBaseAdapter().\n // The factory is created by better-auth's createAdapterFactory and\n // automatically applies modelName/fields transformations declared in\n // the betterAuth config above.\n return createObjectQLAdapterFactory(this.config.dataEngine);\n }\n\n // Fallback warning if no dataEngine is provided\n console.warn(\n '⚠️ WARNING: No dataEngine provided to AuthManager! ' +\n 'Using in-memory storage. This is NOT suitable for production. ' +\n 'Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions.'\n );\n\n // Return a minimal in-memory configuration as fallback\n // This allows the system to work in development/testing without a real database\n return undefined; // better-auth will use its default in-memory adapter\n }\n\n /**\n * Generate a secure secret if not provided\n */\n private generateSecret(): string {\n const envSecret = process.env.AUTH_SECRET;\n\n if (!envSecret) {\n // In production, a secret MUST be provided\n // For development/testing, we'll use a fallback but warn about it\n const fallbackSecret = 'dev-secret-' + Date.now();\n\n console.warn(\n '⚠️ WARNING: No AUTH_SECRET environment variable set! ' +\n 'Using a temporary development secret. ' +\n 'This is NOT secure for production use. ' +\n 'Please set AUTH_SECRET in your environment variables.'\n );\n\n return fallbackSecret;\n }\n\n return envSecret;\n }\n\n /**\n * Get the underlying better-auth instance\n * Useful for advanced use cases\n */\n getAuthInstance(): Auth<any> {\n return this.getOrCreateAuth();\n }\n\n /**\n * Handle an authentication request\n * Forwards the request directly to better-auth's universal handler\n *\n * better-auth catches internal errors (database / adapter / ORM) and\n * returns a 500 Response instead of throwing. We therefore inspect the\n * response status and log server errors so they are not silently swallowed.\n *\n * @param request - Web standard Request object\n * @returns Web standard Response object\n */\n async handleRequest(request: Request): Promise<Response> {\n const auth = this.getOrCreateAuth();\n const response = await auth.handler(request);\n\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n console.error('[AuthManager] better-auth returned error:', response.status, body);\n } catch {\n console.error('[AuthManager] better-auth returned error:', response.status, '(unable to read body)');\n }\n }\n\n return response;\n }\n\n /**\n * Get the better-auth API for programmatic access\n * Use this for server-side operations (e.g., creating users, checking sessions)\n */\n get api() {\n return this.getOrCreateAuth().api;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/core';\nimport { createAdapterFactory } from 'better-auth/adapters';\nimport type { CleanedWhere } from 'better-auth/adapters';\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * Mapping from better-auth model names to ObjectStack protocol object names.\n *\n * better-auth uses hardcoded model names ('user', 'session', 'account', 'verification')\n * while ObjectStack's protocol layer uses `sys_` prefixed names. This map bridges the two.\n */\nexport const AUTH_MODEL_TO_PROTOCOL: Record<string, string> = {\n user: SystemObjectName.USER,\n session: SystemObjectName.SESSION,\n account: SystemObjectName.ACCOUNT,\n verification: SystemObjectName.VERIFICATION,\n};\n\n/**\n * Resolve a better-auth model name to the ObjectStack protocol object name.\n * Falls back to the original model name for custom / non-core models.\n */\nexport function resolveProtocolName(model: string): string {\n return AUTH_MODEL_TO_PROTOCOL[model] ?? model;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert better-auth where clause to ObjectQL query format.\n *\n * Field names in the incoming {@link CleanedWhere} are expected to already be\n * in snake_case (transformed by `createAdapterFactory`).\n */\nfunction convertWhere(where: CleanedWhere[]): Record<string, any> {\n const filter: Record<string, any> = {};\n\n for (const condition of where) {\n const fieldName = condition.field;\n\n if (condition.operator === 'eq') {\n filter[fieldName] = condition.value;\n } else if (condition.operator === 'ne') {\n filter[fieldName] = { $ne: condition.value };\n } else if (condition.operator === 'in') {\n filter[fieldName] = { $in: condition.value };\n } else if (condition.operator === 'gt') {\n filter[fieldName] = { $gt: condition.value };\n } else if (condition.operator === 'gte') {\n filter[fieldName] = { $gte: condition.value };\n } else if (condition.operator === 'lt') {\n filter[fieldName] = { $lt: condition.value };\n } else if (condition.operator === 'lte') {\n filter[fieldName] = { $lte: condition.value };\n } else if (condition.operator === 'contains') {\n filter[fieldName] = { $regex: condition.value };\n }\n }\n\n return filter;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create an ObjectQL adapter **factory** for better-auth.\n *\n * Uses better-auth's official `createAdapterFactory` so that model-name and\n * field-name transformations (declared via `modelName` / `fields` in the\n * betterAuth config) are applied **automatically** before any data reaches\n * ObjectQL. This eliminates the need for manual camelCase ↔ snake_case\n * conversion inside the adapter.\n *\n * The returned value is an `AdapterFactory` – a function of type\n * `(options: BetterAuthOptions) => DBAdapter` – which is the shape expected\n * by `betterAuth({ database: … })`.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth AdapterFactory\n */\nexport function createObjectQLAdapterFactory(dataEngine: IDataEngine) {\n return createAdapterFactory({\n config: {\n adapterId: 'objectql',\n // ObjectQL natively supports these types — no extra conversion needed\n supportsBooleans: true,\n supportsDates: true,\n supportsJSON: true,\n },\n adapter: () => ({\n create: async <T extends Record<string, any>>(\n { model, data, select: _select }: { model: string; data: T; select?: string[] },\n ): Promise<T> => {\n const result = await dataEngine.insert(model, data);\n return result as T;\n },\n\n findOne: async <T>(\n { model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n const result = await dataEngine.findOne(model, { filter, select });\n\n return result ? (result as T) : null;\n },\n\n findMany: async <T>(\n { model, where, limit, offset, sortBy, join: _join }: {\n model: string; where?: CleanedWhere[]; limit: number;\n offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any;\n },\n ): Promise<T[]> => {\n const filter = where ? convertWhere(where) : {};\n\n const sort = sortBy\n ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }]\n : undefined;\n\n const results = await dataEngine.find(model, {\n filter,\n limit: limit || 100,\n skip: offset,\n sort,\n });\n\n return results as T[];\n },\n\n count: async (\n { model, where }: { model: string; where?: CleanedWhere[] },\n ): Promise<number> => {\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(model, { filter });\n },\n\n update: async <T>(\n { model, where, update }: { model: string; where: CleanedWhere[]; update: T },\n ): Promise<T | null> => {\n const filter = convertWhere(where);\n\n // ObjectQL requires an ID for updates – find the record first\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return null;\n\n const result = await dataEngine.update(model, { ...(update as any), id: record.id });\n return result ? (result as T) : null;\n },\n\n updateMany: async (\n { model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n // Sequential updates: ObjectQL requires an ID per update\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.update(model, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<void> => {\n const filter = convertWhere(where);\n\n const record = await dataEngine.findOne(model, { filter });\n if (!record) return;\n\n await dataEngine.delete(model, { filter: { id: record.id } });\n },\n\n deleteMany: async (\n { model, where }: { model: string; where: CleanedWhere[] },\n ): Promise<number> => {\n const filter = convertWhere(where);\n\n const records = await dataEngine.find(model, { filter });\n for (const record of records) {\n await dataEngine.delete(model, { filter: { id: record.id } });\n }\n return records.length;\n },\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Legacy adapter (kept for backward compatibility)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a raw ObjectQL adapter for better-auth (without factory wrapping).\n *\n * > **Prefer {@link createObjectQLAdapterFactory}** for production use.\n * > The factory version leverages `createAdapterFactory` and automatically\n * > handles model-name + field-name transformations declared in the\n * > better-auth config.\n *\n * This function is retained for direct / low-level usage where callers\n * manage field-name conversion themselves.\n *\n * @param dataEngine - ObjectQL data engine instance\n * @returns better-auth CustomAdapter (raw, without factory wrapping)\n */\nexport function createObjectQLAdapter(dataEngine: IDataEngine) {\n return {\n create: async <T extends Record<string, any>>({ model, data, select: _select }: { model: string; data: T; select?: string[] }): Promise<T> => {\n const objectName = resolveProtocolName(model);\n const result = await dataEngine.insert(objectName, data);\n return result as T;\n },\n\n findOne: async <T>({ model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const result = await dataEngine.findOne(objectName, { filter, select });\n return result ? result as T : null;\n },\n\n findMany: async <T>({ model, where, limit, offset, sortBy, join: _join }: { model: string; where?: CleanedWhere[]; limit: number; offset?: number; sortBy?: { field: string; direction: 'asc' | 'desc' }; join?: any }): Promise<T[]> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n const sort = sortBy ? [{ field: sortBy.field, order: sortBy.direction as 'asc' | 'desc' }] : undefined;\n const results = await dataEngine.find(objectName, { filter, limit: limit || 100, skip: offset, sort });\n return results as T[];\n },\n\n count: async ({ model, where }: { model: string; where?: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = where ? convertWhere(where) : {};\n return await dataEngine.count(objectName, { filter });\n },\n\n update: async <T>({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<T | null> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return null;\n const result = await dataEngine.update(objectName, { ...update, id: record.id });\n return result ? result as T : null;\n },\n\n updateMany: async ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record<string, any> }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.update(objectName, { ...update, id: record.id });\n }\n return records.length;\n },\n\n delete: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<void> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const record = await dataEngine.findOne(objectName, { filter });\n if (!record) return;\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n },\n\n deleteMany: async ({ model, where }: { model: string; where: CleanedWhere[] }): Promise<number> => {\n const objectName = resolveProtocolName(model);\n const filter = convertWhere(where);\n const records = await dataEngine.find(objectName, { filter });\n for (const record of records) {\n await dataEngine.delete(objectName, { filter: { id: record.id } });\n }\n return records.length;\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SystemObjectName } from '@objectstack/spec/system';\n\n/**\n * better-auth ↔ ObjectStack Schema Mapping\n *\n * better-auth uses camelCase field names internally (e.g. `emailVerified`, `userId`)\n * while ObjectStack's protocol layer uses snake_case (e.g. `email_verified`, `user_id`).\n *\n * These constants declare the `modelName` and `fields` mappings for each core auth\n * model, following better-auth's official schema customisation API\n * ({@link https://www.better-auth.com/docs/concepts/database}).\n *\n * The mappings serve two purposes:\n * 1. `modelName` — maps the default model name to the ObjectStack protocol name\n * (e.g. `user` → `sys_user`).\n * 2. `fields` — maps camelCase field names to their snake_case database column\n * equivalents. Only fields whose names differ need to be listed; fields that\n * are already identical (e.g. `email`, `name`, `token`) are omitted.\n *\n * These mappings are consumed by:\n * - The `betterAuth()` configuration in {@link AuthManager} so that\n * `getAuthTables()` builds the correct schema.\n * - The ObjectQL adapter factory (via `createAdapterFactory`) which uses the\n * schema to transform data and where-clauses automatically.\n */\n\n// ---------------------------------------------------------------------------\n// User model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `user` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | emailVerified | email_verified |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_USER_CONFIG = {\n modelName: SystemObjectName.USER, // 'sys_user'\n fields: {\n emailVerified: 'email_verified',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Session model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `session` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | userId | user_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n * | ipAddress | ip_address |\n * | userAgent | user_agent |\n */\nexport const AUTH_SESSION_CONFIG = {\n modelName: SystemObjectName.SESSION, // 'sys_session'\n fields: {\n userId: 'user_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n ipAddress: 'ip_address',\n userAgent: 'user_agent',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Account model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `account` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:--------------------------|:-------------------------------|\n * | userId | user_id |\n * | providerId | provider_id |\n * | accountId | account_id |\n * | accessToken | access_token |\n * | refreshToken | refresh_token |\n * | idToken | id_token |\n * | accessTokenExpiresAt | access_token_expires_at |\n * | refreshTokenExpiresAt | refresh_token_expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ACCOUNT_CONFIG = {\n modelName: SystemObjectName.ACCOUNT, // 'sys_account'\n fields: {\n userId: 'user_id',\n providerId: 'provider_id',\n accountId: 'account_id',\n accessToken: 'access_token',\n refreshToken: 'refresh_token',\n idToken: 'id_token',\n accessTokenExpiresAt: 'access_token_expires_at',\n refreshTokenExpiresAt: 'refresh_token_expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Verification model\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth `verification` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_VERIFICATION_CONFIG = {\n modelName: SystemObjectName.VERIFICATION, // 'sys_verification'\n fields: {\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ===========================================================================\n// Plugin Table Mappings\n// ===========================================================================\n//\n// better-auth plugins (organization, two-factor, etc.) introduce additional\n// tables with their own camelCase field names. The mappings below are passed\n// to the plugin's `schema` option so that `createAdapterFactory` transforms\n// them to snake_case automatically, just like the core models above.\n// ===========================================================================\n\n// ---------------------------------------------------------------------------\n// Organization plugin – organization table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `organization` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_ORGANIZATION_SCHEMA = {\n modelName: 'sys_organization',\n fields: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – member table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `member` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_MEMBER_SCHEMA = {\n modelName: 'sys_member',\n fields: {\n organizationId: 'organization_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – invitation table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `invitation` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | inviterId | inviter_id |\n * | expiresAt | expires_at |\n * | createdAt | created_at |\n * | teamId | team_id |\n */\nexport const AUTH_INVITATION_SCHEMA = {\n modelName: 'sys_invitation',\n fields: {\n organizationId: 'organization_id',\n inviterId: 'inviter_id',\n expiresAt: 'expires_at',\n createdAt: 'created_at',\n teamId: 'team_id',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – session additional fields\n// ---------------------------------------------------------------------------\n\n/**\n * Organization plugin adds `activeOrganizationId` (and optionally\n * `activeTeamId`) to the session model. These field mappings are\n * injected via the organization plugin's `schema.session.fields`.\n */\nexport const AUTH_ORG_SESSION_FIELDS = {\n activeOrganizationId: 'active_organization_id',\n activeTeamId: 'active_team_id',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – team table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `team` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | organizationId | organization_id |\n * | createdAt | created_at |\n * | updatedAt | updated_at |\n */\nexport const AUTH_TEAM_SCHEMA = {\n modelName: 'sys_team',\n fields: {\n organizationId: 'organization_id',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Organization plugin – teamMember table (optional, when teams enabled)\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Organization plugin `teamMember` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | teamId | team_id |\n * | userId | user_id |\n * | createdAt | created_at |\n */\nexport const AUTH_TEAM_MEMBER_SCHEMA = {\n modelName: 'sys_team_member',\n fields: {\n teamId: 'team_id',\n userId: 'user_id',\n createdAt: 'created_at',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Two-Factor plugin – twoFactor table\n// ---------------------------------------------------------------------------\n\n/**\n * better-auth Two-Factor plugin `twoFactor` model mapping.\n *\n * | camelCase (better-auth) | snake_case (ObjectStack) |\n * |:------------------------|:-------------------------|\n * | backupCodes | backup_codes |\n * | userId | user_id |\n */\nexport const AUTH_TWO_FACTOR_SCHEMA = {\n modelName: 'sys_two_factor',\n fields: {\n backupCodes: 'backup_codes',\n userId: 'user_id',\n },\n} as const;\n\n/**\n * Two-Factor plugin adds a `twoFactorEnabled` field to the user model.\n */\nexport const AUTH_TWO_FACTOR_USER_FIELDS = {\n twoFactorEnabled: 'two_factor_enabled',\n} as const;\n\n/**\n * Builds the `schema` option for better-auth's `twoFactor()` plugin.\n *\n * @returns An object suitable for `twoFactor({ schema: … })`\n */\nexport function buildTwoFactorPluginSchema() {\n return {\n twoFactor: AUTH_TWO_FACTOR_SCHEMA,\n user: {\n fields: AUTH_TWO_FACTOR_USER_FIELDS,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helper: build organization plugin schema option\n// ---------------------------------------------------------------------------\n\n/**\n * Builds the `schema` option for better-auth's `organization()` plugin.\n *\n * The organization plugin accepts a `schema` sub-option that allows\n * customising model names and field names for each table it manages.\n * This helper assembles the correct snake_case mappings from the\n * individual `AUTH_*_SCHEMA` constants above.\n *\n * @returns An object suitable for `organization({ schema: … })`\n */\nexport function buildOrganizationPluginSchema() {\n return {\n organization: AUTH_ORGANIZATION_SCHEMA,\n member: AUTH_MEMBER_SCHEMA,\n invitation: AUTH_INVITATION_SCHEMA,\n team: AUTH_TEAM_SCHEMA,\n teamMember: AUTH_TEAM_MEMBER_SCHEMA,\n session: {\n fields: AUTH_ORG_SESSION_FIELDS,\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { AuthConfig } from '@objectstack/spec/system';\nimport { AuthManager } from './auth-manager.js';\n\n/**\n * Auth Plugin Options\n * Extends AuthConfig from spec with additional runtime options\n */\nexport interface AuthPluginOptions extends Partial<AuthConfig> {\n /**\n * Whether to automatically register auth routes\n * @default true\n */\n registerRoutes?: boolean;\n \n /**\n * Base path for auth routes\n * @default '/api/v1/auth'\n */\n basePath?: string;\n}\n\n/**\n * Authentication Plugin\n * \n * Provides authentication and identity services for ObjectStack applications.\n * \n * **Dual-Mode Operation:**\n * - **Server mode** (HonoServerPlugin active): Registers HTTP routes at basePath,\n * forwarding all auth requests to better-auth's universal handler.\n * - **MSW/Mock mode** (no HTTP server): Gracefully skips route registration but\n * still registers the `auth` service, allowing HttpDispatcher.handleAuth() to\n * simulate auth flows (sign-up, sign-in, etc.) for development and testing.\n * \n * Features:\n * - Session management\n * - User registration/login\n * - OAuth providers (Google, GitHub, etc.)\n * - Organization/team support\n * - 2FA, passkeys, magic links\n * \n * This plugin registers:\n * - `auth` service (auth manager instance) — always\n * - HTTP routes for authentication endpoints — only when HTTP server is available\n * \n * Integrates with better-auth library to provide comprehensive\n * authentication capabilities including email/password, OAuth, 2FA,\n * magic links, passkeys, and organization support.\n */\nexport class AuthPlugin implements Plugin {\n name = 'com.objectstack.auth';\n type = 'standard';\n version = '1.0.0';\n dependencies: string[] = []; // HTTP server is optional; routes are registered only when available\n \n private options: AuthPluginOptions;\n private authManager: AuthManager | null = null;\n\n constructor(options: AuthPluginOptions = {}) {\n this.options = {\n registerRoutes: true,\n basePath: '/api/v1/auth',\n ...options\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Initializing Auth Plugin...');\n\n // Validate required configuration\n if (!this.options.secret) {\n throw new Error('AuthPlugin: secret is required');\n }\n\n // Get data engine service for database operations\n const dataEngine = ctx.getService<any>('data');\n if (!dataEngine) {\n ctx.logger.warn('No data engine service found - auth will use in-memory storage');\n }\n\n // Initialize auth manager with data engine\n this.authManager = new AuthManager({\n ...this.options,\n dataEngine,\n });\n\n // Register auth service\n ctx.registerService('auth', this.authManager);\n \n ctx.logger.info('Auth Plugin initialized successfully');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Starting Auth Plugin...');\n\n if (!this.authManager) {\n throw new Error('Auth manager not initialized');\n }\n\n // Defer HTTP route registration to kernel:ready hook.\n // This ensures all plugins (including HonoServerPlugin) have completed\n // their init and start phases before we attempt to look up the\n // http-server service — making AuthPlugin resilient to plugin\n // loading order.\n if (this.options.registerRoutes) {\n ctx.hook('kernel:ready', async () => {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // Service not found — expected in MSW/mock mode\n }\n\n if (httpServer) {\n // Route registration errors should propagate (server misconfiguration)\n this.registerAuthRoutes(httpServer, ctx);\n ctx.logger.info(`Auth routes registered at ${this.options.basePath}`);\n } else {\n ctx.logger.warn(\n 'No HTTP server available — auth routes not registered. ' +\n 'Auth service is still available for MSW/mock environments via HttpDispatcher.'\n );\n }\n });\n }\n\n // Register auth middleware on ObjectQL engine (if available)\n try {\n const ql = ctx.getService<any>('objectql');\n if (ql && typeof ql.registerMiddleware === 'function') {\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n // If context already has userId or isSystem, skip auth resolution\n if (opCtx.context?.userId || opCtx.context?.isSystem) {\n return next();\n }\n // Future: resolve session from AsyncLocalStorage or request context\n await next();\n });\n ctx.logger.info('Auth middleware registered on ObjectQL engine');\n }\n } catch (_e) {\n ctx.logger.debug('ObjectQL engine not available, skipping auth middleware registration');\n }\n\n ctx.logger.info('Auth Plugin started successfully');\n }\n\n async destroy(): Promise<void> {\n // Cleanup if needed\n this.authManager = null;\n }\n\n /**\n * Register authentication routes with HTTP server\n * \n * Uses better-auth's universal handler for all authentication requests.\n * This forwards all requests under basePath to better-auth, which handles:\n * - Email/password authentication\n * - OAuth providers (Google, GitHub, etc.)\n * - Session management\n * - Password reset\n * - Email verification\n * - 2FA, passkeys, magic links (if enabled)\n */\n private registerAuthRoutes(httpServer: IHttpServer, ctx: PluginContext): void {\n if (!this.authManager) return;\n\n const basePath = this.options.basePath || '/api/v1/auth';\n\n // Get raw Hono app to use native wildcard routing\n // Type assertion is safe here because we explicitly require Hono server as a dependency\n if (!('getRawApp' in httpServer) || typeof (httpServer as any).getRawApp !== 'function') {\n ctx.logger.error('HTTP server does not support getRawApp() - wildcard routing requires Hono server');\n throw new Error(\n 'AuthPlugin requires HonoServerPlugin for wildcard routing support. ' +\n 'Please ensure HonoServerPlugin is loaded before AuthPlugin.'\n );\n }\n\n const rawApp = (httpServer as any).getRawApp();\n\n // Register wildcard route to forward all auth requests to better-auth.\n // better-auth is configured with basePath matching our route prefix, so we\n // forward the original request directly — no path rewriting needed.\n rawApp.all(`${basePath}/*`, async (c: any) => {\n try {\n // Forward the original request to better-auth handler\n const response = await this.authManager!.handleRequest(c.req.raw);\n\n // better-auth catches internal errors and returns error Responses\n // without throwing, so the catch block below would never trigger.\n // We proactively log server errors here for observability.\n if (response.status >= 500) {\n try {\n const body = await response.clone().text();\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: ${body}`));\n } catch {\n ctx.logger.error('[AuthPlugin] better-auth returned server error', new Error(`HTTP ${response.status}: (unable to read body)`));\n }\n }\n \n return response;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n ctx.logger.error('Auth request error:', err);\n \n // Return error response\n return new Response(\n JSON.stringify({\n success: false,\n error: err.message,\n }),\n {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n }\n );\n }\n });\n\n ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);\n }\n}\n\n\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth User Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - email: string (unique, lowercase)\n * - email_verified: boolean\n * - name: string\n * - image: string | null\n */\nexport const AuthUser = ObjectSchema.create({\n name: 'sys_user',\n label: 'User',\n pluralLabel: 'Users',\n icon: 'user',\n description: 'User accounts for authentication',\n titleFormat: '{name} ({email})',\n compactLayout: ['name', 'email', 'email_verified'],\n \n fields: {\n // ID is auto-generated by ObjectQL\n id: Field.text({\n label: 'User ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n email: Field.email({\n label: 'Email',\n required: true,\n searchable: true,\n }),\n \n email_verified: Field.boolean({\n label: 'Email Verified',\n defaultValue: false,\n }),\n \n name: Field.text({\n label: 'Name',\n required: true,\n searchable: true,\n maxLength: 255,\n }),\n \n image: Field.url({\n label: 'Profile Image',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['email'], unique: true },\n { fields: ['created_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: true,\n searchable: true,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: true,\n },\n \n // Validation Rules\n validations: [\n {\n name: 'email_unique',\n type: 'unique',\n severity: 'error',\n message: 'Email must be unique',\n fields: ['email'],\n caseSensitive: false,\n },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Session Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - user_id: string\n * - expires_at: Date\n * - token: string\n * - ip_address: string | null\n * - user_agent: string | null\n */\nexport const AuthSession = ObjectSchema.create({\n name: 'sys_session',\n label: 'Session',\n pluralLabel: 'Sessions',\n icon: 'key',\n description: 'Active user sessions',\n titleFormat: 'Session {token}',\n compactLayout: ['user_id', 'expires_at', 'ip_address'],\n \n fields: {\n id: Field.text({\n label: 'Session ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n token: Field.text({\n label: 'Session Token',\n required: true,\n }),\n \n ip_address: Field.text({\n label: 'IP Address',\n required: false,\n maxLength: 45, // Support IPv6\n }),\n \n user_agent: Field.textarea({\n label: 'User Agent',\n required: false,\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['token'], unique: true },\n { fields: ['user_id'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false, // Sessions don't need history tracking\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'delete'], // No update for sessions\n trash: false, // Sessions should be hard deleted\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Account Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - provider_id: string (e.g., 'google', 'github')\n * - account_id: string (provider's user ID)\n * - user_id: string (link to user table)\n * - access_token: string | null\n * - refresh_token: string | null\n * - id_token: string | null\n * - access_token_expires_at: Date | null\n * - refresh_token_expires_at: Date | null\n * - scope: string | null\n * - password: string | null (for email/password provider)\n */\nexport const AuthAccount = ObjectSchema.create({\n name: 'sys_account',\n label: 'Account',\n pluralLabel: 'Accounts',\n icon: 'link',\n description: 'OAuth and authentication provider accounts',\n titleFormat: '{provider_id} - {account_id}',\n compactLayout: ['provider_id', 'user_id', 'account_id'],\n \n fields: {\n id: Field.text({\n label: 'Account ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n provider_id: Field.text({\n label: 'Provider ID',\n required: true,\n description: 'OAuth provider identifier (google, github, etc.)',\n }),\n \n account_id: Field.text({\n label: 'Provider Account ID',\n required: true,\n description: \"User's ID in the provider's system\",\n }),\n \n user_id: Field.text({\n label: 'User ID',\n required: true,\n description: 'Link to user table',\n }),\n \n access_token: Field.textarea({\n label: 'Access Token',\n required: false,\n }),\n \n refresh_token: Field.textarea({\n label: 'Refresh Token',\n required: false,\n }),\n \n id_token: Field.textarea({\n label: 'ID Token',\n required: false,\n }),\n \n access_token_expires_at: Field.datetime({\n label: 'Access Token Expires At',\n required: false,\n }),\n \n refresh_token_expires_at: Field.datetime({\n label: 'Refresh Token Expires At',\n required: false,\n }),\n \n scope: Field.text({\n label: 'OAuth Scope',\n required: false,\n }),\n \n password: Field.text({\n label: 'Password Hash',\n required: false,\n description: 'Hashed password for email/password provider',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['user_id'], unique: false },\n { fields: ['provider_id', 'account_id'], unique: true },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'list', 'create', 'update', 'delete'],\n trash: true,\n mru: false,\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * Auth Verification Object\n * \n * Uses better-auth's native schema for seamless migration:\n * - id: string\n * - created_at: Date\n * - updated_at: Date\n * - value: string (verification token/code)\n * - expires_at: Date\n * - identifier: string (email or phone number)\n */\nexport const AuthVerification = ObjectSchema.create({\n name: 'sys_verification',\n label: 'Verification',\n pluralLabel: 'Verifications',\n icon: 'shield-check',\n description: 'Email and phone verification tokens',\n titleFormat: 'Verification for {identifier}',\n compactLayout: ['identifier', 'expires_at', 'created_at'],\n \n fields: {\n id: Field.text({\n label: 'Verification ID',\n required: true,\n readonly: true,\n }),\n \n created_at: Field.datetime({\n label: 'Created At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n updated_at: Field.datetime({\n label: 'Updated At',\n defaultValue: 'NOW()',\n readonly: true,\n }),\n \n value: Field.text({\n label: 'Verification Token',\n required: true,\n description: 'Token or code for verification',\n }),\n \n expires_at: Field.datetime({\n label: 'Expires At',\n required: true,\n }),\n \n identifier: Field.text({\n label: 'Identifier',\n required: true,\n description: 'Email address or phone number',\n }),\n },\n \n // Database indexes for performance\n indexes: [\n { fields: ['value'], unique: true },\n { fields: ['identifier'], unique: false },\n { fields: ['expires_at'], unique: false },\n ],\n \n // Enable features\n enable: {\n trackHistory: false,\n searchable: false,\n apiEnabled: true,\n apiMethods: ['get', 'create', 'delete'], // No list or update\n trash: false, // Hard delete expired tokens\n mru: false,\n },\n});\n"],"mappings":"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","names":["SystemObjectName","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field"]}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/plugin-auth",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Authentication & Identity Plugin for ObjectStack",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"better-auth": "^1.5.4",
|
|
10
|
-
"@objectstack/core": "3.2.
|
|
11
|
-
"@objectstack/spec": "3.2.
|
|
10
|
+
"@objectstack/core": "3.2.5",
|
|
11
|
+
"@objectstack/spec": "3.2.5"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@types/node": "^25.3.5",
|
|
15
15
|
"typescript": "^5.0.0",
|
|
16
16
|
"vitest": "^4.0.18",
|
|
17
|
-
"@objectstack/cli": "3.2.
|
|
17
|
+
"@objectstack/cli": "3.2.5"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsup --config ../../../tsup.config.ts",
|
package/src/auth-manager.test.ts
CHANGED
|
@@ -259,6 +259,45 @@ describe('AuthManager', () => {
|
|
|
259
259
|
});
|
|
260
260
|
});
|
|
261
261
|
|
|
262
|
+
describe('basePath configuration', () => {
|
|
263
|
+
it('should default basePath to /api/v1/auth when not specified', () => {
|
|
264
|
+
let capturedConfig: any;
|
|
265
|
+
(betterAuth as any).mockImplementation((config: any) => {
|
|
266
|
+
capturedConfig = config;
|
|
267
|
+
return { handler: vi.fn(), api: {} };
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
271
|
+
const manager = new AuthManager({
|
|
272
|
+
secret: 'test-secret-at-least-32-chars-long',
|
|
273
|
+
baseUrl: 'http://localhost:3000',
|
|
274
|
+
});
|
|
275
|
+
manager.getAuthInstance();
|
|
276
|
+
warnSpy.mockRestore();
|
|
277
|
+
|
|
278
|
+
expect(capturedConfig.basePath).toBe('/api/v1/auth');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should use custom basePath when provided', () => {
|
|
282
|
+
let capturedConfig: any;
|
|
283
|
+
(betterAuth as any).mockImplementation((config: any) => {
|
|
284
|
+
capturedConfig = config;
|
|
285
|
+
return { handler: vi.fn(), api: {} };
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
289
|
+
const manager = new AuthManager({
|
|
290
|
+
secret: 'test-secret-at-least-32-chars-long',
|
|
291
|
+
baseUrl: 'http://localhost:3000',
|
|
292
|
+
basePath: '/custom/auth',
|
|
293
|
+
});
|
|
294
|
+
manager.getAuthInstance();
|
|
295
|
+
warnSpy.mockRestore();
|
|
296
|
+
|
|
297
|
+
expect(capturedConfig.basePath).toBe('/custom/auth');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
262
301
|
describe('plugin registration', () => {
|
|
263
302
|
it('should not include any plugins when no plugin config is provided', () => {
|
|
264
303
|
let capturedConfig: any;
|
package/src/auth-manager.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
|
32
32
|
* Required for database operations using ObjectQL instead of third-party ORMs
|
|
33
33
|
*/
|
|
34
34
|
dataEngine?: IDataEngine;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Base path for auth routes
|
|
38
|
+
* Forwarded to better-auth's basePath option so it can match incoming
|
|
39
|
+
* request URLs without manual path rewriting.
|
|
40
|
+
* @default '/api/v1/auth'
|
|
41
|
+
*/
|
|
42
|
+
basePath?: string;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
/**
|
|
@@ -79,7 +87,7 @@ export class AuthManager {
|
|
|
79
87
|
// Base configuration
|
|
80
88
|
secret: this.config.secret || this.generateSecret(),
|
|
81
89
|
baseURL: this.config.baseUrl || 'http://localhost:3000',
|
|
82
|
-
basePath: '/',
|
|
90
|
+
basePath: this.config.basePath || '/api/v1/auth',
|
|
83
91
|
|
|
84
92
|
// Database adapter configuration
|
|
85
93
|
database: this.createDatabaseConfig(),
|
package/src/auth-plugin.ts
CHANGED
|
@@ -181,30 +181,13 @@ export class AuthPlugin implements Plugin {
|
|
|
181
181
|
|
|
182
182
|
const rawApp = (httpServer as any).getRawApp();
|
|
183
183
|
|
|
184
|
-
// Register wildcard route to forward all auth requests to better-auth
|
|
185
|
-
//
|
|
184
|
+
// Register wildcard route to forward all auth requests to better-auth.
|
|
185
|
+
// better-auth is configured with basePath matching our route prefix, so we
|
|
186
|
+
// forward the original request directly — no path rewriting needed.
|
|
186
187
|
rawApp.all(`${basePath}/*`, async (c: any) => {
|
|
187
188
|
try {
|
|
188
|
-
//
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
// Create a new Request with the path rewritten to match better-auth's expectations
|
|
192
|
-
// Better-auth expects paths like /sign-in/email, /sign-up/email, etc.
|
|
193
|
-
// We need to strip our basePath prefix
|
|
194
|
-
const url = new URL(request.url);
|
|
195
|
-
const authPath = url.pathname.replace(basePath, '');
|
|
196
|
-
const rewrittenUrl = new URL(authPath || '/', url.origin);
|
|
197
|
-
rewrittenUrl.search = url.search; // Preserve query params
|
|
198
|
-
|
|
199
|
-
const rewrittenRequest = new Request(rewrittenUrl, {
|
|
200
|
-
method: request.method,
|
|
201
|
-
headers: request.headers,
|
|
202
|
-
body: request.body,
|
|
203
|
-
duplex: 'half' as any, // Required for Request with body
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Forward to better-auth handler
|
|
207
|
-
const response = await this.authManager!.handleRequest(rewrittenRequest);
|
|
189
|
+
// Forward the original request to better-auth handler
|
|
190
|
+
const response = await this.authManager!.handleRequest(c.req.raw);
|
|
208
191
|
|
|
209
192
|
// better-auth catches internal errors and returns error Responses
|
|
210
193
|
// without throwing, so the catch block below would never trigger.
|