@nestjs-kit/better-auth 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +1078 -2
  3. package/dist/admin.cjs +88 -0
  4. package/dist/admin.cjs.map +1 -0
  5. package/dist/admin.d.cts +14 -0
  6. package/dist/admin.d.cts.map +1 -0
  7. package/dist/admin.d.mts +14 -0
  8. package/dist/admin.d.mts.map +1 -0
  9. package/dist/admin.mjs +85 -0
  10. package/dist/admin.mjs.map +1 -0
  11. package/dist/api-key.cjs +164 -0
  12. package/dist/api-key.cjs.map +1 -0
  13. package/dist/api-key.d.cts +42 -0
  14. package/dist/api-key.d.cts.map +1 -0
  15. package/dist/api-key.d.mts +42 -0
  16. package/dist/api-key.d.mts.map +1 -0
  17. package/dist/api-key.mjs +160 -0
  18. package/dist/api-key.mjs.map +1 -0
  19. package/dist/auth-contracts-Brh_Qko9.d.cts +1654 -0
  20. package/dist/auth-contracts-Brh_Qko9.d.cts.map +1 -0
  21. package/dist/auth-contracts-Cz7C4_Ch.d.mts +1654 -0
  22. package/dist/auth-contracts-Cz7C4_Ch.d.mts.map +1 -0
  23. package/dist/auth-decorators-B1mFnGJ6.mjs +284 -0
  24. package/dist/auth-decorators-B1mFnGJ6.mjs.map +1 -0
  25. package/dist/auth-decorators-Cu_nxWnF.cjs +421 -0
  26. package/dist/auth-decorators-Cu_nxWnF.cjs.map +1 -0
  27. package/dist/auth-errors-C3rbTV9p.mjs +301 -0
  28. package/dist/auth-errors-C3rbTV9p.mjs.map +1 -0
  29. package/dist/auth-errors-DmiKl0L_.cjs +378 -0
  30. package/dist/auth-errors-DmiKl0L_.cjs.map +1 -0
  31. package/dist/auth-guard-CLyhAEEj.cjs +136 -0
  32. package/dist/auth-guard-CLyhAEEj.cjs.map +1 -0
  33. package/dist/auth-guard-h9QB_l11.mjs +125 -0
  34. package/dist/auth-guard-h9QB_l11.mjs.map +1 -0
  35. package/dist/auth-module-BgcY2HpA.cjs +2809 -0
  36. package/dist/auth-module-BgcY2HpA.cjs.map +1 -0
  37. package/dist/auth-module-CtHx3gRU.mjs +2708 -0
  38. package/dist/auth-module-CtHx3gRU.mjs.map +1 -0
  39. package/dist/auth-scope-interceptor-BjxAL1-S.cjs +290 -0
  40. package/dist/auth-scope-interceptor-BjxAL1-S.cjs.map +1 -0
  41. package/dist/auth-scope-interceptor-DYPn3b8M.mjs +231 -0
  42. package/dist/auth-scope-interceptor-DYPn3b8M.mjs.map +1 -0
  43. package/dist/bridge-protocol-BDw8opAG.cjs +25 -0
  44. package/dist/bridge-protocol-BDw8opAG.cjs.map +1 -0
  45. package/dist/bridge-protocol-c66aEkr5.mjs +8 -0
  46. package/dist/bridge-protocol-c66aEkr5.mjs.map +1 -0
  47. package/dist/decorate-Cr4Q7bNE.cjs +319 -0
  48. package/dist/decorate-Cr4Q7bNE.cjs.map +1 -0
  49. package/dist/decorate-Dt0e9K6R.mjs +80 -0
  50. package/dist/decorate-Dt0e9K6R.mjs.map +1 -0
  51. package/dist/express.cjs +256 -0
  52. package/dist/express.cjs.map +1 -0
  53. package/dist/express.d.cts +37 -0
  54. package/dist/express.d.cts.map +1 -0
  55. package/dist/express.d.mts +37 -0
  56. package/dist/express.d.mts.map +1 -0
  57. package/dist/express.mjs +254 -0
  58. package/dist/express.mjs.map +1 -0
  59. package/dist/fastify.cjs +218 -0
  60. package/dist/fastify.cjs.map +1 -0
  61. package/dist/fastify.d.cts +28 -0
  62. package/dist/fastify.d.cts.map +1 -0
  63. package/dist/fastify.d.mts +28 -0
  64. package/dist/fastify.d.mts.map +1 -0
  65. package/dist/fastify.mjs +216 -0
  66. package/dist/fastify.mjs.map +1 -0
  67. package/dist/graphql.cjs +509 -0
  68. package/dist/graphql.cjs.map +1 -0
  69. package/dist/graphql.d.cts +38 -0
  70. package/dist/graphql.d.cts.map +1 -0
  71. package/dist/graphql.d.mts +38 -0
  72. package/dist/graphql.d.mts.map +1 -0
  73. package/dist/graphql.mjs +503 -0
  74. package/dist/graphql.mjs.map +1 -0
  75. package/dist/index.cjs +86 -0
  76. package/dist/index.d.cts +2 -0
  77. package/dist/index.d.mts +2 -0
  78. package/dist/index.mjs +8 -0
  79. package/dist/microservices.cjs +231 -0
  80. package/dist/microservices.cjs.map +1 -0
  81. package/dist/microservices.d.cts +39 -0
  82. package/dist/microservices.d.cts.map +1 -0
  83. package/dist/microservices.d.mts +39 -0
  84. package/dist/microservices.d.mts.map +1 -0
  85. package/dist/microservices.mjs +222 -0
  86. package/dist/microservices.mjs.map +1 -0
  87. package/dist/organization.cjs +191 -0
  88. package/dist/organization.cjs.map +1 -0
  89. package/dist/organization.d.cts +40 -0
  90. package/dist/organization.d.cts.map +1 -0
  91. package/dist/organization.d.mts +40 -0
  92. package/dist/organization.d.mts.map +1 -0
  93. package/dist/organization.mjs +177 -0
  94. package/dist/organization.mjs.map +1 -0
  95. package/dist/platform-CZl7TY8j.mjs +414 -0
  96. package/dist/platform-CZl7TY8j.mjs.map +1 -0
  97. package/dist/platform-YICqqYv6.cjs +473 -0
  98. package/dist/platform-YICqqYv6.cjs.map +1 -0
  99. package/dist/platform.cjs +10 -0
  100. package/dist/platform.d.cts +43 -0
  101. package/dist/platform.d.cts.map +1 -0
  102. package/dist/platform.d.mts +43 -0
  103. package/dist/platform.d.mts.map +1 -0
  104. package/dist/platform.mjs +1 -0
  105. package/dist/plugin.cjs +323 -0
  106. package/dist/plugin.cjs.map +1 -0
  107. package/dist/plugin.d.cts +24 -0
  108. package/dist/plugin.d.cts.map +1 -0
  109. package/dist/plugin.d.mts +24 -0
  110. package/dist/plugin.d.mts.map +1 -0
  111. package/dist/plugin.mjs +321 -0
  112. package/dist/plugin.mjs.map +1 -0
  113. package/dist/principal-readings-Chmt6rQV.cjs +208 -0
  114. package/dist/principal-readings-Chmt6rQV.cjs.map +1 -0
  115. package/dist/principal-readings-Ied2staw.mjs +191 -0
  116. package/dist/principal-readings-Ied2staw.mjs.map +1 -0
  117. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  118. package/dist/testing/conformance.cjs +6214 -0
  119. package/dist/testing/conformance.cjs.map +1 -0
  120. package/dist/testing/conformance.d.cts +528 -0
  121. package/dist/testing/conformance.d.cts.map +1 -0
  122. package/dist/testing/conformance.d.mts +528 -0
  123. package/dist/testing/conformance.d.mts.map +1 -0
  124. package/dist/testing/conformance.mjs +6204 -0
  125. package/dist/testing/conformance.mjs.map +1 -0
  126. package/dist/testing.cjs +221 -0
  127. package/dist/testing.cjs.map +1 -0
  128. package/dist/testing.d.cts +64 -0
  129. package/dist/testing.d.cts.map +1 -0
  130. package/dist/testing.d.mts +64 -0
  131. package/dist/testing.d.mts.map +1 -0
  132. package/dist/testing.mjs +214 -0
  133. package/dist/testing.mjs.map +1 -0
  134. package/dist/websockets.cjs +396 -0
  135. package/dist/websockets.cjs.map +1 -0
  136. package/dist/websockets.d.cts +67 -0
  137. package/dist/websockets.d.cts.map +1 -0
  138. package/dist/websockets.d.mts +67 -0
  139. package/dist/websockets.d.mts.map +1 -0
  140. package/dist/websockets.mjs +383 -0
  141. package/dist/websockets.mjs.map +1 -0
  142. package/package.json +327 -3
@@ -0,0 +1,1654 @@
1
+ import { IncomingHttpHeaders, IncomingMessage } from "node:http";
2
+ import { CallHandler, CanActivate, DynamicModule, ExecutionContext, InjectionToken, IntrinsicException, Logger, LoggerService, ModuleMetadata, NestInterceptor, OptionalFactoryDependency, Provider, Type } from "@nestjs/common";
3
+ import { AbstractHttpAdapter, DiscoveryService, HttpAdapterHost, ModuleRef, Reflector } from "@nestjs/core";
4
+ import { HookEndpointContext } from "better-auth";
5
+ import { Observable } from "rxjs";
6
+ //#region src/auth-errors.d.ts
7
+ type AuthErrorCode = "UNAUTHENTICATED" | "FORBIDDEN" | "RATE_LIMITED";
8
+ /** A transport-neutral denial. Only transports make it throwable. */
9
+ interface AuthFailure {
10
+ readonly status: 401 | 403 | 429;
11
+ readonly code: AuthErrorCode;
12
+ readonly reason?: string;
13
+ readonly message: string;
14
+ readonly challenge?: string;
15
+ readonly headers?: Headers;
16
+ }
17
+ interface AuthErrorBody {
18
+ statusCode: 401 | 403 | 429;
19
+ error: string;
20
+ code: AuthErrorCode;
21
+ reason?: string;
22
+ message: string;
23
+ }
24
+ interface AuthGraphqlExtensions {
25
+ code: AuthErrorCode | "INTERNAL_SERVER_ERROR";
26
+ reason?: string;
27
+ statusCode: number;
28
+ }
29
+ interface AuthTransportErrorPayload {
30
+ status: "error";
31
+ statusCode: number;
32
+ code: AuthErrorCode;
33
+ reason?: string;
34
+ message: string;
35
+ }
36
+ interface ErrorMappingOptions {
37
+ map?: (failure: AuthFailure, context: ExecutionContext, transport: string) => unknown;
38
+ exposeRawCause?: boolean;
39
+ }
40
+ declare function isAuthFailure(value: unknown): value is AuthFailure;
41
+ declare function isInfrastructureError(value: unknown): value is BetterAuthInfrastructureError;
42
+ declare function isConfigurationError(value: unknown): value is BetterAuthConfigurationError;
43
+ declare function rejection(input: {
44
+ status: 401 | 403 | 429;
45
+ reason?: string;
46
+ message?: string;
47
+ challenge?: string;
48
+ retryAfterSeconds?: number;
49
+ }): AuthFailure;
50
+ declare const AuthFailures: {
51
+ unauthenticated(message?: string): AuthFailure;
52
+ rejected: typeof rejection;
53
+ forbidden(reason: string, message?: string): AuthFailure;
54
+ fromAPIError(error: unknown): AuthFailure | null;
55
+ };
56
+ declare class BetterAuthConfigurationError extends Error {
57
+ readonly code: string;
58
+ readonly detail: string;
59
+ readonly site?: string;
60
+ readonly hint?: string;
61
+ readonly phase: "boot" | "request";
62
+ readonly extensions: {
63
+ readonly code: "INTERNAL_SERVER_ERROR";
64
+ readonly reason: "AUTH_MISCONFIGURED";
65
+ readonly statusCode: 500;
66
+ };
67
+ readonly issues?: readonly BetterAuthConfigurationError[];
68
+ constructor(code: string, detail: string, hint?: string);
69
+ static atRequest(code: string, detail: string, options?: {
70
+ site?: string;
71
+ hint?: string;
72
+ }): BetterAuthConfigurationError;
73
+ }
74
+ declare class BetterAuthInfrastructureError extends Error {
75
+ readonly cause: Error;
76
+ readonly extensions: {
77
+ readonly code: "INTERNAL_SERVER_ERROR";
78
+ readonly reason: "AUTH_UNAVAILABLE";
79
+ readonly statusCode: 500;
80
+ };
81
+ constructor(cause: unknown, options?: {
82
+ secrets?: readonly string[];
83
+ });
84
+ }
85
+ declare function getRawCause(error: BetterAuthInfrastructureError): unknown;
86
+ //#endregion
87
+ //#region src/auth-tokens.d.ts
88
+ declare function getBetterAuthInstanceToken(alias?: string): string;
89
+ declare function getBetterAuthOptionsToken(alias?: string): string;
90
+ declare function getBetterAuthServiceToken(alias?: string): string;
91
+ declare function getBetterAuthHandleToken(alias?: string): string;
92
+ declare const GUARD_CORE: unique symbol;
93
+ declare const SCOPE_CORE: unique symbol;
94
+ declare const PRINCIPAL_RESOLVER: unique symbol;
95
+ declare const POLICY_INVOKER: unique symbol;
96
+ declare const ACCESS_METADATA: unique symbol;
97
+ declare const ACCEPT_PRINCIPALS_METADATA: unique symbol;
98
+ declare const SKIP_DEFAULT_REQUIREMENTS_METADATA: unique symbol;
99
+ declare const AUTH_INSTANCE_METADATA: unique symbol;
100
+ declare const FORWARD_COOKIES_METADATA: unique symbol;
101
+ declare const SKIP_ORIGIN_CHECK_METADATA: unique symbol;
102
+ declare const AUTH_ENHANCER: unique symbol;
103
+ /** Connection-time WebSocket authentication, provided by the socket.io and ws transport definitions. */
104
+ declare const WS_CONNECTION_AUTH: unique symbol;
105
+ //#endregion
106
+ //#region src/auth-module.d.ts
107
+ declare class BetterAuthModule {
108
+ static forRoot<A extends AuthLike = RegisteredAuth>(options: BetterAuthModuleOptions<A> & BetterAuthAppOptions & {
109
+ name?: "default";
110
+ } & DefaultInstanceCheck<A>): DynamicModule;
111
+ static forRoot<A extends AuthLike>(options: BetterAuthModuleOptions<A> & {
112
+ name: string;
113
+ } & NoAppOptions): DynamicModule;
114
+ static forRootAsync<A extends AuthLike = RegisteredAuth>(options: BetterAuthModuleAsyncOptions<A> & BetterAuthAppOptions & {
115
+ name?: "default";
116
+ }): DynamicModule;
117
+ static forRootAsync<A extends AuthLike>(options: BetterAuthModuleAsyncOptions<A> & {
118
+ name: string;
119
+ } & NoAppOptions): DynamicModule;
120
+ }
121
+ //#endregion
122
+ //#region src/bridge-protocol.d.ts
123
+ declare const EXTENSION_DEFINITION: unique symbol;
124
+ declare const UPGRADE_REQUEST: unique symbol;
125
+ interface BridgeHandle {
126
+ readonly protocol: 4;
127
+ readonly clientIpHeader: string | null;
128
+ /** 'unbound' until the first bind(); 'bound' while an application's hooks are registered; 'closed' after its shutdown, hooks still registered. */
129
+ readonly state: "unbound" | "bound" | "closed";
130
+ /** Endpoint dispatches and database-hook dispatches (writes) seen before the first binding, reset by every bind() (B26). */
131
+ readonly unboundDispatches: number;
132
+ /**
133
+ * Registers the application's hooks and returns close(), which the kernel calls in onApplicationShutdown: a closed binding keeps
134
+ * dispatching until another application binds (§10.6). A second bind() from the same owner for the same instance is a no-op that
135
+ * returns the existing registration (NestMicroservice.init() runs lifecycle hooks twice); from the same owner for another instance it
136
+ * throws PLUGIN_SHARED_BETWEEN_INSTANCES (B03). Throws INSTANCE_ALREADY_BOUND if another bootstrapped application is bound; takes over
137
+ * a never-bootstrapped one (reported, B06) and a closed one (silently). A never-bootstrapped binding it takes over becomes
138
+ * 'displaced': an application whose init() was still running when another bound fails in onApplicationBootstrap (B06).
139
+ */
140
+ bind(binding: BridgeBinding): {
141
+ close(): void;
142
+ tookOverFrom?: string;
143
+ };
144
+ /** true when an endpoint returned `value` (an object) in a non-router dispatch observed while bound; false for before-hook short-circuits (LEAD-V37). */
145
+ producedByEndpoint(value: unknown): boolean;
146
+ }
147
+ interface BridgeBinding {
148
+ /** The application: compared by identity for the same-owner no-op; `description` goes into the B06 message. */
149
+ readonly owner: {
150
+ readonly description: string;
151
+ };
152
+ /** The instance this plugin object serves; one plugin object serves one instance (B03). */
153
+ readonly instance: string;
154
+ /**
155
+ * 'initialized' at bind; the kernel sets 'bootstrapped' in onApplicationBootstrap; close() sets 'closed'; bind() of another
156
+ * application sets 'displaced' on an 'initialized' binding it takes over.
157
+ */
158
+ state: "initialized" | "bootstrapped" | "closed" | "displaced";
159
+ /** View of the kernel's RequestScope; the plugin never owns an AsyncLocalStorage. [C] */
160
+ current(): ScopeView | undefined;
161
+ /** Credential header names declared by the instance's principal sources (credential matching, §7.5). */
162
+ readonly credentialHeaders: readonly string[];
163
+ readonly before: readonly CompiledHook[];
164
+ readonly after: readonly CompiledHook[];
165
+ readonly database: Readonly<{ [E in DatabaseHookTarget]: {
166
+ readonly before: readonly DatabaseHookMethod<E, "before">[];
167
+ readonly after: readonly DatabaseHookMethod<E, "after">[];
168
+ }; }>;
169
+ /** Debug log when the bridge drops a direct call's cookies because its credential is foreign. */
170
+ onDropped(path: string): void;
171
+ }
172
+ interface ScopeView {
173
+ /** Cookie-capable forwarding handlers have already passed the enforcing form check on every method/operation (§7.10). */
174
+ readonly cookies: CookieSink | null;
175
+ readonly forward: "none" | "same-credential" | "any";
176
+ readonly inbound: (() => Headers) | undefined;
177
+ readonly internal: boolean;
178
+ /**
179
+ * Present in EVERY handler scope whose transport exposes a browser leg, unless the plan's origin check is 'off' — enforcing or not,
180
+ * so safe methods and GraphQL queries are covered too (§7.5). Returns when this leg has a passing origin verdict for `instance`, or
181
+ * the single kernel disable predicate (§7.10 item 4) holds; a guard run without a passing verdict is insufficient. Otherwise throws the kernel's
182
+ * BetterAuthConfigurationError PUBLIC_HANDLER_USED_CALLER_SESSION.
183
+ */
184
+ readonly checkCallerSession?: (path: string, instance: string) => void;
185
+ /** The browser leg's OWN headers (TransportCall.browser.headers()), before any transport credential mapping replaced them; undefined
186
+ * when the transport exposes no browser leg. Entry 2 recognizes an ambiently carried session cookie with it. */
187
+ readonly browserHeaders: (() => Headers) | undefined;
188
+ }
189
+ interface CompiledHook {
190
+ /**
191
+ * Evaluate the user predicate once and let it throw. The dispatcher logs and
192
+ * converts before-hook matcher failures to APIError 500; after-hook matcher
193
+ * failures cross the next fixed SDK matcher boundary unchanged, so the SDK
194
+ * cannot recover them as handler APIErrors. Never swallow a failure as a
195
+ * non-match or evaluate the predicate again in run().
196
+ */
197
+ matches(ctx: HookEndpointContext, scope: ScopeView | undefined): boolean;
198
+ run(ctx: HookEndpointContext): Promise<unknown>;
199
+ }
200
+ //#endregion
201
+ //#region src/request-scope.d.ts
202
+ interface ScopeState {
203
+ readonly view: ScopeView;
204
+ readonly call?: TransportCall;
205
+ readonly plan?: RoutePlan;
206
+ readonly reading?: () => PrincipalReading;
207
+ }
208
+ /** A requirement's decision and the invocation values of the policy runs that produced an allow. */
209
+ interface DecisionOutcome {
210
+ readonly decision: AuthorizationDecision;
211
+ readonly published: readonly ReadonlyMap<symbol, unknown>[];
212
+ }
213
+ interface RequestState {
214
+ readonly principal: Map<string, Promise<PrincipalResult>>;
215
+ readonly policyIo: Map<string, Promise<unknown>>;
216
+ readonly decisions: Map<string, Promise<DecisionOutcome>>;
217
+ readonly values: Map<symbol, unknown>;
218
+ readonly authorizationCalls: Set<string>;
219
+ readonly origins: Map<string, Promise<AuthFailure | null>>;
220
+ readonly surfaced: Set<string>;
221
+ readonly connections: Map<string, {
222
+ value: Extract<PrincipalResult, {
223
+ outcome: "authenticated" | "absent";
224
+ }>;
225
+ expiresAt: number;
226
+ }>;
227
+ }
228
+ declare class RequestScope {
229
+ #private;
230
+ run<T>(state: ScopeState, fn: () => T): T;
231
+ exit<T>(fn: () => T): T;
232
+ current(): ScopeState | undefined;
233
+ stateFor(key: object): RequestState;
234
+ memoPrincipal(call: Pick<TransportCall, "key" | "connection" | "principalTtlMs">, input: {
235
+ instance: string;
236
+ freshness: "default" | "authoritative";
237
+ sourceSet: string;
238
+ }, compute: () => Promise<PrincipalResult>): Promise<PrincipalResult>;
239
+ memoPolicyIo<T>(request: object, instance: string, concreteKey: string, compute: () => Promise<T>, limit?: number | false): Promise<T>;
240
+ memoDecision(invocation: object, instance: string, concreteKey: string, compute: () => Promise<DecisionOutcome>): Promise<DecisionOutcome>;
241
+ valueKey(instance: string, key: symbol): symbol;
242
+ surfaced(key: object, error: object, context: {
243
+ instance: string;
244
+ site: string;
245
+ lineage?: InvocationLineage;
246
+ }): boolean;
247
+ }
248
+ //#endregion
249
+ //#region src/origin-check.d.ts
250
+ /** Bounded, per-application diagnostics; the next denial rolls the window without timers. */
251
+ declare class OriginDiagnostics {
252
+ #private;
253
+ private readonly logger;
254
+ constructor(logger: {
255
+ warn(message: string): void;
256
+ debug(message: string): void;
257
+ });
258
+ record(instance: string, failure: AuthFailure, headers: Headers, trustedCount: number): void;
259
+ advisoryFailure(instance: string, headers: Headers, trustedCount: number): void;
260
+ }
261
+ interface OriginCheckInit {
262
+ readonly instance: string;
263
+ readonly context: AuthContextView;
264
+ readonly options?: OriginCheckOptions;
265
+ readonly diagnostics?: OriginDiagnostics;
266
+ readonly credentialHeaders?: readonly string[];
267
+ readonly exposeRawCause?: boolean;
268
+ }
269
+ declare class OriginCheck {
270
+ #private;
271
+ private readonly scope;
272
+ private readonly init;
273
+ constructor(scope: RequestScope, init: OriginCheckInit);
274
+ check(browser: BrowserExposure, mode: "cookie" | "form"): Promise<AuthFailure | null>;
275
+ advisory(browser: BrowserExposure): Promise<void>;
276
+ assertCallerSession(browser: BrowserExposure, path: string, instance: string): void;
277
+ private calculationFailures;
278
+ private verdict;
279
+ }
280
+ //#endregion
281
+ //#region src/instance-registry.d.ts
282
+ interface InstanceEntry {
283
+ readonly name: string;
284
+ readonly instance: AuthLike;
285
+ readonly options: BetterAuthRuntimeOptions<AuthLike>;
286
+ readonly staticOptions: BetterAuthStaticOptions;
287
+ readonly sources: readonly PrincipalSource[];
288
+ readonly handle: AuthHandle;
289
+ readonly context: AuthContextView;
290
+ readonly origin: OriginCheck;
291
+ readonly bridge: BridgeHandle;
292
+ readonly credentialHeaders: readonly string[];
293
+ }
294
+ interface InstanceLookup {
295
+ get(name: string): InstanceEntry;
296
+ list(): readonly InstanceEntry[];
297
+ }
298
+ interface InstanceRegistration {
299
+ readonly name: string;
300
+ readonly instance: AuthLike;
301
+ readonly options: BetterAuthRuntimeOptions<AuthLike>;
302
+ readonly staticOptions: BetterAuthStaticOptions;
303
+ readonly appOptions: BetterAuthAppOptions;
304
+ readonly sources: readonly PrincipalSource[];
305
+ readonly handle: AuthHandle;
306
+ }
307
+ declare class InstanceRegistry implements InstanceLookup {
308
+ #private;
309
+ private readonly scope;
310
+ readonly logger: Logger;
311
+ readonly diagnostics: OriginDiagnostics;
312
+ state: "new" | "initialized" | "bootstrapped";
313
+ adapter: AbstractHttpAdapter | null;
314
+ owner: {
315
+ description: string;
316
+ };
317
+ constructor(scope: RequestScope);
318
+ register(options: BetterAuthRuntimeOptions<AuthLike>, staticOptions: BetterAuthStaticOptions, sources: readonly PrincipalSource[], appOptions: BetterAuthAppOptions): InstanceRegistration;
319
+ registrations(): readonly InstanceRegistration[];
320
+ get(name: string): InstanceEntry;
321
+ list(): readonly InstanceEntry[];
322
+ begin(adapter: AbstractHttpAdapter | null): boolean;
323
+ initialize(issues: BetterAuthConfigurationError[]): Promise<void>;
324
+ reset(): void;
325
+ }
326
+ //#endregion
327
+ //#region src/mount-coordinator.d.ts
328
+ declare class MountCoordinator {
329
+ #private;
330
+ readonly host: HttpAdapterHost;
331
+ private readonly registry;
332
+ readonly logger: Logger;
333
+ constructor(host: HttpAdapterHost, registry: InstanceRegistry, scope: RequestScope);
334
+ registerPlatforms(platforms: readonly HttpPlatform[]): void;
335
+ get platform(): HttpPlatform | undefined;
336
+ get adapter(): AbstractHttpAdapter | null;
337
+ private tryPrepare;
338
+ prepareAtInit(issues: BetterAuthConfigurationError[]): void;
339
+ resolve(issues: BetterAuthConfigurationError[]): void;
340
+ route(pathname: string): AuthRouteBinding | undefined;
341
+ binding(name: string): AuthRouteBinding | undefined;
342
+ bindings(): readonly AuthRouteBinding[];
343
+ normalizeApplicationRoute(path: string): string;
344
+ registerApplicationRoutes(routes: readonly ApplicationRouteDescriptor[], issues: BetterAuthConfigurationError[]): void;
345
+ mount(): Promise<void>;
346
+ reset(): void;
347
+ }
348
+ //#endregion
349
+ //#region src/principal-readings.d.ts
350
+ interface ReadingInput {
351
+ readonly args: readonly unknown[];
352
+ readonly call?: TransportCall;
353
+ readonly lineage?: InvocationLineage;
354
+ readonly plan?: Pick<RoutePlan, "access" | "instance" | "site">;
355
+ readonly beforeGuard?: boolean;
356
+ readonly site?: string;
357
+ }
358
+ /** The intrinsic twin suppresses Nest's repeated log, while retaining the safe wire shape. */
359
+ declare class RepeatedReaderError extends IntrinsicException {
360
+ constructor(error: BetterAuthConfigurationError);
361
+ }
362
+ declare class PrincipalReadings {
363
+ private readonly scope;
364
+ constructor(scope: RequestScope);
365
+ record(call: Pick<TransportCall, "invocation" | "lineage">, reading: PrincipalReading): void;
366
+ recordLineage(lineage: InvocationLineage, reading: PrincipalReading): void;
367
+ enclosing(lineage: InvocationLineage, args: readonly unknown[]): PrincipalReading | undefined;
368
+ read(input: ReadingInput): PrincipalReading;
369
+ current(): PrincipalReading;
370
+ project<T>(reading: PrincipalReading, spec: {
371
+ kind?: string;
372
+ reason?: string;
373
+ site: string;
374
+ project: (principal: AuthPrincipal) => T;
375
+ }, input?: ReadingInput): T | null;
376
+ deliver(error: BetterAuthConfigurationError, input?: ReadingInput, instance?: string): BetterAuthConfigurationError | RepeatedReaderError;
377
+ stamp(args: readonly unknown[], instance: string, result: PrincipalResult): void;
378
+ }
379
+ //#endregion
380
+ //#region src/policy-resolver.d.ts
381
+ declare class PolicyResolver {
382
+ private readonly moduleRef;
383
+ private readonly cache;
384
+ constructor(moduleRef: ModuleRef);
385
+ resolve<P>(reference: PolicyRef<P>): AuthorizationPolicy<P, any>;
386
+ }
387
+ //#endregion
388
+ //#region src/transport-registry.d.ts
389
+ declare class TransportRegistry {
390
+ private transports;
391
+ private readonly owners;
392
+ private kit;
393
+ register(transports: readonly AuthTransport[]): void;
394
+ setKit(kit: TransportKit): void;
395
+ list(): readonly AuthTransport[];
396
+ defaultAccessFor(target: Function, method: string): "inherit" | undefined;
397
+ find(context: ExecutionContext): AuthTransport | undefined;
398
+ select(context: ExecutionContext): AuthTransport;
399
+ idOf(call: TransportCall): string;
400
+ describe(context: ExecutionContext, transport?: AuthTransport): TransportCall;
401
+ }
402
+ //#endregion
403
+ //#region src/route-planner.d.ts
404
+ declare class RoutePlanner {
405
+ private readonly instances;
406
+ private readonly policies;
407
+ private readonly transports;
408
+ private readonly cache;
409
+ constructor(instances: InstanceLookup, policies: PolicyResolver, transports: TransportRegistry);
410
+ private prepare;
411
+ requirementsOf(target: Type, method: string): readonly RequirementExpr[];
412
+ forContext(context: ExecutionContext): RoutePlan;
413
+ plan(target: Type, method: string): RoutePlan;
414
+ }
415
+ //#endregion
416
+ //#region src/auth-service.d.ts
417
+ declare class BetterAuthService<A extends AuthLike = RegisteredAuth> {
418
+ #private;
419
+ private readonly registry;
420
+ private readonly scope;
421
+ private readonly readings;
422
+ private readonly planner;
423
+ private readonly transports;
424
+ private readonly coordinator;
425
+ readonly name: string;
426
+ readonly instance: A;
427
+ readonly api: A["api"];
428
+ constructor(options: BetterAuthRuntimeOptions<A>, registry: InstanceRegistry, scope: RequestScope, readings: PrincipalReadings, planner: RoutePlanner, transports: TransportRegistry, coordinator: MountCoordinator);
429
+ context(): Promise<Awaited<A["$context"]>>;
430
+ mount(): {
431
+ readonly basePath: string;
432
+ readonly bodyLimit: number;
433
+ readonly platform: string;
434
+ } | undefined;
435
+ getPrincipal(): Promise<AuthPrincipal | null>;
436
+ getSession(): Promise<SessionOf<A> | null>;
437
+ principalFor(context: ExecutionContext): PrincipalReading;
438
+ headersFrom(platformRequest: unknown): Headers;
439
+ forwardForeignCookies<T>(fn: () => Promise<T>): Promise<T>;
440
+ withoutCookies<T>(fn: () => Promise<T>): Promise<T>;
441
+ runOutsideScope<T>(fn: () => Promise<T>): Promise<T>;
442
+ }
443
+ //#endregion
444
+ //#region src/authorization-evaluator.d.ts
445
+ declare const allow: () => AuthorizationDecision;
446
+ declare const deny: (input: {
447
+ reason: string;
448
+ status?: 401 | 403;
449
+ message?: string;
450
+ challenge?: string;
451
+ }) => AuthorizationDecision;
452
+ declare function definePolicy<P, K extends PrincipalKind = PrincipalKind>(policy: AuthorizationPolicy<P, PrincipalOfKind<K>>): (params: P, options?: RequirementOptions) => Requirement<P>;
453
+ declare class AuthorizationEvaluator {
454
+ private readonly policies;
455
+ private readonly invoker;
456
+ private readonly resolver;
457
+ private readonly scope;
458
+ private readonly keys;
459
+ constructor(policies: PolicyResolver, invoker: PolicyInvoker, resolver: PrincipalResolver, scope: RequestScope);
460
+ evaluate(plan: RoutePlan, principal: AuthPrincipal, call: TransportCall, entry: InstanceEntry, execution: ExecutionContext, transport: string): Promise<AuthorizationDecision>;
461
+ }
462
+ //#endregion
463
+ //#region src/auth-guard.d.ts
464
+ declare class BetterAuthGuard implements CanActivate {
465
+ private readonly core;
466
+ static readonly [AUTH_ENHANCER] = "guard";
467
+ readonly [AUTH_ENHANCER] = "guard";
468
+ constructor(core: Pick<GuardCore, "canActivate">);
469
+ canActivate(context: ExecutionContext): Promise<boolean>;
470
+ }
471
+ declare class GuardCore implements CanActivate {
472
+ private readonly instances;
473
+ private readonly planner;
474
+ private readonly transports;
475
+ private readonly resolver;
476
+ private readonly evaluator;
477
+ private readonly scope;
478
+ private readonly readings;
479
+ constructor(instances: InstanceLookup, planner: RoutePlanner, transports: TransportRegistry, resolver: PrincipalResolver, evaluator: AuthorizationEvaluator, scope: RequestScope, readings: PrincipalReadings);
480
+ canActivate(context: ExecutionContext): Promise<boolean>;
481
+ }
482
+ //#endregion
483
+ //#region src/auth-scope-interceptor.d.ts
484
+ declare class BetterAuthScopeInterceptor implements NestInterceptor {
485
+ private readonly core;
486
+ static readonly [AUTH_ENHANCER] = "scope";
487
+ readonly [AUTH_ENHANCER] = "scope";
488
+ constructor(core: Pick<ScopeCore, "intercept">);
489
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
490
+ }
491
+ declare class ScopeCore implements NestInterceptor {
492
+ private readonly instances;
493
+ private readonly planner;
494
+ private readonly transports;
495
+ private readonly scope;
496
+ private readonly readings;
497
+ constructor(instances: InstanceLookup, planner: RoutePlanner, transports: TransportRegistry, scope: RequestScope, readings: PrincipalReadings);
498
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
499
+ }
500
+ //#endregion
501
+ //#region src/auth-module-definition.d.ts
502
+ declare function defineExtension<T>(definition: Omit<ExtensionDefinition<T>, typeof EXTENSION_DEFINITION>): ExtensionDefinition<T>;
503
+ declare function defineHttpPlatform<P extends HttpPlatform>(platform: P): P;
504
+ declare function defineTransport<T extends AuthTransport>(transport: T): T;
505
+ //#endregion
506
+ //#region src/auth-decorators.d.ts
507
+ declare const Public: () => import("@nestjs/common").CustomDecorator<typeof ACCESS_METADATA>;
508
+ declare const OptionalAuth: () => import("@nestjs/common").CustomDecorator<typeof ACCESS_METADATA>;
509
+ declare const RequireAuth: (options?: {
510
+ authoritative?: boolean;
511
+ }) => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
512
+ declare const UseAuthInstance: (name: string) => import("@nestjs/common").CustomDecorator<typeof AUTH_INSTANCE_METADATA>;
513
+ declare const AcceptPrincipals: (...kinds: readonly [PrincipalKind, ...PrincipalKind[]]) => import("@nestjs/common").CustomDecorator<typeof ACCEPT_PRINCIPALS_METADATA>;
514
+ declare const SkipDefaultRequirements: () => import("@nestjs/common").CustomDecorator<typeof SKIP_DEFAULT_REQUIREMENTS_METADATA>;
515
+ declare const ForwardAuthCookies: (enabled?: boolean) => import("@nestjs/common").CustomDecorator<typeof FORWARD_COOKIES_METADATA>;
516
+ declare const SkipOriginCheck: () => import("@nestjs/common").CustomDecorator<typeof SKIP_ORIGIN_CHECK_METADATA>;
517
+ declare const UseBetterAuth: () => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
518
+ declare function Require(...requirements: readonly RequirementExpr[]): ClassDecorator & MethodDecorator;
519
+ declare const anyOf: (...requirements: readonly RequirementExpr[]) => RequirementExpr;
520
+ declare const allOf: (...requirements: readonly RequirementExpr[]) => RequirementExpr;
521
+ declare const requirement: <P>(policy: PolicyRef<P>, params: P, options?: RequirementOptions) => Requirement<P>;
522
+ declare const CurrentPrincipal: () => ParameterDecorator;
523
+ declare function definePrincipalParam<K extends PrincipalKind, R>(spec: {
524
+ readonly kind: K;
525
+ readonly reason: string;
526
+ readonly project: (principal: PrincipalOfKind<K>) => R;
527
+ }): () => ParameterDecorator;
528
+ declare function defineInvocationParam(slot: symbol, spec: {
529
+ readonly missing: string;
530
+ }): () => ParameterDecorator;
531
+ declare const BeforeAuth: <const P extends EndpointPath | (string & {})>(match?: P | readonly P[] | HookPredicate, options?: HookOptions) => HookMethodDecorator<P>;
532
+ declare const AfterAuth: <const P extends EndpointPath | (string & {})>(match?: P | readonly P[] | HookPredicate, options?: HookOptions) => HookMethodDecorator<P>;
533
+ declare const BeforeDatabase: <E extends DatabaseHookTarget>(target: E, options?: DbHookOptions) => DbHookMethodDecorator<E, "before">;
534
+ declare const AfterDatabase: <E extends DatabaseHookTarget>(target: E, options?: DbHookOptions) => DbHookMethodDecorator<E, "after">;
535
+ //#endregion
536
+ //#region src/session-principal.d.ts
537
+ declare const SESSION_PRINCIPAL_KIND: "session";
538
+ declare const CurrentSession: () => ParameterDecorator;
539
+ declare const CurrentUser: () => ParameterDecorator;
540
+ declare function sessionPrincipal<S = AuthSession>(options?: SessionPrincipalOptions<S>): PrincipalSource<SessionPrincipal<S>>;
541
+ declare const freshSession: (options?: {
542
+ maxAgeSeconds?: number;
543
+ }) => Requirement<{
544
+ maxAgeSeconds?: number;
545
+ }>;
546
+ declare const RequireFreshSession: (options?: {
547
+ maxAgeSeconds?: number;
548
+ }) => ClassDecorator & MethodDecorator;
549
+ //#endregion
550
+ //#region src/principal-resolver.d.ts
551
+ declare const definePrincipalSource: <P extends AuthPrincipalBase>(source: PrincipalSource<P>) => PrincipalSource<P>;
552
+ declare const authenticated: <P extends AuthPrincipalBase>(principal: P) => PrincipalResult<P>;
553
+ declare const absent: () => PrincipalResult<never>;
554
+ declare const rejected: (failure: AuthFailure) => PrincipalResult<never>;
555
+ //#endregion
556
+ //#region src/auth-exchange.d.ts
557
+ /** Credentialed CORS for the auth mount, using the initialized SDK trusted origins. */
558
+ declare function betterAuthCorsOrigin(auth: AuthLike, options?: CorsOriginOptions): (origin: string | undefined, cb: (err: Error | null, allow?: boolean) => void) => void;
559
+ //#endregion
560
+ //#region src/http-transport.d.ts
561
+ declare function httpTransport(): ExtensionRef<AuthTransport>;
562
+ //#endregion
563
+ //#region src/index.d.ts
564
+ /** Augment this public interface to register an optional principal kind. */
565
+ interface PrincipalKinds {
566
+ session: SessionPrincipal;
567
+ }
568
+ //#endregion
569
+ //#region src/auth-types.d.ts
570
+ /** Structural minimum; accepts any betterAuth() result incl. plugins and customSession (EXP-C2, EXP-A:types/). */
571
+ interface AuthLike {
572
+ handler(request: Request): Promise<Response>;
573
+ api: {
574
+ getSession(ctx: {
575
+ headers: Headers;
576
+ query?: {
577
+ disableCookieCache?: boolean;
578
+ disableRefresh?: boolean;
579
+ };
580
+ returnHeaders?: boolean;
581
+ }): Promise<unknown>;
582
+ };
583
+ $context: Promise<unknown>;
584
+ }
585
+ /** What getSession({ returnHeaders: true }) resolves to. `headers` is undefined when a before-hook short-circuited the call (LEAD-V37). */
586
+ interface GetSessionWithHeaders {
587
+ readonly headers?: Headers | null;
588
+ readonly response: unknown;
589
+ }
590
+ /** Augment this interface with the application auth instance and named instances. */
591
+ interface Register {}
592
+ type IsRegistered = Register extends {
593
+ auth: AuthLike;
594
+ } ? true : false;
595
+ type RegisteredAuth = Register extends {
596
+ auth: infer A extends AuthLike;
597
+ } ? A : AuthLike;
598
+ type RegisteredInstances = Register extends {
599
+ instances: infer I extends Record<string, AuthLike>;
600
+ } ? I : Record<never, never>;
601
+ type AuthOf<N extends string = "default"> = N extends "default" ? RegisteredAuth : N extends keyof RegisteredInstances ? RegisteredInstances[N] : AuthLike;
602
+ /** Unregistered fallback = better-auth's default core shape (Session/User are exported by better-auth, EXP-C11). */
603
+ type DefaultSession = {
604
+ session: import("better-auth").Session;
605
+ user: import("better-auth").User;
606
+ };
607
+ type SessionOf<A> = A extends {
608
+ $Infer: {
609
+ Session: infer S;
610
+ };
611
+ } ? NonNullable<S> : DefaultSession;
612
+ type UserOf<A> = SessionOf<A> extends {
613
+ user: infer U;
614
+ } ? U : never;
615
+ type AuthSession<N extends string = "default"> = SessionOf<AuthOf<N>>;
616
+ type AuthUser<N extends string = "default"> = UserOf<AuthOf<N>>;
617
+ type HasUserId<S> = S extends {
618
+ user: {
619
+ id: string;
620
+ };
621
+ } ? true : false;
622
+ type BodyOf<F> = F extends ((ctx: infer C) => unknown) ? [NonNullable<C>] extends [{
623
+ body?: infer B;
624
+ }] ? NonNullable<B> : never : never;
625
+ type PermissionsOf<A, K extends string> = A extends {
626
+ api: { [k in K]: infer F; };
627
+ } ? BodyOf<F> extends {
628
+ permissions?: infer P;
629
+ } ? NonNullable<P> : never : never;
630
+ type AdminPermissions<N extends string = "default"> = IsRegistered extends true ? PermissionsOf<AuthOf<N>, "userHasPermission"> : Record<string, readonly string[]>;
631
+ type OrgPermissions<N extends string = "default"> = IsRegistered extends true ? PermissionsOf<AuthOf<N>, "hasPermission"> : Record<string, readonly string[]>;
632
+ type ApiOf<A> = A extends {
633
+ api: infer Api;
634
+ } ? Api : never;
635
+ type EndpointPath<A = RegisteredAuth> = { [K in keyof ApiOf<A>]: ApiOf<A>[K] extends {
636
+ path: infer P extends string;
637
+ } ? P : never; }[keyof ApiOf<A>];
638
+ type EndpointAt<P extends string, A = RegisteredAuth> = { [K in keyof ApiOf<A>]: ApiOf<A>[K] extends {
639
+ path: P;
640
+ } ? ApiOf<A>[K] : never; }[keyof ApiOf<A>];
641
+ type BodyAt<P extends string> = [EndpointAt<P>] extends [never] ? unknown : BodyOf<EndpointAt<P>>;
642
+ /**
643
+ * The RAW body a hook sees: the endpoint's schema has not run yet (before-hooks) or ran on the same raw input (after-hooks).
644
+ * Known keys autocomplete; every value is `unknown` until the hook checks it.
645
+ */
646
+ type UnvalidatedBody<B> = [B] extends [never] ? unknown : B extends object ? { readonly [K in keyof B]?: unknown; } & Readonly<Record<string, unknown>> : unknown;
647
+ type AuthHookContext<P extends string = string> = Omit<import("better-auth").HookEndpointContext, "path" | "body"> & {
648
+ path: P;
649
+ body: string extends P ? unknown : UnvalidatedBody<BodyAt<P>>;
650
+ };
651
+ type AuthAfterHookContext<P extends string = string> = AuthHookContext<P>;
652
+ type DbHooks = NonNullable<import("better-auth").BetterAuthOptions["databaseHooks"]>;
653
+ /** The SDK's own hook signature for a target, independent of the registered instance. Internal to the dispatcher. */
654
+ type DbHookFn<E extends DatabaseHookTarget, Ph extends "before" | "after"> = E extends `${infer M extends keyof DbHooks & string}.${infer O extends "create" | "update" | "delete"}` ? NonNullable<NonNullable<NonNullable<DbHooks[M]>[O]>[Ph]> : never;
655
+ /** Best effort: the registered instance's additional user/session fields, Partial for updates; nothing when not derivable. */
656
+ type RegisteredRow<M> = M extends "user" ? UserOf<RegisteredAuth> : M extends "session" ? SessionOf<RegisteredAuth> extends {
657
+ session: infer S;
658
+ } ? S : never : never;
659
+ type ExtraFields<E extends DatabaseHookTarget> = E extends `${infer M}.${infer O}` ? [RegisteredRow<M>] extends [never] ? Record<never, never> : O extends "update" ? Partial<RegisteredRow<M>> : RegisteredRow<M> : Record<never, never>;
660
+ /** The payload better-auth passes: e.g. update.before → Partial<User> & Record<string, unknown>. */
661
+ type DatabaseHookData<E extends DatabaseHookTarget, Ph extends "before" | "after" = "before"> = Parameters<DbHookFn<E, Ph>>[0] & ExtraFields<E>;
662
+ /** What better-auth accepts back: create/update.before → boolean | void | { data }; delete.before → boolean | void; after → void. */
663
+ type DatabaseHookResult<E extends DatabaseHookTarget, Ph extends "before" | "after"> = Awaited<ReturnType<DbHookFn<E, Ph>>>;
664
+ type DatabaseHookMethod<E extends DatabaseHookTarget, Ph extends "before" | "after"> = (data: DatabaseHookData<E, Ph>, ctx: import("better-auth").GenericEndpointContext | null | undefined) => DatabaseHookResult<E, Ph> | Promise<DatabaseHookResult<E, Ph>>;
665
+ /** Guard-established invariant helper (reference #163). Prefer @ActiveOrganizationId() (§8.3). */
666
+ type WithActiveOrganization<S> = S & {
667
+ session: {
668
+ activeOrganizationId: string;
669
+ };
670
+ };
671
+ type PrincipalKind = keyof PrincipalKinds & string;
672
+ type AuthPrincipal = PrincipalKinds[PrincipalKind];
673
+ type PrincipalOfKind<K extends PrincipalKind> = PrincipalKinds[K];
674
+ interface SessionPrincipal<S = AuthSession> extends AuthPrincipalBase {
675
+ readonly kind: "session";
676
+ readonly session: S;
677
+ }
678
+ //#endregion
679
+ //#region src/auth-contracts.d.ts
680
+ interface BetterAuthAppOptions {
681
+ /** HTTP platforms. Exactly one must support the running HTTP adapter when the app has one. Default []. */
682
+ platforms?: readonly ExtensionRef<HttpPlatform>[];
683
+ /** Transports, consulted in order BEFORE the built-in httpTransport(). Default []. */
684
+ transports?: readonly ExtensionRef<AuthTransport>[];
685
+ }
686
+ type NoAppOptions = {
687
+ readonly platforms?: never;
688
+ readonly transports?: never;
689
+ };
690
+ /** Instance-shape options: they decide which providers exist, so they are fixed at definition time. [A][B][C] */
691
+ interface BetterAuthStaticOptions {
692
+ /** Instance name. Default 'default'. Named instances get their own tokens and mount (§5.5). */
693
+ name?: string;
694
+ /** Register the module as global. Default true. */
695
+ global?: boolean;
696
+ /**
697
+ * Register BetterAuthGuard as APP_GUARD (via useExisting). Default true for the default instance, false for named instances.
698
+ * Since v6 this no longer controls APP_INTERCEPTOR: see globalScope.
699
+ */
700
+ globalGuard?: boolean;
701
+ /**
702
+ * Register BetterAuthScopeInterceptor as APP_INTERCEPTOR (via useExisting). Default true for the DEFAULT instance, whatever
703
+ * globalGuard says; named instances never register it (one scope opener per app). The interceptor denies nothing: it opens the
704
+ * scope that carries the cookie sink, refresh suppression and the caller-session check (§7.5, ADR-70). Setting it false is an
705
+ * explicit opt-out, printed in the boot summary and warned about (W_NO_GLOBAL_SCOPE, §5.4 B31). [§17 Q38]
706
+ */
707
+ globalScope?: boolean;
708
+ /** Principal sources of this instance, tried in order BEFORE the built-in session source, filtered per route by the kinds it accepts (§7.6). Default []. */
709
+ principals?: readonly ExtensionRef<PrincipalSource>[];
710
+ }
711
+ /** Runtime options: read by providers after DI resolution, so they may come from useFactory. */
712
+ type BetterAuthRuntimeOptions<A extends AuthLike = RegisteredAuth> = {
713
+ /** The object returned by betterAuth(). Never wrapped, cloned or mutated. */
714
+ auth: A;
715
+ /** Access for handlers without an access decorator, unless a transport supplies the default (field resolvers inherit, §7.6). Default 'authenticated'. [C] */
716
+ defaultAccess?: "authenticated" | "public";
717
+ /**
718
+ * Requirements every 'required' plan of this instance carries, ahead of its class and method requirements: a company-wide
719
+ * rule such as orgMember(). Compiled into the plan, so boot validation, coverage, the guard and the decorators see one plan.
720
+ * Replaces v2's protected BetterAuthGuard.planFor seam (§7.6). A handler or controller opts out visibly with
721
+ * @SkipDefaultRequirements() (counted in the boot summary), and a plan whose AND-ed requirements admit no common principal kind
722
+ * fails boot (B15 UNSATISFIABLE_PRINCIPAL_KINDS). Default [].
723
+ */
724
+ defaultRequirements?: readonly RequirementExpr[];
725
+ /** HTTP mount options for this instance. */
726
+ http?: HttpMountOptions;
727
+ /** Set-Cookie forwarding for application-level direct auth.api calls (§7.5). */
728
+ cookies?: CookieForwardingOptions;
729
+ /** better-auth's origin rule on cookie-carrying app operations, and its form-CSRF rule on handlers that forward auth cookies (§7.10). */
730
+ originCheck?: OriginCheckOptions;
731
+ /** Per-instance override of transport error objects for this instance's plans, and error logging (§13.5). */
732
+ errors?: ErrorMappingOptions;
733
+ /** Bounds on the authorization work one logical request can cause (§8.1). */
734
+ limits?: AuthorizationLimits;
735
+ /** Log the one-line boot summary. Default true. */
736
+ logSummary?: boolean;
737
+ } & SessionOption<A>;
738
+ interface AuthorizationLimits {
739
+ /**
740
+ * Distinct better-auth calls that policies may make through AuthorizationContext.memo for one logical request (memo hits do
741
+ * not count). Past it, a requirement denies 429 TOO_MANY_AUTHORIZATION_CHECKS instead of calling better-auth, so N aliased
742
+ * GraphQL fields with N different organizations cannot exhaust the connection pool. false disables the bound. Default 100.
743
+ */
744
+ maxAuthorizationCallsPerRequest?: number | false;
745
+ }
746
+ interface CookieForwardingOptions {
747
+ /**
748
+ * Forward Set-Cookie produced by direct auth.api.* calls that application code makes inside a handler, when the call
749
+ * carries the caller's own credential or none. Default false, which is better-auth's server-call semantics
750
+ * (a server call never touches the browser unless you ask). Per handler: @ForwardAuthCookies(). The library's own calls
751
+ * (principal sources and policies) always forward. Handlers with forwarding on enforce better-auth's form-CSRF origin rule on EVERY
752
+ * method and operation kind, including safe cookie-free GET/query calls (§7.10), so turning this on instance-wide applies it everywhere. v3's per-call-site opt-in is gone: it forwarded
753
+ * without the form check (§7.5).
754
+ */
755
+ forwardDirectCalls?: boolean;
756
+ }
757
+ interface OriginCheckOptions {
758
+ /**
759
+ * 'cookie' (default): an unsafe operation whose browser leg carries a cookie must come from a trusted origin, whatever credential
760
+ * authenticated it, exactly as on better-auth's routes (§7.10). 'off': disabled (B19 warns in production).
761
+ */
762
+ mode?: "cookie" | "off";
763
+ /**
764
+ * A cookie-authenticated unsafe operation without Origin or Referer.
765
+ * 'reject' (default): 403 MISSING_OR_NULL_ORIGIN, exactly as better-auth's own routes answer (LEAD-V16).
766
+ * 'allow-non-browser': allowed when Origin, Referer and Sec-Fetch-Site are all absent (no browser sends such a request cross-site).
767
+ * Native clients that send the session cookie themselves (better-auth's Expo client on your own routes, SSR servers) send none of
768
+ * them; under 'reject' they need an Origin header. See §17 Q15.
769
+ */
770
+ missingOrigin?: "reject" | "allow-non-browser";
771
+ }
772
+ /** `session` is REQUIRED (with userId) when the session shape lacks user.id, i.e. customSession (§11.5). [C] */
773
+ type SessionOption<A extends AuthLike> = HasUserId<SessionOf<A>> extends true ? {
774
+ session?: SessionPrincipalOptions<SessionOf<A>> | false;
775
+ } : {
776
+ session: (SessionPrincipalOptions<SessionOf<A>> & {
777
+ userId: (s: SessionOf<A>) => string | null;
778
+ }) | false;
779
+ };
780
+ interface SessionPrincipalOptions<S> {
781
+ /** Map a (possibly customSession-shaped) session to its user id. Default s => s.user?.id ?? null. */
782
+ userId?: (session: S) => string | null;
783
+ /** Default freshness of identity reads; per route: @RequireAuth({ authoritative: true }). Default 'default'. */
784
+ freshness?: "default" | "authoritative";
785
+ /**
786
+ * Does any apiKey() configuration of this instance set enableSessionForAPIKeys? The api-key plugin object does not expose
787
+ * its configuration, so no unit can read it (§8.3.1). Unset (default): when the api-key plugin is present, the session
788
+ * unit's boot advice W_API_KEY_FULL_SESSION / W_API_KEY_SESSION_MULTIPLIER is worded conditionally. false: you assert
789
+ * that none does, and the advice is dropped. true: the advice is worded as a fact.
790
+ */
791
+ apiKeySessions?: boolean;
792
+ }
793
+ interface HttpMountOptions {
794
+ /** Mount better-auth routes on the platform. Default true. false = guard-only service. */
795
+ mount?: boolean;
796
+ /** Maximum auth-route request body. Default 1_048_576 (1 MiB). 413 above. */
797
+ bodyLimit?: number | `${number}${"b" | "kb" | "mb"}`;
798
+ /** Wrap every auth-route exchange (ORM request context, tracing). Outermost first. [A][C] */
799
+ around?: readonly AuthHandlerInterceptor[];
800
+ /** Allow a mount path of '/'. Default false (a root mount captures every unmatched route). [A] */
801
+ allowRootMount?: boolean;
802
+ /**
803
+ * Allow an unset baseURL in production (production = NODE_ENV is not development, dev or test, §5.4). better-auth then derives
804
+ * the base URL, token links and a trusted origin from each request's Host header. Default false: boot fails with UNSAFE_BASE_URL
805
+ * (B23).
806
+ */
807
+ allowRequestDerivedBaseURL?: boolean;
808
+ /** Log each auth path better-auth answers 404 for once, with a "did you mean" (capped at 100 paths). Default: true outside production. */
809
+ diagnostics?: boolean;
810
+ }
811
+ type AuthHandlerInterceptor = (call: {
812
+ readonly request: Request;
813
+ readonly platformRequest: unknown;
814
+ readonly instance: string;
815
+ }, next: (request?: Request) => Promise<Response>) => Promise<Response>;
816
+ type BetterAuthModuleOptions<A extends AuthLike> = BetterAuthStaticOptions & BetterAuthRuntimeOptions<A>;
817
+ interface BetterAuthModuleAsyncOptions<A extends AuthLike> extends BetterAuthStaticOptions {
818
+ imports?: ModuleMetadata["imports"];
819
+ inject?: readonly (InjectionToken | OptionalFactoryDependency)[];
820
+ useFactory: (...args: any[]) => BetterAuthFactoryResult<A> | Promise<BetterAuthFactoryResult<A>>;
821
+ }
822
+ /** A static or app-level key returned from useFactory is a compile error with a readable message (EXP-C9). [C] */
823
+ type BetterAuthFactoryResult<A extends AuthLike> = BetterAuthRuntimeOptions<A> & { [K in keyof (BetterAuthStaticOptions & BetterAuthAppOptions)]?: StaticOptionMustBePassedToForRootAsync<K>; };
824
+ interface StaticOptionMustBePassedToForRootAsync<K extends string> {
825
+ readonly __error: `'${K}' is a static option: pass it to forRootAsync() next to useFactory, not in its result`;
826
+ }
827
+ /** Rejects a different instance than the registered one for the default instance (EXP-C2). [C] */
828
+ type DefaultInstanceCheck<A> = IsRegistered extends true ? A extends RegisteredAuth ? unknown : {
829
+ auth: RegisteredAuth;
830
+ } : unknown;
831
+ /** A class (DI-constructed), a ready instance, or a definition that brings its own providers. */
832
+ type ExtensionRef<T> = Type<T> | T | ExtensionDefinition<T>;
833
+ interface ExtensionDefinition<T> {
834
+ readonly [EXTENSION_DEFINITION]: true;
835
+ readonly use: {
836
+ readonly useClass: Type<T>;
837
+ } | {
838
+ readonly useFactory: (...deps: any[]) => T | Promise<T>;
839
+ readonly inject?: readonly InjectionToken[];
840
+ } | {
841
+ readonly useExisting: InjectionToken<T>;
842
+ };
843
+ readonly providers?: readonly Provider[];
844
+ readonly imports?: ModuleMetadata["imports"];
845
+ /** Tokens (from `providers`) the forRoot module re-exports, globally unless `global` is false: helper services for users. */
846
+ readonly exports?: readonly InjectionToken[];
847
+ }
848
+ interface RoutePlan {
849
+ readonly instance: string;
850
+ /**
851
+ * 'inherit': a handler a transport declares as reached only through an already authorized operation (GraphQL field resolvers
852
+ * without method-level access, acceptance or requirement metadata; class-level metadata never applies to them, §7.6 step 3). The
853
+ * guard performs no principal I/O: it checks the enclosing reading and also enforces form mode when forwarding is declared; readers take the nearest
854
+ * enclosing invocation's reading (§7.8).
855
+ */
856
+ readonly access: "public" | "optional" | "required" | "inherit";
857
+ /** A transport's defaultAccessFor answered 'inherit' for the handler (a nested handler, §7.6 step 3), whatever its effective access. */
858
+ readonly nested: boolean;
859
+ /**
860
+ * defaultRequirements first (unless @SkipDefaultRequirements() applies), then class requirements (base first; not for handlers a
861
+ * transport nests), then method requirements; empty unless access is 'required'.
862
+ */
863
+ readonly requirements: readonly RequirementExpr[];
864
+ /** @SkipDefaultRequirements() removed the instance's defaultRequirements from this plan (counted in the boot summary, B22). */
865
+ readonly skipsDefaultRequirements: boolean;
866
+ /**
867
+ * The handler declares something in B16's sense (§5.4), computed with the same scoping as the plan: method-level metadata, class-level
868
+ * metadata unless a transport nests the handler, and direct-call forwarding (@ForwardAuthCookies() or cookies.forwardDirectCalls).
869
+ * The other instance-wide defaults (defaultAccess, the default kinds, defaultRequirements) never count.
870
+ */
871
+ readonly declares: boolean;
872
+ /** 'authoritative' when the route asks for it, session.freshness says so, or a requirement demands fresh identity. */
873
+ readonly freshness: "default" | "authoritative";
874
+ /** Principal kinds admitted on the route (§7.6): @AcceptPrincipals or the instance's default kinds, plus kinds requirements name. */
875
+ readonly accepts: ReadonlySet<string>;
876
+ /** Kind constraints contributed by param decorators (e.g. @CurrentSession() → { kind: 'session', reason: 'SESSION_REQUIRED' }); checked at boot (B15). */
877
+ readonly principalParams: readonly {
878
+ readonly kind: string;
879
+ readonly reason: string;
880
+ }[];
881
+ /**
882
+ * The origin check (§7.10). 'cookie': better-auth's rule when the browser leg carries a cookie. 'form': better-auth's
883
+ * form-CSRF rule, enforcing on every method/operation even without an inbound cookie (handlers declaring cookie forwarding).
884
+ * 'off': @SkipOriginCheck() or originCheck.mode 'off'.
885
+ */
886
+ readonly originCheck: "cookie" | "form" | "off";
887
+ /** Content key of the sources this plan consults (ordered source indexes, interned per instance); part of the principal memo key (§7.2). */
888
+ readonly sourceSet: string;
889
+ /** Set-Cookie of application direct calls is forwarded (§7.5). */
890
+ readonly forwardDirectCalls: boolean;
891
+ readonly site: string;
892
+ }
893
+ type PrincipalReading = (PrincipalResult | {
894
+ readonly outcome: "no-identity";
895
+ }) & {
896
+ readonly instance: string;
897
+ };
898
+ /**
899
+ * The resolver port the guard uses. Default: ChainPrincipalResolver. Readers never call it: they read the results the guard
900
+ * recorded (§7.8), so v3's peek() is gone. [B]
901
+ */
902
+ interface PrincipalResolver {
903
+ resolve(call: TransportCall, request: ResolutionRequest): Promise<PrincipalResult>;
904
+ }
905
+ interface ResolutionRequest {
906
+ readonly auth: AuthHandle;
907
+ readonly freshness: "default" | "authoritative";
908
+ /** Kinds the route accepts; sources producing none of them are not consulted (§7.6). */
909
+ readonly accepts: ReadonlySet<string>;
910
+ /** The plan's content key of those sources (RoutePlan.sourceSet). */
911
+ readonly sourceSet: string;
912
+ /**
913
+ * The re-classification read of a generic 401 a policy call met (§8.1): resolve again through the one source that produced the
914
+ * principal (its id), which must declare sessionBacked, bypassing the principal memo. The evaluator shares one such read per logical
915
+ * request (§7.2).
916
+ */
917
+ readonly reclassify?: {
918
+ readonly sourceId: string;
919
+ };
920
+ }
921
+ /** The port the evaluator calls a policy through. Default: policy.evaluate(params, context). Tests substitute it (overrideDecisions, §14.8). */
922
+ interface PolicyInvoker {
923
+ invoke<P>(policy: AuthorizationPolicy<P, any>, params: P, context: AuthorizationContext): Promise<AuthorizationDecision>;
924
+ }
925
+ interface RequirementOptions {
926
+ readonly label?: string;
927
+ /** Overrides policy.requires.principals for this requirement. */
928
+ readonly principals?: readonly PrincipalKind[];
929
+ /** Overrides policy.requires.freshIdentity for this requirement. */
930
+ readonly freshIdentity?: boolean;
931
+ }
932
+ interface HookOptions {
933
+ /** 'http' = requests routed by auth.handler; 'server' = direct auth.api calls. Default 'all'. */
934
+ calls?: "all" | "http" | "server";
935
+ /** Skip calls this library makes (guard getSession, policy checks). Default false (better-auth semantics). */
936
+ skipInternal?: boolean;
937
+ /** Ascending order among Nest hooks; ties keep discovery order. Default 0. */
938
+ order?: number;
939
+ /** Named instance the hook binds to. Default 'default'. */
940
+ instance?: string;
941
+ }
942
+ interface DbHookOptions {
943
+ order?: number;
944
+ instance?: string;
945
+ }
946
+ type HookPredicate = (ctx: AuthHookContext) => boolean;
947
+ /**
948
+ * The decorated method type M is inferred and constrained rather than fixed: TypedPropertyDescriptor is invariant, so a fixed
949
+ * descriptor type would reject sound methods that declare fewer parameters or return a narrower result.
950
+ */
951
+ type HookMethodDecorator<P extends string> = <M extends (ctx: AuthHookContext<P>) => unknown>(target: object, key: string | symbol, descriptor: TypedPropertyDescriptor<M>) => void;
952
+ /** Constrains the decorated method like HookMethodDecorator; the SDK's payload, context and result types still apply. */
953
+ type DbHookMethodDecorator<E extends DatabaseHookTarget, Ph extends "before" | "after"> = <M extends DatabaseHookMethod<E, Ph>>(target: object, key: string | symbol, descriptor: TypedPropertyDescriptor<M>) => void;
954
+ type DatabaseHookTarget = `${"user" | "session" | "account" | "verification"}.${"create" | "update" | "delete"}`;
955
+ interface CorsOriginOptions {
956
+ /** Also honor wildcard (`https://*.vercel.app`) and custom-scheme trusted origins for credentialed CORS. Default false: exact origins only. */
957
+ allowPatterns?: boolean;
958
+ }
959
+ interface ExpressPlatformOptions {
960
+ /** Client IP for better-auth's rate limiter. Default req.ip (honors Express `trust proxy`). */
961
+ clientIp?: (req: IncomingMessage & {
962
+ ip?: string;
963
+ }) => string | null;
964
+ }
965
+ interface FastifyPlatformOptions {
966
+ /** Default request.ip (honors Fastify `trustProxy`). */
967
+ clientIp?: (request: {
968
+ ip?: string;
969
+ raw: IncomingMessage;
970
+ }) => string | null;
971
+ }
972
+ interface ConformanceCase {
973
+ readonly id: string;
974
+ readonly title: string;
975
+ /** Why the case does not apply, when that is known before it runs. */
976
+ readonly skip?: string;
977
+ /**
978
+ * Runs the case. It resolves with a ConformanceSkip when the case finds at run time that it does not apply, for
979
+ * example because a unit registered through defineExtension() lacks an optional capability.
980
+ */
981
+ run(): Promise<ConformanceOutcome>;
982
+ }
983
+ /** A case that found at run time that it does not apply. */
984
+ interface ConformanceSkip {
985
+ readonly skipped: string;
986
+ }
987
+ /** What a case's run() resolves with: nothing when it passed, a ConformanceSkip when it does not apply. */
988
+ type ConformanceOutcome = void | ConformanceSkip;
989
+ interface ConformanceRunner {
990
+ describe(name: string, fn: () => void): void;
991
+ /**
992
+ * Registers a test. runConformance passes a function without declared parameters; when the runner hands it a test
993
+ * context with skip(note) (Vitest, node:test), a case that skips at run time is reported as skipped.
994
+ */
995
+ it(name: string, fn: (context?: unknown) => Promise<void>): void;
996
+ }
997
+ interface BootAdvice {
998
+ readonly level: "warn" | "info";
999
+ /** Stable code, e.g. 'W_API_KEY_SESSION_MULTIPLIER'. */
1000
+ readonly code: string;
1001
+ readonly message: string;
1002
+ readonly hint?: string;
1003
+ }
1004
+ interface BootAdviceContext {
1005
+ readonly instance: string;
1006
+ readonly auth: AuthHandle;
1007
+ /** Awaited $context (post-init options, plugins, sessionConfig, authCookies). */
1008
+ readonly context: AuthContextView;
1009
+ /** true unless NODE_ENV is 'development', 'dev' or 'test' (an unset NODE_ENV counts as production, §5.4). */
1010
+ readonly production: boolean;
1011
+ readonly hasHttpAdapter: boolean;
1012
+ /** The instance's effective origin-check options (the session unit's W_ORIGIN_CHECK_NATIVE_CLIENTS reads missingOrigin). */
1013
+ readonly originCheck: {
1014
+ readonly mode: "cookie" | "off";
1015
+ readonly missingOrigin: "reject" | "allow-non-browser";
1016
+ };
1017
+ /** Every compiled handler of this instance (controllers, resolvers, gateways, message handlers) with what transports claimed for it. */
1018
+ readonly handlers: readonly AdvisedHandler[];
1019
+ readonly transports: readonly {
1020
+ readonly id: string;
1021
+ readonly connectionTtlMs?: number;
1022
+ }[];
1023
+ readonly sources: readonly Pick<PrincipalSource, "id" | "kinds" | "acceptance" | "effects">[];
1024
+ /** Policies referenced by any plan (resolved instances, §4.4.3), with their declared requirements. */
1025
+ readonly policies: readonly Pick<AuthorizationPolicy, "id" | "requires">[];
1026
+ }
1027
+ interface AdvisedHandler {
1028
+ readonly plan: RoutePlan;
1029
+ /** Ids of the transports that claimed the handler; empty when none did. */
1030
+ readonly transports: readonly string[];
1031
+ /** Named inputs the claiming transports reported (ClaimOptions.inputs): HTTP route params, GraphQL @Args names. Empty when unknown. */
1032
+ readonly inputs: readonly string[];
1033
+ }
1034
+ interface HttpPlatform {
1035
+ /** Diagnostic id ('express', 'fastify', 'hono'…). Core prints it and never compares it. */
1036
+ readonly id: string;
1037
+ /**
1038
+ * Capabilities. http2: the conformance kit exercises HTTP/2. prepareAtInit: prepare() may also run from the core module's
1039
+ * onModuleInit, because nothing it installs has to precede Nest's init(); core then prepares a second application's adapter on the
1040
+ * same container instead of failing with APP_ADAPTER_CHANGED (§5.7).
1041
+ */
1042
+ readonly capabilities?: {
1043
+ readonly http2?: boolean;
1044
+ readonly prepareAtInit?: boolean;
1045
+ };
1046
+ /** Does this platform drive the running Nest HTTP adapter? Adapter-owned predicate. Never called with a null adapter. */
1047
+ supports(adapter: AbstractHttpAdapter): boolean;
1048
+ /**
1049
+ * Phase 1, optional, synchronous. Called once, as soon as BOTH the running adapter (HttpAdapterHost.init$) and the app's
1050
+ * platform list are known, whichever arrives last (§5.7, LEAD-EXP-3):
1051
+ * NestFactory → inside NestFactory.create(), before it resolves: before main.ts code and before init() registers parsers;
1052
+ * @nestjs/testing → inside createNestApplication(), before it returns.
1053
+ * Use it only for work that must precede body parsing (Express raw capture) or per-request bookkeeping
1054
+ * (Fastify request→reply map). Mount paths are not resolved yet: call ctx.route(pathname) per request.
1055
+ */
1056
+ prepare?(ctx: PlatformPrepareContext): void;
1057
+ /**
1058
+ * Phase 2, required. Called once per auth instance from onModuleInit, after parsers, main.ts middleware/CORS,
1059
+ * MiddlewareConsumer middleware and controller routes are registered, and before Nest's 404/error handlers.
1060
+ * Route every method for binding.basePath and binding.basePath/* to binding.handle(). May be async. Failures of
1061
+ * handle() must reach the host's error pipeline at REQUEST time (H8), even if routes are registered eagerly.
1062
+ */
1063
+ mount(ctx: PlatformMountContext): void | Promise<void>;
1064
+ /** Per-request access for transports running on this platform (HTTP controllers, GraphQL over HTTP). */
1065
+ readonly requests: HttpRequestAccessor;
1066
+ /** How the platform resolves the client IP behind proxies (boot summary, W_PROXY_* warnings; §6.8). */
1067
+ proxyTrust?(adapter: AbstractHttpAdapter): ProxyTrust;
1068
+ /** Optional boot-time checks (throw BetterAuthConfigurationError to fail fast). */
1069
+ validate?(adapter: AbstractHttpAdapter): void;
1070
+ /**
1071
+ * Native controller routes and resolved auth bindings. Called after Nest has registered routes and auth mounts are resolved,
1072
+ * before the application starts accepting requests. Platforms own route-grammar matching for controller precedence.
1073
+ */
1074
+ applicationRoutes?(routes: readonly ApplicationRouteDescriptor[], bindings: readonly AuthRouteBinding[]): void;
1075
+ /** Optional boot advice (§4). */
1076
+ advise?(ctx: BootAdviceContext): readonly BootAdvice[] | Promise<readonly BootAdvice[]>;
1077
+ }
1078
+ interface ApplicationRouteDescriptor {
1079
+ /** Nest RequestMethod name (GET, POST, ALL, ...). */
1080
+ readonly method: string;
1081
+ /** Paths resolved by Nest's RoutePathFactory, including module/global prefixes and URI versions. */
1082
+ readonly paths: readonly string[];
1083
+ /** Conditions evaluated inside the selected controller route after Express body parsing. */
1084
+ readonly conditions: readonly ("host" | "version")[];
1085
+ /** Controller and method name for boot diagnostics. */
1086
+ readonly source: string;
1087
+ }
1088
+ interface ProxyTrust {
1089
+ /**
1090
+ * 'none': the socket address is the client IP (behind a proxy, every client shares the proxy's IP);
1091
+ * 'all': every forwarded hop is trusted (clients can choose their IP); 'partial': hop count, subnet list or function;
1092
+ * 'unknown': the platform cannot tell.
1093
+ */
1094
+ readonly mode: "none" | "all" | "partial" | "unknown";
1095
+ readonly detail: string;
1096
+ }
1097
+ interface PlatformPrepareContext {
1098
+ readonly adapter: AbstractHttpAdapter;
1099
+ readonly logger: LoggerService;
1100
+ /** The binding whose base path matches this raw (undecoded, query-less) pathname; undefined before mount or if none. */
1101
+ route(pathname: string): AuthRouteBinding | undefined;
1102
+ }
1103
+ interface PlatformMountContext {
1104
+ readonly adapter: AbstractHttpAdapter;
1105
+ readonly logger: LoggerService;
1106
+ readonly binding: AuthRouteBinding;
1107
+ }
1108
+ interface AuthRouteBinding {
1109
+ readonly instance: string;
1110
+ /** Normalized mount path: no trailing slash; never '/' unless allowRootMount. */
1111
+ readonly basePath: string;
1112
+ readonly bodyLimit: number;
1113
+ /** Static baseURL origin (e.g. 'https://api.example.com'), or undefined for unset/dynamic baseURL. */
1114
+ readonly staticOrigin: string | undefined;
1115
+ /** pathname === basePath || pathname.startsWith(basePath + '/'), on the raw pathname. */
1116
+ matches(pathname: string): boolean;
1117
+ /**
1118
+ * Core exchange (§6.3). Resolves a Response for every better-auth outcome; rejects with BetterAuthInfrastructureError,
1119
+ * or with the original APIError when the instance sets onAPIError.throw (§6.10).
1120
+ */
1121
+ handle(inbound: InboundAuthRequest): Promise<Response>;
1122
+ /** Canonical 413: better-auth's error shape { code: 'PAYLOAD_TOO_LARGE', message }. */
1123
+ payloadTooLarge(): Response;
1124
+ }
1125
+ interface InboundAuthRequest {
1126
+ readonly method: string;
1127
+ /** Absolute URL: platform trust-proxy-aware origin + the ORIGINAL path and query (never a rewritten req.url). */
1128
+ readonly url: string;
1129
+ /** Original headers as Web Headers (toWebHeaders drops pseudo-headers); never rewritten by the platform. */
1130
+ readonly headers: Headers;
1131
+ /** Exact bytes (<= bodyLimit), or a stream core buffers up to bodyLimit, or null when there is no body. */
1132
+ readonly body: Uint8Array | ReadableStream<Uint8Array> | null;
1133
+ /** Client IP from the platform's own trust-proxy resolution; never read from a raw header by core. */
1134
+ readonly clientIp: string | null;
1135
+ /** Opaque; passed to AuthHandlerInterceptor. */
1136
+ readonly platformRequest: unknown;
1137
+ /** Aborted on client disconnect. */
1138
+ readonly signal?: AbortSignal;
1139
+ }
1140
+ interface HttpRequestAccessor {
1141
+ /**
1142
+ * Is `value` this platform's request object, as frameworks layered on HTTP hand it on (Express: an IncomingMessage carrying `res`;
1143
+ * Fastify: a request whose `raw` is an IncomingMessage)? A WebSocket upgrade request, or an object a user's GraphQL context built,
1144
+ * is not. GraphQL transports take the HTTP branch only on a positive answer (§9.2).
1145
+ */
1146
+ isRequest(value: unknown): boolean;
1147
+ /**
1148
+ * REQUIRED since v6: is the response to this request still unsent (Express: !req.res.writableEnded; Fastify: the reply of request.raw
1149
+ * is not sent; a Web-native platform: !c.finalized)? GraphQL transports refuse a context whose request already finished, which a context
1150
+ * object or a caching context function shared across requests carries (§9.2). It was optional in v5, so a third-party platform silently
1151
+ * lost the runtime half of that defense with no boot or runtime signal — including this document's own Hono sketch, which omitted it.
1152
+ * It is a security defense, not a convenience, and two lines on every platform.
1153
+ */
1154
+ isLive(req: unknown): boolean;
1155
+ /** Stable identity of one request for guards, pipes, interceptors and GraphQL (Fastify: request.raw). */
1156
+ key(req: unknown): object;
1157
+ /** Request headers as Web Headers (pseudo-headers dropped). */
1158
+ headers(req: unknown): Headers;
1159
+ /** Method and absolute URL (trust-proxy-aware origin, original path). DPoP-bound tokens and the origin check need both. */
1160
+ request(req: unknown): {
1161
+ readonly method: string;
1162
+ readonly url: string;
1163
+ };
1164
+ /** Trust-proxy-aware client IP, or null. */
1165
+ clientIp(req: unknown): string | null;
1166
+ /** Route param. */
1167
+ param(req: unknown, name: string): string | undefined;
1168
+ /** Sink writing Set-Cookie onto the native response; null if unreachable. */
1169
+ cookieSink(req: unknown, res: unknown): CookieSink | null;
1170
+ /** Locate the native response for a request when a framework dropped it (Apollo on Fastify, LEAD-V8). */
1171
+ responseFor?(req: unknown): unknown | undefined;
1172
+ }
1173
+ interface CookieSink {
1174
+ /** Append each value as its own Set-Cookie header. Returns false (and writes nothing) if headers were already sent. */
1175
+ append(setCookies: readonly string[]): boolean;
1176
+ }
1177
+ interface AuthTransport {
1178
+ /** Diagnostics only. */
1179
+ readonly id: string;
1180
+ /**
1181
+ * Adapter-owned, side-effect free predicate (e.g. ctx.getType() === 'graphql' plus shape probes). It must not depend on the
1182
+ * presence of credentials: a context without credentials is still this transport's (T1).
1183
+ */
1184
+ handles(context: ExecutionContext): boolean;
1185
+ /**
1186
+ * Build a synchronous, side-effect-free envelope whenever handles() accepted the context; never throw merely because credentials,
1187
+ * an upgrade request or a live platform request cannot be extracted. key/invocation/lineage are structural. Deferred request-dependent
1188
+ * getters (headers, browser, cookies, request, clientIp) throw the original configuration error only when consumed.
1189
+ */
1190
+ describe(context: ExecutionContext, kit: TransportKit): TransportCall;
1191
+ /** Turn a denial into what this transport's runtime delivers correctly (thrown by the guard). */
1192
+ toException(failure: AuthFailure, context: ExecutionContext): unknown;
1193
+ /**
1194
+ * Optional: what to throw for a request-time infrastructure or configuration error. Default: the error itself (HTTP, WS
1195
+ * and RPC runtimes already answer a generic 500 / "Internal server error"). GraphQL returns a generic error with
1196
+ * extensions, because drivers expose error messages to clients. `repeated` is true when an earlier invocation of the same logical
1197
+ * request already surfaced this error: return something the runtime delivers identically but does not log (an IntrinsicException),
1198
+ * so one outage costs one ERROR line per request, not one per aliased field (§13.4).
1199
+ */
1200
+ toInternalException?(error: BetterAuthInfrastructureError | BetterAuthConfigurationError, context: ExecutionContext, info: {
1201
+ readonly repeated: boolean;
1202
+ }): unknown;
1203
+ /**
1204
+ * Optional boot step: claim every handler this transport serves, with how BetterAuthGuard reaches it (ctx.claim). Core applies
1205
+ * one coverage rule to the claims (§5.4 B16). A transport may also throw BetterAuthConfigurationError for its own prerequisites.
1206
+ */
1207
+ validate?(ctx: TransportValidationContext): void | Promise<void>;
1208
+ /**
1209
+ * Optional: 'inherit' marks a NESTED handler, one that runs only inside an operation this transport already authorized (GraphQL field
1210
+ * resolvers). For a nested handler the planner ignores class-level access, acceptance and requirement metadata; method-level metadata
1211
+ * gives it a real plan, and without any it inherits (§7.6 step 3). An entry point of an operation (a GraphQL root field, a federation
1212
+ * reference resolver) is never nested. The planner asks every registered transport; the first answer wins.
1213
+ *
1214
+ */
1215
+ defaultAccessFor?(target: Function, method: string): "inherit" | undefined;
1216
+ /**
1217
+ * Optional, synchronous, side-effect free: the lineage of this invocation (TransportCall.lineage) without building the whole call.
1218
+ * The guard asks for it on public plans, which select no transport otherwise, to record "no identity" for the invocations nested in
1219
+ * a public operation, and on inherit plans, to check that an enclosing invocation recorded a reading (§7.1, §7.8). Transports without
1220
+ * nesting omit it.
1221
+ */
1222
+ lineage?(context: ExecutionContext): InvocationLineage | undefined;
1223
+ /** better-auth prerequisites. hostlessCalls: this transport's calls carry no Host header (RPC), so a dynamic baseURL needs a fallback (B20). */
1224
+ readonly requires?: {
1225
+ readonly hostlessCalls?: boolean;
1226
+ };
1227
+ /** Optional boot advice (§4). */
1228
+ advise?(ctx: BootAdviceContext): readonly BootAdvice[] | Promise<readonly BootAdvice[]>;
1229
+ }
1230
+ interface TransportCall {
1231
+ /**
1232
+ * Identity of ONE logical request: the HTTP request (GraphQL queries and mutations over HTTP share it), a GraphQL operation carried by a WebSocket,
1233
+ * a WS message, an RPC message. Scope of the principal memo and of policies' I/O memo.
1234
+ */
1235
+ readonly key: object;
1236
+ /**
1237
+ * Identity of ONE handler invocation, chosen by the transport: the finest object that is distinct for each invocation of the
1238
+ * handler, including invocations the transport reaches in parallel from one entry point, for which an operation-level object
1239
+ * would be shared. Built-in choices: HTTP = the request; GraphQL = the field's `info` object (distinct per root field, alias
1240
+ * and batched operation), except where one `info` serves several parallel invocations, where it is the per-invocation input
1241
+ * the transport passes instead (the GraphQL units use the representation, gql.getRoot(), for federation reference resolvers,
1242
+ * §9.2, T7); WS = the per-message args array; RPC = the message context. Scope of authorization decisions and per-invocation
1243
+ * values. Never a connection.
1244
+ */
1245
+ readonly invocation: object;
1246
+ /** Connection identity for principal reuse (WS socket, subscription connection); used only when principalTtlMs > 0. */
1247
+ readonly connection?: object;
1248
+ /** Reuse an authenticated/absent principal on `connection` for this long, for plans whose freshness is not 'authoritative' (§7.2). Default 0. */
1249
+ readonly principalTtlMs?: number;
1250
+ /**
1251
+ * Where this invocation sits inside an enclosing operation (GraphQL fields), so that readers of a nested invocation take the reading of
1252
+ * the nearest enclosing invocation whose guard decided (§7.8). Replaces v3's carrier, whose map by memo key let a field read a
1253
+ * sibling root field's principal. Absent for transports whose invocations do not nest (HTTP, WS, RPC).
1254
+ */
1255
+ readonly lineage?: InvocationLineage;
1256
+ /**
1257
+ * Credentials as Web Headers, plus the leg's `host`, `x-forwarded-host` and `x-forwarded-proto` when the transport has a request or an
1258
+ * upgrade request (better-auth resolves a dynamic base URL from them, even where a user mapping replaced the credentials). Core then
1259
+ * strips any inbound client-IP header and sets it from clientIp. Throws a request-time configuration error when extraction is unavailable; public scope creation never calls it.
1260
+ */
1261
+ headers(): Headers;
1262
+ /** Trust-proxy-aware client IP when the transport knows it (HTTP); null otherwise. */
1263
+ readonly clientIp: string | null;
1264
+ /** null = this transport cannot deliver Set-Cookie: core suppresses session refresh (§7.4). */
1265
+ readonly cookies: CookieSink | null;
1266
+ /** Method and URL when the transport has them (DPoP-bound tokens need both). */
1267
+ readonly request?: {
1268
+ readonly method: string;
1269
+ readonly url: string;
1270
+ };
1271
+ /** Named input for policies: route param, GraphQL arg, WS/RPC payload field. */
1272
+ param(name: string): unknown;
1273
+ /** The browser leg of this operation, for the origin check (§7.10). Absent when no browser can reach it (RPC). */
1274
+ readonly browser?: BrowserExposure;
1275
+ }
1276
+ interface BrowserExposure {
1277
+ /**
1278
+ * Cookie-mode enforcement classification only (form mode always enforces, regardless of this value). The operation is unsafe: HTTP methods other than GET/HEAD/OPTIONS, GraphQL mutations over HTTP, and EVERY
1279
+ * operation carried by a WebSocket (messages; GraphQL subscriptions, queries and mutations over graphql-ws), because a cross-site
1280
+ * socket can read replies.
1281
+ */
1282
+ readonly enforce: boolean;
1283
+ /**
1284
+ * The headers the browser itself sent on this leg: the request, or the WebSocket upgrade / handshake request. Never values a
1285
+ * transport copied from handshake.auth, connectionParams, _connectionInit, payloads or query strings.
1286
+ */
1287
+ headers(): Headers;
1288
+ /** Absolute URL of the leg (scheme://host/path; WebSocket transports use upgradeRequestUrl). */
1289
+ readonly url: string;
1290
+ /** Identity of the leg (the HTTP request, the socket, the upgrade request): the origin verdict is computed once per leg. */
1291
+ readonly key: object;
1292
+ }
1293
+ /** Where an invocation sits inside an operation, for readers of nested invocations (§7.8). */
1294
+ interface InvocationLineage {
1295
+ /** One of ctx.getArgs(), shared by every invocation of the operation (the GraphQL context). Readings are recorded on it under a Symbol.for slot. */
1296
+ readonly carrier: object;
1297
+ /**
1298
+ * This invocation's position, a string the transport derives from the invocation's own args. GraphQL: an id of `info.operation`
1299
+ * (batched operations can share a context) plus the serialized response path of `info.path`, list indexes included, e.g.
1300
+ * `'1:reports.0.owner'`. Aliases give distinct paths. Federation reference resolvers, whose representations share the `_entities`
1301
+ * field's `info`, take `'<op>:_entities#<__typename>'`: invocations of one handler plan in one request, whose readings are identical
1302
+ * (§9.2).
1303
+ */
1304
+ readonly position: string;
1305
+ /**
1306
+ * Positions of the invocations that enclose the invocation whose args are given, nearest first (GraphQL: walking `info.path.prev`).
1307
+ * It is a function of the args alone, so the guard can record it on the carrier and param factories, which have no DI access, can
1308
+ * walk the lineage too. Every enclosing invocation completes its guard run before a nested one starts: GraphQL resolves a field
1309
+ * before it executes the field's selections.
1310
+ */
1311
+ readonly enclosing: (args: readonly unknown[]) => Iterable<string>;
1312
+ /**
1313
+ * Optional transport-owned validation before exposing an authenticated reading from this lineage. Structural lineage() never
1314
+ * runs it. GraphQL units provide the current invocation's request-liveness check; readers without DI obtain the callback from
1315
+ * the recorded carrier metadata, supplying their own args (never a closure over a sibling/finished request).
1316
+ */
1317
+ readonly assertReadable?: (args: readonly unknown[]) => void;
1318
+ }
1319
+ interface TransportKit {
1320
+ /** Accessor of the running app's HTTP platform (for transports layered on HTTP, e.g. GraphQL); null without one. */
1321
+ readonly http: HttpRequestAccessor | null;
1322
+ }
1323
+ interface TransportValidationContext {
1324
+ readonly discovery: DiscoveryService;
1325
+ readonly reflector: Reflector;
1326
+ readonly moduleRef: ModuleRef;
1327
+ readonly hasHttpAdapter: boolean;
1328
+ /** Compiled plan of a handler (class + method). */
1329
+ readonly planOf: (target: Function, method: string) => RoutePlan;
1330
+ /**
1331
+ * Declare a handler this transport serves and how BetterAuthGuard reaches it. `method` undefined claims a class whose handlers the
1332
+ * transport could not enumerate (a class-level decorator is then required). Core decides coverage (§5.4 B16).
1333
+ */
1334
+ claim(target: Function, method: string | undefined, reach: GuardReach, options: ClaimOptions): void;
1335
+ readonly logger: LoggerService;
1336
+ }
1337
+ /**
1338
+ * 'global': the app's global guards reach the handler: covered when they include BetterAuthGuard, however registered or overridden.
1339
+ * BetterAuthScopeInterceptor is not part of global coverage; absence is B31's warning, including globalScope: false. (§5.4 B16)
1340
+ *
1341
+ * 'explicit': @UseBetterAuth(), or BOTH @UseGuards(BetterAuthGuard) and @UseInterceptors(BetterAuthScopeInterceptor), must apply locally.
1342
+ * 'none': no guard ever runs for it (GraphQL field resolvers without fieldResolverEnhancers: ['guards']).
1343
+ */
1344
+ type GuardReach = "global" | "explicit" | "none";
1345
+ interface ClaimOptions {
1346
+ /** Stable code of the coverage report, e.g. 'GATEWAY_UNGUARDED'. */
1347
+ readonly code: string;
1348
+ /** Copy-paste fix, e.g. 'Add @UseBetterAuth() (or @Public() to opt out); global guards do not reach gateways on NestJS 11.' */
1349
+ readonly hint: string;
1350
+ /** Severity when the handler is not covered. Default 'error'. */
1351
+ readonly coverage?: "error" | "warn" | "off";
1352
+ /** true: every handler needs coverage, even one without library metadata (gateways, hybrid message handlers). false: only handlers that declare something (§5.4 B16). Default false. */
1353
+ readonly everyHandler?: boolean;
1354
+ /**
1355
+ * Names of the handler's inputs that ctx.param(name) can read, as far as the transport knows them statically: HTTP route params,
1356
+ * GraphQL @Args names. Core passes them to boot advice (AdvisedHandler.inputs) and never interprets them. Default [].
1357
+ */
1358
+ readonly inputs?: readonly string[];
1359
+ }
1360
+ interface AuthPrincipalBase {
1361
+ readonly kind: string;
1362
+ /** PrincipalSource.id that produced it. */
1363
+ readonly source: string;
1364
+ /** The better-auth user this principal acts for; null for machine or organization principals. */
1365
+ readonly userId: string | null;
1366
+ /**
1367
+ * Present when the principal acts with a NARROWER grant than its owner's rights (API key, OAuth token). Built-in policies
1368
+ * AND it with the owner's rights (Z5); a policy that does not name the principal's kind never receives it.
1369
+ */
1370
+ readonly delegation?: PrincipalDelegation;
1371
+ }
1372
+ interface PrincipalDelegation {
1373
+ /** Does the credential's own grant include these permissions? (api-key: role(key.permissions).authorize) */
1374
+ allows(permissions: Readonly<Record<string, readonly string[]>>): boolean;
1375
+ /** Diagnostics, e.g. 'api-key permissions', 'oauth scopes'. */
1376
+ readonly description: string;
1377
+ }
1378
+ interface PrincipalSource<P extends AuthPrincipalBase = AuthPrincipal> {
1379
+ readonly id: string;
1380
+ /** The principal kinds this source produces. Data: core compares them with the route's accepted kinds (§7.6). */
1381
+ readonly kinds: readonly P["kind"][];
1382
+ /**
1383
+ * 'default': accepted on every authenticated route of the instance. 'explicit': only where a route (@AcceptPrincipals) or a
1384
+ * requirement's policy names one of its kinds. Default 'explicit', so a new source never widens existing routes.
1385
+ */
1386
+ readonly acceptance?: "default" | "explicit";
1387
+ /**
1388
+ * Request headers this source reads credentials from (e.g. ['x-api-key']). The cookie bridge compares them (§7.5), and their
1389
+ * values are redacted from error causes (§13.4). They play no part in the origin check (§7.10).
1390
+ */
1391
+ readonly credentialHeaders?: readonly string[];
1392
+ /** Side effects of one resolution (drive boot advice, e.g. quota consumption per WS message). */
1393
+ readonly effects?: {
1394
+ readonly consumesQuota?: boolean;
1395
+ readonly writes?: boolean;
1396
+ };
1397
+ /**
1398
+ * The principal IS better-auth's session read of the request's headers (auth.api.getSession). Only then does a generic "no session"
1399
+ * 401 that a policy's later better-auth call meets contradict the principal, and only then does the evaluator re-read through this
1400
+ * source to tell a lost session from a swallowed storage failure (§8.1). The built-in session source sets it; a source that
1401
+ * authenticates by other means (API keys, OAuth tokens) must not. Default false.
1402
+ */
1403
+ readonly sessionBacked?: boolean;
1404
+ /**
1405
+ * The principals this source produces carry `delegation` (API keys, OAuth tokens). Data for B15: a requirement whose policy names no kinds
1406
+ * judges only non-delegated principals (§8.1), so at boot it admits exactly the kinds of the instance's sources that do not delegate.
1407
+ * S-delegation checks the declaration against the principals the source produces. Default false.
1408
+ */
1409
+ readonly delegates?: boolean;
1410
+ /**
1411
+ * better-auth plugins this source needs, checked at boot with $context.hasPlugin (B13); hostlessCalls: some of its auth.api calls
1412
+ * carry no Host header whatever the transport, so a dynamic baseURL needs a fallback (B20).
1413
+ */
1414
+ readonly requires?: {
1415
+ readonly plugins?: readonly string[];
1416
+ readonly hostlessCalls?: boolean;
1417
+ };
1418
+ /** Cheap synchronous sniffing (e.g. a header is present). false = skip without I/O. Default: true. */
1419
+ appliesTo?(request: PrincipalRequest): boolean;
1420
+ /**
1421
+ * Denials are values: return authenticated(p), absent() (no credential this source understands) or
1422
+ * rejected(failure) (a credential was presented and is invalid). A throw means infrastructure (5xx).
1423
+ */
1424
+ resolve(request: PrincipalRequest): Promise<PrincipalResult<P>>;
1425
+ /** Optional boot advice (§4). */
1426
+ advise?(ctx: BootAdviceContext): readonly BootAdvice[] | Promise<readonly BootAdvice[]>;
1427
+ }
1428
+ type PrincipalResult<P extends AuthPrincipalBase = AuthPrincipal> = {
1429
+ readonly outcome: "authenticated";
1430
+ readonly principal: P;
1431
+ } | {
1432
+ readonly outcome: "absent";
1433
+ } | {
1434
+ readonly outcome: "rejected";
1435
+ readonly failure: AuthFailure;
1436
+ };
1437
+ interface PrincipalRequest {
1438
+ readonly headers: Headers;
1439
+ readonly cookies: CookieSink | null;
1440
+ readonly transport: string;
1441
+ readonly request?: {
1442
+ readonly method: string;
1443
+ readonly url: string;
1444
+ };
1445
+ readonly freshness: "default" | "authoritative";
1446
+ readonly auth: AuthHandle;
1447
+ /** Memo per logical request (e.g. one verifyApiKey per request: it consumes quota). */
1448
+ memo<T>(key: string | object | symbol, compute: () => Promise<T>): Promise<T>;
1449
+ }
1450
+ /** What extensions receive instead of the raw instance. [A][C] */
1451
+ interface AuthHandle<A extends AuthLike = AuthLike> {
1452
+ readonly name: string;
1453
+ readonly instance: A;
1454
+ readonly api: A["api"];
1455
+ context(): Promise<AuthContextView>;
1456
+ hasPlugin(id: string): Promise<boolean>;
1457
+ /** Run auth.api calls inside a scope: cookie capability, forwarding mode, internal flag (§7.4, §7.5, §10.4). */
1458
+ run<T>(init: ScopeInit, fn: () => Promise<T>): Promise<T>;
1459
+ /** Origin/form rule for a browser leg (§7.10): null on pass/nonapplication, else denial; memoized per leg. browser.enforce gates cookie mode only; false never bypasses form mode. */
1460
+ checkOrigin(browser: BrowserExposure, mode: "cookie" | "form"): Promise<AuthFailure | null>;
1461
+ /**
1462
+ * Did a better-auth endpoint itself produce `value` in a dispatch the plugin observed while bound, as opposed to a before-hook
1463
+ * short-circuit (LEAD-V37, LEAD-EXP-8)? Authoritative identity reads accept only such sessions (§7.3).
1464
+ */
1465
+ producedByEndpoint(value: unknown): boolean;
1466
+ /** Is `origin` trusted by this instance (post-init set; function-valued trustedOrigins get `request`)? (§6.9) */
1467
+ isTrustedOrigin(origin: string, request?: Request): Promise<boolean>;
1468
+ }
1469
+ interface ScopeInit {
1470
+ /** null = cannot write cookies → the plugin suppresses session refresh for every call in fn. */
1471
+ readonly cookies: CookieSink | null;
1472
+ /**
1473
+ * Set-Cookie forwarding through the plugin's bridge for calls in fn. 'same-credential' (default): only calls that carry the
1474
+ * scope's credential (see `inbound`) or none; 'any': every call; 'none': no forwarding.
1475
+ */
1476
+ readonly forward?: "none" | "same-credential" | "any";
1477
+ /** The inbound request's headers, the credential 'same-credential' compares against. */
1478
+ readonly inbound?: () => Headers;
1479
+ /** Mark calls as library-internal (HookOptions.skipInternal). Default false. */
1480
+ readonly internal?: boolean;
1481
+ }
1482
+ /** The subset of better-auth's AuthContext the library reads (structural: any version in range fits). [C] */
1483
+ interface AuthContextView {
1484
+ readonly baseURL: string;
1485
+ readonly trustedOrigins: readonly string[];
1486
+ readonly skipCSRFCheck: boolean;
1487
+ readonly skipOriginCheck: boolean | readonly string[];
1488
+ /** The resolved signing secret; B29 compares it with better-auth's public default (LEAD-V48). */
1489
+ readonly secret: string;
1490
+ /** better-auth's resolved rate limiter; B30 reports it off in production (LEAD-V52). */
1491
+ readonly rateLimit: {
1492
+ readonly enabled: boolean;
1493
+ };
1494
+ readonly options: {
1495
+ readonly baseURL?: unknown;
1496
+ readonly basePath?: string;
1497
+ readonly database?: unknown;
1498
+ readonly secondaryStorage?: unknown;
1499
+ /** `enabled` is undefined unless the user set it (B30 tells an explicit false from better-auth's NODE_ENV default). */
1500
+ readonly rateLimit?: {
1501
+ readonly enabled?: boolean;
1502
+ };
1503
+ readonly trustedOrigins?: readonly string[] | ((request?: Request) => unknown);
1504
+ readonly plugins?: readonly {
1505
+ readonly id: string;
1506
+ readonly hooks?: {
1507
+ readonly after?: readonly unknown[];
1508
+ };
1509
+ }[];
1510
+ readonly onAPIError?: {
1511
+ readonly throw?: boolean;
1512
+ };
1513
+ readonly advanced?: {
1514
+ readonly ipAddress?: {
1515
+ readonly ipAddressHeaders?: readonly string[];
1516
+ readonly trustedProxies?: readonly string[];
1517
+ };
1518
+ readonly disableOriginCheck?: boolean;
1519
+ readonly disableCSRFCheck?: boolean;
1520
+ readonly cookiePrefix?: string;
1521
+ };
1522
+ };
1523
+ /** The admin policy's owner-row read for delegated principals (the ban rule), the call userHasPermission itself makes (LEAD-V45). */
1524
+ readonly internalAdapter: {
1525
+ findUserById(userId: string): Promise<{
1526
+ readonly id: string;
1527
+ readonly role?: string | null;
1528
+ readonly banned?: boolean | null;
1529
+ readonly banExpires?: Date | string | null;
1530
+ } | null>;
1531
+ };
1532
+ readonly authCookies: Readonly<Record<"sessionToken" | "sessionData" | "dontRememberToken" | "accountData", {
1533
+ readonly name: string;
1534
+ readonly attributes: {
1535
+ readonly domain?: string;
1536
+ readonly path?: string;
1537
+ };
1538
+ }>>;
1539
+ readonly sessionConfig: {
1540
+ readonly freshAge: number;
1541
+ readonly updateAge: number;
1542
+ readonly expiresIn: number;
1543
+ };
1544
+ readonly adapter: {
1545
+ findOne(input: {
1546
+ model: string;
1547
+ where: readonly {
1548
+ field: string;
1549
+ value: unknown;
1550
+ }[];
1551
+ }): Promise<unknown>;
1552
+ updateMany(input: {
1553
+ model: string;
1554
+ where: readonly {
1555
+ field: string;
1556
+ value: unknown;
1557
+ }[];
1558
+ update: Record<string, unknown>;
1559
+ }): Promise<number>;
1560
+ };
1561
+ hasPlugin(id: string): boolean;
1562
+ getPlugin(id: string): unknown;
1563
+ isTrustedOrigin(url: string, settings?: {
1564
+ allowRelativePaths: boolean;
1565
+ }): boolean;
1566
+ }
1567
+ interface AuthorizationPolicy<Params = unknown, P extends AuthPrincipalBase = AuthPrincipal> {
1568
+ /** Diagnostic id ('better-auth:admin/permission'); never used for dispatch. */
1569
+ readonly id: string;
1570
+ readonly requires?: {
1571
+ /** better-auth plugin ids validated at boot through $context.hasPlugin (B13). */
1572
+ readonly plugins?: readonly string[];
1573
+ /**
1574
+ * Principal kinds this policy can judge; others are denied 403 PRINCIPAL_NOT_SUPPORTED (data-driven, no core branch).
1575
+ * Naming a kind also ADMITS it on routes that use the policy (§7.6). Omitted: any kind WITHOUT `delegation`; delegated
1576
+ * principals are denied 403 PRINCIPAL_NOT_SUPPORTED, so a policy must opt in to judging them (Z5). At boot such a policy admits
1577
+ * exactly the kinds of the instance's sources that do not declare `delegates` (B15).
1578
+ */
1579
+ readonly principals?: readonly P["kind"][];
1580
+ /** The decision needs an identity read that bypasses the cookie cache (admin-grade decisions): raises the route's freshness (§8.4). */
1581
+ readonly freshIdentity?: boolean;
1582
+ /** The policy calls better-auth with the request's credential headers, so credential hooks re-run per call (api-key quota). Boot advice only. */
1583
+ readonly presentsCredentials?: boolean;
1584
+ /** Some of the policy's auth.api calls carry no Host header (e.g. userHasPermission without headers), so a dynamic baseURL needs a fallback (B20). */
1585
+ readonly hostlessCalls?: boolean;
1586
+ };
1587
+ /** Denials are values. Throw only for infrastructure faults (they become 5xx, never 403). */
1588
+ evaluate(params: Params, context: AuthorizationContext<P>): AuthorizationDecision | Promise<AuthorizationDecision>;
1589
+ /** Optional boot validation of params (e.g. empty permission maps). Throw BetterAuthConfigurationError. */
1590
+ validate?(params: Params, boot: PolicyBootContext): void | Promise<void>;
1591
+ /** Optional boot advice (§4). */
1592
+ advise?(ctx: BootAdviceContext): readonly BootAdvice[] | Promise<readonly BootAdvice[]>;
1593
+ }
1594
+ /**
1595
+ * A policy value, or a provider class/token resolved through ModuleRef (for DI-backed policies). Whatever the ref, the planner, the
1596
+ * evaluator and B14 see the same resolved instance (U19), so `requires` declared as an instance field reaches the plan.
1597
+ */
1598
+ type PolicyRef<Params> = AuthorizationPolicy<Params, any> | Type<AuthorizationPolicy<Params, any>> | InjectionToken;
1599
+ interface Requirement<Params = unknown> {
1600
+ readonly policy: PolicyRef<Params>;
1601
+ readonly params: Params;
1602
+ readonly label?: string;
1603
+ /** Overrides policy.requires.principals for this requirement (e.g. permission(p, { principals })). */
1604
+ readonly principals?: readonly string[];
1605
+ /** Overrides policy.requires.freshIdentity for this requirement. */
1606
+ readonly freshIdentity?: boolean;
1607
+ }
1608
+ type RequirementExpr = Requirement | {
1609
+ readonly anyOf: readonly RequirementExpr[];
1610
+ } | {
1611
+ readonly allOf: readonly RequirementExpr[];
1612
+ };
1613
+ type AuthorizationDecision = {
1614
+ readonly effect: "allow";
1615
+ } | {
1616
+ readonly effect: "deny";
1617
+ readonly status?: 401 | 403;
1618
+ readonly reason: string;
1619
+ readonly message?: string;
1620
+ readonly challenge?: string;
1621
+ };
1622
+ interface AuthorizationContext<P extends AuthPrincipalBase = AuthPrincipal> {
1623
+ readonly principal: P;
1624
+ readonly instance: string;
1625
+ readonly transport: string;
1626
+ readonly headers: Headers;
1627
+ readonly cookies: CookieSink | null;
1628
+ readonly request?: {
1629
+ readonly method: string;
1630
+ readonly url: string;
1631
+ };
1632
+ param(name: string): unknown;
1633
+ readonly handler: {
1634
+ readonly class: Type;
1635
+ readonly method: string;
1636
+ };
1637
+ readonly auth: AuthHandle;
1638
+ /**
1639
+ * Memo per LOGICAL REQUEST (HTTP request, WS message, …), never per connection: dedupe better-auth I/O by concrete inputs,
1640
+ * serialized as a tuple, e.g. `JSON.stringify(['org:hasPermission', orgId, stablePermissions])`, so no input can collide with another key's sentinel. Decisions themselves are never shared across invocations.
1641
+ */
1642
+ memo<T>(key: string | object | symbol, compute: () => Promise<T>): Promise<T>;
1643
+ /** Publish a per-invocation value for defineInvocationParam decorators (e.g. the resolved organization id). */
1644
+ provide(slot: symbol, value: unknown): void;
1645
+ readonly execution: ExecutionContext;
1646
+ }
1647
+ interface PolicyBootContext {
1648
+ readonly auth: AuthHandle;
1649
+ readonly context: AuthContextView;
1650
+ readonly site: string;
1651
+ }
1652
+ //#endregion
1653
+ export { PrincipalDelegation as $, ErrorMappingOptions as $n, SESSION_PRINCIPAL_KIND as $t, DatabaseHookTarget as A, BetterAuthService as An, GetSessionWithHeaders as At, HookOptions as B, WS_CONNECTION_AUTH as Bn, UnvalidatedBody as Bt, ConformanceCase as C, defineHttpPlatform as Cn, AuthPrincipal as Ct, CookieForwardingOptions as D, allow as Dn, DatabaseHookMethod as Dt, ConformanceSkip as E, BetterAuthGuard as En, DatabaseHookData as Et, ExtensionDefinition as F, BetterAuthModule as Fn, Register as Ft, InboundAuthRequest as G, AuthErrorBody as Gn, betterAuthCorsOrigin as Gt, HttpMountOptions as H, getBetterAuthInstanceToken as Hn, WithActiveOrganization as Ht, ExtensionRef as I, GUARD_CORE as In, RegisteredAuth as It, PlatformMountContext as J, AuthFailures as Jn, definePrincipalSource as Jt, InvocationLineage as K, AuthErrorCode as Kn, absent as Kt, FastifyPlatformOptions as L, POLICY_INVOKER as Ln, RegisteredInstances as Lt, DbHookOptions as M, InstanceLookup as Mn, OrgPermissions as Mt, DefaultInstanceCheck as N, EXTENSION_DEFINITION as Nn, PrincipalKind as Nt, CookieSink as O, definePolicy as On, DatabaseHookResult as Ot, ExpressPlatformOptions as P, UPGRADE_REQUEST as Pn, PrincipalOfKind as Pt, PolicyRef as Q, BetterAuthInfrastructureError as Qn, RequireFreshSession as Qt, GuardReach as R, PRINCIPAL_RESOLVER as Rn, SessionOf as Rt, ClaimOptions as S, defineExtension as Sn, AuthOf as St, ConformanceRunner as T, BetterAuthScopeInterceptor as Tn, AuthUser as Tt, HttpPlatform as U, getBetterAuthOptionsToken as Un, PrincipalKinds as Ut, HookPredicate as V, getBetterAuthHandleToken as Vn, UserOf as Vt, HttpRequestAccessor as W, getBetterAuthServiceToken as Wn, httpTransport as Wt, PolicyBootContext as X, AuthTransportErrorPayload as Xn, CurrentSession as Xt, PlatformPrepareContext as Y, AuthGraphqlExtensions as Yn, rejected as Yt, PolicyInvoker as Z, BetterAuthConfigurationError as Zn, CurrentUser as Zt, BetterAuthRuntimeOptions as _, allOf as _n, TransportValidationContext as _t, AuthHandlerInterceptor as a, BeforeAuth as an, ProxyTrust as at, BootAdviceContext as b, definePrincipalParam as bn, AuthHookContext as bt, AuthTransport as c, ForwardAuthCookies as cn, RequirementOptions as ct, AuthorizationLimits as d, Require as dn, ScopeInit as dt, freshSession as en, getRawCause as er, PrincipalReading as et, AuthorizationPolicy as f, RequireAuth as fn, SessionOption as ft, BetterAuthModuleOptions as g, UseBetterAuth as gn, TransportKit as gt, BetterAuthModuleAsyncOptions as h, UseAuthInstance as hn, TransportCall as ht, AuthHandle as i, AfterDatabase as in, PrincipalSource as it, DbHookMethodDecorator as j, TransportRegistry as jn, IsRegistered as jt, CorsOriginOptions as k, deny as kn, EndpointPath as kt, AuthorizationContext as l, OptionalAuth as ln, ResolutionRequest as lt, BetterAuthFactoryResult as m, SkipOriginCheck as mn, StaticOptionMustBePassedToForRootAsync as mt, ApplicationRouteDescriptor as n, AcceptPrincipals as nn, isConfigurationError as nr, PrincipalResolver as nt, AuthPrincipalBase as o, BeforeDatabase as on, Requirement as ot, BetterAuthAppOptions as p, SkipDefaultRequirements as pn, SessionPrincipalOptions as pt, OriginCheckOptions as q, AuthFailure as qn, authenticated as qt, AuthContextView as r, AfterAuth as rn, isInfrastructureError as rr, PrincipalResult as rt, AuthRouteBinding as s, CurrentPrincipal as sn, RequirementExpr as st, AdvisedHandler as t, sessionPrincipal as tn, isAuthFailure as tr, PrincipalRequest as tt, AuthorizationDecision as u, Public as un, RoutePlan as ut, BetterAuthStaticOptions as v, anyOf as vn, AdminPermissions as vt, ConformanceOutcome as w, defineTransport as wn, AuthSession as wt, BrowserExposure as x, requirement as xn, AuthLike as xt, BootAdvice as y, defineInvocationParam as yn, AuthAfterHookContext as yt, HookMethodDecorator as z, SCOPE_CORE as zn, SessionPrincipal as zt };
1654
+ //# sourceMappingURL=auth-contracts-Brh_Qko9.d.cts.map