@stratal/framework 0.0.26 → 0.1.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 +596 -0
- package/README.md +166 -24
- package/dist/access-control/index.d.mts +43 -16
- package/dist/access-control/index.d.mts.map +1 -1
- package/dist/access-control/index.mjs +5 -5
- package/dist/access-control/index.mjs.map +1 -1
- package/dist/{access.service-BmDhE-re.mjs → access.service-BjmnWBEo.mjs} +35 -17
- package/dist/access.service-BjmnWBEo.mjs.map +1 -0
- package/dist/auth/index.d.mts +105 -103
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +123 -24
- package/dist/auth/index.mjs.map +1 -1
- package/dist/{auth-context-CGVbiSX3.d.mts → auth-context-C1om3Zsr.d.mts} +1 -2
- package/dist/auth-context-C1om3Zsr.d.mts.map +1 -0
- package/dist/{auth-context-C8NBfiMa.mjs → auth-context-cNSS1rmh.mjs} +2 -2
- package/dist/{auth-context-C8NBfiMa.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
- package/dist/auth.service-Onf3JkyL.d.mts +44 -0
- package/dist/auth.service-Onf3JkyL.d.mts.map +1 -0
- package/dist/context/index.d.mts +4 -5
- package/dist/context/index.d.mts.map +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/database/index.d.mts +3 -3
- package/dist/database/index.mjs +413 -34
- package/dist/database/index.mjs.map +1 -1
- package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
- package/dist/{decorateParam-DwV9LSPl.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
- package/dist/{decorateParam-DwV9LSPl.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
- package/dist/factory/index.d.mts +3 -5
- package/dist/factory/index.d.mts.map +1 -1
- package/dist/factory/index.mjs.map +1 -1
- package/dist/guards/index.d.mts +3 -4
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +4 -4
- package/dist/guards/index.mjs.map +1 -1
- package/dist/index-e_u1SRyd.d.mts +921 -0
- package/dist/index-e_u1SRyd.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/{types-CWZ9q74G.d.mts → types-B35g-lXi.d.mts} +18 -4
- package/dist/types-B35g-lXi.d.mts.map +1 -0
- package/package.json +31 -27
- package/dist/access.service-BmDhE-re.mjs.map +0 -1
- package/dist/auth-context-CGVbiSX3.d.mts.map +0 -1
- package/dist/index-Dt0YUA7r.d.mts +0 -446
- package/dist/index-Dt0YUA7r.d.mts.map +0 -1
- package/dist/types-CWZ9q74G.d.mts.map +0 -1
- package/dist/types-DabF8LGz.d.mts +0 -11
- package/dist/types-DabF8LGz.d.mts.map +0 -1
package/dist/auth/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/context/router-context.augment.ts","../../src/auth/auth.tokens.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/rate-limit-bridge.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/utils/better-auth-error-handler.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/services/auth.service.ts","../../src/auth/auth.module.ts"],"sourcesContent":["/**\n * Augments Stratal's `RouterContext` with a `user()` accessor backed by the\n * request-scoped {@link AuthContext}.\n *\n * Side-effect import: registers the `user` macro on `RouterContext` and the\n * `declare module` augmentation that exposes it at the type level. Imported by\n * {@link AuthModule} so it runs whenever auth is configured.\n */\nimport { DI_TOKENS } from 'stratal/di'\nimport { RouterContext } from 'stratal/router'\nimport type { AuthContext, AuthUser } from './auth-context'\n\ndeclare module 'stratal/router' {\n interface RouterContext {\n /**\n * The authenticated user for the current request.\n *\n * Throws `UserNotAuthenticatedError` if the request is unauthenticated.\n * Provided by `@stratal/framework`'s `AuthModule` via {@link AuthContext}.\n */\n user(): AuthUser\n }\n}\n\nRouterContext.macro('user', function (this: RouterContext): AuthUser {\n return this.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext).requireUser()\n})\n","/** Token for AuthService - core authentication service */\nexport const AUTH_SERVICE = Symbol.for('stratal:auth:service')\n\n/** Token for Better Auth options configuration */\nexport const AUTH_OPTIONS = Symbol.for('stratal:auth:options')\n","import { DI_TOKENS, inject, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { type AuthContext } from '../../context/auth-context'\nimport { AUTH_SERVICE } from '../auth.tokens'\nimport type { AuthService } from '../services/auth.service'\n\n/**\n * Session Verification Middleware\n *\n * Verifies user session via Better Auth and populates AuthContext with\n * the authenticated user.\n *\n * **Responsibilities:**\n * - Calls Better Auth's getSession() API\n * - Populates AuthContext with the user record if the session is valid\n * - Continues request chain regardless of session status\n */\n@Transient()\nexport class SessionVerificationMiddleware implements Middleware {\n constructor(\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n try {\n const result = await this.authService.auth.api.getSession({\n headers: ctx.c.req.raw.headers\n })\n\n if (result) {\n const authContext = ctx.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext)\n authContext.setAuthContext({\n user: result.user,\n })\n }\n } catch (error: unknown) {\n this.logger.debug('Session validation failed (e.g., invalidated in DB)', { error })\n }\n\n return next()\n }\n}\n","/**\n * Rate-limit bridge between Stratal's `RateLimiterModule` and better-auth.\n *\n * Importing this file (transitively, via `auth.module.ts`) does two things:\n *\n * 1. Augments `RateLimiterRegistry` with `forPath()` + `pathEntries()` via\n * Stratal's `Macroable`. Path-keyed rules registered on the same registry\n * used for Stratal's own throttling are projected into better-auth's\n * `customRules` by {@link projectCustomRules}.\n * 2. Exports {@link createBetterAuthRateLimitStorage} — adapts Stratal's\n * {@link IRateLimiterStore} into better-auth's `customStorage`, so both\n * systems share one backing store.\n *\n * `AuthModule.forRootAsync` wires both automatically when `RateLimiterModule`\n * is imported. Users with explicit `rateLimit.customStorage` /\n * `rateLimit.customRules` keys in their auth factory keep precedence.\n *\n * Frictions, documented for path-keyed entries:\n *\n * - `Limit.by(...)` is meaningless. Better-auth scopes per-IP+path.\n * - Multiple `Limit`s reduce to the most restrictive (smallest max-per-second).\n * - `Limit.none()` projects to `false` (better-auth's \"disable\" sentinel).\n * - `Limit.response(...)` is a no-op. Better-auth renders its own 429.\n * - Snapshot caveat: `customRules` is built once at AuthService construction,\n * so register all `forPath()` entries inside `OnInitialize` hooks.\n */\nimport { type IRateLimiterStore, type Limit, RateLimiterRegistry } from 'stratal/rate-limiter'\n\n/**\n * Resolver attached to a path-keyed limiter entry. Receives the native\n * `Request` (better-auth's customRules invokes us with the live Request)\n * and returns one or more `Limit`s. Async is supported.\n */\nexport type PathLimitResolver = (\n req: Request,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface BetterAuthRateLimit {\n key: string\n count: number\n lastRequest: number\n}\n\ninterface BetterAuthRateLimitRule {\n window: number\n max: number\n}\n\ntype BetterAuthCustomRule =\n | BetterAuthRateLimitRule\n | false\n | ((req: Request) => Promise<BetterAuthRateLimitRule | false>)\n\n// Per-instance path map — keyed by registry so we don't pin GC roots.\nconst pathResolvers = new WeakMap<RateLimiterRegistry, Map<string, PathLimitResolver>>()\n\nfunction getOrCreatePathMap(registry: RateLimiterRegistry): Map<string, PathLimitResolver> {\n let map = pathResolvers.get(registry)\n if (!map) {\n map = new Map()\n pathResolvers.set(registry, map)\n }\n return map\n}\n\nRateLimiterRegistry.macro('forPath', function (\n this: RateLimiterRegistry,\n path: string,\n resolver: PathLimitResolver,\n): void {\n getOrCreatePathMap(this).set(path, resolver)\n})\n\nRateLimiterRegistry.macro('pathEntries', function (\n this: RateLimiterRegistry,\n): IterableIterator<[string, PathLimitResolver]> {\n return (pathResolvers.get(this) ?? new Map<string, PathLimitResolver>()).entries()\n})\n\ndeclare module 'stratal/rate-limiter' {\n interface RateLimiterRegistry {\n /**\n * Register a rate-limit rule for a better-auth path pattern. The rule\n * is projected into better-auth's `rateLimit.customRules` automatically\n * when both modules are imported.\n *\n * @example\n * limiter.forPath('/sign-in/email', () => Limit.perSeconds(10, 3))\n * limiter.forPath('/two-factor/*', async (req) => { ... })\n * limiter.forPath('/forget-password', () => Limit.none())\n */\n forPath(path: string, resolver: PathLimitResolver): void\n\n /**\n * Iterate every path-keyed entry registered via `forPath`. Used by the\n * auth bridge to project entries into better-auth's `customRules`.\n */\n pathEntries(): IterableIterator<[string, PathLimitResolver]>\n }\n}\n\n// Better-auth manages window expiry itself by reading `lastRequest`. We still\n// need a TTL on the underlying KV so dead records don't accumulate. 1 day\n// covers any reasonable better-auth window without colliding with the next.\nconst BETTER_AUTH_TTL_SECONDS = 86_400\nconst BETTER_AUTH_KEY_PREFIX = 'ba-rl:'\n\n/**\n * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.\n * Better-auth supplies its own `RateLimit` records (`{ key, count, lastRequest }`);\n * the adapter just persists them under a separate key namespace.\n */\nexport function createBetterAuthRateLimitStorage(store: IRateLimiterStore): {\n get: (key: string) => Promise<BetterAuthRateLimit | null>\n set: (key: string, value: BetterAuthRateLimit, update?: boolean) => Promise<void>\n} {\n return {\n async get(key) {\n return await store.get<BetterAuthRateLimit>(`${BETTER_AUTH_KEY_PREFIX}${key}`)\n },\n async set(key, value, _update) {\n await store.set(`${BETTER_AUTH_KEY_PREFIX}${key}`, value, BETTER_AUTH_TTL_SECONDS)\n },\n }\n}\n\n/**\n * Project every `forPath` entry on the registry into better-auth's\n * `customRules` shape. Each entry becomes an async function that resolves\n * the user's `Limit`(s) and reduces them to a single `{ window, max }` pair\n * (or `false` for `Limit.none()`).\n *\n * Multi-`Limit` reduction picks the most restrictive — smallest\n * `max / windowSeconds` ratio; ties favour the first.\n */\nexport function projectCustomRules(\n registry: RateLimiterRegistry,\n): Record<string, BetterAuthCustomRule> {\n const rules: Record<string, BetterAuthCustomRule> = {}\n\n for (const [path, resolver] of registry.pathEntries()) {\n rules[path] = async (req: Request): Promise<BetterAuthRateLimitRule | false> => {\n const resolved = await resolver(req)\n const candidates = (Array.isArray(resolved) ? resolved : [resolved]).filter((l) => !l.disabled)\n if (candidates.length === 0) return false\n\n const chosen = candidates.reduce((a, b) =>\n a.max / a.windowSeconds <= b.max / b.windowSeconds ? a : b,\n )\n\n return { window: chosen.windowSeconds, max: chosen.max }\n }\n }\n\n return rules\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotFoundError extends HttpException {\n constructor(public readonly email?: string) {\n super(404, 'User not found')\n }\n}\n\nexport class InvalidCredentialsError extends HttpException {\n constructor() { super(401, 'Invalid email or password') }\n}\n\nexport class InvalidPasswordError extends HttpException {\n constructor() { super(401, 'Invalid password') }\n}\n\nexport class InvalidEmailError extends HttpException {\n constructor(public readonly email?: string) {\n super(422, 'Invalid email address')\n }\n}\n\nexport class SessionExpiredError extends HttpException {\n constructor() { super(401, 'Session expired') }\n}\n\nexport class FreshSessionRequiredError extends HttpException {\n constructor() { super(403, 'Fresh session required') }\n}\n\nexport class EmailNotVerifiedError extends HttpException {\n constructor(public readonly email?: string) {\n super(403, 'Email not verified')\n }\n}\n\nexport class PasswordTooShortError extends HttpException {\n constructor(public readonly minLength?: number) {\n super(422, 'Password too short')\n }\n}\n\nexport class PasswordTooLongError extends HttpException {\n constructor(public readonly maxLength?: number) {\n super(422, 'Password too long')\n }\n}\n\nexport class AccountAlreadyExistsError extends HttpException {\n constructor(public readonly email?: string) {\n super(409, 'Account already exists')\n }\n}\n\nexport class SocialAccountLinkedError extends HttpException {\n constructor(public readonly provider?: string) {\n super(409, 'Social account already linked')\n }\n}\n\nexport class CannotUnlinkLastAccountError extends HttpException {\n constructor() { super(409, 'Cannot unlink last account') }\n}\n\nexport class ProviderNotFoundError extends HttpException {\n constructor(public readonly provider?: string) {\n super(404, 'Authentication provider not found')\n }\n}\n\nexport class UserEmailNotFoundError extends HttpException {\n constructor() { super(404, 'User email not found') }\n}\n\nexport class AccountNotFoundError extends HttpException {\n constructor() { super(404, 'Account not found') }\n}\n\nexport class CredentialAccountNotFoundError extends HttpException {\n constructor() { super(404, 'Credential account not found') }\n}\n\nexport class UserAlreadyHasPasswordError extends HttpException {\n constructor() { super(409, 'User already has a password') }\n}\n\nexport class EmailCannotBeUpdatedError extends HttpException {\n constructor(public readonly reason?: string) {\n super(422, 'Email cannot be updated')\n }\n}\n\nexport class IdTokenNotSupportedError extends HttpException {\n constructor() { super(422, 'ID token not supported') }\n}\n\nexport class TokenExpiredError extends HttpException {\n constructor() { super(401, 'Token expired') }\n}\n\nexport class InvalidCallbackUrlError extends HttpException {\n constructor() { super(422, 'Invalid callback URL') }\n}\n\nexport class InvalidOriginError extends HttpException {\n constructor() { super(403, 'Invalid request origin') }\n}\n\nexport class AuthValidationFailedError extends HttpException {\n constructor() { super(422, 'Authentication validation failed') }\n}\n\nexport class EmailAlreadyVerifiedError extends HttpException {\n constructor() { super(409, 'Email already verified') }\n}\n\nexport class EmailMismatchError extends HttpException {\n constructor() { super(422, 'Email mismatch') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class InvalidTokenError extends HttpException {\n constructor() { super(401, 'Invalid or expired token') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class OrganizationNotFoundError extends HttpException {\n constructor() { super(404, 'Organization not found') }\n}\nexport class OrganizationMemberNotFoundError extends HttpException {\n constructor() { super(404, 'Organization member not found') }\n}\nexport class OrganizationInvitationNotFoundError extends HttpException {\n constructor() { super(404, 'Invitation not found') }\n}\nexport class OrganizationPermissionDeniedError extends HttpException {\n constructor() { super(403, 'Organization permission denied') }\n}\nexport class OrganizationInvitationRecipientMismatchError extends HttpException {\n constructor() { super(403, 'Invitation recipient mismatch') }\n}\nexport class OrganizationConflictError extends HttpException {\n constructor() { super(409, 'Organization resource conflict') }\n}\nexport class OrganizationLimitReachedError extends HttpException {\n constructor() { super(422, 'Organization limit reached') }\n}\nexport class OrganizationMembershipError extends HttpException {\n constructor() { super(422, 'Organization membership constraint violated') }\n}\nexport class OrganizationTeamNotFoundError extends HttpException {\n constructor() { super(404, 'Team not found') }\n}\nexport class OrganizationRoleNotFoundError extends HttpException {\n constructor() { super(404, 'Role not found') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class TokenRequiredError extends HttpException {\n constructor() { super(401, 'Verification token is required') }\n}\n","import { APIError } from 'better-auth/api'\nimport { AuthError } from 'stratal/errors'\nimport type { ApplicationError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n AuthValidationFailedError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailAlreadyVerifiedError,\n EmailCannotBeUpdatedError,\n EmailMismatchError,\n EmailNotVerifiedError,\n FreshSessionRequiredError,\n IdTokenNotSupportedError,\n InvalidCallbackUrlError,\n InvalidCredentialsError,\n InvalidEmailError,\n InvalidOriginError,\n InvalidPasswordError,\n InvalidTokenError,\n OrganizationConflictError,\n OrganizationInvitationNotFoundError,\n OrganizationInvitationRecipientMismatchError,\n OrganizationLimitReachedError,\n OrganizationMemberNotFoundError,\n OrganizationMembershipError,\n OrganizationNotFoundError,\n OrganizationPermissionDeniedError,\n OrganizationRoleNotFoundError,\n OrganizationTeamNotFoundError,\n PasswordTooLongError,\n PasswordTooShortError,\n ProviderNotFoundError,\n SessionExpiredError,\n SocialAccountLinkedError,\n TokenExpiredError,\n UserAlreadyHasPasswordError,\n UserEmailNotFoundError,\n UserNotFoundError,\n} from '../errors'\n\n/**\n * Maps Better Auth API error codes to ApplicationError instances.\n */\nexport function mapBetterAuthError(error: APIError): ApplicationError {\n const errorCode = error.body?.code\n\n if (error.status === 'FOUND') {\n const headers = error.headers as Headers\n const location = headers.get('location') ?? ''\n\n if (location.includes('INVALID_TOKEN')) return new InvalidTokenError()\n if (location.includes('EXPIRED_TOKEN')) return new TokenExpiredError()\n if (location.includes('ATTEMPTS_EXCEEDED')) return new InvalidTokenError()\n if (location.includes('new_user_signup_disabled')) return new UserNotFoundError()\n if (location.includes('failed_to_create_user')) return new AuthError('Failed to create user')\n if (location.includes('failed_to_create_session')) return new AuthError('Failed to create session')\n }\n\n if (!errorCode) {\n return new AuthError('An authentication error occurred')\n }\n\n // ── Base Error Codes ──────────────────────────────────────────────────\n\n // User errors\n if (errorCode === 'USER_NOT_FOUND' || errorCode === 'INVALID_USER') return new UserNotFoundError()\n if (errorCode === 'USER_EMAIL_NOT_FOUND') return new UserEmailNotFoundError()\n\n // Credential errors\n if (errorCode === 'INVALID_EMAIL_OR_PASSWORD') return new InvalidCredentialsError()\n if (errorCode === 'INVALID_PASSWORD') return new InvalidPasswordError()\n if (errorCode === 'INVALID_EMAIL') return new InvalidEmailError()\n\n // Session errors\n if (errorCode === 'SESSION_EXPIRED') return new SessionExpiredError()\n if (errorCode === 'SESSION_NOT_FRESH') return new FreshSessionRequiredError()\n if (errorCode === 'FAILED_TO_CREATE_SESSION') return new AuthError('Failed to create session')\n if (errorCode === 'FAILED_TO_GET_SESSION') return new AuthError('Failed to retrieve session')\n\n // Email verification\n if (errorCode === 'EMAIL_NOT_VERIFIED') return new EmailNotVerifiedError()\n if (errorCode === 'EMAIL_CAN_NOT_BE_UPDATED') return new EmailCannotBeUpdatedError()\n if (errorCode === 'EMAIL_ALREADY_VERIFIED') return new EmailAlreadyVerifiedError()\n if (errorCode === 'EMAIL_MISMATCH') return new EmailMismatchError()\n\n // Password validation\n if (errorCode === 'PASSWORD_TOO_SHORT') return new PasswordTooShortError(8)\n if (errorCode === 'PASSWORD_TOO_LONG') return new PasswordTooLongError(128)\n\n // Account errors\n if (errorCode === 'USER_ALREADY_EXISTS' || errorCode === 'USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL') {\n return new AccountAlreadyExistsError()\n }\n if (errorCode === 'ACCOUNT_NOT_FOUND') return new AccountNotFoundError()\n if (errorCode === 'CREDENTIAL_ACCOUNT_NOT_FOUND') return new CredentialAccountNotFoundError()\n if (errorCode === 'FAILED_TO_UNLINK_LAST_ACCOUNT') return new CannotUnlinkLastAccountError()\n\n // User creation/update errors\n if (errorCode === 'FAILED_TO_CREATE_USER') return new AuthError('Failed to create user')\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new AuthError('Failed to update user')\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new AuthError('Failed to retrieve user info')\n\n // Social account errors\n if (errorCode === 'SOCIAL_ACCOUNT_ALREADY_LINKED' || errorCode === 'LINKED_ACCOUNT_ALREADY_EXISTS') {\n return new SocialAccountLinkedError()\n }\n if (errorCode === 'PROVIDER_NOT_FOUND') return new ProviderNotFoundError()\n\n // Token errors\n if (errorCode === 'ID_TOKEN_NOT_SUPPORTED') return new IdTokenNotSupportedError()\n if (errorCode === 'INVALID_TOKEN') return new InvalidTokenError()\n if (errorCode === 'TOKEN_EXPIRED') return new TokenExpiredError()\n\n // Password management\n if (errorCode === 'USER_ALREADY_HAS_PASSWORD' || errorCode === 'PASSWORD_ALREADY_SET') {\n return new UserAlreadyHasPasswordError()\n }\n\n // Callback/redirect URL errors\n if (\n errorCode === 'INVALID_CALLBACK_URL'\n || errorCode === 'INVALID_REDIRECT_URL'\n || errorCode === 'INVALID_NEW_USER_CALLBACK_URL'\n || errorCode === 'INVALID_ERROR_CALLBACK_URL'\n || errorCode === 'CALLBACK_URL_REQUIRED'\n ) {\n return new InvalidCallbackUrlError()\n }\n\n // Origin/CORS errors\n if (\n errorCode === 'INVALID_ORIGIN'\n || errorCode === 'MISSING_OR_NULL_ORIGIN'\n || errorCode === 'CROSS_SITE_NAVIGATION_LOGIN_BLOCKED'\n ) {\n return new InvalidOriginError()\n }\n\n // Validation errors\n if (\n errorCode === 'VALIDATION_ERROR'\n || errorCode === 'MISSING_FIELD'\n || errorCode === 'FIELD_NOT_ALLOWED'\n || errorCode === 'BODY_MUST_BE_AN_OBJECT'\n || errorCode === 'ASYNC_VALIDATION_NOT_SUPPORTED'\n || errorCode === 'METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED'\n ) {\n return new AuthValidationFailedError()\n }\n\n // Verification errors\n if (errorCode === 'FAILED_TO_CREATE_VERIFICATION' || errorCode === 'VERIFICATION_EMAIL_NOT_ENABLED') {\n return new AuthError('Failed to create session')\n }\n\n // ── Organization Plugin Error Codes ───────────────────────────────────\n\n // Organization not found\n if (errorCode === 'ORGANIZATION_NOT_FOUND' || errorCode === 'NO_ACTIVE_ORGANIZATION') {\n return new OrganizationNotFoundError()\n }\n\n // Member not found\n if (\n errorCode === 'MEMBER_NOT_FOUND'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_TEAM'\n ) {\n return new OrganizationMemberNotFoundError()\n }\n\n // Invitation not found\n if (errorCode === 'INVITATION_NOT_FOUND' || errorCode === 'FAILED_TO_RETRIEVE_INVITATION') {\n return new OrganizationInvitationNotFoundError()\n }\n\n // Invitation recipient mismatch\n if (\n errorCode === 'YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION'\n || errorCode === 'EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION'\n ) {\n return new OrganizationInvitationRecipientMismatchError()\n }\n\n // Team not found\n if (errorCode === 'TEAM_NOT_FOUND' || errorCode === 'YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM') {\n return new OrganizationTeamNotFoundError()\n }\n\n // Role not found\n if (errorCode === 'ROLE_NOT_FOUND' || errorCode === 'INVALID_RESOURCE') {\n return new OrganizationRoleNotFoundError()\n }\n\n // Organization conflict/already exists\n if (\n errorCode === 'ORGANIZATION_ALREADY_EXISTS'\n || errorCode === 'ORGANIZATION_SLUG_ALREADY_TAKEN'\n || errorCode === 'USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION'\n || errorCode === 'TEAM_ALREADY_EXISTS'\n || errorCode === 'ROLE_NAME_IS_ALREADY_TAKEN'\n ) {\n return new OrganizationConflictError()\n }\n\n // Organization limit reached\n if (\n errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS'\n || errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS'\n || errorCode === 'ORGANIZATION_MEMBERSHIP_LIMIT_REACHED'\n || errorCode === 'INVITATION_LIMIT_REACHED'\n || errorCode === 'TEAM_MEMBER_LIMIT_REACHED'\n || errorCode === 'TOO_MANY_ROLES'\n ) {\n return new OrganizationLimitReachedError()\n }\n\n // Organization membership constraints\n if (\n errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER'\n || errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER'\n || errorCode === 'UNABLE_TO_REMOVE_LAST_TEAM'\n || errorCode === 'CANNOT_DELETE_A_PRE_DEFINED_ROLE'\n || errorCode === 'ROLE_IS_ASSIGNED_TO_MEMBERS'\n || errorCode === 'YOU_CANNOT_IMPERSONATE_ADMINS'\n || errorCode === 'YOU_CANNOT_BAN_YOURSELF'\n || errorCode === 'YOU_CANNOT_REMOVE_YOURSELF'\n || errorCode === 'INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION'\n ) {\n return new OrganizationMembershipError()\n }\n\n // Organization permission denied (catch-all for YOU_ARE_NOT_ALLOWED_TO_* patterns)\n if (\n errorCode.startsWith('YOU_ARE_NOT_ALLOWED_TO_')\n || errorCode === 'YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM'\n || errorCode === 'YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE'\n || errorCode === 'MISSING_AC_INSTANCE'\n ) {\n return new OrganizationPermissionDeniedError()\n }\n\n // Unknown error code\n return new AuthError('An authentication error occurred')\n}\n\n/**\n * Type guard to check if an error is a Better Auth APIError.\n * Uses duck typing to handle bundler environments (e.g. Vite)\n * where instanceof may fail across module boundaries.\n */\nexport function isAPIError(error: unknown): error is APIError {\n if (error instanceof APIError) return true\n\n return (\n error instanceof Error\n && error.name === 'APIError'\n && 'status' in error\n && 'statusCode' in error\n )\n}\n","import type { BetterAuthOptions } from 'better-auth'\nimport { isAPIError, mapBetterAuthError } from './better-auth-error-handler'\n\n/**\n * Get shared Better Auth error handler configuration.\n * Use this in Better Auth config's onAPIError option.\n */\nexport function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'] {\n return {\n throw: false,\n onError: (error) => {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n },\n }\n}\n\n/**\n * Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.\n */\nexport const wrapBetterAuth = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (error) {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n }\n}\n","import type { Auth, BetterAuthOptions } from 'better-auth';\nimport { betterAuth } from 'better-auth/minimal';\nimport { inject, Request } from 'stratal/di';\nimport { AUTH_OPTIONS, AUTH_SERVICE } from '../auth.tokens';\nimport { getErrorHandlerConfig } from '../utils';\n\n/**\n * AuthService\n *\n * Base authentication service using Better Auth.\n * Configured via AuthModule.forRootAsync() from the application layer.\n *\n * **Extensibility:**\n * Extend this class to add custom methods. Subclasses inherit\n * `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.\n *\n * @example\n * ```typescript\n * @Request(AUTH_SERVICE)\n * export class AppAuthService extends AuthService<AuthOptions> {\n * async signInMagicLink(email: string) {\n * return wrapBetterAuth(async () => {\n * return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })\n * })\n * }\n * }\n * ```\n */\n@Request(AUTH_SERVICE)\nexport class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {\n private _authInstance?: Auth<TOptions>\n\n constructor(\n @inject(AUTH_OPTIONS) protected readonly options: TOptions\n ) {}\n\n /**\n * Get the Better Auth instance.\n */\n get auth(): Auth<TOptions> {\n this._authInstance ??= betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>;\n\n return this._authInstance\n }\n}\n","/**\n * Auth Module\n *\n * Provides configurable authentication using Better Auth.\n * Use `forRootAsync` to configure Better Auth options from the application layer.\n *\n * Optionally pass `accessControl` to enable permission-based authorization.\n * This auto-adds the Stratal AC plugin to Better Auth and registers `AccessService`.\n *\n * @example Without access control\n * ```typescript\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database, CONFIG_TOKENS.ConfigService],\n * useFactory: (db, config) => createAuthOptions(db, config)\n * })\n * ]\n * })\n * export class AppModule {}\n * ```\n *\n * @example With access control\n * ```typescript\n * import { createAccessControl } from '@stratal/framework/access-control'\n * import { admin } from 'better-auth/plugins'\n *\n * const permissions = createAccessControl({\n * resources: { posts: ['create', 'read', 'update', 'delete'] } as const,\n * roles: { admin: { posts: ['create', 'read', 'update', 'delete'] }, user: { posts: ['read'] } },\n * })\n *\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database],\n * useFactory: (db) => ({\n * database: ...,\n * plugins: [admin({ ...permissions })],\n * }),\n * accessControl: permissions,\n * })\n * ]\n * })\n * ```\n */\n\nimport type { BetterAuthOptions } from 'better-auth'\nimport { CONTAINER_TOKEN, type Container } from 'stratal/di'\nimport type { AsyncModuleOptions, DynamicModule } from 'stratal/module'\nimport { Module } from 'stratal/module'\nimport type { IRateLimiterStore, RateLimiterRegistry } from 'stratal/rate-limiter'\nimport { RATE_LIMITER_TOKENS } from 'stratal/rate-limiter'\nimport type { RouteConfigurable, Router } from 'stratal/router'\nimport { createStratalAcPlugin } from '../access-control/plugin'\nimport { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AccessControlOptions } from '../access-control/types'\nimport { AuthContext } from '../context/auth-context'\n// Side-effect import: registers the `user()` macro on `RouterContext` and its\n// type augmentation, backed by the request-scoped `AuthContext`.\nimport '../context/router-context.augment'\nimport { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\n// Side-effect import: registers `forPath`/`pathEntries` macros on\n// `RateLimiterRegistry` and the `declare module` augmentation that exposes\n// them at the type level. Must run before any consumer calls `forPath()`.\nimport {\n createBetterAuthRateLimitStorage,\n projectCustomRules,\n} from './rate-limit-bridge'\nimport { AuthService } from './services/auth.service'\n\nexport interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions>\n extends AsyncModuleOptions<TOptions> {\n /**\n * Optional access control configuration.\n * When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.\n */\n accessControl?: AccessControlOptions\n}\n\n@Module({\n providers: [AuthContext]\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * SessionVerificationMiddleware verifies the session and populates the\n * request-scoped AuthContext with the authenticated user.\n */\n configureRoutes(router: Router): void {\n router.use(SessionVerificationMiddleware)\n }\n\n /**\n * Configure AuthModule with async options factory.\n * Optionally provide `accessControl` to enable permission-based authorization.\n *\n * When `RateLimiterModule` is also imported, better-auth's `rateLimit`\n * block is auto-wired: `customStorage` shares Stratal's backing store, and\n * any `RateLimiterRegistry.forPath(...)` entries are projected into\n * `customRules`. User-supplied `rateLimit.{customStorage, customRules}` keys\n * take precedence on a per-key basis.\n */\n static forRootAsync<TOptions extends BetterAuthOptions>(\n options: AuthModuleAsyncOptions<TOptions>\n ): DynamicModule {\n const { accessControl } = options\n const userInject = options.inject ?? []\n const userFactory = options.useFactory as (...args: unknown[]) => TOptions\n\n const authOptionsProvider = {\n provide: AUTH_OPTIONS,\n useFactory: (container: Container, ...userDeps: unknown[]): BetterAuthOptions => {\n let raw = userFactory(...userDeps) as BetterAuthOptions\n\n if (accessControl) {\n raw = {\n ...raw,\n plugins: [createStratalAcPlugin(accessControl), ...(raw.plugins ?? [])],\n }\n }\n\n const rateLimiterPresent = container.isRegistered(\n RATE_LIMITER_TOKENS.ModuleMarker,\n )\n\n if (rateLimiterPresent) {\n const store = container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n const registry = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n\n raw = {\n ...raw,\n rateLimit: {\n enabled: true,\n ...raw.rateLimit,\n customStorage: raw.rateLimit?.customStorage ?? createBetterAuthRateLimitStorage(store),\n customRules: {\n ...projectCustomRules(registry),\n ...(raw.rateLimit?.customRules ?? {}),\n },\n },\n }\n }\n\n return raw\n },\n inject: [CONTAINER_TOKEN, ...userInject],\n }\n\n return {\n module: AuthModule,\n providers: [\n authOptionsProvider,\n {\n provide: AUTH_SERVICE,\n useClass: AuthService,\n },\n ...(accessControl\n ? [\n { provide: AC_TOKENS.Options, useValue: accessControl as unknown as object },\n { provide: AC_TOKENS.AccessService, useClass: AccessService },\n ]\n : []),\n ],\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/context/router-context.augment.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/rate-limit-bridge.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/utils/better-auth-error-handler.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/services/auth.service.ts","../../src/auth/auth.module.ts","../../src/auth/auth.primers.ts"],"sourcesContent":["/**\n * Augments Stratal's `RouterContext` with a `user()` accessor backed by the\n * request-scoped {@link AuthContext}.\n *\n * Side-effect import: registers the `user` macro on `RouterContext` and the\n * `declare module` augmentation that exposes it at the type level. Imported by\n * {@link AuthModule} so it runs whenever auth is configured.\n */\nimport { DI_TOKENS } from 'stratal/di'\nimport { RouterContext } from 'stratal/router'\nimport type { AuthContext, AuthUser } from './auth-context'\n\ndeclare module 'stratal/router' {\n interface RouterContext {\n /**\n * The authenticated user for the current request.\n *\n * Throws `UserNotAuthenticatedError` if the request is unauthenticated.\n * Provided by `@stratal/framework`'s `AuthModule` via {@link AuthContext}.\n */\n user(): AuthUser\n }\n}\n\nRouterContext.macro('user', function (this: RouterContext): AuthUser {\n return this.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext).requireUser()\n})\n","import { parseSetCookieHeader } from 'better-auth/cookies'\nimport { DI_TOKENS, inject, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { type AuthContext } from '../../context/auth-context'\nimport { AUTH_SERVICE } from '../auth.tokens'\nimport type { AuthService } from '../services/auth.service'\n\n/** Every cookie name a `Set-Cookie` value assigns, read with Better Auth's own parser. */\nfunction namesIn(setCookie: string): string[] {\n return [...parseSetCookieHeader(setCookie).keys()]\n}\n\n/**\n * Session Verification Middleware\n *\n * Verifies user session via Better Auth and populates AuthContext with\n * the authenticated user.\n *\n * **Responsibilities:**\n * - Calls Better Auth's getSession() API\n * - Populates AuthContext with the user record if the session is valid\n * - Carries any cookie that read issued through to the response\n * - Continues request chain regardless of session status\n */\n@Transient()\nexport class SessionVerificationMiddleware implements Middleware {\n constructor(\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<Response | void> {\n /**\n * The cookies Better Auth wrote while reading the session.\n *\n * Reading one is not always free of side effects: under `session.cookieCache`\n * it mints the cached-session cookie, and once past `session.updateAge` it\n * reissues the session cookie against the extended expiry. Both are `Set-Cookie`\n * on a response Better Auth builds internally, which is discarded unless asked\n * for — so left unforwarded, the cache is written on every request and read on\n * none, and a sliding expiry never reaches the browser at all.\n */\n let issued: string[] = []\n\n try {\n const { headers, response } = await this.authService.auth.api.getSession({\n headers: ctx.c.req.raw.headers,\n returnHeaders: true,\n })\n\n if (response) {\n const authContext = ctx.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext)\n authContext.setAuthContext({\n user: response.user,\n })\n }\n\n // Last, and after the user is established: this shares a `catch` with the session\n // read, so anything thrown while collecting cookies would otherwise cost the request\n // its user and turn a cookie problem into an unauthenticated one, logged at debug.\n // `getSetCookie` is the reachable candidate — it postdates the rest of `Headers`.\n issued = headers.getSetCookie()\n } catch (error: unknown) {\n this.logger.debug('Session validation failed (e.g., invalidated in DB)', { error })\n }\n\n await next()\n\n if (issued.length === 0) return\n\n // `c.header()` rebuilds the response as `new Response(c.res.body, c.res)`, and that\n // constructor rejects any status outside 200-599 — a 101 upgrade, or the 0 of\n // `Response.error()`, would throw here rather than pass through untouched.\n const status = ctx.c.res?.status\n if (typeof status !== 'number' || status < 200 || status > 599) return\n\n /**\n * A name the handler has already spoken for, which this must not overwrite.\n *\n * Sign-out is the case that makes this load-bearing rather than tidy. The session\n * is still valid when it is read, so a cached-session cookie can be minted on the\n * way in; the handler then clears that same cookie on the way out. Appending\n * afterwards would put it back — a signed-out browser holding a cached session,\n * which is the one thing the cache must never survive.\n */\n const spokenFor = new Set(ctx.c.res.headers.getSetCookie().flatMap(namesIn))\n\n // Appended rather than set, and one header per value: `Set-Cookie` does not fold\n // into a comma-separated list the way other repeated headers do.\n for (const cookie of issued) {\n if (namesIn(cookie).some((name) => spokenFor.has(name))) continue\n ctx.c.header('set-cookie', cookie, { append: true })\n }\n }\n}\n","/**\n * Rate-limit bridge between Stratal's `RateLimiterModule` and better-auth.\n *\n * Importing this file (transitively, via `auth.module.ts`) does two things:\n *\n * 1. Augments `RateLimiterRegistry` with `forPath()` + `pathEntries()` via\n * Stratal's `Macroable`. Path-keyed rules registered on the same registry\n * used for Stratal's own throttling are projected into better-auth's\n * `customRules` by {@link projectCustomRules}.\n * 2. Exports {@link createBetterAuthRateLimitStorage} — adapts Stratal's\n * {@link IRateLimiterStore} into better-auth's `customStorage`, so both\n * systems share one backing store.\n *\n * `AuthModule.forRootAsync` wires both automatically when `RateLimiterModule`\n * is imported. Users with explicit `rateLimit.customStorage` /\n * `rateLimit.customRules` keys in their auth factory keep precedence.\n *\n * Frictions, documented for path-keyed entries:\n *\n * - `Limit.by(...)` is meaningless. Better-auth scopes per-IP+path.\n * - Multiple `Limit`s reduce to the most restrictive (smallest max-per-second).\n * - `Limit.none()` projects to `false` (better-auth's \"disable\" sentinel).\n * - `Limit.response(...)` is a no-op. Better-auth renders its own 429.\n * - Snapshot caveat: `customRules` is built once at AuthService construction,\n * so register all `forPath()` entries inside `OnInitialize` hooks.\n */\nimport { type IRateLimiterStore, type Limit, RateLimiterRegistry } from 'stratal/rate-limiter'\n\n/**\n * Resolver attached to a path-keyed limiter entry. Receives the native\n * `Request` (better-auth's customRules invokes us with the live Request)\n * and returns one or more `Limit`s. Async is supported.\n */\nexport type PathLimitResolver = (\n req: Request,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface BetterAuthRateLimit {\n key: string\n count: number\n lastRequest: number\n}\n\ninterface BetterAuthRateLimitRule {\n window: number\n max: number\n}\n\ntype BetterAuthCustomRule =\n | BetterAuthRateLimitRule\n | false\n | ((req: Request) => Promise<BetterAuthRateLimitRule | false>)\n\n// Per-instance path map — keyed by registry so we don't pin GC roots.\nconst pathResolvers = new WeakMap<RateLimiterRegistry, Map<string, PathLimitResolver>>()\n\nfunction getOrCreatePathMap(registry: RateLimiterRegistry): Map<string, PathLimitResolver> {\n let map = pathResolvers.get(registry)\n if (!map) {\n map = new Map()\n pathResolvers.set(registry, map)\n }\n return map\n}\n\nRateLimiterRegistry.macro('forPath', function (\n this: RateLimiterRegistry,\n path: string,\n resolver: PathLimitResolver,\n): void {\n getOrCreatePathMap(this).set(path, resolver)\n})\n\nRateLimiterRegistry.macro('pathEntries', function (\n this: RateLimiterRegistry,\n): IterableIterator<[string, PathLimitResolver]> {\n return (pathResolvers.get(this) ?? new Map<string, PathLimitResolver>()).entries()\n})\n\ndeclare module 'stratal/rate-limiter' {\n interface RateLimiterRegistry {\n /**\n * Register a rate-limit rule for a better-auth path pattern. The rule\n * is projected into better-auth's `rateLimit.customRules` automatically\n * when both modules are imported.\n *\n * @example\n * limiter.forPath('/sign-in/email', () => Limit.perSeconds(10, 3))\n * limiter.forPath('/two-factor/*', async (req) => { ... })\n * limiter.forPath('/forget-password', () => Limit.none())\n */\n forPath(path: string, resolver: PathLimitResolver): void\n\n /**\n * Iterate every path-keyed entry registered via `forPath`. Used by the\n * auth bridge to project entries into better-auth's `customRules`.\n */\n pathEntries(): IterableIterator<[string, PathLimitResolver]>\n }\n}\n\nconst BETTER_AUTH_KEY_PREFIX = 'ba-rl:'\n\n/** Outcome of a single `consume` step. */\ninterface BetterAuthConsumeResult {\n allowed: boolean\n retryAfter: number | null\n}\n\n/**\n * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.\n *\n * Better-auth requires a single atomic `consume(key, rule)` rather than the\n * separate `get`/`set` pair it used to accept, because a split read and write\n * cannot hold a limit under concurrent requests. `IRateLimiterStore` is a plain\n * typed KV with no compare-and-set, so this is better-auth's documented\n * read-decide-write fallback for backends lacking an atomic primitive, and the\n * decision below mirrors their reference implementation exactly.\n *\n * Accuracy therefore follows the configured store, and matches what Stratal's\n * own throttling already does on the same backend: exact on\n * `InMemoryRateLimiterStore` (read-decide-write is atomic in a single isolate),\n * best-effort on `KvRateLimiterStore`, where concurrent writes from different\n * edge locations may undercount. Register a Durable Object store for strict\n * accuracy across edges — the same escape hatch the KV store documents.\n */\nexport function createBetterAuthRateLimitStorage(store: IRateLimiterStore): {\n consume: (key: string, rule: BetterAuthRateLimitRule) => Promise<BetterAuthConsumeResult>\n} {\n return {\n async consume(key, rule) {\n const storageKey = `${BETTER_AUTH_KEY_PREFIX}${key}`\n const windowMs = rule.window * 1000\n const now = Date.now()\n const record = await store.get<BetterAuthRateLimit>(storageKey)\n\n // TTL is the window itself: once it elapses an expired record and a\n // surviving one are treated the same, so there is nothing to preserve.\n // `KvRateLimiterStore` raises this to KV's 60s floor on its own.\n const write = async (count: number, lastRequest: number): Promise<void> => {\n await store.set(storageKey, { key, count, lastRequest }, rule.window)\n }\n\n // Unseen key, or the previous window has elapsed — open a new one at 1.\n if (!record || now - record.lastRequest >= windowMs) {\n await write(1, now)\n return { allowed: true, retryAfter: null }\n }\n\n // Window is live and already spent. Leave the record alone so a rejected\n // request can't extend the window it just bounced off.\n if (record.count >= rule.max) {\n return {\n allowed: false,\n retryAfter: Math.ceil((record.lastRequest + windowMs - now) / 1000),\n }\n }\n\n await write(record.count + 1, now)\n return { allowed: true, retryAfter: null }\n },\n }\n}\n\n/**\n * Project every `forPath` entry on the registry into better-auth's\n * `customRules` shape. Each entry becomes an async function that resolves\n * the user's `Limit`(s) and reduces them to a single `{ window, max }` pair\n * (or `false` for `Limit.none()`).\n *\n * Multi-`Limit` reduction picks the most restrictive — smallest\n * `max / windowSeconds` ratio; ties favour the first.\n */\nexport function projectCustomRules(\n registry: RateLimiterRegistry,\n): Record<string, BetterAuthCustomRule> {\n const rules: Record<string, BetterAuthCustomRule> = {}\n\n for (const [path, resolver] of registry.pathEntries()) {\n rules[path] = async (req: Request): Promise<BetterAuthRateLimitRule | false> => {\n const resolved = await resolver(req)\n const candidates = (Array.isArray(resolved) ? resolved : [resolved]).filter((l) => !l.disabled)\n if (candidates.length === 0) return false\n\n const chosen = candidates.reduce((a, b) =>\n a.max / a.windowSeconds <= b.max / b.windowSeconds ? a : b,\n )\n\n return { window: chosen.windowSeconds, max: chosen.max }\n }\n }\n\n return rules\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotFoundError extends HttpException {\n constructor(public readonly email?: string) {\n super(404, 'User not found')\n }\n}\n\nexport class InvalidCredentialsError extends HttpException {\n constructor() { super(401, 'Invalid email or password') }\n}\n\nexport class InvalidPasswordError extends HttpException {\n constructor() { super(401, 'Invalid password') }\n}\n\nexport class InvalidEmailError extends HttpException {\n constructor(public readonly email?: string) {\n super(422, 'Invalid email address')\n }\n}\n\nexport class SessionExpiredError extends HttpException {\n constructor() { super(401, 'Session expired') }\n}\n\nexport class FreshSessionRequiredError extends HttpException {\n constructor() { super(403, 'Fresh session required') }\n}\n\nexport class EmailNotVerifiedError extends HttpException {\n constructor(public readonly email?: string) {\n super(403, 'Email not verified')\n }\n}\n\nexport class PasswordTooShortError extends HttpException {\n constructor(public readonly minLength?: number) {\n super(422, 'Password too short')\n }\n}\n\nexport class PasswordTooLongError extends HttpException {\n constructor(public readonly maxLength?: number) {\n super(422, 'Password too long')\n }\n}\n\nexport class AccountAlreadyExistsError extends HttpException {\n constructor(public readonly email?: string) {\n super(409, 'Account already exists')\n }\n}\n\nexport class SocialAccountLinkedError extends HttpException {\n constructor(public readonly provider?: string) {\n super(409, 'Social account already linked')\n }\n}\n\nexport class CannotUnlinkLastAccountError extends HttpException {\n constructor() { super(409, 'Cannot unlink last account') }\n}\n\nexport class ProviderNotFoundError extends HttpException {\n constructor(public readonly provider?: string) {\n super(404, 'Authentication provider not found')\n }\n}\n\nexport class UserEmailNotFoundError extends HttpException {\n constructor() { super(404, 'User email not found') }\n}\n\nexport class AccountNotFoundError extends HttpException {\n constructor() { super(404, 'Account not found') }\n}\n\nexport class CredentialAccountNotFoundError extends HttpException {\n constructor() { super(404, 'Credential account not found') }\n}\n\nexport class UserAlreadyHasPasswordError extends HttpException {\n constructor() { super(409, 'User already has a password') }\n}\n\nexport class EmailCannotBeUpdatedError extends HttpException {\n constructor(public readonly reason?: string) {\n super(422, 'Email cannot be updated')\n }\n}\n\nexport class IdTokenNotSupportedError extends HttpException {\n constructor() { super(422, 'ID token not supported') }\n}\n\nexport class TokenExpiredError extends HttpException {\n constructor() { super(401, 'Token expired') }\n}\n\nexport class InvalidCallbackUrlError extends HttpException {\n constructor() { super(422, 'Invalid callback URL') }\n}\n\nexport class InvalidOriginError extends HttpException {\n constructor() { super(403, 'Invalid request origin') }\n}\n\nexport class AuthValidationFailedError extends HttpException {\n constructor() { super(422, 'Authentication validation failed') }\n}\n\nexport class EmailAlreadyVerifiedError extends HttpException {\n constructor() { super(409, 'Email already verified') }\n}\n\nexport class EmailMismatchError extends HttpException {\n constructor() { super(422, 'Email mismatch') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class InvalidTokenError extends HttpException {\n constructor() { super(401, 'Invalid or expired token') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class OrganizationNotFoundError extends HttpException {\n constructor() { super(404, 'Organization not found') }\n}\nexport class OrganizationMemberNotFoundError extends HttpException {\n constructor() { super(404, 'Organization member not found') }\n}\nexport class OrganizationInvitationNotFoundError extends HttpException {\n constructor() { super(404, 'Invitation not found') }\n}\nexport class OrganizationPermissionDeniedError extends HttpException {\n constructor() { super(403, 'Organization permission denied') }\n}\nexport class OrganizationInvitationRecipientMismatchError extends HttpException {\n constructor() { super(403, 'Invitation recipient mismatch') }\n}\nexport class OrganizationConflictError extends HttpException {\n constructor() { super(409, 'Organization resource conflict') }\n}\nexport class OrganizationLimitReachedError extends HttpException {\n constructor() { super(422, 'Organization limit reached') }\n}\nexport class OrganizationMembershipError extends HttpException {\n constructor() { super(422, 'Organization membership constraint violated') }\n}\nexport class OrganizationTeamNotFoundError extends HttpException {\n constructor() { super(404, 'Team not found') }\n}\nexport class OrganizationRoleNotFoundError extends HttpException {\n constructor() { super(404, 'Role not found') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class TokenRequiredError extends HttpException {\n constructor() { super(401, 'Verification token is required') }\n}\n","import { APIError } from 'better-auth/api'\nimport { AuthError } from 'stratal/errors'\nimport type { ApplicationError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n AuthValidationFailedError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailAlreadyVerifiedError,\n EmailCannotBeUpdatedError,\n EmailMismatchError,\n EmailNotVerifiedError,\n FreshSessionRequiredError,\n IdTokenNotSupportedError,\n InvalidCallbackUrlError,\n InvalidCredentialsError,\n InvalidEmailError,\n InvalidOriginError,\n InvalidPasswordError,\n InvalidTokenError,\n OrganizationConflictError,\n OrganizationInvitationNotFoundError,\n OrganizationInvitationRecipientMismatchError,\n OrganizationLimitReachedError,\n OrganizationMemberNotFoundError,\n OrganizationMembershipError,\n OrganizationNotFoundError,\n OrganizationPermissionDeniedError,\n OrganizationRoleNotFoundError,\n OrganizationTeamNotFoundError,\n PasswordTooLongError,\n PasswordTooShortError,\n ProviderNotFoundError,\n SessionExpiredError,\n SocialAccountLinkedError,\n TokenExpiredError,\n UserAlreadyHasPasswordError,\n UserEmailNotFoundError,\n UserNotFoundError,\n} from '../errors'\n\n/**\n * Maps Better Auth API error codes to ApplicationError instances.\n */\nexport function mapBetterAuthError(error: APIError): ApplicationError {\n const errorCode = error.body?.code\n\n if (error.status === 'FOUND') {\n const headers = error.headers as Headers\n const location = headers.get('location') ?? ''\n\n if (location.includes('INVALID_TOKEN')) return new InvalidTokenError()\n if (location.includes('EXPIRED_TOKEN')) return new TokenExpiredError()\n if (location.includes('ATTEMPTS_EXCEEDED')) return new InvalidTokenError()\n if (location.includes('new_user_signup_disabled')) return new UserNotFoundError()\n if (location.includes('failed_to_create_user')) return new AuthError('Failed to create user')\n if (location.includes('failed_to_create_session')) return new AuthError('Failed to create session')\n }\n\n if (!errorCode) {\n return new AuthError('An authentication error occurred')\n }\n\n // ── Base Error Codes ──────────────────────────────────────────────────\n\n // User errors\n if (errorCode === 'USER_NOT_FOUND' || errorCode === 'INVALID_USER') return new UserNotFoundError()\n if (errorCode === 'USER_EMAIL_NOT_FOUND') return new UserEmailNotFoundError()\n\n // Credential errors\n if (errorCode === 'INVALID_EMAIL_OR_PASSWORD') return new InvalidCredentialsError()\n if (errorCode === 'INVALID_PASSWORD') return new InvalidPasswordError()\n if (errorCode === 'INVALID_EMAIL') return new InvalidEmailError()\n\n // Session errors\n if (errorCode === 'SESSION_EXPIRED') return new SessionExpiredError()\n if (errorCode === 'SESSION_NOT_FRESH') return new FreshSessionRequiredError()\n if (errorCode === 'FAILED_TO_CREATE_SESSION') return new AuthError('Failed to create session')\n if (errorCode === 'FAILED_TO_GET_SESSION') return new AuthError('Failed to retrieve session')\n\n // Email verification\n if (errorCode === 'EMAIL_NOT_VERIFIED') return new EmailNotVerifiedError()\n if (errorCode === 'EMAIL_CAN_NOT_BE_UPDATED') return new EmailCannotBeUpdatedError()\n if (errorCode === 'EMAIL_ALREADY_VERIFIED') return new EmailAlreadyVerifiedError()\n if (errorCode === 'EMAIL_MISMATCH') return new EmailMismatchError()\n\n // Password validation\n if (errorCode === 'PASSWORD_TOO_SHORT') return new PasswordTooShortError(8)\n if (errorCode === 'PASSWORD_TOO_LONG') return new PasswordTooLongError(128)\n\n // Account errors\n if (errorCode === 'USER_ALREADY_EXISTS' || errorCode === 'USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL') {\n return new AccountAlreadyExistsError()\n }\n if (errorCode === 'ACCOUNT_NOT_FOUND') return new AccountNotFoundError()\n if (errorCode === 'CREDENTIAL_ACCOUNT_NOT_FOUND') return new CredentialAccountNotFoundError()\n if (errorCode === 'FAILED_TO_UNLINK_LAST_ACCOUNT') return new CannotUnlinkLastAccountError()\n\n // User creation/update errors\n if (errorCode === 'FAILED_TO_CREATE_USER') return new AuthError('Failed to create user')\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new AuthError('Failed to update user')\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new AuthError('Failed to retrieve user info')\n\n // Social account errors\n if (errorCode === 'SOCIAL_ACCOUNT_ALREADY_LINKED' || errorCode === 'LINKED_ACCOUNT_ALREADY_EXISTS') {\n return new SocialAccountLinkedError()\n }\n if (errorCode === 'PROVIDER_NOT_FOUND') return new ProviderNotFoundError()\n\n // Token errors\n if (errorCode === 'ID_TOKEN_NOT_SUPPORTED') return new IdTokenNotSupportedError()\n if (errorCode === 'INVALID_TOKEN') return new InvalidTokenError()\n if (errorCode === 'TOKEN_EXPIRED') return new TokenExpiredError()\n\n // Password management\n if (errorCode === 'USER_ALREADY_HAS_PASSWORD' || errorCode === 'PASSWORD_ALREADY_SET') {\n return new UserAlreadyHasPasswordError()\n }\n\n // Callback/redirect URL errors\n if (\n errorCode === 'INVALID_CALLBACK_URL'\n || errorCode === 'INVALID_REDIRECT_URL'\n || errorCode === 'INVALID_NEW_USER_CALLBACK_URL'\n || errorCode === 'INVALID_ERROR_CALLBACK_URL'\n || errorCode === 'CALLBACK_URL_REQUIRED'\n ) {\n return new InvalidCallbackUrlError()\n }\n\n // Origin/CORS errors\n if (\n errorCode === 'INVALID_ORIGIN'\n || errorCode === 'MISSING_OR_NULL_ORIGIN'\n || errorCode === 'CROSS_SITE_NAVIGATION_LOGIN_BLOCKED'\n ) {\n return new InvalidOriginError()\n }\n\n // Validation errors\n if (\n errorCode === 'VALIDATION_ERROR'\n || errorCode === 'MISSING_FIELD'\n || errorCode === 'FIELD_NOT_ALLOWED'\n || errorCode === 'BODY_MUST_BE_AN_OBJECT'\n || errorCode === 'ASYNC_VALIDATION_NOT_SUPPORTED'\n || errorCode === 'METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED'\n ) {\n return new AuthValidationFailedError()\n }\n\n // Verification errors\n if (errorCode === 'FAILED_TO_CREATE_VERIFICATION' || errorCode === 'VERIFICATION_EMAIL_NOT_ENABLED') {\n return new AuthError('Failed to create session')\n }\n\n // ── Organization Plugin Error Codes ───────────────────────────────────\n\n // Organization not found\n if (errorCode === 'ORGANIZATION_NOT_FOUND' || errorCode === 'NO_ACTIVE_ORGANIZATION') {\n return new OrganizationNotFoundError()\n }\n\n // Member not found\n if (\n errorCode === 'MEMBER_NOT_FOUND'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_TEAM'\n ) {\n return new OrganizationMemberNotFoundError()\n }\n\n // Invitation not found\n if (errorCode === 'INVITATION_NOT_FOUND' || errorCode === 'FAILED_TO_RETRIEVE_INVITATION') {\n return new OrganizationInvitationNotFoundError()\n }\n\n // Invitation recipient mismatch\n if (\n errorCode === 'YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION'\n || errorCode === 'EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION'\n ) {\n return new OrganizationInvitationRecipientMismatchError()\n }\n\n // Team not found\n if (errorCode === 'TEAM_NOT_FOUND' || errorCode === 'YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM') {\n return new OrganizationTeamNotFoundError()\n }\n\n // Role not found\n if (errorCode === 'ROLE_NOT_FOUND' || errorCode === 'INVALID_RESOURCE') {\n return new OrganizationRoleNotFoundError()\n }\n\n // Organization conflict/already exists\n if (\n errorCode === 'ORGANIZATION_ALREADY_EXISTS'\n || errorCode === 'ORGANIZATION_SLUG_ALREADY_TAKEN'\n || errorCode === 'USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION'\n || errorCode === 'TEAM_ALREADY_EXISTS'\n || errorCode === 'ROLE_NAME_IS_ALREADY_TAKEN'\n ) {\n return new OrganizationConflictError()\n }\n\n // Organization limit reached\n if (\n errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS'\n || errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS'\n || errorCode === 'ORGANIZATION_MEMBERSHIP_LIMIT_REACHED'\n || errorCode === 'INVITATION_LIMIT_REACHED'\n || errorCode === 'TEAM_MEMBER_LIMIT_REACHED'\n || errorCode === 'TOO_MANY_ROLES'\n ) {\n return new OrganizationLimitReachedError()\n }\n\n // Organization membership constraints\n if (\n errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER'\n || errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER'\n || errorCode === 'UNABLE_TO_REMOVE_LAST_TEAM'\n || errorCode === 'CANNOT_DELETE_A_PRE_DEFINED_ROLE'\n || errorCode === 'ROLE_IS_ASSIGNED_TO_MEMBERS'\n || errorCode === 'YOU_CANNOT_IMPERSONATE_ADMINS'\n || errorCode === 'YOU_CANNOT_BAN_YOURSELF'\n || errorCode === 'YOU_CANNOT_REMOVE_YOURSELF'\n || errorCode === 'INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION'\n ) {\n return new OrganizationMembershipError()\n }\n\n // Organization permission denied (catch-all for YOU_ARE_NOT_ALLOWED_TO_* patterns)\n if (\n errorCode.startsWith('YOU_ARE_NOT_ALLOWED_TO_')\n || errorCode === 'YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM'\n || errorCode === 'YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE'\n || errorCode === 'MISSING_AC_INSTANCE'\n ) {\n return new OrganizationPermissionDeniedError()\n }\n\n // Unknown error code\n return new AuthError('An authentication error occurred')\n}\n\n/**\n * Type guard to check if an error is a Better Auth APIError.\n * Uses duck typing to handle bundler environments (e.g. Vite)\n * where instanceof may fail across module boundaries.\n */\nexport function isAPIError(error: unknown): error is APIError {\n if (error instanceof APIError) return true\n\n return (\n error instanceof Error\n && error.name === 'APIError'\n && 'status' in error\n && 'statusCode' in error\n )\n}\n","import type { BetterAuthOptions } from 'better-auth'\nimport { isAPIError, mapBetterAuthError } from './better-auth-error-handler'\n\n/**\n * Get shared Better Auth error handler configuration.\n * Use this in Better Auth config's onAPIError option.\n */\nexport function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'] {\n return {\n throw: false,\n onError: (error) => {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n },\n }\n}\n\n/**\n * Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.\n */\nexport const wrapBetterAuth = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (error) {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n }\n}\n","import type { Auth, BetterAuthOptions } from 'better-auth';\nimport { betterAuth } from 'better-auth/minimal';\nimport { inject, Request } from 'stratal/di';\nimport { AUTH_OPTIONS, AUTH_SERVICE } from '../auth.tokens';\nimport { getErrorHandlerConfig } from '../utils';\n\n/**\n * AuthService\n *\n * Base authentication service using Better Auth.\n * Configured via AuthModule.forRootAsync() from the application layer.\n *\n * **Extensibility:**\n * Extend this class to add custom methods. Subclasses inherit\n * `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.\n *\n * @example\n * ```typescript\n * @Request(AUTH_SERVICE)\n * export class AppAuthService extends AuthService<AuthOptions> {\n * async signInMagicLink(email: string) {\n * return wrapBetterAuth(async () => {\n * return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })\n * })\n * }\n * }\n * ```\n */\n@Request(AUTH_SERVICE)\nexport class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {\n private _authInstance?: Auth<TOptions>\n\n constructor(\n @inject(AUTH_OPTIONS) protected readonly options: TOptions\n ) {}\n\n /**\n * Get the Better Auth instance.\n */\n get auth(): Auth<TOptions> {\n this._authInstance ??= betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>;\n\n return this._authInstance\n }\n}\n","/**\n * Auth Module\n *\n * Provides configurable authentication using Better Auth.\n * Use `forRootAsync` to configure Better Auth options from the application layer.\n *\n * Optionally pass `accessControl` to enable permission-based authorization.\n * This auto-adds the Stratal AC plugin to Better Auth and registers `AccessService`.\n *\n * @example Without access control\n * ```typescript\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database, CONFIG_TOKENS.ConfigService],\n * useFactory: (db, config) => createAuthOptions(db, config)\n * })\n * ]\n * })\n * export class AppModule {}\n * ```\n *\n * @example With access control\n * ```typescript\n * import { createAccessControl } from '@stratal/framework/access-control'\n * import { admin } from 'better-auth/plugins'\n *\n * const permissions = createAccessControl({\n * resources: { posts: ['create', 'read', 'update', 'delete'] } as const,\n * roles: { admin: { posts: ['create', 'read', 'update', 'delete'] }, user: { posts: ['read'] } },\n * })\n *\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database],\n * useFactory: (db) => ({\n * database: ...,\n * plugins: [admin({ ...permissions })],\n * }),\n * accessControl: permissions,\n * })\n * ]\n * })\n * ```\n */\n\nimport type { BetterAuthOptions } from 'better-auth'\nimport { CONTAINER_TOKEN, type Container } from 'stratal/di'\nimport type { AsyncModuleOptions, DynamicModule } from 'stratal/module'\nimport { Module } from 'stratal/module'\nimport type { IRateLimiterStore, RateLimiterRegistry } from 'stratal/rate-limiter'\nimport { RATE_LIMITER_TOKENS } from 'stratal/rate-limiter'\nimport type { RouteConfigurable, Router } from 'stratal/router'\nimport { AccessShareMiddleware } from '../access-control/access-share.middleware'\nimport { createStratalAcPlugin } from '../access-control/plugin'\nimport { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AccessControlOptions } from '../access-control/types'\nimport { AuthContext } from '../context/auth-context'\n// Side-effect import: registers the `user()` macro on `RouterContext` and its\n// type augmentation, backed by the request-scoped `AuthContext`.\nimport '../context/router-context.augment'\nimport { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\n// Side-effect import: registers `forPath`/`pathEntries` macros on\n// `RateLimiterRegistry` and the `declare module` augmentation that exposes\n// them at the type level. Must run before any consumer calls `forPath()`.\nimport {\n createBetterAuthRateLimitStorage,\n projectCustomRules,\n} from './rate-limit-bridge'\nimport { AuthService } from './services/auth.service'\n\nexport interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions>\n extends AsyncModuleOptions<TOptions> {\n /**\n * Optional access control configuration.\n * When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.\n */\n accessControl?: AccessControlOptions\n}\n\n@Module({\n providers: [AuthContext]\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * SessionVerificationMiddleware verifies the session and populates the\n * request-scoped AuthContext with the authenticated user.\n *\n * AccessShareMiddleware then shares the resulting roles and permissions to\n * Inertia. Order matters: it reads what session verification wrote. Both\n * no-op when their dependencies are absent.\n */\n configureRoutes(router: Router): void {\n router.use(SessionVerificationMiddleware)\n router.use(AccessShareMiddleware)\n }\n\n /**\n * Configure AuthModule with async options factory.\n * Optionally provide `accessControl` to enable permission-based authorization.\n *\n * When `RateLimiterModule` is also imported, better-auth's `rateLimit`\n * block is auto-wired: `customStorage` shares Stratal's backing store, and\n * any `RateLimiterRegistry.forPath(...)` entries are projected into\n * `customRules`. User-supplied `rateLimit.{customStorage, customRules}` keys\n * take precedence on a per-key basis.\n */\n static forRootAsync<TOptions extends BetterAuthOptions>(\n options: AuthModuleAsyncOptions<TOptions>\n ): DynamicModule {\n const { accessControl } = options\n const userInject = options.inject ?? []\n const userFactory = options.useFactory as (...args: unknown[]) => TOptions\n\n const authOptionsProvider = {\n provide: AUTH_OPTIONS,\n useFactory: (container: Container, ...userDeps: unknown[]): BetterAuthOptions => {\n let raw = userFactory(...userDeps) as BetterAuthOptions\n\n if (accessControl) {\n raw = {\n ...raw,\n plugins: [createStratalAcPlugin(accessControl), ...(raw.plugins ?? [])],\n }\n }\n\n const rateLimiterPresent = container.isRegistered(\n RATE_LIMITER_TOKENS.ModuleMarker,\n )\n\n if (rateLimiterPresent) {\n const store = container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n const registry = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n\n raw = {\n ...raw,\n rateLimit: {\n enabled: true,\n ...raw.rateLimit,\n customStorage: raw.rateLimit?.customStorage ?? createBetterAuthRateLimitStorage(store),\n customRules: {\n ...projectCustomRules(registry),\n ...(raw.rateLimit?.customRules ?? {}),\n },\n },\n }\n }\n\n return raw\n },\n inject: [CONTAINER_TOKEN, ...userInject],\n }\n\n return {\n module: AuthModule,\n providers: [\n authOptionsProvider,\n {\n provide: AUTH_SERVICE,\n useClass: AuthService,\n },\n ...(accessControl\n ? [\n { provide: AC_TOKENS.Options, useValue: accessControl as unknown as object },\n { provide: AC_TOKENS.AccessService, useClass: AccessService },\n ]\n : []),\n ],\n }\n }\n}\n","import { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\n\n/**\n * Middleware the response-cache gateway must run before resolving partitions.\n *\n * `ctx.user()` reads the request-scoped `AuthContext`, which\n * `SessionVerificationMiddleware` populates. The gateway runs outside the\n * app's middleware chain, so without this a partition resolver calling\n * `ctx.user()` would throw `UserNotAuthenticatedError` on every request.\n *\n * Pass it alongside `gateway: { entrypoint }` — `primers` without a configured\n * gateway is a boot error, because nothing would ever run the middleware\n * listed here.\n *\n * The cost lands in the primer, not the accessor: `ctx.user()` is a memory\n * read, while `SessionVerificationMiddleware` calls\n * `authService.auth.api.getSession()`. On a cache **miss** that call happens\n * twice — once in the gateway to resolve the partition, once in the app's own\n * chain, since each runs in its own request scope. On a **hit** the app never\n * runs, so only the gateway's single call is paid.\n *\n * @example\n * ```typescript\n * ResponseCacheModule.forRoot({\n * gateway: { entrypoint: 'Cached' },\n * primers: AUTH_GATEWAY_PRIMERS,\n * partitions: { user: (ctx) => ctx.user().id },\n * })\n * ```\n */\nexport const AUTH_GATEWAY_PRIMERS = [SessionVerificationMiddleware] as const\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BaseUser } from "@better-auth/core/db";
|
|
2
|
-
|
|
3
2
|
//#region src/context/auth-context.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Authenticated user shape stored in {@link AuthContext}.
|
|
@@ -82,4 +81,4 @@ declare class AuthContext {
|
|
|
82
81
|
}
|
|
83
82
|
//#endregion
|
|
84
83
|
export { AuthInfo as n, AuthUser as r, AuthContext as t };
|
|
85
|
-
//# sourceMappingURL=auth-context-
|
|
84
|
+
//# sourceMappingURL=auth-context-C1om3Zsr.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-context-C1om3Zsr.d.mts","names":[],"sources":["../src/context/auth-context.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UA8BiB,iBAAiB;UAEjB;EACf,MAAM;;cAIK;YACD,OAAO;;;;;EAMjB,eAAe,MAAM;;;;;EAQrB,WAAW;;;;EAOX,eAAe;;;;;EAWf;;;;;EAQA;;;;EAOA,eAAe;;;;;;;EAaf;;;;;EAQA;;;;EASA;;;;;EAQA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __decorate } from "./decorate-
|
|
1
|
+
import { t as __decorate } from "./decorate-RQD1h28J.mjs";
|
|
2
2
|
import { n as UserNotAuthenticatedError } from "./errors-BvJSaUTW.mjs";
|
|
3
3
|
import { DI_TOKENS, Request } from "stratal/di";
|
|
4
4
|
import { AuthError } from "stratal/errors";
|
|
@@ -83,4 +83,4 @@ AuthContext = __decorate([Request(DI_TOKENS.AuthContext)], AuthContext);
|
|
|
83
83
|
//#endregion
|
|
84
84
|
export { AuthContext as t };
|
|
85
85
|
|
|
86
|
-
//# sourceMappingURL=auth-context-
|
|
86
|
+
//# sourceMappingURL=auth-context-cNSS1rmh.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-context-
|
|
1
|
+
{"version":3,"file":"auth-context-cNSS1rmh.mjs","names":[],"sources":["../src/context/auth-context.ts"],"sourcesContent":["import type { BaseUser } from '@better-auth/core/db'\nimport { Request, DI_TOKENS } from 'stratal/di'\nimport { AuthError } from 'stratal/errors'\nimport {\n UserNotAuthenticatedError\n} from './errors'\n\n/**\n * Authenticated user shape stored in {@link AuthContext}.\n *\n * Inherits Better Auth's base user fields. Apps whose schema stores\n * `firstName`/`lastName` instead of a `name` column should expose a `name`\n * via a ZenStack result extension (see\n * https://zenstack.dev/docs/orm/plugins/extending-orm-client#adding-fields-to-query-results)\n * so reads return a populated `name` for free.\n *\n * Augment via TypeScript module declaration to add app-specific fields. Match\n * the augmentation to whatever your Better Auth `user.additionalFields` /\n * plugins are configured to return:\n *\n * @example\n * ```ts\n * declare module '@stratal/framework/context' {\n * interface AuthUser {\n * role: string\n * locale: string\n * }\n * }\n * ```\n */\nexport interface AuthUser extends BaseUser {}\n\nexport interface AuthInfo {\n user: AuthUser\n}\n\n@Request(DI_TOKENS.AuthContext)\nexport class AuthContext {\n protected user?: AuthUser\n\n /**\n * Set authentication context.\n * This should be called once per request with the authenticated user.\n */\n setAuthContext(info: AuthInfo): void {\n this.user = info.user\n }\n\n /**\n * Get the authenticated user if available.\n * Returns undefined if no user is authenticated.\n */\n getUser(): AuthUser | undefined {\n return this.user\n }\n\n /**\n * Get the authenticated user or throw if not authenticated.\n */\n requireUser(): AuthUser {\n if (!this.user) {\n throw new UserNotAuthenticatedError()\n }\n return this.user\n }\n\n /**\n * Get user ID if available.\n * Returns undefined if no user is authenticated.\n */\n getUserId(): string | undefined {\n return this.user?.id\n }\n\n /**\n * Get user ID or throw if not authenticated.\n * Use this when authentication is required.\n */\n requireUserId(): string {\n return this.requireUser().id\n }\n\n /**\n * Get full authentication context or throw if not initialized.\n */\n getAuthInfo(): AuthInfo {\n if (!this.user) {\n throw new AuthError('Auth context has not been initialized')\n }\n return { user: this.user }\n }\n\n /**\n * Get the raw role string from the authenticated user.\n *\n * Reads from `user.role` — apps that use roles should augment {@link AuthUser}\n * with `role: string` (or similar) so this returns a typed value.\n */\n getRole(): string | undefined {\n return (this.user as { role?: string } | undefined)?.role\n }\n\n /**\n * Get the user's roles as an array.\n * Returns an empty array if no role is set or user is not authenticated.\n */\n getRoles(): string[] {\n const role = this.getRole()\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n }\n\n /**\n * Check if user is authenticated.\n */\n isAuthenticated(): boolean {\n return !!this.user\n }\n\n /**\n * Clear authentication context.\n * Useful for testing or cleanup.\n */\n clearAuthContext(): void {\n this.user = undefined\n }\n}\n"],"mappings":";;;;;AAqCO,IAAM,cAAN,MAAM,YAAY;CACvB;;;;;CAMA,eAAe,MAAsB;EACnC,KAAK,OAAO,KAAK;CACnB;;;;;CAMA,UAAgC;EAC9B,OAAO,KAAK;CACd;;;;CAKA,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,0BAA0B;EAEtC,OAAO,KAAK;CACd;;;;;CAMA,YAAgC;EAC9B,OAAO,KAAK,MAAM;CACpB;;;;;CAMA,gBAAwB;EACtB,OAAO,KAAK,YAAY,CAAC,CAAC;CAC5B;;;;CAKA,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,UAAU,uCAAuC;EAE7D,OAAO,EAAE,MAAM,KAAK,KAAK;CAC3B;;;;;;;CAQA,UAA8B;EAC5B,OAAQ,KAAK,MAAwC;CACvD;;;;;CAMA,WAAqB;EACnB,MAAM,OAAO,KAAK,QAAQ;EAC1B,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO;CAC1D;;;;CAKA,kBAA2B;EACzB,OAAO,CAAC,CAAC,KAAK;CAChB;;;;;CAMA,mBAAyB;EACvB,KAAK,OAAO,KAAA;CACd;AACF;AA1FC,cAAA,WAAA,CAAA,QAAQ,UAAU,WAAW,CAAA,GAAA,WAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AccessControl, Role, Statements } from "better-auth/plugins/access";
|
|
2
|
+
import { Auth, BetterAuthOptions } from "better-auth";
|
|
3
|
+
//#region src/access-control/types.d.ts
|
|
4
|
+
type RolePermissions<TStatements extends Statements> = { [K in keyof TStatements]?: readonly TStatements[K][number][]; };
|
|
5
|
+
interface AccessControlOptions<TStatements extends Statements = Statements, TRoles extends Record<string, RolePermissions<TStatements>> = Record<string, RolePermissions<TStatements>>> {
|
|
6
|
+
ac: AccessControl<TStatements>;
|
|
7
|
+
roles: { [K in keyof TRoles]: Role; };
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/auth/services/auth.service.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* AuthService
|
|
13
|
+
*
|
|
14
|
+
* Base authentication service using Better Auth.
|
|
15
|
+
* Configured via AuthModule.forRootAsync() from the application layer.
|
|
16
|
+
*
|
|
17
|
+
* **Extensibility:**
|
|
18
|
+
* Extend this class to add custom methods. Subclasses inherit
|
|
19
|
+
* `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* @Request(AUTH_SERVICE)
|
|
24
|
+
* export class AppAuthService extends AuthService<AuthOptions> {
|
|
25
|
+
* async signInMagicLink(email: string) {
|
|
26
|
+
* return wrapBetterAuth(async () => {
|
|
27
|
+
* return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })
|
|
28
|
+
* })
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {
|
|
34
|
+
protected readonly options: TOptions;
|
|
35
|
+
private _authInstance?;
|
|
36
|
+
constructor(options: TOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Get the Better Auth instance.
|
|
39
|
+
*/
|
|
40
|
+
get auth(): Auth<TOptions>;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { AccessControlOptions as n, RolePermissions as r, AuthService as t };
|
|
44
|
+
//# sourceMappingURL=auth.service-Onf3JkyL.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.service-Onf3JkyL.d.mts","names":[],"sources":["../src/access-control/types.ts","../src/auth/services/auth.service.ts"],"mappings":";;;KAEY,gBAAgB,oBAAoB,iBAC7C,WAAW,wBAAwB,YAAY;UAGjC,qBAAqB,oBAAoB,aAAa,YAAY,eAAe,eAAe,gBAAgB,gBAAgB,eAAe,gBAAgB;EAC9K,IAAI,cAAc;EAClB,UAAU,WAAW,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;cCqBnB,YAAY,iBAAiB,oBAAoB;qBAIjB,SAAS;UAH5C;EAER,YAC2C,SAAS;;;;MAMhD,QAAQ,KAAK"}
|
package/dist/context/index.d.mts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { n as AuthInfo, r as AuthUser, t as AuthContext } from "../auth-context-
|
|
1
|
+
import { n as AuthInfo, r as AuthUser, t as AuthContext } from "../auth-context-C1om3Zsr.mjs";
|
|
2
2
|
import { HttpException } from "stratal/errors";
|
|
3
|
-
|
|
4
3
|
//#region src/context/errors/user-not-authenticated.error.d.ts
|
|
5
|
-
declare class UserNotAuthenticatedError extends HttpException {
|
|
4
|
+
export declare class UserNotAuthenticatedError extends HttpException {
|
|
6
5
|
constructor();
|
|
7
6
|
}
|
|
8
7
|
//#endregion
|
|
9
8
|
//#region src/context/errors/user-not-authorized.error.d.ts
|
|
10
|
-
declare class UserNotAuthorizedError extends HttpException {
|
|
9
|
+
export declare class UserNotAuthorizedError extends HttpException {
|
|
11
10
|
constructor();
|
|
12
11
|
}
|
|
13
12
|
//#endregion
|
|
14
|
-
export { AuthContext, AuthInfo, AuthUser
|
|
13
|
+
export { AuthContext, AuthInfo, AuthUser };
|
|
15
14
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/context/errors/user-not-authenticated.error.ts","../../src/context/errors/user-not-authorized.error.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/context/errors/user-not-authenticated.error.ts","../../src/context/errors/user-not-authorized.error.ts"],"mappings":";;;qBAEa,kCAAkC;EAC7C;;;;qBCDW,+BAA+B;EAC1C"}
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as UserNotAuthenticatedError, t as UserNotAuthorizedError } from "../errors-BvJSaUTW.mjs";
|
|
2
|
-
import { t as AuthContext } from "../auth-context-
|
|
2
|
+
import { t as AuthContext } from "../auth-context-cNSS1rmh.mjs";
|
|
3
3
|
export { AuthContext, UserNotAuthenticatedError, UserNotAuthorizedError };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { A as
|
|
3
|
-
export { ConnectionName, DATABASE_TOKENS, DatabaseConnectionConfig, DatabaseEventName, DatabaseEvents, DatabaseModule, DatabaseModuleConfig, DatabaseOperation, DatabaseService, DbGenerateCommand, DbPullCommand, DbPushCommand, DefaultConnectionName, EntityEventName, EntityEvents, EntityMutationVerb, ErrorHandlerPlugin, EventEmitterPlugin, EventEmitterPluginOptions, EventPhase, GetData, GetEntity, GetResult, InferAnySchema, InferConnectionExtensions, InferConnectionSchema, InjectDB, InternalDatabaseEventContext, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, ModelName, ParseEvent, RecordNotFoundError, SchemaSwitcher, StratalDatabase, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
|
|
1
|
+
import { a as InferAnySchema, c as InternalDatabaseEventContext, i as ExtractPluginClientMembers, l as StratalDatabase, n as DeclaredMembers, o as InferConnectionExtensions, r as DefaultConnectionName, s as InferConnectionSchema, t as ConnectionName } from "../types-B35g-lXi.mjs";
|
|
2
|
+
import { $ as CursorPageDelegate, A as CursorOrderingError, B as CursorModelKey, C as GetResult, D as UniqueConstraintError, E as fromZenStackError, F as DATABASE_TOKENS, G as CursorSourceArgs, H as CursorOrderByOf, I as connectionSymbol, J as CursorSourceReader, K as CursorSourceColumn, L as DatabaseService, M as InjectDB, N as DB_SHARED_POOL_ENV, O as RecordNotFoundError, P as createPoolFactory, Q as CursorPageArgs, R as TransactionService, S as GetEntity, T as ParseEvent, U as CursorReader, V as CursorModelReader, W as CursorReaderArgs, X as CursorFindManyArgs, Y as createCursorReader, Z as CursorOrderBy, _ as EntityEventName, a as DbPushCommand, at as DatabaseModule, b as EventPhase, c as ZenStackCommand, d as EventEmitterPluginOptions, et as CursorPageResult, f as ErrorHandlerPlugin, g as DatabaseOperation, h as DatabaseEvents, i as MigrateDeployCommand, it as DatabaseConnectionConfig, j as CursorModelUnavailableError, k as MalformedCursorError, l as SchemaSwitcher, m as DatabaseEventName, n as MigrateResetCommand, nt as decodeCursor, o as DbPullCommand, ot as DatabaseModuleConfig, p as databaseMessages, q as CursorSourceOrderBy, r as MigrateDevCommand, rt as encodeCursor, s as DbGenerateCommand, t as MigrateStatusCommand, tt as CursorSortOrder, u as EventEmitterPlugin, v as EntityEvents, w as ModelName, x as GetData, y as EntityMutationVerb, z as CursorClientMembers } from "../index-e_u1SRyd.mjs";
|
|
3
|
+
export { ConnectionName, CursorClientMembers, type CursorFindManyArgs, CursorModelKey, CursorModelReader, CursorModelUnavailableError, type CursorOrderBy, CursorOrderByOf, CursorOrderingError, type CursorPageArgs, type CursorPageDelegate, type CursorPageResult, CursorReader, CursorReaderArgs, type CursorSortOrder, CursorSourceArgs, CursorSourceColumn, CursorSourceOrderBy, CursorSourceReader, DATABASE_TOKENS, DB_SHARED_POOL_ENV, DatabaseConnectionConfig, DatabaseEventName, DatabaseEvents, DatabaseModule, DatabaseModuleConfig, DatabaseOperation, DatabaseService, DbGenerateCommand, DbPullCommand, DbPushCommand, DeclaredMembers, DefaultConnectionName, EntityEventName, EntityEvents, EntityMutationVerb, ErrorHandlerPlugin, EventEmitterPlugin, EventEmitterPluginOptions, EventPhase, ExtractPluginClientMembers, GetData, GetEntity, GetResult, InferAnySchema, InferConnectionExtensions, InferConnectionSchema, InjectDB, InternalDatabaseEventContext, MalformedCursorError, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, ModelName, ParseEvent, RecordNotFoundError, SchemaSwitcher, StratalDatabase, TransactionService, UniqueConstraintError, ZenStackCommand, connectionSymbol, createCursorReader, createPoolFactory, databaseMessages, decodeCursor, encodeCursor, fromZenStackError };
|