@stratal/framework 0.0.18 → 0.0.20

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.
Files changed (54) hide show
  1. package/dist/access-control/index.d.mts +180 -0
  2. package/dist/access-control/index.d.mts.map +1 -0
  3. package/dist/access-control/index.mjs +71 -0
  4. package/dist/access-control/index.mjs.map +1 -0
  5. package/dist/access.service-BjYVtUJw.mjs +145 -0
  6. package/dist/access.service-BjYVtUJw.mjs.map +1 -0
  7. package/dist/auth/index.d.mts +131 -6
  8. package/dist/auth/index.d.mts.map +1 -1
  9. package/dist/auth/index.mjs +341 -67
  10. package/dist/auth/index.mjs.map +1 -1
  11. package/dist/auth-context-6Li1JkIq.mjs +85 -0
  12. package/dist/auth-context-6Li1JkIq.mjs.map +1 -0
  13. package/dist/auth-context-B44CDZCt.d.mts +86 -0
  14. package/dist/auth-context-B44CDZCt.d.mts.map +1 -0
  15. package/dist/context/index.d.mts +2 -2
  16. package/dist/context/index.mjs +2 -2
  17. package/dist/database/index.d.mts +3 -3
  18. package/dist/database/index.mjs +54 -46
  19. package/dist/database/index.mjs.map +1 -1
  20. package/dist/{decorate-C12QolJF.mjs → decorate-CdfCRvAc.mjs} +1 -1
  21. package/dist/{decorateMetadata-rWbWGUuO.mjs → decorateMetadata-CqtSx3_1.mjs} +1 -1
  22. package/dist/decorateParam-Dc5DGEpb.mjs +18 -0
  23. package/dist/decorateParam-Dc5DGEpb.mjs.map +1 -0
  24. package/dist/{errors-C_KIIU1v.mjs → errors-B1vVXc1T.mjs} +1 -1
  25. package/dist/{errors-C_KIIU1v.mjs.map → errors-B1vVXc1T.mjs.map} +1 -1
  26. package/dist/factory/index.d.mts +1 -1
  27. package/dist/guards/index.d.mts +7 -6
  28. package/dist/guards/index.d.mts.map +1 -1
  29. package/dist/guards/index.mjs +38 -29
  30. package/dist/guards/index.mjs.map +1 -1
  31. package/dist/{index-B1iGBJcO.d.mts → index-CCDPF-1Y.d.mts} +30 -42
  32. package/dist/index-CCDPF-1Y.d.mts.map +1 -0
  33. package/dist/index.d.mts +2 -2
  34. package/dist/insufficient-permissions.error-CRnOHYvq.mjs +23 -0
  35. package/dist/insufficient-permissions.error-CRnOHYvq.mjs.map +1 -0
  36. package/dist/types-BLyu9dAd.d.mts +11 -0
  37. package/dist/types-BLyu9dAd.d.mts.map +1 -0
  38. package/dist/types-BZlcRR2M.d.mts +92 -0
  39. package/dist/types-BZlcRR2M.d.mts.map +1 -0
  40. package/package.json +23 -22
  41. package/dist/auth-context-BD2ApWg1.d.mts +0 -38
  42. package/dist/auth-context-BD2ApWg1.d.mts.map +0 -1
  43. package/dist/auth-context-BfekHvM9.mjs +0 -55
  44. package/dist/auth-context-BfekHvM9.mjs.map +0 -1
  45. package/dist/decorateParam-WGqsyT5s.mjs +0 -8
  46. package/dist/index-B1iGBJcO.d.mts.map +0 -1
  47. package/dist/rbac/index.d.mts +0 -206
  48. package/dist/rbac/index.d.mts.map +0 -1
  49. package/dist/rbac/index.mjs +0 -346
  50. package/dist/rbac/index.mjs.map +0 -1
  51. package/dist/tokens-Di1ofovy.mjs +0 -32
  52. package/dist/tokens-Di1ofovy.mjs.map +0 -1
  53. package/dist/types-Gjk0d2qB.d.mts +0 -47
  54. package/dist/types-Gjk0d2qB.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/auth/auth.tokens.ts","../../src/auth/middleware/auth-context.middleware.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/errors/verification-failed.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":["/** 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, Transient } from 'stratal/di'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { AuthContext } from '../../context/auth-context'\n\n/**\n * Auth Context Middleware\n *\n * Registers AuthContext in the request container at the start of each request.\n * This MUST run before SessionVerificationMiddleware and any other middleware\n * that depends on AuthContext.\n */\n@Transient()\nexport class AuthContextMiddleware implements Middleware {\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n const requestContainer = ctx.getContainer()\n\n const authContext = new AuthContext()\n requestContainer.registerValue(DI_TOKENS.AuthContext, authContext)\n\n await next()\n }\n}\n","import { DI_TOKENS, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\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 userId.\n *\n * **Responsibilities:**\n * - Calls Better Auth's getSession() API\n * - Populates AuthContext with userId if 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 session = await this.authService.auth.api.getSession({\n headers: ctx.c.req.raw.headers\n })\n\n if (session) {\n const authContext = ctx.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext)\n authContext.setAuthContext({ userId: session.user.id })\n }\n\n await next()\n } catch (error: unknown) {\n this.logger.debug('Session validation failed (e.g., invalidated in DB)', { error });\n await next()\n }\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotFoundError extends ApplicationError {\n constructor(email?: string) {\n super('errors.auth.userNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, email ? { email } : undefined)\n }\n}\n\nexport class InvalidCredentialsError extends ApplicationError {\n constructor() {\n super('errors.auth.invalidCredentials', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n\nexport class InvalidPasswordError extends ApplicationError {\n constructor() {\n super('errors.auth.invalidPassword', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n\nexport class InvalidEmailError extends ApplicationError {\n constructor(email?: string) {\n super('errors.auth.invalidEmail', ERROR_CODES.VALIDATION.INVALID_FORMAT, email ? { email } : undefined)\n }\n}\n\nexport class SessionExpiredError extends ApplicationError {\n constructor() {\n super('errors.auth.sessionExpired', ERROR_CODES.AUTH.SESSION_EXPIRED)\n }\n}\n\nexport class EmailNotVerifiedError extends ApplicationError {\n constructor(email?: string) {\n super('errors.auth.emailNotVerified', ERROR_CODES.AUTH.EMAIL_NOT_VERIFIED, email ? { email } : undefined)\n }\n}\n\nexport class PasswordTooShortError extends ApplicationError {\n constructor(minLength: number) {\n super('errors.auth.passwordTooShort', ERROR_CODES.AUTH.PASSWORD_TOO_SHORT, { minLength })\n }\n}\n\nexport class PasswordTooLongError extends ApplicationError {\n constructor(maxLength: number) {\n super('errors.auth.passwordTooLong', ERROR_CODES.AUTH.PASSWORD_TOO_LONG, { maxLength })\n }\n}\n\nexport class AccountAlreadyExistsError extends ApplicationError {\n constructor(email?: string) {\n super('errors.auth.accountAlreadyExists', ERROR_CODES.AUTH.ACCOUNT_ALREADY_EXISTS, email ? { email } : undefined)\n }\n}\n\nexport class FailedToCreateUserError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.failedToCreateUser', ERROR_CODES.AUTH.FAILED_TO_CREATE_USER, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToCreateSessionError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.failedToCreateSession', ERROR_CODES.AUTH.FAILED_TO_CREATE_SESSION, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToUpdateUserError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.failedToUpdateUser', ERROR_CODES.AUTH.FAILED_TO_UPDATE_USER, reason ? { reason } : undefined)\n }\n}\n\nexport class SocialAccountLinkedError extends ApplicationError {\n constructor(provider?: string) {\n super('errors.auth.socialAccountLinked', ERROR_CODES.AUTH.SOCIAL_ACCOUNT_LINKED, provider ? { provider } : undefined)\n }\n}\n\nexport class CannotUnlinkLastAccountError extends ApplicationError {\n constructor() {\n super('errors.auth.cannotUnlinkLastAccount', ERROR_CODES.AUTH.CANNOT_UNLINK_LAST_ACCOUNT)\n }\n}\n\nexport class ProviderNotFoundError extends ApplicationError {\n constructor(provider?: string) {\n super('errors.auth.providerNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, provider ? { provider } : undefined)\n }\n}\n\nexport class UserEmailNotFoundError extends ApplicationError {\n constructor() {\n super('errors.auth.userEmailNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class AccountNotFoundError extends ApplicationError {\n constructor() {\n super('errors.auth.accountNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class CredentialAccountNotFoundError extends ApplicationError {\n constructor() {\n super('errors.auth.credentialAccountNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class UserAlreadyHasPasswordError extends ApplicationError {\n constructor() {\n super('errors.auth.userAlreadyHasPassword', ERROR_CODES.RESOURCE.CONFLICT)\n }\n}\n\nexport class EmailCannotBeUpdatedError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.emailCannotBeUpdated', ERROR_CODES.VALIDATION.GENERIC, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToGetSessionError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.failedToGetSession', ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToGetUserInfoError extends ApplicationError {\n constructor(reason?: string) {\n super('errors.auth.failedToGetUserInfo', ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : undefined)\n }\n}\n\nexport class IdTokenNotSupportedError extends ApplicationError {\n constructor() {\n super('errors.auth.invalidToken', ERROR_CODES.VALIDATION.GENERIC)\n }\n}\n\nexport class TokenExpiredError extends ApplicationError {\n constructor() {\n super('errors.auth.tokenExpired', ERROR_CODES.VALIDATION.GENERIC)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class InvalidTokenError extends ApplicationError {\n constructor() {\n super('errors.auth.invalidToken', ERROR_CODES.AUTH.INVALID_TOKEN)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class TokenRequiredError extends ApplicationError {\n constructor() {\n super('errors.auth.tokenRequired', ERROR_CODES.VALIDATION.REQUIRED_FIELD, { field: 'token' })\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class VerificationFailedError extends ApplicationError {\n constructor() {\n super('errors.auth.verificationFailed', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n","import { type BASE_ERROR_CODES } from '@better-auth/core/error'\nimport { APIError } from 'better-auth/api'\nimport type { ApplicationError } from 'stratal/errors'\nimport { InternalError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailCannotBeUpdatedError,\n EmailNotVerifiedError,\n FailedToCreateSessionError,\n FailedToCreateUserError,\n FailedToGetSessionError,\n FailedToGetUserInfoError,\n FailedToUpdateUserError,\n IdTokenNotSupportedError,\n InvalidCredentialsError,\n InvalidEmailError,\n InvalidPasswordError,\n InvalidTokenError,\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 as keyof typeof BASE_ERROR_CODES | 'TOKEN_EXPIRED' | undefined\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 FailedToCreateUserError()\n if (location.includes('failed_to_create_session')) return new FailedToCreateSessionError()\n }\n\n if (!errorCode) {\n return new InternalError({\n originalError: `Better Auth error: ${error.message}`,\n stack: error.stack,\n })\n }\n\n // User errors\n if (errorCode === 'USER_NOT_FOUND') 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 === 'FAILED_TO_CREATE_SESSION') return new FailedToCreateSessionError()\n if (errorCode === 'FAILED_TO_GET_SESSION') return new FailedToGetSessionError()\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\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 FailedToCreateUserError()\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new FailedToUpdateUserError()\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new FailedToGetUserInfoError()\n\n // Social account errors\n if (errorCode === 'SOCIAL_ACCOUNT_ALREADY_LINKED') return new SocialAccountLinkedError()\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 IdTokenNotSupportedError()\n if (errorCode === 'TOKEN_EXPIRED') return new TokenExpiredError()\n\n // Password management\n if (errorCode === 'USER_ALREADY_HAS_PASSWORD') return new UserAlreadyHasPasswordError()\n\n // Unknown error code\n return new InternalError({\n originalError: `Better Auth error [${errorCode}]: ${error.message}`,\n stack: error.stack,\n })\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'\nimport { inject } from 'tsyringe'\nimport { Transient } 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 in application layer to add custom methods.\n *\n * @example\n * ```typescript\n * @Transient(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@Transient(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 this.authInstance = betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>\n }\n\n /**\n * Get the Better Auth instance\n */\n get auth(): Auth<TOptions> {\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 * @example\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\nimport type { BetterAuthOptions } from 'better-auth'\nimport type { RouteConfigurable, Router } from 'stratal/router'\nimport { Module } from 'stratal/module'\nimport type { AsyncModuleOptions, DynamicModule } from 'stratal/module'\nimport { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { AuthContextMiddleware } from './middleware/auth-context.middleware'\nimport { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\nimport { AuthService } from './services/auth.service'\n\n@Module({\n providers: []\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * Registers middlewares in order:\n * 1. AuthContextMiddleware - Creates and registers AuthContext in request container\n * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId\n */\n configureRoutes(router: Router): void {\n router.use(AuthContextMiddleware, SessionVerificationMiddleware)\n }\n\n /**\n * Configure AuthModule with async options factory\n */\n static forRootAsync<TOptions extends BetterAuthOptions>(\n options: AsyncModuleOptions<TOptions>\n ): DynamicModule {\n return {\n module: AuthModule,\n providers: [\n {\n provide: AUTH_OPTIONS,\n useFactory: options.useFactory,\n inject: options.inject\n },\n {\n provide: AUTH_SERVICE,\n useClass: AuthService\n }\n ]\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["inject","inject"],"sources":["../../src/auth/auth.tokens.ts","../../src/auth/i18n/en.ts","../../src/auth/middleware/auth-context.middleware.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/errors/verification-failed.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":["/** 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","export const authMessages = {\n en: {\n auth: {\n errors: {\n tokenRequired: 'Verification token is required',\n invalidToken: 'Invalid or expired verification token',\n verificationFailed: 'Verification failed. Please try again.',\n userNotFound: 'User not found. Please check your credentials.',\n invalidCredentials: 'Invalid email or password',\n invalidPassword: 'Invalid password',\n invalidEmail: 'Invalid email address',\n sessionExpired: 'Your session has expired. Please sign in again.',\n emailNotVerified: 'Please verify your email address before signing in',\n passwordTooShort: 'Password must be at least {minLength} characters',\n passwordTooLong: 'Password must be at most {maxLength} characters',\n accountAlreadyExists: 'An account with this email already exists',\n failedToCreateUser: 'Failed to create user account. Please try again.',\n failedToCreateSession: 'Failed to create session. Please try again.',\n failedToGetSession: 'Failed to retrieve session. Please try again.',\n failedToUpdateUser: 'Failed to update user information. Please try again.',\n failedToGetUserInfo: 'Failed to retrieve user information. Please try again.',\n socialAccountLinked: 'This social account is already linked to another user',\n providerNotFound: 'Authentication provider not found',\n userEmailNotFound: 'User email address not found',\n accountNotFound: 'Account not found',\n credentialAccountNotFound: 'Credential account not found',\n cannotUnlinkLastAccount: 'Cannot unlink your last account',\n userAlreadyHasPassword: 'User already has a password set',\n emailCannotBeUpdated: 'Email address cannot be updated at this time',\n tokenExpired: 'The verification token has expired. Please request a new verification email.',\n invalidCallbackUrl: 'Invalid callback URL',\n invalidOrigin: 'Request origin is not allowed',\n validationFailed: 'Authentication validation failed',\n emailAlreadyVerified: 'Email address is already verified',\n emailMismatch: 'Email address does not match',\n unknownError: 'An authentication error occurred',\n },\n org: {\n organizationNotFound: 'Organization not found',\n memberNotFound: 'Member not found',\n invitationNotFound: 'Invitation not found',\n permissionDenied: 'You do not have permission to perform this action',\n invitationRecipientMismatch: 'You are not the recipient of this invitation',\n conflict: 'A resource with this identifier already exists',\n limitReached: 'The maximum limit has been reached',\n membershipError: 'This action cannot be performed due to membership constraints',\n teamNotFound: 'Team not found',\n roleNotFound: 'Role not found',\n },\n },\n },\n} as const\n\ndeclare module 'stratal/i18n' {\n interface AppMessageNamespaces {\n auth: typeof authMessages['en']['auth']\n }\n}\n","import { DI_TOKENS, Transient } from 'stratal/di'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { AuthContext } from '../../context/auth-context'\n\n/**\n * Auth Context Middleware\n *\n * Registers AuthContext in the request container at the start of each request.\n * This MUST run before SessionVerificationMiddleware and any other middleware\n * that depends on AuthContext.\n */\n@Transient()\nexport class AuthContextMiddleware implements Middleware {\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n const requestContainer = ctx.getContainer()\n\n const authContext = new AuthContext()\n requestContainer.registerValue(DI_TOKENS.AuthContext, authContext)\n\n return next()\n }\n}\n","import { DI_TOKENS, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\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 { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class UserNotFoundError extends ApplicationError {\n constructor(email?: string) {\n super('auth.errors.userNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, email ? { email } : undefined)\n }\n}\n\nexport class InvalidCredentialsError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidCredentials', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n\nexport class InvalidPasswordError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidPassword', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n\nexport class InvalidEmailError extends ApplicationError {\n constructor(email?: string) {\n super('auth.errors.invalidEmail', ERROR_CODES.VALIDATION.INVALID_FORMAT, email ? { email } : undefined)\n }\n}\n\nexport class SessionExpiredError extends ApplicationError {\n constructor() {\n super('auth.errors.sessionExpired', ERROR_CODES.AUTH.SESSION_EXPIRED)\n }\n}\n\nexport class EmailNotVerifiedError extends ApplicationError {\n constructor(email?: string) {\n super('auth.errors.emailNotVerified', ERROR_CODES.AUTH.EMAIL_NOT_VERIFIED, email ? { email } : undefined)\n }\n}\n\nexport class PasswordTooShortError extends ApplicationError {\n constructor(minLength: number) {\n super('auth.errors.passwordTooShort', ERROR_CODES.AUTH.PASSWORD_TOO_SHORT, { minLength })\n }\n}\n\nexport class PasswordTooLongError extends ApplicationError {\n constructor(maxLength: number) {\n super('auth.errors.passwordTooLong', ERROR_CODES.AUTH.PASSWORD_TOO_LONG, { maxLength })\n }\n}\n\nexport class AccountAlreadyExistsError extends ApplicationError {\n constructor(email?: string) {\n super('auth.errors.accountAlreadyExists', ERROR_CODES.AUTH.ACCOUNT_ALREADY_EXISTS, email ? { email } : undefined)\n }\n}\n\nexport class FailedToCreateUserError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.failedToCreateUser', ERROR_CODES.AUTH.FAILED_TO_CREATE_USER, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToCreateSessionError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.failedToCreateSession', ERROR_CODES.AUTH.FAILED_TO_CREATE_SESSION, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToUpdateUserError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.failedToUpdateUser', ERROR_CODES.AUTH.FAILED_TO_UPDATE_USER, reason ? { reason } : undefined)\n }\n}\n\nexport class SocialAccountLinkedError extends ApplicationError {\n constructor(provider?: string) {\n super('auth.errors.socialAccountLinked', ERROR_CODES.AUTH.SOCIAL_ACCOUNT_LINKED, provider ? { provider } : undefined)\n }\n}\n\nexport class CannotUnlinkLastAccountError extends ApplicationError {\n constructor() {\n super('auth.errors.cannotUnlinkLastAccount', ERROR_CODES.AUTH.CANNOT_UNLINK_LAST_ACCOUNT)\n }\n}\n\nexport class ProviderNotFoundError extends ApplicationError {\n constructor(provider?: string) {\n super('auth.errors.providerNotFound', ERROR_CODES.RESOURCE.NOT_FOUND, provider ? { provider } : undefined)\n }\n}\n\nexport class UserEmailNotFoundError extends ApplicationError {\n constructor() {\n super('auth.errors.userEmailNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class AccountNotFoundError extends ApplicationError {\n constructor() {\n super('auth.errors.accountNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class CredentialAccountNotFoundError extends ApplicationError {\n constructor() {\n super('auth.errors.credentialAccountNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class UserAlreadyHasPasswordError extends ApplicationError {\n constructor() {\n super('auth.errors.userAlreadyHasPassword', ERROR_CODES.RESOURCE.CONFLICT)\n }\n}\n\nexport class EmailCannotBeUpdatedError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.emailCannotBeUpdated', ERROR_CODES.VALIDATION.GENERIC, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToGetSessionError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.failedToGetSession', ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : undefined)\n }\n}\n\nexport class FailedToGetUserInfoError extends ApplicationError {\n constructor(reason?: string) {\n super('auth.errors.failedToGetUserInfo', ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : undefined)\n }\n}\n\nexport class IdTokenNotSupportedError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidToken', ERROR_CODES.VALIDATION.GENERIC)\n }\n}\n\nexport class TokenExpiredError extends ApplicationError {\n constructor() {\n super('auth.errors.tokenExpired', ERROR_CODES.VALIDATION.GENERIC)\n }\n}\n\nexport class InvalidCallbackUrlError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidCallbackUrl', ERROR_CODES.VALIDATION.INVALID_FORMAT)\n }\n}\n\nexport class InvalidOriginError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidOrigin', ERROR_CODES.AUTHZ.FORBIDDEN)\n }\n}\n\nexport class AuthValidationFailedError extends ApplicationError {\n constructor() {\n super('auth.errors.validationFailed', ERROR_CODES.VALIDATION.GENERIC)\n }\n}\n\nexport class EmailAlreadyVerifiedError extends ApplicationError {\n constructor() {\n super('auth.errors.emailAlreadyVerified', ERROR_CODES.RESOURCE.CONFLICT)\n }\n}\n\nexport class EmailMismatchError extends ApplicationError {\n constructor() {\n super('auth.errors.emailMismatch', ERROR_CODES.VALIDATION.INVALID_FORMAT)\n }\n}\n\nexport class BetterAuthUnknownError extends ApplicationError {\n constructor(errorCode?: string) {\n super('auth.errors.unknownError', ERROR_CODES.SYSTEM.INTERNAL_ERROR, errorCode ? { errorCode } : undefined)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class InvalidTokenError extends ApplicationError {\n constructor() {\n super('auth.errors.invalidToken', ERROR_CODES.AUTH.INVALID_TOKEN)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class OrganizationNotFoundError extends ApplicationError {\n constructor() {\n super('auth.org.organizationNotFound', ERROR_CODES.AUTH.ORGANIZATION_NOT_FOUND)\n }\n}\n\nexport class OrganizationMemberNotFoundError extends ApplicationError {\n constructor() {\n super('auth.org.memberNotFound', ERROR_CODES.AUTH.MEMBER_NOT_FOUND)\n }\n}\n\nexport class OrganizationInvitationNotFoundError extends ApplicationError {\n constructor() {\n super('auth.org.invitationNotFound', ERROR_CODES.AUTH.INVITATION_NOT_FOUND)\n }\n}\n\nexport class OrganizationPermissionDeniedError extends ApplicationError {\n constructor() {\n super('auth.org.permissionDenied', ERROR_CODES.AUTHZ.FORBIDDEN)\n }\n}\n\nexport class OrganizationInvitationRecipientMismatchError extends ApplicationError {\n constructor() {\n super('auth.org.invitationRecipientMismatch', ERROR_CODES.AUTH.INVITATION_RECIPIENT_MISMATCH)\n }\n}\n\nexport class OrganizationConflictError extends ApplicationError {\n constructor() {\n super('auth.org.conflict', ERROR_CODES.RESOURCE.CONFLICT)\n }\n}\n\nexport class OrganizationLimitReachedError extends ApplicationError {\n constructor() {\n super('auth.org.limitReached', ERROR_CODES.AUTH.ORGANIZATION_LIMIT_REACHED)\n }\n}\n\nexport class OrganizationMembershipError extends ApplicationError {\n constructor() {\n super('auth.org.membershipError', ERROR_CODES.AUTH.ORGANIZATION_MEMBERSHIP_REQUIRED)\n }\n}\n\nexport class OrganizationTeamNotFoundError extends ApplicationError {\n constructor() {\n super('auth.org.teamNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n\nexport class OrganizationRoleNotFoundError extends ApplicationError {\n constructor() {\n super('auth.org.roleNotFound', ERROR_CODES.RESOURCE.NOT_FOUND)\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class TokenRequiredError extends ApplicationError {\n constructor() {\n super('auth.errors.tokenRequired', ERROR_CODES.VALIDATION.REQUIRED_FIELD, { field: 'token' })\n }\n}\n","import { ApplicationError, ERROR_CODES } from 'stratal/errors'\n\nexport class VerificationFailedError extends ApplicationError {\n constructor() {\n super('auth.errors.verificationFailed', ERROR_CODES.AUTH.INVALID_CREDENTIALS)\n }\n}\n","import { APIError } from 'better-auth/api'\nimport type { ApplicationError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n AuthValidationFailedError,\n BetterAuthUnknownError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailAlreadyVerifiedError,\n EmailCannotBeUpdatedError,\n EmailMismatchError,\n EmailNotVerifiedError,\n FailedToCreateSessionError,\n FailedToCreateUserError,\n FailedToGetSessionError,\n FailedToGetUserInfoError,\n FailedToUpdateUserError,\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 FailedToCreateUserError()\n if (location.includes('failed_to_create_session')) return new FailedToCreateSessionError()\n }\n\n if (!errorCode) {\n return new BetterAuthUnknownError()\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' || errorCode === 'SESSION_NOT_FRESH') return new SessionExpiredError()\n if (errorCode === 'FAILED_TO_CREATE_SESSION') return new FailedToCreateSessionError()\n if (errorCode === 'FAILED_TO_GET_SESSION') return new FailedToGetSessionError()\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 FailedToCreateUserError()\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new FailedToUpdateUserError()\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new FailedToGetUserInfoError()\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 FailedToCreateSessionError()\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 BetterAuthUnknownError(errorCode)\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'\nimport { inject } from 'tsyringe'\nimport { Transient } 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 in application layer to add custom methods.\n *\n * @example\n * ```typescript\n * @Transient(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@Transient(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 this.authInstance = betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>\n }\n\n /**\n * Get the Better Auth instance\n */\n get auth(): Auth<TOptions> {\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 { I18nModule } from 'stratal/i18n'\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 { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { authMessages } from './i18n'\nimport { AuthContextMiddleware } from './middleware/auth-context.middleware'\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 imports: [\n I18nModule.registerMessages(authMessages),\n ],\n providers: []\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * Registers middlewares in order:\n * 1. AuthContextMiddleware - Creates and registers AuthContext in request container\n * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId + role\n */\n configureRoutes(router: Router): void {\n router.use(AuthContextMiddleware, 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 tsyringe = container.getTsyringeContainer()\n const rateLimiterPresent = tsyringe.isRegistered(\n RATE_LIMITER_TOKENS.ModuleMarker,\n true,\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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"}
@@ -0,0 +1,85 @@
1
+ import { t as __decorate } from "./decorate-CdfCRvAc.mjs";
2
+ import { n as UserNotAuthenticatedError, r as ContextNotInitializedError } from "./errors-B1vVXc1T.mjs";
3
+ import { DI_TOKENS, Transient } from "stratal/di";
4
+ //#region src/context/auth-context.ts
5
+ let AuthContext = class AuthContext {
6
+ user;
7
+ /**
8
+ * Set authentication context.
9
+ * This should be called once per request with the authenticated user.
10
+ */
11
+ setAuthContext(info) {
12
+ this.user = info.user;
13
+ }
14
+ /**
15
+ * Get the authenticated user if available.
16
+ * Returns undefined if no user is authenticated.
17
+ */
18
+ getUser() {
19
+ return this.user;
20
+ }
21
+ /**
22
+ * Get the authenticated user or throw if not authenticated.
23
+ */
24
+ requireUser() {
25
+ if (!this.user) throw new UserNotAuthenticatedError();
26
+ return this.user;
27
+ }
28
+ /**
29
+ * Get user ID if available.
30
+ * Returns undefined if no user is authenticated.
31
+ */
32
+ getUserId() {
33
+ return this.user?.id;
34
+ }
35
+ /**
36
+ * Get user ID or throw if not authenticated.
37
+ * Use this when authentication is required.
38
+ */
39
+ requireUserId() {
40
+ return this.requireUser().id;
41
+ }
42
+ /**
43
+ * Get full authentication context or throw if not initialized.
44
+ */
45
+ getAuthInfo() {
46
+ if (!this.user) throw new ContextNotInitializedError("Authentication");
47
+ return { user: this.user };
48
+ }
49
+ /**
50
+ * Get the raw role string from the authenticated user.
51
+ *
52
+ * Reads from `user.role` — apps that use roles should augment {@link AuthUser}
53
+ * with `role: string` (or similar) so this returns a typed value.
54
+ */
55
+ getRole() {
56
+ return this.user?.role;
57
+ }
58
+ /**
59
+ * Get the user's roles as an array.
60
+ * Returns an empty array if no role is set or user is not authenticated.
61
+ */
62
+ getRoles() {
63
+ const role = this.getRole();
64
+ if (!role) return [];
65
+ return role.split(",").map((r) => r.trim()).filter(Boolean);
66
+ }
67
+ /**
68
+ * Check if user is authenticated.
69
+ */
70
+ isAuthenticated() {
71
+ return !!this.user;
72
+ }
73
+ /**
74
+ * Clear authentication context.
75
+ * Useful for testing or cleanup.
76
+ */
77
+ clearAuthContext() {
78
+ this.user = void 0;
79
+ }
80
+ };
81
+ AuthContext = __decorate([Transient(DI_TOKENS.AuthContext)], AuthContext);
82
+ //#endregion
83
+ export { AuthContext as t };
84
+
85
+ //# sourceMappingURL=auth-context-6Li1JkIq.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-context-6Li1JkIq.mjs","names":[],"sources":["../src/context/auth-context.ts"],"sourcesContent":["import type { BaseUser } from '@better-auth/core/db'\nimport { Transient, DI_TOKENS } from 'stratal/di'\nimport {\n ContextNotInitializedError,\n UserNotAuthenticatedError\n} from './errors'\n\n/**\n * Authenticated user shape stored in {@link AuthContext}.\n *\n * Inherits Better Auth's base user fields, with `name` made optional so apps\n * that store `firstName`/`lastName` (or other name conventions) instead of\n * `name` aren't forced to declare a phantom value.\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 * firstName: string\n * lastName: string\n * role: string\n * }\n * }\n * ```\n */\nexport interface AuthUser extends Omit<BaseUser, 'name'> {\n name?: string\n}\n\nexport interface AuthInfo {\n user: AuthUser\n}\n\n@Transient(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 ContextNotInitializedError('Authentication')\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":";;;;AAsCO,IAAA,cAAA,MAAM,YAAY;CACvB;;;;;CAMA,eAAe,MAAsB;AACnC,OAAK,OAAO,KAAK;;;;;;CAOnB,UAAgC;AAC9B,SAAO,KAAK;;;;;CAMd,cAAwB;AACtB,MAAI,CAAC,KAAK,KACR,OAAM,IAAI,2BAA2B;AAEvC,SAAO,KAAK;;;;;;CAOd,YAAgC;AAC9B,SAAO,KAAK,MAAM;;;;;;CAOpB,gBAAwB;AACtB,SAAO,KAAK,aAAa,CAAC;;;;;CAM5B,cAAwB;AACtB,MAAI,CAAC,KAAK,KACR,OAAM,IAAI,2BAA2B,iBAAiB;AAExD,SAAO,EAAE,MAAM,KAAK,MAAM;;;;;;;;CAS5B,UAA8B;AAC5B,SAAQ,KAAK,MAAwC;;;;;;CAOvD,WAAqB;EACnB,MAAM,OAAO,KAAK,SAAS;AAC3B,MAAI,CAAC,KAAM,QAAO,EAAE;AACpB,SAAO,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;;;;;CAM3D,kBAA2B;AACzB,SAAO,CAAC,CAAC,KAAK;;;;;;CAOhB,mBAAyB;AACvB,OAAK,OAAO,KAAA;;;0BAxFf,UAAU,UAAU,YAAY,CAAA,EAAA,YAAA"}
@@ -0,0 +1,86 @@
1
+ import { BaseUser } from "@better-auth/core/db";
2
+
3
+ //#region src/context/auth-context.d.ts
4
+ /**
5
+ * Authenticated user shape stored in {@link AuthContext}.
6
+ *
7
+ * Inherits Better Auth's base user fields, with `name` made optional so apps
8
+ * that store `firstName`/`lastName` (or other name conventions) instead of
9
+ * `name` aren't forced to declare a phantom value.
10
+ *
11
+ * Augment via TypeScript module declaration to add app-specific fields. Match
12
+ * the augmentation to whatever your Better Auth `user.additionalFields` /
13
+ * plugins are configured to return:
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * declare module '@stratal/framework/context' {
18
+ * interface AuthUser {
19
+ * firstName: string
20
+ * lastName: string
21
+ * role: string
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ interface AuthUser extends Omit<BaseUser, 'name'> {
27
+ name?: string;
28
+ }
29
+ interface AuthInfo {
30
+ user: AuthUser;
31
+ }
32
+ declare class AuthContext {
33
+ protected user?: AuthUser;
34
+ /**
35
+ * Set authentication context.
36
+ * This should be called once per request with the authenticated user.
37
+ */
38
+ setAuthContext(info: AuthInfo): void;
39
+ /**
40
+ * Get the authenticated user if available.
41
+ * Returns undefined if no user is authenticated.
42
+ */
43
+ getUser(): AuthUser | undefined;
44
+ /**
45
+ * Get the authenticated user or throw if not authenticated.
46
+ */
47
+ requireUser(): AuthUser;
48
+ /**
49
+ * Get user ID if available.
50
+ * Returns undefined if no user is authenticated.
51
+ */
52
+ getUserId(): string | undefined;
53
+ /**
54
+ * Get user ID or throw if not authenticated.
55
+ * Use this when authentication is required.
56
+ */
57
+ requireUserId(): string;
58
+ /**
59
+ * Get full authentication context or throw if not initialized.
60
+ */
61
+ getAuthInfo(): AuthInfo;
62
+ /**
63
+ * Get the raw role string from the authenticated user.
64
+ *
65
+ * Reads from `user.role` — apps that use roles should augment {@link AuthUser}
66
+ * with `role: string` (or similar) so this returns a typed value.
67
+ */
68
+ getRole(): string | undefined;
69
+ /**
70
+ * Get the user's roles as an array.
71
+ * Returns an empty array if no role is set or user is not authenticated.
72
+ */
73
+ getRoles(): string[];
74
+ /**
75
+ * Check if user is authenticated.
76
+ */
77
+ isAuthenticated(): boolean;
78
+ /**
79
+ * Clear authentication context.
80
+ * Useful for testing or cleanup.
81
+ */
82
+ clearAuthContext(): void;
83
+ }
84
+ //#endregion
85
+ export { AuthInfo as n, AuthUser as r, AuthContext as t };
86
+ //# sourceMappingURL=auth-context-B44CDZCt.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-context-B44CDZCt.d.mts","names":[],"sources":["../src/context/auth-context.ts"],"mappings":";;;;;AA6BA;;;;;;;;;AAIA;;;;;AAIA;;;;;;UARiB,QAAA,SAAiB,IAAA,CAAK,QAAA;EACrC,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,cAIK,WAAA;EAAA,UACD,IAAA,GAAO,QAAA;EAMF;;;;EAAf,cAAA,CAAe,IAAA,EAAM,QAAA;EA0BrB;;;;EAlBA,OAAA,CAAA,GAAW,QAAA;EAsDX;;;EA/CA,WAAA,CAAA,GAAe,QAAA;EAgEC;;;;EArDhB,SAAA,CAAA;;;;;EAQA,aAAA,CAAA;;;;EAOA,WAAA,CAAA,GAAe,QAAA;;;;;;;EAaf,OAAA,CAAA;;;;;EAQA,QAAA,CAAA;;;;EASA,eAAA,CAAA;;;;;EAQA,gBAAA,CAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { n as AuthInfo, t as AuthContext } from "../auth-context-BD2ApWg1.mjs";
1
+ import { n as AuthInfo, r as AuthUser, t as AuthContext } from "../auth-context-B44CDZCt.mjs";
2
2
  import { ApplicationError } from "stratal/errors";
3
3
 
4
4
  //#region src/context/errors/context-not-initialized.error.d.ts
@@ -16,5 +16,5 @@ declare class UserNotAuthorizedError extends ApplicationError {
16
16
  constructor();
17
17
  }
18
18
  //#endregion
19
- export { AuthContext, AuthInfo, ContextNotInitializedError, UserNotAuthenticatedError, UserNotAuthorizedError };
19
+ export { AuthContext, AuthInfo, AuthUser, ContextNotInitializedError, UserNotAuthenticatedError, UserNotAuthorizedError };
20
20
  //# sourceMappingURL=index.d.mts.map
@@ -1,3 +1,3 @@
1
- import { n as UserNotAuthenticatedError, r as ContextNotInitializedError, t as UserNotAuthorizedError } from "../errors-C_KIIU1v.mjs";
2
- import { t as AuthContext } from "../auth-context-BfekHvM9.mjs";
1
+ import { n as UserNotAuthenticatedError, r as ContextNotInitializedError, t as UserNotAuthorizedError } from "../errors-B1vVXc1T.mjs";
2
+ import { t as AuthContext } from "../auth-context-6Li1JkIq.mjs";
3
3
  export { AuthContext, ContextNotInitializedError, UserNotAuthenticatedError, UserNotAuthorizedError };
@@ -1,3 +1,3 @@
1
- import { a as InternalDatabaseEventContext, i as InferConnectionSchema, n as DefaultConnectionName, o as StratalDatabase, r as InferAnySchema, t as ConnectionName } from "../types-Gjk0d2qB.mjs";
2
- import { A as InjectDB, C as fromZenStackError, D as ForeignKeyConstraintError, E as InvalidErrorCodeRangeError, F as DatabaseModule, I as DatabaseModuleConfig, M as connectionSymbol, N as DatabaseService, O as DatabaseConfigError, P as DatabaseConnectionConfig, S as ParseEvent, T as RecordNotFoundError, _ as DatabaseOperation, a as DbPushCommand, b as GetResult, c as ZenStackCommand, d as EventEmitterPlugin, f as EventEmitterPluginOptions, g as DatabaseEvents, h as DatabaseEventName, i as MigrateDeployCommand, j as DATABASE_TOKENS, k as DatabaseError, l as SchemaSwitcherPlugin, m as databaseI18n, n as MigrateResetCommand, o as DbPullCommand, p as ErrorHandlerPlugin, r as MigrateDevCommand, s as DbGenerateCommand, t as MigrateStatusCommand, u as SchemaSwitcherPluginOptions, v as EventPhase, w as UniqueConstraintError, x as ModelName, y as GetData } from "../index-B1iGBJcO.mjs";
3
- export { ConnectionName, DATABASE_TOKENS, DatabaseConfigError, DatabaseConnectionConfig, DatabaseError, DatabaseEventName, DatabaseEvents, DatabaseModule, DatabaseModuleConfig, DatabaseOperation, DatabaseService, DbGenerateCommand, DbPullCommand, DbPushCommand, DefaultConnectionName, ErrorHandlerPlugin, EventEmitterPlugin, EventEmitterPluginOptions, EventPhase, ForeignKeyConstraintError, GetData, GetResult, InferAnySchema, InferConnectionSchema, InjectDB, InternalDatabaseEventContext, InvalidErrorCodeRangeError, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, ModelName, ParseEvent, RecordNotFoundError, SchemaSwitcherPlugin, SchemaSwitcherPluginOptions, StratalDatabase, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseI18n, fromZenStackError };
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, o as InternalDatabaseEventContext, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "../types-BZlcRR2M.mjs";
2
+ import { A as DATABASE_TOKENS, C as UniqueConstraintError, D as DatabaseConfigError, E as ForeignKeyConstraintError, F as DatabaseModuleConfig, M as DatabaseService, N as DatabaseConnectionConfig, O as DatabaseError, P as DatabaseModule, S as fromZenStackError, T as InvalidErrorCodeRangeError, _ as EventPhase, a as DbPushCommand, b as ModelName, c as ZenStackCommand, d as EventEmitterPluginOptions, f as ErrorHandlerPlugin, g as DatabaseOperation, h as DatabaseEvents, i as MigrateDeployCommand, j as connectionSymbol, k as InjectDB, l as SchemaSwitcher, m as DatabaseEventName, n as MigrateResetCommand, o as DbPullCommand, p as databaseMessages, r as MigrateDevCommand, s as DbGenerateCommand, t as MigrateStatusCommand, u as EventEmitterPlugin, v as GetData, w as RecordNotFoundError, x as ParseEvent, y as GetResult } from "../index-CCDPF-1Y.mjs";
3
+ export { ConnectionName, DATABASE_TOKENS, DatabaseConfigError, DatabaseConnectionConfig, DatabaseError, DatabaseEventName, DatabaseEvents, DatabaseModule, DatabaseModuleConfig, DatabaseOperation, DatabaseService, DbGenerateCommand, DbPullCommand, DbPushCommand, DefaultConnectionName, ErrorHandlerPlugin, EventEmitterPlugin, EventEmitterPluginOptions, EventPhase, ForeignKeyConstraintError, GetData, GetResult, InferAnySchema, InferConnectionExtensions, InferConnectionSchema, InjectDB, InternalDatabaseEventContext, InvalidErrorCodeRangeError, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, ModelName, ParseEvent, RecordNotFoundError, SchemaSwitcher, StratalDatabase, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
@@ -1,9 +1,10 @@
1
- import { t as __decorate } from "../decorate-C12QolJF.mjs";
2
- import { t as __decorateMetadata } from "../decorateMetadata-rWbWGUuO.mjs";
3
- import { Module } from "stratal/module";
1
+ import { t as __decorateMetadata } from "../decorateMetadata-CqtSx3_1.mjs";
2
+ import { t as __decorate } from "../decorate-CdfCRvAc.mjs";
4
3
  import { DI_TOKENS, Scope, Transient, delay } from "stratal/di";
4
+ import { I18nModule } from "stratal/i18n";
5
+ import { Module } from "stratal/module";
5
6
  import { ApplicationError, ERROR_CODES } from "stratal/errors";
6
- import { inject } from "tsyringe";
7
+ import { inject as inject$1 } from "tsyringe";
7
8
  import { Command } from "stratal/quarry";
8
9
  import { ORMError, ORMErrorReason, ZenStackClient } from "@zenstackhq/orm";
9
10
  import { withI18n, z } from "stratal/validation";
@@ -365,30 +366,32 @@ var EventEmitterPlugin = class {
365
366
  };
366
367
  };
367
368
  //#endregion
368
- //#region src/database/plugins/schema-switcher.plugin.ts
369
+ //#region src/database/plugins/schema-switcher.ts
369
370
  /**
370
- * ZenStack runtime plugin that sets PostgreSQL search_path before each query.
371
- * Used for tenant isolation in multi-tenant applications.
371
+ * Switches the active schema on a ZenStack/Kysely database client by mutating
372
+ * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to
373
+ * generate fully-qualified table references (e.g. `"tenant_123"."User"`).
372
374
  *
373
- * @example
374
- * ```typescript
375
- * super(schema, {
376
- * dialect: new PostgresDialect({ pool }),
377
- * plugins: [
378
- * new SchemaSwitcherPlugin({ schemaName: `tenant_${tenantId}` })
379
- * ]
380
- * })
381
- * ```
375
+ * Must be called BEFORE any queries are made on the client.
376
+ *
377
+ * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are
378
+ * already resolved, so a plugin-based approach cannot set the schema prefix.
379
+ * Direct client mutation is the only supported method.
382
380
  */
383
- var SchemaSwitcherPlugin = class {
384
- id = "schema-switcher";
385
- constructor(options) {
386
- this.options = options;
381
+ var SchemaSwitcher = class {
382
+ static apply(client, schemaName) {
383
+ const c = client;
384
+ const switched = {
385
+ ...c.$schema,
386
+ provider: {
387
+ ...c.$schema.provider,
388
+ defaultSchema: schemaName
389
+ }
390
+ };
391
+ c.$schema = switched;
392
+ c.schema = switched;
393
+ return client;
387
394
  }
388
- onQuery = async ({ args, proceed, client }) => {
389
- await client.$executeRawUnsafe(`SET search_path TO "${this.options.schemaName}"`);
390
- return proceed(args);
391
- };
392
395
  };
393
396
  //#endregion
394
397
  //#region src/database/database.helpers.ts
@@ -396,7 +399,8 @@ const databaseConnectionSchema = z.object({
396
399
  name: z.string().min(1, withI18n("database.connectionNameRequired")),
397
400
  schema: z.object({}).loose(),
398
401
  dialect: z.function(),
399
- plugins: z.array(z.object({}).loose()).optional()
402
+ plugins: z.array(z.object({}).loose()).optional(),
403
+ computedFields: z.object({}).loose().optional()
400
404
  });
401
405
  z.object({
402
406
  default: z.string().min(1, withI18n("database.defaultConnectionRequired")),
@@ -416,7 +420,8 @@ function createDatabaseService(conn, eventRegistry) {
416
420
  const dialect = conn.dialect();
417
421
  super(conn.schema, {
418
422
  dialect,
419
- plugins
423
+ plugins,
424
+ computedFields: conn.computedFields
420
425
  });
421
426
  }
422
427
  };
@@ -433,6 +438,15 @@ function connectionSymbol(name) {
433
438
  return Symbol.for(`stratal:database:connection:${name}`);
434
439
  }
435
440
  //#endregion
441
+ //#region src/database/i18n/en.ts
442
+ const databaseMessages = { en: {
443
+ connectionNameRequired: "Connection name is required",
444
+ defaultConnectionRequired: "Default connection name is required",
445
+ connectionRequired: "At least one connection is required",
446
+ duplicateConnections: "Duplicate connection names found",
447
+ defaultConnectionNotFound: "Default connection not found in connections"
448
+ } };
449
+ //#endregion
436
450
  //#region src/database/database.module.ts
437
451
  var _DatabaseModule;
438
452
  let DatabaseModule = _DatabaseModule = class DatabaseModule {
@@ -470,30 +484,24 @@ let DatabaseModule = _DatabaseModule = class DatabaseModule {
470
484
  context.logger.info("DatabaseModule shutdown");
471
485
  }
472
486
  };
473
- DatabaseModule = _DatabaseModule = __decorate([Module({ providers: [
474
- DbGenerateCommand,
475
- DbPushCommand,
476
- DbPullCommand,
477
- MigrateDevCommand,
478
- MigrateDeployCommand,
479
- MigrateStatusCommand,
480
- MigrateResetCommand
481
- ] })], DatabaseModule);
487
+ DatabaseModule = _DatabaseModule = __decorate([Module({
488
+ imports: [I18nModule.registerMessages({ en: { database: databaseMessages.en } })],
489
+ providers: [
490
+ DbGenerateCommand,
491
+ DbPushCommand,
492
+ DbPullCommand,
493
+ MigrateDevCommand,
494
+ MigrateDeployCommand,
495
+ MigrateStatusCommand,
496
+ MigrateResetCommand
497
+ ]
498
+ })], DatabaseModule);
482
499
  //#endregion
483
500
  //#region src/database/decorators/inject-db.decorator.ts
484
501
  function InjectDB(name) {
485
- return inject(connectionSymbol(name));
502
+ return inject$1(connectionSymbol(name));
486
503
  }
487
504
  //#endregion
488
- //#region src/database/i18n/en.ts
489
- const databaseI18n = { database: {
490
- connectionNameRequired: "Connection name is required",
491
- defaultConnectionRequired: "Default connection name is required",
492
- connectionRequired: "At least one connection is required",
493
- duplicateConnections: "Duplicate connection names found",
494
- defaultConnectionNotFound: "Default connection not found in connections"
495
- } };
496
- //#endregion
497
- export { DATABASE_TOKENS, DatabaseConfigError, DatabaseError, DatabaseModule, DbGenerateCommand, DbPullCommand, DbPushCommand, ErrorHandlerPlugin, EventEmitterPlugin, ForeignKeyConstraintError, InjectDB, InvalidErrorCodeRangeError, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, RecordNotFoundError, SchemaSwitcherPlugin, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseI18n, fromZenStackError };
505
+ export { DATABASE_TOKENS, DatabaseConfigError, DatabaseError, DatabaseModule, DbGenerateCommand, DbPullCommand, DbPushCommand, ErrorHandlerPlugin, EventEmitterPlugin, ForeignKeyConstraintError, InjectDB, InvalidErrorCodeRangeError, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, RecordNotFoundError, SchemaSwitcher, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
498
506
 
499
507
  //# sourceMappingURL=index.mjs.map