@velajs/better-auth 0.6.1 → 2.0.0
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/CHANGELOG.md +12 -0
- package/README.md +42 -32
- package/dist/{better-auth.service-BMkyFX-w.js → better-auth.service-DurQ4JQf.js} +4 -4
- package/dist/better-auth.service-DurQ4JQf.js.map +1 -0
- package/dist/better-auth.service-NOlWXxd4.d.ts +68 -0
- package/dist/index.d.ts +33 -152
- package/dist/index.js +255 -230
- package/dist/index.js.map +1 -1
- package/dist/testing/index.d.ts +4 -3
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +25 -23
- package/dist/better-auth.service-BMkyFX-w.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["normalizeRoles"],"sources":["../src/decorators/public.decorator.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/decorators/roles.decorator.ts","../src/guards/roles.guard.ts","../src/authz-bridge.ts","../src/decorators/require-permission.decorator.ts","../src/guards/permission.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","import { All, Controller, Inject, Injectable, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n @Public(true)\n @Controller(basePath)\n @Injectable()\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n\n/**\n * Default-path (`/api/auth`) catch-all controller. Retained for back-compat;\n * `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with\n * the configured `basePath`. Prefer the factory for a custom base path.\n */\nexport const BetterAuthCatchallController = createBetterAuthCatchallController();\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthModuleOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthModuleOptions>(\n 'vela.BetterAuthOptions',\n);\n\nexport const AUTH_USER_KEY = Symbol.for('vela.better-auth.user');\nexport const AUTH_SESSION_KEY = Symbol.for('vela.better-auth.session');\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n REQUEST_CONTEXT,\n Reflector,\n UnauthorizedException,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_SESSION_KEY, AUTH_USER_KEY, BETTER_AUTH_OPTIONS } from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthModuleOptions } from '../better-auth.types';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n constructor(\n // Inject BetterAuthService rather than the raw better-auth instance.\n // The service's lazy `.auth` getter defers construction to first use, so\n // forRootAsync factories that depend on values only available at\n // request time (Cloudflare D1/KV bindings, etc.) build safely on the\n // first canActivate — not at module-load bootstrap.\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthModuleOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const request = context.getRequest();\n const path = new URL(request.url).pathname;\n const basePath = this.opts.basePath ?? '/api/auth';\n if (path === basePath || path.startsWith(`${basePath}/`)) return true;\n\n const data = await this.auth.api.getSession({ headers: request.headers });\n\n if (data) {\n const reqCtx = resolveRequestContext(context);\n reqCtx.set(AUTH_USER_KEY, data.user);\n reqCtx.set(AUTH_SESSION_KEY, data.session);\n return true;\n }\n\n if (\n this.opts.defaultPolicy === 'allow' ||\n this.reflector.getAllAndOverride(OptionalAuth, context)\n ) {\n return true;\n }\n\n throw new UnauthorizedException('Authentication required');\n }\n}\n\ninterface ContainerLike {\n resolve<T>(token: unknown): T;\n}\n\nfunction resolveRequestContext(context: ExecutionContext): RequestContext {\n const honoCtx = context.getContext() as { get: (k: string) => ContainerLike };\n const container = honoCtx.get('container');\n return container.resolve<RequestContext>(REQUEST_CONTEXT);\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const Roles = Reflector.createDecorator<string[]>({ key: 'vela.auth.roles' });\nexport const ROLES_KEY = Roles.KEY;\n","import {\n ForbiddenException,\n Injectable,\n REQUEST_CONTEXT,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\nimport { Roles } from '../decorators/roles.decorator';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n canActivate(context: ExecutionContext): boolean {\n const required = this.reflector.getAllAndOverride(Roles, context);\n if (!required || required.length === 0) return true;\n\n const honoCtx = context.getContext() as {\n get: (k: string) => { resolve<T>(t: unknown): T };\n };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n const user = reqCtx.get<User & { role?: string | string[] }>(AUTH_USER_KEY);\n if (!user) {\n throw new ForbiddenException('Role check requires authentication');\n }\n\n const userRoles = normalizeRoles(user.role);\n const ok = required.some((r) => userRoles.includes(r));\n if (!ok) {\n throw new ForbiddenException(`Insufficient role; one of [${required.join(', ')}] required`);\n }\n return true;\n }\n}\n\nfunction normalizeRoles(role: string | string[] | undefined): string[] {\n if (!role) return [];\n if (Array.isArray(role)) return role;\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n}\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = User & { role?: string | string[] | null };\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Adapts a better-auth user into a `@velajs/authz` {@link Identity}. Maps\n * `user.id` → `userId` and the admin-plugin `role` field → `roles`.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (user: AuthUser | null | undefined): Identity => {\n if (!user) return { roles: [] };\n return {\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n","import { Reflector } from '@velajs/vela';\n\n/**\n * Declares the `@velajs/authz` permission(s) required to reach a controller or\n * route handler. Read via `Reflector` in an authorization guard, then checked\n * against the caller's `Identity` with `authz.can(...)`.\n *\n * ```ts\n * @RequirePermission(['posts:write'])\n * @Post()\n * create() { ... }\n * ```\n *\n * The metadata is a plain `string[]` of permission strings in the granted-side\n * format `@velajs/authz` matches (`resource:action`, or wildcards like\n * `posts:*`). Handler-level metadata overrides class-level (standard\n * `Reflector.getAllAndOverride` precedence).\n *\n * Semantics are **require-ALL** (AND): every listed permission must be granted\n * for access — the `PermissionGuard` denies if any one is missing. This\n * contrasts with `@Roles`, which is **OR** (any one of the listed roles\n * suffices).\n */\nexport const RequirePermission = Reflector.createDecorator<string[]>({\n key: 'vela.authz.permissions',\n});\n\nexport const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;\n","import {\n ForbiddenException,\n Injectable,\n InjectionToken,\n REQUEST_CONTEXT,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTHZ } from '@velajs/authz/vela';\nimport type { Authz } from '@velajs/authz';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\nimport { identityFromUser } from '../authz-bridge';\nimport { RequirePermission } from '../decorators/require-permission.decorator';\n\n// The linked `@velajs/authz` is built against its own (newer) `@velajs/vela`\n// copy, so the `AUTHZ` token's `InjectionToken` type is nominally distinct from\n// this package's `InjectionToken` — even though it is the very same runtime\n// token object (the DI container matches tokens by object identity). Re-type it\n// to the local `InjectionToken` so the request-time `container.resolve(...)`\n// accepts it without a structural clash. This is purely a compile-time alias;\n// it changes nothing at runtime. (Version-skew workaround until both publish.)\nconst AUTHZ_TOKEN = AUTHZ as unknown as InjectionToken<Authz>;\n\n/**\n * Enforces the `@RequirePermission(...)` metadata against the `@velajs/authz`\n * engine. For each required permission it calls `authz.can(identity, perm)`,\n * requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which\n * is OR over roles). The caller's `Identity` is derived from the better-auth\n * user that {@link AuthGuard} placed in the request context, so this guard must\n * run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).\n *\n * `AUTHZ` is resolved at **request time** from the per-request container (the\n * same container `REQUEST_CONTEXT` is resolved from), not constructor-injected.\n * This deliberately avoids DI visibility coupling: the guard works whether or\n * not `AuthzModule` is registered as global — a present-but-non-global\n * `AuthzModule` resolves fine and, crucially, never crashes bootstrap. If\n * `AuthzModule` is not registered at all the resolve fails and the guard fails\n * closed (403) rather than granting access.\n *\n * Fail-closed on every abnormal path — no branch grants access on missing\n * wiring or a missing caller:\n * - no required permissions → allow (nothing to enforce);\n * - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);\n * - no authenticated user in the request context → deny;\n * - any single required permission not granted → deny.\n *\n * The guard is stateless (no injected dependencies), so it is safe to register\n * as a plain provided guard.\n */\n@Injectable()\nexport class PermissionGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const required = this.reflector.getAllAndOverride(RequirePermission, context);\n if (!required || required.length === 0) return true;\n\n const honoCtx = context.getContext() as {\n get: (k: string) => { resolve<T>(t: unknown): T };\n };\n const container = honoCtx.get('container');\n\n // Resolve AUTHZ at request time from the per-request container — the SAME\n // container REQUEST_CONTEXT resolves from. Because this lookup carries no\n // requesting module, it matches AUTHZ by its exporter, so a non-global\n // `AuthzModule` is reachable without forcing the app to declare it global.\n // `resolve` throws (or, defensively, could yield undefined) for an\n // unregistered token, so wrap it and fail closed on any failure.\n let authz: Authz | undefined;\n try {\n authz = container.resolve<Authz>(AUTHZ_TOKEN);\n } catch {\n authz = undefined;\n }\n if (!authz) {\n throw new ForbiddenException('Authorization is not configured');\n }\n\n const reqCtx = container.resolve<RequestContext>(REQUEST_CONTEXT);\n const user = reqCtx.get<User & { id?: string; role?: string | string[] }>(AUTH_USER_KEY);\n if (!user) {\n throw new ForbiddenException('Permission check requires authentication');\n }\n\n const identity = identityFromUser(user);\n for (const permission of required) {\n if (!(await authz.can(identity, permission))) {\n throw new ForbiddenException(`Missing permission: ${permission}`);\n }\n }\n return true;\n }\n}\n","import {\n defineModule,\n lazyProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type ProviderOptions,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { RolesGuard } from './guards/roles.guard';\nimport { PermissionGuard } from './guards/permission.guard';\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n isGlobal: boolean;\n defaultPolicy: 'deny' | 'allow';\n mountHandler: boolean;\n}\n\nfunction normalize(options: Partial<BetterAuthModuleOptions>): NormalizedOptions {\n return {\n basePath: options.basePath ?? DEFAULT_BASE_PATH,\n isGlobal: options.isGlobal ?? false,\n defaultPolicy: options.defaultPolicy ?? 'deny',\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: Array<Type | ProviderOptions>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard, RolesGuard, PermissionGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard, RolesGuard, PermissionGuard],\n };\n}\n\n/**\n * The blessed engine generates `forRoot`. `setup` runs once per instance at\n * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,\n * derives the auth builder from those options, mounts the catch-all controller,\n * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.\n *\n * `isGlobal` here means \"apply AuthGuard app-wide\", NOT \"make this a global\n * module\", so the default `isGlobal → global: true` extras transform is\n * replaced with identity; the flag reaches `setup` through the options bag.\n */\nconst authModuleHost = defineModule<BetterAuthModuleOptions>({\n name: 'BetterAuth',\n optionsToken: BETTER_AUTH_OPTIONS,\n transform: (definition) => definition,\n // The auth instance is a stateful value — key off the structural subset only.\n key: (options) => stableHash(normalize(options)),\n setup: ({ OPTIONS, options }) => {\n const n = normalize(options);\n const common = commonContributions(n);\n const auth = (options as BetterAuthModuleOptions).auth;\n return {\n providers: [\n // Override the auto-provided raw bag with the normalized shape so\n // BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.\n { provide: OPTIONS, useValue: { ...n, auth } },\n // Eager auth: the builder hands back the instance the caller passed in.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: [OPTIONS],\n useFactory: (o: BetterAuthModuleOptions) => o.auth,\n }),\n ...common.providers,\n ],\n controllers: common.controllers,\n exports: common.exports,\n global: n.isGlobal ? { guards: [AuthGuard] } : undefined,\n };\n },\n});\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [D1Service, ConfigService], // captured as readonly tuple\n * useFactory: (d1, config) => // d1: D1Service, config: ConfigService\n * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<\n Inject extends readonly Token<unknown>[] = readonly Token<unknown>[],\n> {\n inject?: Inject;\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n defaultPolicy?: 'deny' | 'allow';\n key?: string;\n}\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n // Delegate to the generated static, then rebrand the module identity so the\n // public `BetterAuthModule` class is the one registered (consistent with\n // `forRootAsync` and better diagnostics).\n return { ...authModuleHost.ConfigurableModuleClass.forRoot(options), module: BetterAuthModule };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured behind {@link lazyProvider}'s memoized thunk. This is what makes\n * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but\n * it IS by the time a request flows through and the guard / catch-all reads\n * the service. Inject deps resolve at module load (cheap BindingRef wrappers);\n * their *values* are read at first auth use, inside your factory body.\n */\n static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n return {\n module: BetterAuthModule,\n key: options.key ?? stableHash({ ...n, inject: options.inject }),\n imports: options.imports ?? [],\n providers: [\n { provide: BETTER_AUTH_OPTIONS, useValue: n },\n // The deferred auth builder: `lazyProvider` wraps the user factory in a\n // memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: options.inject,\n useFactory: options.useFactory,\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import {\n createLazyParamDecorator,\n REQUEST_CONTEXT,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createLazyParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const honoCtx = ctx.getContext() as { get: (k: string) => { resolve<T>(t: unknown): T } };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n return reqCtx.get<User>(AUTH_USER_KEY);\n});\n","import {\n createLazyParamDecorator,\n REQUEST_CONTEXT,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_SESSION_KEY } from '../better-auth.tokens';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createLazyParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const honoCtx = ctx.getContext() as { get: (k: string) => { resolve<T>(t: unknown): T } };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n return reqCtx.get<Session>(AUTH_SESSION_KEY);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/decorators/public.decorator.ts","../src/base-path.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/auth-request-state.ts","../src/session-data.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts","../src/authz-bridge.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","export const DEFAULT_BETTER_AUTH_BASE_PATH = '/api/auth';\n\n/** Validate and canonicalize the route prefix used by the public auth controller. */\nexport function normalizeBetterAuthBasePath(value?: string): string {\n const basePath = value ?? DEFAULT_BETTER_AUTH_BASE_PATH;\n if (\n basePath.length === 0 ||\n basePath !== basePath.trim() ||\n !basePath.startsWith('/') ||\n basePath.startsWith('//') ||\n basePath === '/' ||\n basePath.endsWith('/') ||\n /[\\\\?#*]/u.test(basePath) ||\n /%(?:2e|2f|5c)/iu.test(basePath)\n ) {\n throw new Error(\n '@velajs/better-auth: basePath must be a canonical absolute path such as \"/api/auth\"',\n );\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(basePath);\n } catch {\n throw new Error('@velajs/better-auth: basePath contains invalid percent encoding');\n }\n if (decoded.split('/').some((segment) => segment === '.' || segment === '..')) {\n throw new Error('@velajs/better-auth: basePath must not contain dot segments');\n }\n return basePath;\n}\n","import { All, Controller, Inject, Injectable, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n const normalizedBasePath = normalizeBetterAuthBasePath(basePath);\n @Public(true)\n @Controller(normalizedBasePath)\n @Injectable()\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthRuntimeOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthRuntimeOptions>(\n 'vela.BetterAuthOptions',\n);\n","import {\n clearTrustedRequestIdentity,\n getTrustedRequestIdentity,\n setTrustedRequestIdentity,\n type ExecutionContext,\n type TrustedRequestIdentity,\n} from '@velajs/vela';\nimport type { SessionData } from './session-data';\n\n// This map stores provider payload, never a second authentication authority.\n// A clear, expiry, or identity replacement makes the payload unreachable.\nconst sessions = new WeakMap<TrustedRequestIdentity, SessionData>();\n\nexport function beginAuthRequest(context: ExecutionContext): void {\n clearTrustedRequestIdentity(context.getRequest());\n}\n\nexport function authenticateRequest(\n context: ExecutionContext,\n data: SessionData,\n issuer: string,\n): void {\n const request = context.getRequest();\n setTrustedRequestIdentity(request, {\n principal: { issuer, subject: data.user.id, principalType: 'user' },\n expiresAtMs: data.session.expiresAt.getTime(),\n roles: data.roles,\n ...(data.tenantId === undefined ? {} : { tenantId: data.tenantId }),\n });\n const identity = getTrustedRequestIdentity(request);\n if (identity) sessions.set(identity, data);\n}\n\nexport function getAuthRequestState(context: ExecutionContext): SessionData | undefined {\n if (context.getType() !== 'http') return undefined;\n const identity = getTrustedRequestIdentity(context.getRequest());\n return identity && sessions.get(identity);\n}\n","import type { Session, User } from './better-auth.types';\n\nexport interface SessionData {\n readonly user: User;\n readonly session: Session;\n readonly roles: readonly string[];\n readonly tenantId?: string;\n}\n\nfunction own(value: unknown, key: string): unknown {\n if (typeof value !== 'object' || value === null) return undefined;\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor && 'value' in descriptor ? descriptor.value : undefined;\n}\n\nfunction requiredString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction date(value: unknown): Date | undefined {\n return value instanceof Date && Number.isFinite(value.getTime()) ? new Date(value) : undefined;\n}\n\nfunction optionalString(value: unknown): value is string | null | undefined {\n return value === undefined || value === null || typeof value === 'string';\n}\n\n/** Keep plugin data opaque; base model fields below are validated individually. */\nfunction dataProperties(value: object): Record<string, unknown> {\n return Object.fromEntries(Object.keys(value).map((key) => [key, own(value, key)]));\n}\n\n/** Validate the full Better Auth base models, not merely ids masquerading as them. */\nexport function validateSessionData(value: unknown): SessionData | undefined {\n try {\n const user = own(value, 'user');\n const session = own(value, 'session');\n if (\n typeof user !== 'object' ||\n user === null ||\n typeof session !== 'object' ||\n session === null\n ) {\n return undefined;\n }\n const id = own(user, 'id');\n const email = own(user, 'email');\n const name = own(user, 'name');\n const emailVerified = own(user, 'emailVerified');\n const image = own(user, 'image');\n const createdAt = date(own(user, 'createdAt'));\n const updatedAt = date(own(user, 'updatedAt'));\n const sessionId = own(session, 'id');\n const userId = own(session, 'userId');\n const token = own(session, 'token');\n const sessionCreatedAt = date(own(session, 'createdAt'));\n const sessionUpdatedAt = date(own(session, 'updatedAt'));\n const expiresAt = date(own(session, 'expiresAt'));\n const ipAddress = own(session, 'ipAddress');\n const userAgent = own(session, 'userAgent');\n const organization = own(session, 'activeOrganizationId');\n const role = own(user, 'role');\n let roles: string[] = [];\n if (role !== undefined && role !== null) {\n if (typeof role === 'string')\n roles = role\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean);\n else if (\n Array.isArray(role) &&\n role.every((entry: unknown): entry is string => typeof entry === 'string')\n ) {\n roles = role.map((entry: string) => entry.trim()).filter(Boolean);\n } else return undefined;\n }\n if (\n !requiredString(id) ||\n !requiredString(email) ||\n typeof name !== 'string' ||\n typeof emailVerified !== 'boolean' ||\n !optionalString(image) ||\n !createdAt ||\n !updatedAt ||\n !requiredString(sessionId) ||\n userId !== id ||\n !requiredString(token) ||\n !sessionCreatedAt ||\n !sessionUpdatedAt ||\n !expiresAt ||\n expiresAt.getTime() <= Date.now() ||\n !optionalString(ipAddress) ||\n !optionalString(userAgent) ||\n (organization !== undefined && organization !== null && !requiredString(organization))\n )\n return undefined;\n return {\n user: Object.freeze({\n ...dataProperties(user),\n id,\n email,\n name,\n emailVerified,\n createdAt,\n updatedAt,\n image,\n }),\n session: Object.freeze({\n ...dataProperties(session),\n id: sessionId,\n userId: id,\n token,\n createdAt: sessionCreatedAt,\n updatedAt: sessionUpdatedAt,\n expiresAt,\n ipAddress,\n userAgent,\n }),\n roles: Object.freeze(roles),\n ...(typeof organization === 'string' ? { tenantId: organization } : {}),\n };\n } catch {\n return undefined;\n }\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n Reflector,\n UnauthorizedException,\n type CanActivate,\n type ExecutionContext,\n} from '@velajs/vela';\nimport { getContextIdentity } from '@velajs/authz/vela';\nimport { BETTER_AUTH_OPTIONS } from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthRuntimeOptions } from '../better-auth.types';\nimport { authenticateRequest, beginAuthRequest } from '../auth-request-state';\nimport { validateSessionData } from '../session-data';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n constructor(\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthRuntimeOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // Frames trust only the authenticated connection attachment; HTTP cookies\n // and request payload never become ambient WebSocket authentication.\n if (context.getType() === 'ws') {\n if (getContextIdentity(context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n beginAuthRequest(context);\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const raw = await this.auth.api.getSession({ headers: context.getRequest().headers });\n const data = validateSessionData(raw);\n if (data) {\n authenticateRequest(context, data, this.opts.issuer ?? 'better-auth');\n return true;\n }\n if (this.reflector.getAllAndOverride(OptionalAuth, context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n}\n","import {\n defineProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type {\n BetterAuthInstance,\n BetterAuthModuleOptions,\n BetterAuthRuntimeOptions,\n} from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\nconst referenceIds = new WeakMap<object, number>();\nconst explicitKeyClaims = new Map<\n string,\n { readonly kind: 'auth' | 'factory'; readonly reference: object; readonly shape: string }\n>();\nlet nextReferenceId = 1;\n\nfunction referenceId(reference: object): number {\n const existing = referenceIds.get(reference);\n if (existing !== undefined) return existing;\n const id = nextReferenceId++;\n referenceIds.set(reference, id);\n return id;\n}\n\nfunction claimExplicitKey(\n key: string,\n kind: 'auth' | 'factory',\n reference: object,\n shape: string,\n): string {\n if (key.length === 0 || key !== key.trim()) {\n throw new Error('@velajs/better-auth: an explicit module key must be a non-empty string');\n }\n const existing = explicitKeyClaims.get(key);\n if (\n existing !== undefined &&\n (existing.kind !== kind || existing.reference !== reference || existing.shape !== shape)\n ) {\n throw new Error(\n `@velajs/better-auth: explicit module key \"${key}\" is already bound to a different auth registration`,\n );\n }\n explicitKeyClaims.set(key, { kind, reference, shape });\n return `explicit:${key}:ref:${referenceId(reference)}`;\n}\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n issuer: string;\n isGlobal: boolean;\n mountHandler: boolean;\n}\n\nfunction normalize(options: BetterAuthRuntimeOptions): NormalizedOptions {\n const basePath = normalizeBetterAuthBasePath(options.basePath);\n const issuer = options.issuer ?? `better-auth:${basePath}`;\n if (issuer.length === 0 || issuer !== issuer.trim()) {\n throw new Error('@velajs/better-auth: issuer must be a non-empty stable namespace');\n }\n return {\n basePath,\n issuer,\n isGlobal: options.isGlobal ?? true,\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: NonNullable<DynamicModule['providers']>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard],\n };\n}\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [WORKER_ENV, ConfigService], // captured as readonly tuple\n * useFactory: (env, config) => // inferred from the tokens\n * betterAuth({ database: drizzleAdapter(drizzle(env.DB), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> {\n inject: Inject;\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n issuer?: string;\n key?: string;\n}\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n const normalized = normalize(options);\n const shape = stableHash(normalized);\n const key =\n options.key === undefined\n ? `${shape}:auth:${referenceId(options.auth)}`\n : claimExplicitKey(options.key, 'auth', options.auth, shape);\n const common = commonContributions(normalized);\n return {\n module: BetterAuthModule,\n key,\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: normalized }),\n defineProvider(BETTER_AUTH_BUILDER, { useValue: () => options.auth }),\n ...common.providers,\n ...(normalized.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured in the checked builder provider. The Workers adapter supplies its\n * native environment before DI; the lazily constructed auth instance belongs\n * to that environment's application and never captures another app's bindings.\n */\n static forRootAsync<const Inject extends readonly Token[] = readonly Token[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n const shape = stableHash({ ...n, inject: options.inject });\n const key =\n options.key === undefined\n ? `${shape}:factory:${referenceId(options.useFactory)}`\n : claimExplicitKey(options.key, 'factory', options.useFactory, shape);\n return {\n module: BetterAuthModule,\n key,\n imports: options.imports ?? [],\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: n }),\n defineProvider(BETTER_AUTH_BUILDER, {\n inject: options.inject,\n useFactory:\n (...deps: InferTokens<Inject>) =>\n () =>\n options.useFactory(...deps),\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): User | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.user;\n },\n);\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): Session | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.session;\n },\n);\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = Pick<User, 'id'> & { role?: string | string[] | null };\n\n/** Stable issuer namespace used for better-auth session principals. */\nexport const BETTER_AUTH_ISSUER = 'better-auth';\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Pure authorization projection; this does not authenticate or publish trusted state.\n * Adapts an already verified better-auth user into a stable `@velajs/authz` {@link Identity}.\n * The issuer scopes `user.id` as both `subject` and the compatibility `userId`;\n * the admin-plugin `role` field supplies local roles.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (\n user: AuthUser | null | undefined,\n issuer: string = BETTER_AUTH_ISSUER,\n principalType: 'user' | 'service' = 'user',\n): Identity => {\n if (!user || typeof user.id !== 'string' || user.id.length === 0) return { roles: [] };\n if (issuer.length === 0)\n throw new Error('@velajs/better-auth: identity issuer must be non-empty');\n return {\n issuer,\n subject: user.id,\n principalType,\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n"],"mappings":"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"}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as BetterAuthService } from "../better-auth.service-NOlWXxd4.js";
|
|
1
2
|
//#region src/testing/acting-as.d.ts
|
|
2
3
|
/**
|
|
3
4
|
* The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
* it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.
|
|
7
8
|
*/
|
|
8
9
|
interface TestModuleLike {
|
|
9
|
-
get
|
|
10
|
+
get(token: typeof BetterAuthService): BetterAuthService;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* A test principal. Opaque `Record<string, unknown>` to stay compatible with
|
|
@@ -44,7 +45,7 @@ type ActingAsPrincipal = Record<string, unknown>;
|
|
|
44
45
|
* await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();
|
|
45
46
|
* ```
|
|
46
47
|
*/
|
|
47
|
-
declare function actingAs(module: TestModuleLike, principal: ActingAsPrincipal): Promise<Headers>;
|
|
48
|
+
export declare function actingAs(module: TestModuleLike, principal: ActingAsPrincipal): Promise<Headers>;
|
|
48
49
|
//#endregion
|
|
49
|
-
export {
|
|
50
|
+
export type { ActingAsPrincipal, TestModuleLike };
|
|
50
51
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/testing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as BetterAuthService } from "../better-auth.service-
|
|
1
|
+
import { n as BetterAuthService } from "../better-auth.service-DurQ4JQf.js";
|
|
2
2
|
import { makeSignature } from "better-auth/crypto";
|
|
3
3
|
//#region src/testing/acting-as.ts
|
|
4
4
|
/**
|
|
@@ -27,6 +27,7 @@ import { makeSignature } from "better-auth/crypto";
|
|
|
27
27
|
*/
|
|
28
28
|
async function actingAs(module, principal) {
|
|
29
29
|
const ctx = await module.get(BetterAuthService).auth.$context;
|
|
30
|
+
if (!ctx) throw new Error("actingAs: the configured authentication provider has no Better Auth context");
|
|
30
31
|
const internalAdapter = ctx.internalAdapter;
|
|
31
32
|
const id = typeof principal.id === "string" ? principal.id : void 0;
|
|
32
33
|
const email = typeof principal.email === "string" ? principal.email : void 0;
|
|
@@ -42,7 +43,7 @@ async function actingAs(module, principal) {
|
|
|
42
43
|
email,
|
|
43
44
|
name: name ?? email,
|
|
44
45
|
...id ? { id } : {}
|
|
45
|
-
});
|
|
46
|
+
}, { method: "admin" });
|
|
46
47
|
}
|
|
47
48
|
const session = await internalAdapter.createSession(user.id, false, {
|
|
48
49
|
ipAddress: "127.0.0.1",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/testing/acting-as.ts"],"sourcesContent":["// Mechanics adapted from @stratal/testing (MIT, © Temitayo Fadojutimi):\n// mint a real better-auth session via `$context.internalAdapter` and hand back\n// a signed session-cookie header. Adapted to better-auth >=1.6: the session\n// cookie is signed with the package's own `makeSignature` (better-auth/crypto)\n// — the same primitive better-auth's built-in test cookie builder uses — so no\n// endpoint-context shim or `setSessionCookie` mock is needed.\nimport { makeSignature } from 'better-auth/crypto';\nimport { BetterAuthService } from '../better-auth.service';\n\n/**\n * The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.\n * Declared structurally so `@velajs/better-auth/testing` carries NO runtime\n * (or type) dependency on `@velajs/testing` — a real `TestingModule` satisfies\n * it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.\n */\nexport interface TestModuleLike {\n get
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/testing/acting-as.ts"],"sourcesContent":["// Mechanics adapted from @stratal/testing (MIT, © Temitayo Fadojutimi):\n// mint a real better-auth session via `$context.internalAdapter` and hand back\n// a signed session-cookie header. Adapted to better-auth >=1.6: the session\n// cookie is signed with the package's own `makeSignature` (better-auth/crypto)\n// — the same primitive better-auth's built-in test cookie builder uses — so no\n// endpoint-context shim or `setSessionCookie` mock is needed.\nimport { makeSignature } from 'better-auth/crypto';\nimport { BetterAuthService } from '../better-auth.service';\n\n/**\n * The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.\n * Declared structurally so `@velajs/better-auth/testing` carries NO runtime\n * (or type) dependency on `@velajs/testing` — a real `TestingModule` satisfies\n * it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.\n */\nexport interface TestModuleLike {\n get(token: typeof BetterAuthService): BetterAuthService;\n}\n\n/**\n * A test principal. Opaque `Record<string, unknown>` to stay compatible with\n * `@velajs/testing`'s `TestPrincipal`. Recognized fields:\n *\n * - `id` — reuse the user with this id if it already exists.\n * - `email` — reuse the user with this email, else create one.\n * - `name` — display name for a created user (defaults to the email).\n *\n * Any other fields are forwarded to `internalAdapter.createUser` (e.g. `role`)\n * when a new user is minted, so role-guarded routes can be exercised.\n */\nexport type ActingAsPrincipal = Record<string, unknown>;\n\n/**\n * actingAs — a `@velajs/testing` auth resolver for better-auth.\n *\n * Resolves {@link BetterAuthService} from the module, mints a REAL better-auth\n * session for `principal` through `auth.$context.internalAdapter`, and returns\n * a `Headers` carrying a properly signed session cookie. Guarded routes\n * (`AuthGuard`) then accept requests carrying those headers because\n * `auth.api.getSession` validates the cookie against the same session store.\n *\n * The signature `(module, principal) => Promise<Headers>` is exactly\n * `@velajs/testing`'s `ActingAsResolver`, so it plugs straight in:\n *\n * @example\n * ```ts\n * import { actingAs } from '@velajs/better-auth/testing';\n *\n * // As the default resolver for the module:\n * module.setAuthResolver(actingAs);\n * await module.http.get('/me').actingAs({ email: 'ada@example.com' }).send();\n *\n * // Or passed per-request:\n * await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();\n * ```\n */\nexport async function actingAs(\n module: TestModuleLike,\n principal: ActingAsPrincipal,\n): Promise<Headers> {\n const auth = module.get(BetterAuthService).auth;\n const ctx = await auth.$context;\n if (!ctx)\n throw new Error('actingAs: the configured authentication provider has no Better Auth context');\n const internalAdapter = ctx.internalAdapter;\n\n const id = typeof principal.id === 'string' ? principal.id : undefined;\n const email = typeof principal.email === 'string' ? principal.email : undefined;\n const name = typeof principal.name === 'string' ? principal.name : undefined;\n\n // `findUserById`/`createUser` yield a bare user; `findUserByEmail` nests it\n // under `{ user, accounts }` — normalize to the id we need.\n let user: { id: string } | null = null;\n if (id) user = await internalAdapter.findUserById(id);\n if (!user && email) {\n const found = await internalAdapter.findUserByEmail(email);\n user = found?.user ?? null;\n }\n if (!user) {\n if (!email) {\n throw new Error(\n 'actingAs: principal must carry an `email` (to create a user) or an ' +\n '`id` matching an existing user.',\n );\n }\n const { id: _id, email: _email, name: _name, ...extra } = principal;\n user = await internalAdapter.createUser(\n {\n ...extra,\n email,\n name: name ?? email,\n ...(id ? { id } : {}),\n },\n { method: 'admin' },\n );\n }\n\n const session = await internalAdapter.createSession(user.id, false, {\n ipAddress: '127.0.0.1',\n userAgent: 'vela-test',\n });\n\n const cookieName = ctx.authCookies.sessionToken.name;\n const signedToken = `${session.token}.${await makeSignature(session.token, ctx.secret)}`;\n\n const headers = new Headers();\n headers.set('Cookie', `${cookieName}=${signedToken}`);\n return headers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,eAAsB,SACpB,QACA,WACkB;CAElB,MAAM,MAAM,MADC,OAAO,IAAI,iBAAiB,CAAC,CAAC,KACpB;CACvB,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,6EAA6E;CAC/F,MAAM,kBAAkB,IAAI;CAE5B,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,KAAK,KAAA;CAC7D,MAAM,QAAQ,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ,KAAA;CACtE,MAAM,OAAO,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,KAAA;CAInE,IAAI,OAA8B;CAClC,IAAI,IAAI,OAAO,MAAM,gBAAgB,aAAa,EAAE;CACpD,IAAI,CAAC,QAAQ,OAEX,QAAO,MADa,gBAAgB,gBAAgB,KAAK,EAAA,EAC3C,QAAQ;CAExB,IAAI,CAAC,MAAM;EACT,IAAI,CAAC,OACH,MAAM,IAAI,MACR,oGAEF;EAEF,MAAM,EAAE,IAAI,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG,UAAU;EAC1D,OAAO,MAAM,gBAAgB,WAC3B;GACE,GAAG;GACH;GACA,MAAM,QAAQ;GACd,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;EACrB,GACA,EAAE,QAAQ,QAAQ,CACpB;CACF;CAEA,MAAM,UAAU,MAAM,gBAAgB,cAAc,KAAK,IAAI,OAAO;EAClE,WAAW;EACX,WAAW;CACb,CAAC;CAED,MAAM,aAAa,IAAI,YAAY,aAAa;CAChD,MAAM,cAAc,GAAG,QAAQ,MAAM,GAAG,MAAM,cAAc,QAAQ,OAAO,IAAI,MAAM;CAErF,MAAM,UAAU,IAAI,QAAQ;CAC5B,QAAQ,IAAI,UAAU,GAAG,WAAW,GAAG,aAAa;CACpD,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "better-auth integration for the Vela framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
"session",
|
|
13
13
|
"vela"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://github.com/velajs/
|
|
15
|
+
"homepage": "https://github.com/velajs/vela/tree/main/auth#readme",
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/velajs/
|
|
17
|
+
"url": "https://github.com/velajs/vela/issues"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "ksh",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/velajs/
|
|
23
|
+
"url": "git+https://github.com/velajs/vela.git",
|
|
24
|
+
"directory": "auth"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"dist",
|
|
@@ -43,32 +44,36 @@
|
|
|
43
44
|
}
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@velajs/authz": "
|
|
47
|
+
"@velajs/authz": "2.0.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@arethetypeswrong/cli": "
|
|
50
|
-
"@changesets/cli": "
|
|
51
|
-
"@swc/core": "
|
|
52
|
-
"@velajs/testing": "^0.4.0",
|
|
53
|
-
"@velajs/vela": "^1.12.0",
|
|
50
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
51
|
+
"@changesets/cli": "2.31.0",
|
|
52
|
+
"@swc/core": "1.15.43",
|
|
54
53
|
"better-auth": "^1.6.20",
|
|
55
|
-
"hono": "
|
|
56
|
-
"oxfmt": "
|
|
57
|
-
"oxlint": "
|
|
58
|
-
"publint": "
|
|
59
|
-
"tsdown": "
|
|
60
|
-
"typescript": "
|
|
61
|
-
"unplugin-swc": "
|
|
62
|
-
"
|
|
54
|
+
"hono": "4.13.8",
|
|
55
|
+
"oxfmt": "0.58.0",
|
|
56
|
+
"oxlint": "1.73.0",
|
|
57
|
+
"publint": "0.3.21",
|
|
58
|
+
"tsdown": "0.23.0",
|
|
59
|
+
"typescript": "7.0.2",
|
|
60
|
+
"unplugin-swc": "1.5.9",
|
|
61
|
+
"vite": "^8.0.16",
|
|
62
|
+
"vitest": "4.1.10",
|
|
63
|
+
"@velajs/vela": "2.0.0",
|
|
64
|
+
"@velajs/testing": "2.0.0"
|
|
63
65
|
},
|
|
64
66
|
"peerDependencies": {
|
|
65
|
-
"@velajs/vela": ">=1.11.0",
|
|
66
67
|
"better-auth": ">=1.2.0",
|
|
67
|
-
"hono": ">=4"
|
|
68
|
+
"hono": ">=4",
|
|
69
|
+
"@velajs/vela": "^2.0.0"
|
|
68
70
|
},
|
|
69
71
|
"engines": {
|
|
70
72
|
"node": ">=24"
|
|
71
73
|
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
},
|
|
72
77
|
"scripts": {
|
|
73
78
|
"build": "tsdown",
|
|
74
79
|
"test": "vitest run",
|
|
@@ -78,9 +83,6 @@
|
|
|
78
83
|
"format:check": "oxfmt --check .",
|
|
79
84
|
"publint": "publint",
|
|
80
85
|
"attw": "attw --pack . --profile esm-only",
|
|
81
|
-
"changeset": "changeset",
|
|
82
|
-
"version-packages": "changeset version",
|
|
83
|
-
"release": "pnpm build && changeset publish",
|
|
84
86
|
"verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
|
|
85
87
|
}
|
|
86
88
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"better-auth.service-BMkyFX-w.js","names":[],"sources":["../src/better-auth.service.ts"],"sourcesContent":["import { Inject, Injectable, InjectionToken } from '@velajs/vela';\nimport type { BetterAuthInstance } from './better-auth.types';\n\n/**\n * Internal token holding the auth-construction closure with its inject deps\n * closed over. Resolves cheaply at module load (just captures references);\n * the inner call happens lazily on first auth use (see `BetterAuthService`).\n *\n * Not exported from the public surface — only the service consumes it.\n */\nexport const BETTER_AUTH_BUILDER = new InjectionToken<() => BetterAuthInstance>(\n 'vela.better-auth.Builder',\n);\n\n/**\n * The single injectable consumers reach for to interact with better-auth.\n * Wraps the underlying `betterAuth({...})` instance with lazy construction:\n *\n * - `forRoot({ auth })` — the builder returns the eagerly-provided instance,\n * so the first `.auth` / `.api` / `.handler` access is effectively a\n * read-and-cache.\n * - `forRootAsync({ inject, useFactory })` — the builder wraps the user's\n * factory + inject deps. First access triggers `useFactory(...deps)`. This\n * is what makes Cloudflare D1/KV bindings work: at module load the factory\n * doesn't run; on first request (when AuthGuard or the catch-all calls\n * `service.api` / `service.handler`), the bindings are populated and the\n * factory can read them safely.\n *\n * Used directly by AuthGuard and the catch-all controller. Consumers in\n * application code inject the same way: `@Inject(BetterAuthService)`.\n */\n@Injectable()\nexport class BetterAuthService {\n private cached: BetterAuthInstance | undefined;\n\n constructor(@Inject(BETTER_AUTH_BUILDER) private readonly build: () => BetterAuthInstance) {}\n\n /**\n * The underlying better-auth instance. Constructed once on first access.\n * Safe to call from any request-time code path (guards, controllers,\n * services invoked from handlers).\n */\n get auth(): BetterAuthInstance {\n if (!this.cached) this.cached = this.build();\n return this.cached;\n }\n\n /** Convenience accessor — equivalent to `service.auth.api`. */\n get api(): BetterAuthInstance['api'] {\n return this.auth.api;\n }\n\n /** Convenience accessor — equivalent to `service.auth.handler`. */\n get handler(): BetterAuthInstance['handler'] {\n return this.auth.handler;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,MAAa,sBAAsB,IAAI,eACrC,0BACF;AAoBO,IAAA,oBAAA,MAAM,kBAAkB;CAG6B;CAF1D;CAEA,YAAY,OAA+E;EAAjC,KAAA,QAAA;CAAkC;;;;;;CAO5F,IAAI,OAA2B;EAC7B,IAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,MAAM;EAC3C,OAAO,KAAK;CACd;;CAGA,IAAI,MAAiC;EACnC,OAAO,KAAK,KAAK;CACnB;;CAGA,IAAI,UAAyC;EAC3C,OAAO,KAAK,KAAK;CACnB;AACF;;CAzBC,WAAW;oBAIG,OAAO,mBAAmB,CAAA"}
|