import { Plugin, PluginContext, IDataEngine } from '@objectstack/core'; import * as better_auth from 'better-auth'; import { BetterAuthOptions, Auth } from 'better-auth'; import { AuthConfig, OidcProvidersConfig } from '@objectstack/spec/system'; export { AuthConfig, AuthPluginConfig, AuthProviderConfig } from '@objectstack/spec/system'; import { IEmailService } from '@objectstack/spec/contracts'; import * as better_auth_adapters from 'better-auth/adapters'; import { CleanedWhere } from 'better-auth/adapters'; /** * Auth Plugin Options * Extends AuthConfig from spec with additional runtime options */ interface AuthPluginOptions extends Partial { /** * Whether to automatically register auth routes * @default true */ registerRoutes?: boolean; /** * Base path for auth routes * @default '/api/v1/auth' */ basePath?: string; /** * Override the datasource that owns the identity tables (sys_user, * sys_session, …) when AuthPlugin's manifest is registered. * * Defaults to `'cloud'` (control-plane DB) so the historical * single-tenant control-plane behaviour is preserved. Per-project * kernels in objectos pass `'default'` so identity tables live in the * project's own database — each project owns its own users. */ manifestDatasource?: string; /** * Application-specific organization roles to register with Better-Auth's * organization plugin so invitations to those roles aren't rejected with * ROLE_NOT_FOUND. Forwarded as-is to AuthManager. See * {@link AuthManagerOptions.additionalOrgRoles} for details. */ additionalOrgRoles?: string[]; /** * Pass-through to better-auth's `databaseHooks` option. Used by * platform consumers (objectos kernel) to attach a * `user.create.after` hook that auto-provisions a personal * organization for JIT-created SSO users — better-auth's adapter * bypasses kernel-level ObjectQL middleware, so this is the only * hook point that fires for every user creation path (email signup, * social/OIDC sign-in, admin-created accounts). */ databaseHooks?: BetterAuthOptions['databaseHooks']; } /** * Authentication Plugin * * Provides authentication and identity services for ObjectStack applications. * * **Dual-Mode Operation:** * - **Server mode** (HonoServerPlugin active): Registers HTTP routes at basePath, * forwarding all auth requests to better-auth's universal handler. * - **MSW/Mock mode** (no HTTP server): Gracefully skips route registration but * still registers the `auth` service, allowing HttpDispatcher.handleAuth() to * simulate auth flows (sign-up, sign-in, etc.) for development and testing. * * Features: * - Session management * - User registration/login * - OAuth providers (Google, GitHub, etc.) * - Organization/team support * - 2FA, passkeys, magic links * * This plugin registers: * - `auth` service (auth manager instance) — always * - `app.com.objectstack.system` service (system object definitions) — always * - HTTP routes for authentication endpoints — only when HTTP server is available * * Integrates with better-auth library to provide comprehensive * authentication capabilities including email/password, OAuth, 2FA, * magic links, passkeys, and organization support. */ declare class AuthPlugin implements Plugin { name: string; type: string; version: string; dependencies: string[]; private options; private authManager; private configuredSocialProviders; constructor(options?: AuthPluginOptions); /** * Open-source provider fallback: enable Google sign-in from conventional * provider env vars when the application did not configure Google itself. * Enterprise / product packages can contribute richer provider sets through * the `auth:configure` hook below. */ private applyEnvSocialProviderFallbacks; init(ctx: PluginContext): Promise; start(ctx: PluginContext): Promise; /** * Bind the small open-source auth settings namespace to better-auth config. * * Only explicit settings values (stored or OS_AUTH_* env overrides) affect * runtime config. Manifest defaults are UI defaults and do not mask code or * deployment configuration. */ private bindAuthSettings; destroy(): Promise; /** * Dev-only admin bootstrap. * * On an EMPTY database (zero users), provision a well-known, loginable * admin (admin@objectos.ai / admin123 by default) so backend debugging * never blocks on a first-run sign-up wizard. The account is created * through better-auth's real server-side `signUpEmail` pipeline (hashed * credential + the same hooks the HTTP endpoint runs), so it is fully * loginable; plugin-security's first-user middleware then promotes it to * platform admin automatically. * * This replaces two earlier, divergent seeds: * • the CLI-side HTTP seed (`os dev`), which POSTed the public sign-up * endpoint from the parent process — racing server readiness and * targeting a hard-coded port that broke under dev port auto-shift; and * • plugin-dev's raw `sys_user` insert, which produced a credential-less, * un-loginable row. * Running it in-process needs no port and no readiness polling. * * Idempotent and non-destructive: it only ever acts on a zero-user DB and * never touches an existing account, so a custom password is never * overwritten. * * HARD-GATED to development (NODE_ENV==='development'): a known-credential * admin can never be provisioned in production. Opt out within dev via * OS_SEED_ADMIN=0 (or false/off/no). */ private maybeSeedDevAdmin; /** * Register authentication routes with HTTP server * * Uses better-auth's universal handler for all authentication requests. * This forwards all requests under basePath to better-auth, which handles: * - Email/password authentication * - OAuth providers (Google, GitHub, etc.) * - Session management * - Password reset * - Email verification * - 2FA, passkeys, magic links (if enabled) */ private registerAuthRoutes; /** * Mount the OIDC / OAuth 2.0 well-known discovery documents at the root * URL. Required by RFC 8414 §3 and OpenID Connect Discovery 1.0 §4 — the * documents must live at `/.well-known/{oauth-authorization-server,openid-configuration}` * relative to the issuer, not under the auth basePath. */ private registerOidcDiscoveryRoutes; } /** * Extended options for AuthManager */ interface AuthManagerOptions extends Partial { /** * Better-Auth instance (for advanced use cases) * If not provided, one will be created from config */ authInstance?: Auth; /** * ObjectQL Data Engine instance * Required for database operations using ObjectQL instead of third-party ORMs */ dataEngine?: IDataEngine; /** * Base path for auth routes * Forwarded to better-auth's basePath option so it can match incoming * request URLs without manual path rewriting. * @default '/api/v1/auth' */ basePath?: string; /** * OIDC / Generic OAuth2 providers for enterprise SSO. * Each entry is passed to better-auth's genericOAuth plugin. */ oidcProviders?: OidcProvidersConfig; /** * Application-specific organization roles to register with Better-Auth's * organization plugin. Each name becomes a valid role for invitations and * member assignments without going through Better-Auth's default * `owner|admin|member` whitelist. * * The ObjectStack SecurityPlugin handles real RBAC enforcement by matching * these role names against `permission` metadata (PermissionSets / Profiles), * so Better-Auth only needs to accept them as opaque strings. Each role is * registered with the minimum access-control privileges (equivalent to * Better-Auth's `member` role) so it cannot inadvertently grant org-level * admin capabilities. * * Typical source: the union of `permission` metadata names that have * `isProfile: true`, collected from the loaded stack at CLI boot. * * @example ['sales_rep', 'sales_manager', 'service_agent'] */ additionalOrgRoles?: string[]; /** * Optional outbound email service used by better-auth callbacks * (`sendResetPassword`, `sendVerificationEmail`, `sendInvitationEmail`, * `sendMagicLink`). When omitted, those callbacks degrade to logging * the action URL — keeping flows usable in pilots / local dev — but * production deployments SHOULD wire one via `setEmailService()`. * * Resolved lazily through {@link AuthManager.getEmailService}; safe * to set after construction. AuthPlugin wires this from the kernel * service registry on `kernel:ready`. */ emailService?: IEmailService; /** * Display name used by built-in auth email templates (`{{appName}}` * placeholder). Defaults to `'ObjectStack'` when omitted. */ appName?: string; /** * Pass-through to better-auth's `databaseHooks` option. better-auth fires * these around its own adapter writes (e.g. when `genericOAuth` creates * a JIT user during SSO login), which the kernel-level ObjectQL * middleware does NOT observe — better-auth's adapter goes through * `dataEngine` directly, bypassing the `ql.registerMiddleware` chain. * * The platform uses this to attach a `user.create.after` hook that * auto-provisions a personal organization for every newly-created user * (mirroring what SecurityPlugin's middleware does for direct * ObjectQL inserts) so SSO-arriving users don't land on the empty * "create organization" screen. */ databaseHooks?: BetterAuthOptions['databaseHooks']; } /** * Authentication Manager * * Wraps better-auth and provides authentication services for ObjectStack. * Supports multiple authentication methods: * - Email/password * - OAuth providers (Google, GitHub, etc.) * - Magic links * - Two-factor authentication * - Passkeys * - Organization/teams */ declare class AuthManager { private auth; private config; /** * Result of the dev-only admin seed (set by `AuthPlugin.maybeSeedDevAdmin` * when it provisions the well-known admin on an empty DB). The `serve` * command reads this after boot to surface the credentials in the startup * banner. Undefined when no seed ran (production, opt-out, or a DB that * already had a user). */ devSeedResult?: { email: string; password: string; }; constructor(config: AuthManagerOptions); /** * Get or create the better-auth instance (lazy initialization) */ private getOrCreateAuth; /** * Create a better-auth instance from configuration */ private createAuthInstance; /** * Detect WebContainer (StackBlitz) and swap in a pure-JS scrypt hasher. * * better-auth defaults to `@better-auth/utils/password.node`, which calls * `node:crypto.scrypt`. WebContainer polyfills that API incompletely and * signup throws `TypeError: y.run is not a function`. * * We can't dynamic-import `@better-auth/utils/password` because that * package's `exports` map gates the pure-JS build behind a non-`"node"` * condition — Node-the-runtime (which WebContainer reports itself as) * always resolves to `password.node.mjs`. So we reimplement the same hash * here using `@noble/hashes/scrypt` directly, with byte-identical params * (N=16384, r=16, p=1, dkLen=64) and the same `{saltHex}:{keyHex}` storage * format. Hashes produced by either implementation verify against the * other — no migration needed. * * Returns `undefined` outside WebContainer so production deployments keep * the native (fast) hasher and never load `@noble/hashes`. */ private resolvePasswordHasher; /** * Build the list of better-auth plugins based on AuthPluginConfig flags. * * Each plugin that introduces its own database tables is configured with * a `schema` option containing the appropriate snake_case field mappings, * so that `createAdapterFactory` transforms them automatically. */ private buildPluginList; /** * Create database configuration using ObjectQL adapter * * better-auth resolves the `database` option as follows: * - `undefined` → in-memory adapter * - `typeof fn === "function"` → treated as `DBAdapterInstance`, called with `(options)` * - otherwise → forwarded to Kysely adapter factory (pool/dialect) * * A raw `CustomAdapter` object would fall into the third branch and fail * silently. We therefore wrap the ObjectQL adapter in a factory function * so it is correctly recognised as a `DBAdapterInstance`. */ private createDatabaseConfig; /** * Generate a secure secret if not provided */ private generateSecret; /** * Update the base URL at runtime. * * This **must** be called before the first request triggers lazy * initialisation of the better-auth instance — typically from a * `kernel:ready` hook where the actual server port is known. * * If the auth instance has already been created this is a no-op and * a warning is emitted. */ setRuntimeBaseUrl(url: string): void; /** * Merge runtime configuration into the manager. * * Settings-backed auth policy can change after the manager is constructed. * better-auth itself is created lazily, so changing config before the first * request is enough. If an instance already exists, reset it so the next * request rebuilds with the new policy. */ applyConfigPatch(patch: Partial): void; /** * Inject (or replace) the outbound email service used by better-auth * callbacks. Safe to call after construction but BEFORE the first * request hits the auth handler — callbacks read this via * {@link getEmailService} when invoked. * * AuthPlugin calls this on `kernel:ready` once `ctx.getService('email')` * resolves. For tests / serverless, callers may invoke directly. */ setEmailService(email: IEmailService | undefined): void; /** @internal Used by callback closures. */ private getEmailService; /** * Override the brand name surfaced in built-in auth emails (`{{appName}}`), * sourced from the live `branding.workspace_name` setting. * * AuthPlugin calls this on `kernel:ready` (and again whenever the setting * changes) once the `settings` service resolves. Passing `undefined` clears * the override so resolution falls back to the configured `appName`. The * value only reflects an *explicitly set* setting — when the operator has * not customised it, AuthPlugin passes `undefined` so a deployment's * configured `appName` (e.g. `OS_APP_NAME`) keeps precedence. */ setAppName(name: string | undefined): void; private appNameOverride?; /** @internal `{{appName}}` placeholder value for built-in templates. */ private getAppName; /** * Get the underlying better-auth instance * Useful for advanced use cases */ getAuthInstance(): Promise>; /** * Handle an authentication request * Forwards the request directly to better-auth's universal handler * * better-auth catches internal errors (database / adapter / ORM) and * returns a 500 Response instead of throwing. We therefore inspect the * response status and log server errors so they are not silently swallowed. * * @param request - Web standard Request object * @returns Web standard Response object */ handleRequest(request: Request): Promise; /** * Get the better-auth API for programmatic access * Use this for server-side operations (e.g., creating users, checking sessions) */ getApi(): Promise['api']>; /** * Get the underlying better-auth context for low-level operations such as * `internalAdapter.createAccount` / `password.hash`. * * Used by routes that need to write to better-auth's tables outside the * normal endpoint surface — currently only `set-initial-password`, which * provisions a credential account for SSO-onboarded users so they can * sign in with email/password going forward. */ getAuthContext(): Promise; getPublicConfig(): { emailPassword: { enabled: boolean; disableSignUp: boolean; requireEmailVerification: boolean; }; socialProviders: ({ id: string; name: string; enabled: boolean; type: "social"; } | { id: string; name: string; enabled: boolean; type: "oidc"; })[]; features: { privacyUrl?: string | undefined; termsUrl?: string | undefined; twoFactor: boolean; passkeys: boolean; magicLink: boolean; organization: boolean; multiOrgEnabled: boolean; oidcProvider: boolean; deviceAuthorization: boolean; admin: boolean; }; }; /** * Returns the data engine wired into this auth manager. Used by route * handlers (e.g. bootstrap-status) that need to query identity tables * directly without going through better-auth. */ getDataEngine(): IDataEngine | undefined; } /** * Shared `set-initial-password` handler. * * better-auth ships a `setPassword` operation that does EXACTLY what we want * (require a session, enforce min/max length, link a `credential` account if * none exists, refuse if one already does). But it is registered with * `createAuthEndpoint({ ... })` — note: NO leading path string — which means * better-auth deliberately exposes it as a **server-only** `auth.api.setPassword` * call and gives it no HTTP route. Setting a password without proving the old * one is privilege-sensitive, so it must not be reachable over the wire by * default. * * To let an SSO-onboarded user set an *initial* local password from the * browser, we wrap that server API in our own authenticated HTTP route. This * helper is the single source of truth for that route body so the two mount * points — the full `AuthPlugin` (host kernel) and the cloud `AuthProxyPlugin` * (per-environment runtime) — stay in lockstep instead of hand-copying ~50 * lines of hash/createAccount logic (the original drift that let #1544 ship a * route on one path but not the other). */ /** Minimal shape of the better-auth server API we depend on. */ interface SetPasswordCapableApi { setPassword(opts: { body: { newPassword: string; }; headers: Headers; }): Promise; } interface SetInitialPasswordResult { /** HTTP status to return to the caller. */ status: number; /** JSON body; mirrors the `{ success, error: { code, message } }` envelope the client parses. */ body: { success: boolean; error?: { code: string; message: string; }; }; } /** * Run set-initial-password against the environment's better-auth API. * * @param authApi the better-auth server api (`auth.api`, via `AuthManager.getApi()`) * @param request the raw Web `Request` — its `headers` carry the session * cookie that better-auth's session middleware reads, and its * body carries `{ newPassword }`. */ declare function runSetInitialPassword(authApi: SetPasswordCapableApi, request: Request): Promise; /** * Mapping from better-auth model names to ObjectStack protocol object names. * * better-auth uses hardcoded model names ('user', 'session', 'account', 'verification') * while ObjectStack's protocol layer uses `sys_` prefixed names. This map bridges the two. */ declare const AUTH_MODEL_TO_PROTOCOL: Record; /** * Resolve a better-auth model name to the ObjectStack protocol object name. * Falls back to the original model name for custom / non-core models. */ declare function resolveProtocolName(model: string): string; /** * Create an ObjectQL adapter **factory** for better-auth. * * Uses better-auth's official `createAdapterFactory` so that model-name and * field-name transformations (declared via `modelName` / `fields` in the * betterAuth config) are applied **automatically** before any data reaches * ObjectQL. This eliminates the need for manual camelCase ↔ snake_case * conversion inside the adapter. * * The returned value is an `AdapterFactory` – a function of type * `(options: BetterAuthOptions) => DBAdapter` – which is the shape expected * by `betterAuth({ database: … })`. * * @param dataEngine - ObjectQL data engine instance * @returns better-auth AdapterFactory */ declare function createObjectQLAdapterFactory(dataEngine: IDataEngine): better_auth_adapters.AdapterFactory; /** * Create a raw ObjectQL adapter for better-auth (without factory wrapping). * * > **Prefer {@link createObjectQLAdapterFactory}** for production use. * > The factory version leverages `createAdapterFactory` and automatically * > handles model-name + field-name transformations declared in the * > better-auth config. * * This function is retained for direct / low-level usage where callers * manage field-name conversion themselves. * * @param dataEngine - ObjectQL data engine instance * @returns better-auth CustomAdapter (raw, without factory wrapping) */ declare function createObjectQLAdapter(dataEngine: IDataEngine): { create: >({ model, data, select: _select }: { model: string; data: T; select?: string[]; }) => Promise; findOne: ({ model, where, select, join: _join }: { model: string; where: CleanedWhere[]; select?: string[]; join?: any; }) => Promise; findMany: ({ model, where, limit, offset, sortBy, join: _join }: { model: string; where?: CleanedWhere[]; limit: number; offset?: number; sortBy?: { field: string; direction: "asc" | "desc"; }; join?: any; }) => Promise; count: ({ model, where }: { model: string; where?: CleanedWhere[]; }) => Promise; update: ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record; }) => Promise; updateMany: ({ model, where, update }: { model: string; where: CleanedWhere[]; update: Record; }) => Promise; delete: ({ model, where }: { model: string; where: CleanedWhere[]; }) => Promise; deleteMany: ({ model, where }: { model: string; where: CleanedWhere[]; }) => Promise; }; /** * better-auth ↔ ObjectStack Schema Mapping * * better-auth uses camelCase field names internally (e.g. `emailVerified`, `userId`) * while ObjectStack's protocol layer uses snake_case (e.g. `email_verified`, `user_id`). * * These constants declare the `modelName` and `fields` mappings for each core auth * model, following better-auth's official schema customisation API * ({@link https://www.better-auth.com/docs/concepts/database}). * * The mappings serve two purposes: * 1. `modelName` — maps the default model name to the ObjectStack protocol name * (e.g. `user` → `sys_user`). * 2. `fields` — maps camelCase field names to their snake_case database column * equivalents. Only fields whose names differ need to be listed; fields that * are already identical (e.g. `email`, `name`, `token`) are omitted. * * These mappings are consumed by: * - The `betterAuth()` configuration in {@link AuthManager} so that * `getAuthTables()` builds the correct schema. * - The ObjectQL adapter factory (via `createAdapterFactory`) which uses the * schema to transform data and where-clauses automatically. */ /** * better-auth `user` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | emailVerified | email_verified | * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_USER_CONFIG: { readonly modelName: "sys_user"; readonly fields: { readonly emailVerified: "email_verified"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth `session` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | userId | user_id | * | expiresAt | expires_at | * | createdAt | created_at | * | updatedAt | updated_at | * | ipAddress | ip_address | * | userAgent | user_agent | */ declare const AUTH_SESSION_CONFIG: { readonly modelName: "sys_session"; readonly fields: { readonly userId: "user_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; readonly ipAddress: "ip_address"; readonly userAgent: "user_agent"; }; }; /** * better-auth `account` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:--------------------------|:-------------------------------| * | userId | user_id | * | providerId | provider_id | * | accountId | account_id | * | accessToken | access_token | * | refreshToken | refresh_token | * | idToken | id_token | * | accessTokenExpiresAt | access_token_expires_at | * | refreshTokenExpiresAt | refresh_token_expires_at | * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_ACCOUNT_CONFIG: { readonly modelName: "sys_account"; readonly fields: { readonly userId: "user_id"; readonly providerId: "provider_id"; readonly accountId: "account_id"; readonly accessToken: "access_token"; readonly refreshToken: "refresh_token"; readonly idToken: "id_token"; readonly accessTokenExpiresAt: "access_token_expires_at"; readonly refreshTokenExpiresAt: "refresh_token_expires_at"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth `verification` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | expiresAt | expires_at | * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_VERIFICATION_CONFIG: { readonly modelName: "sys_verification"; readonly fields: { readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth Organization plugin `organization` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_ORGANIZATION_SCHEMA: { readonly modelName: "sys_organization"; readonly fields: { readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth Organization plugin `member` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | organizationId | organization_id | * | userId | user_id | * | createdAt | created_at | */ declare const AUTH_MEMBER_SCHEMA: { readonly modelName: "sys_member"; readonly fields: { readonly organizationId: "organization_id"; readonly userId: "user_id"; readonly createdAt: "created_at"; }; }; /** * better-auth Organization plugin `invitation` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | organizationId | organization_id | * | inviterId | inviter_id | * | expiresAt | expires_at | * | createdAt | created_at | * | teamId | team_id | */ declare const AUTH_INVITATION_SCHEMA: { readonly modelName: "sys_invitation"; readonly fields: { readonly organizationId: "organization_id"; readonly inviterId: "inviter_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly teamId: "team_id"; }; }; /** * Organization plugin adds `activeOrganizationId` (and optionally * `activeTeamId`) to the session model. These field mappings are * injected via the organization plugin's `schema.session.fields`. */ declare const AUTH_ORG_SESSION_FIELDS: { readonly activeOrganizationId: "active_organization_id"; readonly activeTeamId: "active_team_id"; }; /** * better-auth Organization plugin `team` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | organizationId | organization_id | * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_TEAM_SCHEMA: { readonly modelName: "sys_team"; readonly fields: { readonly organizationId: "organization_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth Organization plugin `teamMember` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | teamId | team_id | * | userId | user_id | * | createdAt | created_at | */ declare const AUTH_TEAM_MEMBER_SCHEMA: { readonly modelName: "sys_team_member"; readonly fields: { readonly teamId: "team_id"; readonly userId: "user_id"; readonly createdAt: "created_at"; }; }; /** * better-auth Two-Factor plugin `twoFactor` model mapping. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | backupCodes | backup_codes | * | userId | user_id | */ declare const AUTH_TWO_FACTOR_SCHEMA: { readonly modelName: "sys_two_factor"; readonly fields: { readonly backupCodes: "backup_codes"; readonly userId: "user_id"; }; }; /** * Two-Factor plugin adds a `twoFactorEnabled` field to the user model. */ declare const AUTH_TWO_FACTOR_USER_FIELDS: { readonly twoFactorEnabled: "two_factor_enabled"; }; /** * Admin plugin adds platform-level admin fields to the `user` model. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | banReason | ban_reason | * | banExpires | ban_expires | * * `role` and `banned` already have matching snake_case names and are * therefore omitted from this mapping (better-auth's database hooks * read them by the auto-derived column names). */ declare const AUTH_ADMIN_USER_FIELDS: { readonly banReason: "ban_reason"; readonly banExpires: "ban_expires"; }; /** * Admin plugin adds an `impersonatedBy` field to the session model * recording the operator user id when an admin impersonates someone. */ declare const AUTH_ADMIN_SESSION_FIELDS: { readonly impersonatedBy: "impersonated_by"; }; /** * `@better-auth/oauth-provider` plugin `oauthClient` model mapping. * * The model name (`oauthClient`) is mapped to the existing * `sys_oauth_application` table to preserve data continuity from the * deprecated `oidc-provider` plugin. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:---------------------------|:--------------------------------| * | clientId | client_id | * | clientSecret | client_secret | * | skipConsent | skip_consent | * | enableEndSession | enable_end_session | * | subjectType | subject_type | * | userId | user_id | * | createdAt | created_at | * | updatedAt | updated_at | * | redirectUris | redirect_uris | * | postLogoutRedirectUris | post_logout_redirect_uris | * | tokenEndpointAuthMethod | token_endpoint_auth_method | * | grantTypes | grant_types | * | responseTypes | response_types | * | requirePKCE | require_pkce | * | softwareId | software_id | * | softwareVersion | software_version | * | softwareStatement | software_statement | * | referenceId | reference_id | */ declare const AUTH_OAUTH_CLIENT_SCHEMA: { readonly modelName: "sys_oauth_application"; readonly fields: { readonly clientId: "client_id"; readonly clientSecret: "client_secret"; readonly skipConsent: "skip_consent"; readonly enableEndSession: "enable_end_session"; readonly subjectType: "subject_type"; readonly userId: "user_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; readonly redirectUris: "redirect_uris"; readonly postLogoutRedirectUris: "post_logout_redirect_uris"; readonly tokenEndpointAuthMethod: "token_endpoint_auth_method"; readonly grantTypes: "grant_types"; readonly responseTypes: "response_types"; readonly requirePKCE: "require_pkce"; readonly softwareId: "software_id"; readonly softwareVersion: "software_version"; readonly softwareStatement: "software_statement"; readonly referenceId: "reference_id"; }; }; /** * @deprecated Use {@link AUTH_OAUTH_CLIENT_SCHEMA}. Retained as an alias for * historical imports; the new package renamed `oauthApplication` → `oauthClient`. */ declare const AUTH_OAUTH_APPLICATION_SCHEMA: { readonly modelName: "sys_oauth_application"; readonly fields: { readonly clientId: "client_id"; readonly clientSecret: "client_secret"; readonly skipConsent: "skip_consent"; readonly enableEndSession: "enable_end_session"; readonly subjectType: "subject_type"; readonly userId: "user_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; readonly redirectUris: "redirect_uris"; readonly postLogoutRedirectUris: "post_logout_redirect_uris"; readonly tokenEndpointAuthMethod: "token_endpoint_auth_method"; readonly grantTypes: "grant_types"; readonly responseTypes: "response_types"; readonly requirePKCE: "require_pkce"; readonly softwareId: "software_id"; readonly softwareVersion: "software_version"; readonly softwareStatement: "software_statement"; readonly referenceId: "reference_id"; }; }; /** * `@better-auth/oauth-provider` plugin `oauthAccessToken` model mapping. * * In the new package, access tokens and refresh tokens are stored in * **separate** models. `oauthAccessToken` no longer carries a refresh token; * see {@link AUTH_OAUTH_REFRESH_TOKEN_SCHEMA} for the companion model. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | clientId | client_id | * | sessionId | session_id | * | userId | user_id | * | referenceId | reference_id | * | refreshId | refresh_id | * | expiresAt | expires_at | * | createdAt | created_at | */ declare const AUTH_OAUTH_ACCESS_TOKEN_SCHEMA: { readonly modelName: "sys_oauth_access_token"; readonly fields: { readonly clientId: "client_id"; readonly sessionId: "session_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly refreshId: "refresh_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; }; }; /** * `@better-auth/oauth-provider` plugin `oauthRefreshToken` model mapping. * * Refresh tokens are linked to a session (via `session_id`) and to the * issuing client. Each access token rotation produces a new refresh-token * row. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | clientId | client_id | * | sessionId | session_id | * | userId | user_id | * | referenceId | reference_id | * | expiresAt | expires_at | * | createdAt | created_at | * | authTime | auth_time | */ declare const AUTH_OAUTH_REFRESH_TOKEN_SCHEMA: { readonly modelName: "sys_oauth_refresh_token"; readonly fields: { readonly clientId: "client_id"; readonly sessionId: "session_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly authTime: "auth_time"; }; }; /** * `@better-auth/oauth-provider` plugin `oauthConsent` model mapping. * * The new package dropped the boolean `consentGiven` flag — the presence of * a row implies consent was given for the listed scopes. A new * `referenceId` column was added for client-supplied correlation. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | clientId | client_id | * | userId | user_id | * | referenceId | reference_id | * | createdAt | created_at | * | updatedAt | updated_at | */ declare const AUTH_OAUTH_CONSENT_SCHEMA: { readonly modelName: "sys_oauth_consent"; readonly fields: { readonly clientId: "client_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; /** * better-auth `device-authorization` plugin `deviceCode` model mapping. * * Implements RFC 8628 (OAuth 2.0 Device Authorization Grant). Stores * pending device-flow requests issued via `POST /device/code`, polled at * `POST /device/token`, and approved/denied via `POST /device/{approve,deny}`. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | deviceCode | device_code | * | userCode | user_code | * | userId | user_id | * | expiresAt | expires_at | * | lastPolledAt | last_polled_at | * | pollingInterval | polling_interval | * | clientId | client_id | */ declare const AUTH_DEVICE_CODE_SCHEMA: { readonly modelName: "sys_device_code"; readonly fields: { readonly deviceCode: "device_code"; readonly userCode: "user_code"; readonly userId: "user_id"; readonly expiresAt: "expires_at"; readonly lastPolledAt: "last_polled_at"; readonly pollingInterval: "polling_interval"; readonly clientId: "client_id"; }; }; /** * Builds the `schema` option for better-auth's `twoFactor()` plugin. * * @returns An object suitable for `twoFactor({ schema: … })` */ declare function buildTwoFactorPluginSchema(): { twoFactor: { readonly modelName: "sys_two_factor"; readonly fields: { readonly backupCodes: "backup_codes"; readonly userId: "user_id"; }; }; user: { fields: { readonly twoFactorEnabled: "two_factor_enabled"; }; }; }; /** * Builds the `schema` option for better-auth's `admin()` plugin. * * The admin plugin extends the user model with `role`/`banned`/`banReason`/ * `banExpires` and the session model with `impersonatedBy`. Only the * snake_case-differing fields are mapped explicitly. */ declare function buildAdminPluginSchema(): { user: { fields: { readonly banReason: "ban_reason"; readonly banExpires: "ban_expires"; }; }; session: { fields: { readonly impersonatedBy: "impersonated_by"; }; }; }; /** * Builds the `schema` option for better-auth's `organization()` plugin. * * The organization plugin accepts a `schema` sub-option that allows * customising model names and field names for each table it manages. * This helper assembles the correct snake_case mappings from the * individual `AUTH_*_SCHEMA` constants above. * * @returns An object suitable for `organization({ schema: … })` */ declare function buildOrganizationPluginSchema(): { organization: { readonly modelName: "sys_organization"; readonly fields: { readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; member: { readonly modelName: "sys_member"; readonly fields: { readonly organizationId: "organization_id"; readonly userId: "user_id"; readonly createdAt: "created_at"; }; }; invitation: { readonly modelName: "sys_invitation"; readonly fields: { readonly organizationId: "organization_id"; readonly inviterId: "inviter_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly teamId: "team_id"; }; }; team: { readonly modelName: "sys_team"; readonly fields: { readonly organizationId: "organization_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; teamMember: { readonly modelName: "sys_team_member"; readonly fields: { readonly teamId: "team_id"; readonly userId: "user_id"; readonly createdAt: "created_at"; }; }; session: { fields: { readonly activeOrganizationId: "active_organization_id"; readonly activeTeamId: "active_team_id"; }; }; }; /** * better-auth `jwt` plugin `jwks` model mapping. * * The JWT plugin maintains a small set of rotating asymmetric key pairs * used to sign and verify issued JWTs (id_tokens for OIDC, JWT access * tokens). It is required by the `@better-auth/oauth-provider` plugin. * * | camelCase (better-auth) | snake_case (ObjectStack) | * |:------------------------|:-------------------------| * | publicKey | public_key | * | privateKey | private_key | * | createdAt | created_at | * | expiresAt | expires_at | */ declare const AUTH_JWKS_SCHEMA: { readonly modelName: "sys_jwks"; readonly fields: { readonly publicKey: "public_key"; readonly privateKey: "private_key"; readonly createdAt: "created_at"; readonly expiresAt: "expires_at"; }; }; /** * Builds the `schema` option for better-auth's `jwt()` plugin. * * @returns An object suitable for `jwt({ schema: … })` */ declare function buildJwtPluginSchema(): { jwks: { readonly modelName: "sys_jwks"; readonly fields: { readonly publicKey: "public_key"; readonly privateKey: "private_key"; readonly createdAt: "created_at"; readonly expiresAt: "expires_at"; }; }; }; /** * Builds the `schema` option for `@better-auth/oauth-provider`'s * `oauthProvider()` plugin. * * The plugin manages four tables: `oauthClient` (registered client apps — * mapped to ObjectStack's `sys_oauth_application` table for backwards * compatibility), `oauthAccessToken` (issued access tokens), * `oauthRefreshToken` (issued refresh tokens, linked to a session), and * `oauthConsent` (recorded user consents). * * @returns An object suitable for `oauthProvider({ schema: … })` */ declare function buildOauthProviderPluginSchema(): { oauthClient: { readonly modelName: "sys_oauth_application"; readonly fields: { readonly clientId: "client_id"; readonly clientSecret: "client_secret"; readonly skipConsent: "skip_consent"; readonly enableEndSession: "enable_end_session"; readonly subjectType: "subject_type"; readonly userId: "user_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; readonly redirectUris: "redirect_uris"; readonly postLogoutRedirectUris: "post_logout_redirect_uris"; readonly tokenEndpointAuthMethod: "token_endpoint_auth_method"; readonly grantTypes: "grant_types"; readonly responseTypes: "response_types"; readonly requirePKCE: "require_pkce"; readonly softwareId: "software_id"; readonly softwareVersion: "software_version"; readonly softwareStatement: "software_statement"; readonly referenceId: "reference_id"; }; }; oauthAccessToken: { readonly modelName: "sys_oauth_access_token"; readonly fields: { readonly clientId: "client_id"; readonly sessionId: "session_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly refreshId: "refresh_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; }; }; oauthRefreshToken: { readonly modelName: "sys_oauth_refresh_token"; readonly fields: { readonly clientId: "client_id"; readonly sessionId: "session_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly expiresAt: "expires_at"; readonly createdAt: "created_at"; readonly authTime: "auth_time"; }; }; oauthConsent: { readonly modelName: "sys_oauth_consent"; readonly fields: { readonly clientId: "client_id"; readonly userId: "user_id"; readonly referenceId: "reference_id"; readonly createdAt: "created_at"; readonly updatedAt: "updated_at"; }; }; }; /** * @deprecated Use {@link buildOauthProviderPluginSchema}. Retained as an * alias for callers that imported the previous name during the migration * from the deprecated `better-auth/plugins/oidc-provider` plugin. */ declare const buildOidcProviderPluginSchema: typeof buildOauthProviderPluginSchema; /** * Builds the `schema` option for better-auth's `deviceAuthorization()` plugin. * * The plugin manages a single `deviceCode` table tracking pending RFC 8628 * device-flow requests. This helper returns the snake_case mappings that * point the plugin at ObjectStack's `sys_device_code` object. * * @returns An object suitable for `deviceAuthorization({ schema: … })` */ declare function buildDeviceAuthorizationPluginSchema(): { deviceCode: { readonly modelName: "sys_device_code"; readonly fields: { readonly deviceCode: "device_code"; readonly userCode: "user_code"; readonly userId: "user_id"; readonly expiresAt: "expires_at"; readonly lastPolledAt: "last_polled_at"; readonly pollingInterval: "polling_interval"; readonly clientId: "client_id"; }; }; }; export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, type SetInitialPasswordResult, type SetPasswordCapableApi, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName, runSetInitialPassword };