@velajs/better-auth 0.6.0 → 1.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 (37) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +19 -14
  3. package/dist/better-auth.service-BMkyFX-w.js +63 -0
  4. package/dist/better-auth.service-BMkyFX-w.js.map +1 -0
  5. package/dist/index.d.ts +280 -12
  6. package/dist/index.js +581 -15
  7. package/dist/index.js.map +1 -0
  8. package/dist/testing/index.d.ts +50 -2
  9. package/dist/testing/index.js +60 -3
  10. package/dist/testing/index.js.map +1 -0
  11. package/package.json +62 -42
  12. package/dist/better-auth.controller.d.ts +0 -21
  13. package/dist/better-auth.controller.js +0 -68
  14. package/dist/better-auth.module.d.ts +0 -52
  15. package/dist/better-auth.module.js +0 -138
  16. package/dist/better-auth.service.d.ts +0 -42
  17. package/dist/better-auth.service.js +0 -51
  18. package/dist/better-auth.tokens.d.ts +0 -5
  19. package/dist/better-auth.tokens.js +0 -4
  20. package/dist/better-auth.types.d.ts +0 -11
  21. package/dist/better-auth.types.js +0 -1
  22. package/dist/decorators/current-session.decorator.d.ts +0 -1
  23. package/dist/decorators/current-session.decorator.js +0 -7
  24. package/dist/decorators/current-user.decorator.d.ts +0 -1
  25. package/dist/decorators/current-user.decorator.js +0 -7
  26. package/dist/decorators/optional-auth.decorator.d.ts +0 -2
  27. package/dist/decorators/optional-auth.decorator.js +0 -5
  28. package/dist/decorators/public.decorator.d.ts +0 -2
  29. package/dist/decorators/public.decorator.js +0 -5
  30. package/dist/decorators/roles.decorator.d.ts +0 -2
  31. package/dist/decorators/roles.decorator.js +0 -5
  32. package/dist/guards/auth.guard.d.ts +0 -10
  33. package/dist/guards/auth.guard.js +0 -68
  34. package/dist/guards/roles.guard.d.ts +0 -5
  35. package/dist/guards/roles.guard.js +0 -36
  36. package/dist/testing/acting-as.d.ts +0 -46
  37. package/dist/testing/acting-as.js +0 -70
package/dist/index.js CHANGED
@@ -1,15 +1,581 @@
1
- // Module
2
- export { BetterAuthModule } from "./better-auth.module.js";
3
- export { BetterAuthService } from "./better-auth.service.js";
4
- export { BetterAuthCatchallController, createBetterAuthCatchallController } from "./better-auth.controller.js";
5
- // Tokens & symbols
6
- export { BETTER_AUTH_OPTIONS, AUTH_USER_KEY, AUTH_SESSION_KEY } from "./better-auth.tokens.js";
7
- // Guards
8
- export { AuthGuard } from "./guards/auth.guard.js";
9
- export { RolesGuard } from "./guards/roles.guard.js";
10
- // Decorators
11
- export { CurrentUser } from "./decorators/current-user.decorator.js";
12
- export { CurrentSession } from "./decorators/current-session.decorator.js";
13
- export { Public, PUBLIC_KEY } from "./decorators/public.decorator.js";
14
- export { OptionalAuth, OPTIONAL_AUTH_KEY } from "./decorators/optional-auth.decorator.js";
15
- export { Roles, ROLES_KEY } from "./decorators/roles.decorator.js";
1
+ import { a as __decorateMetadata, i as __decorateParam, n as BetterAuthService, r as __decorate, t as BETTER_AUTH_BUILDER } from "./better-auth.service-BMkyFX-w.js";
2
+ import { All, Controller, ForbiddenException, Inject, Injectable, InjectionToken, REQUEST_CONTEXT, Reflector, Req, UnauthorizedException, clearTrustedRequestIdentity, createParamDecorator, defineModule, lazyProvider, provideGlobal, setTrustedRequestIdentity, stableHash } from "@velajs/vela";
3
+ import { AUTHZ } from "@velajs/authz/vela";
4
+ //#region src/decorators/public.decorator.ts
5
+ const Public = Reflector.createDecorator({ key: "vela.auth.public" });
6
+ const PUBLIC_KEY = Public.KEY;
7
+ /** Validate and canonicalize the route prefix used by the public auth controller. */
8
+ function normalizeBetterAuthBasePath(value) {
9
+ const basePath = value ?? "/api/auth";
10
+ if (basePath.length === 0 || basePath !== basePath.trim() || !basePath.startsWith("/") || basePath.startsWith("//") || basePath === "/" || basePath.endsWith("/") || /[\\?#*]/u.test(basePath) || /%(?:2e|2f|5c)/iu.test(basePath)) throw new Error("@velajs/better-auth: basePath must be a canonical absolute path such as \"/api/auth\"");
11
+ let decoded;
12
+ try {
13
+ decoded = decodeURIComponent(basePath);
14
+ } catch {
15
+ throw new Error("@velajs/better-auth: basePath contains invalid percent encoding");
16
+ }
17
+ if (decoded.split("/").some((segment) => segment === "." || segment === "..")) throw new Error("@velajs/better-auth: basePath must not contain dot segments");
18
+ return basePath;
19
+ }
20
+ //#endregion
21
+ //#region src/better-auth.controller.ts
22
+ /**
23
+ * Build a catch-all controller that mounts better-auth's handler at `basePath`
24
+ * (default `/api/auth`). This is a factory because vela reads a controller's
25
+ * route off the class at decoration time, so a custom base path needs its own
26
+ * decorated class — the path can't be parametrized on a single shared class.
27
+ *
28
+ * Two base paths to keep consistent:
29
+ * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);
30
+ * - better-auth routes against its OWN absolute `basePath` (the one you pass to
31
+ * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.
32
+ *
33
+ * Both default to `/api/auth`, so the no-prefix / no-config case just works.
34
+ */
35
+ function createBetterAuthCatchallController(basePath = "/api/auth") {
36
+ const normalizedBasePath = normalizeBetterAuthBasePath(basePath);
37
+ let BetterAuthCatchallController = class BetterAuthCatchallController {
38
+ auth;
39
+ constructor(auth) {
40
+ this.auth = auth;
41
+ }
42
+ async handle(c) {
43
+ return this.auth.handler(c.req.raw);
44
+ }
45
+ };
46
+ __decorate([
47
+ All("/*"),
48
+ __decorateParam(0, Req()),
49
+ __decorateMetadata("design:type", Function),
50
+ __decorateMetadata("design:paramtypes", [Object]),
51
+ __decorateMetadata("design:returntype", Promise)
52
+ ], BetterAuthCatchallController.prototype, "handle", null);
53
+ BetterAuthCatchallController = __decorate([
54
+ Public(true),
55
+ Controller(normalizedBasePath),
56
+ Injectable(),
57
+ __decorateParam(0, Inject(BetterAuthService)),
58
+ __decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService])
59
+ ], BetterAuthCatchallController);
60
+ return BetterAuthCatchallController;
61
+ }
62
+ /**
63
+ * Default-path (`/api/auth`) catch-all controller. Retained for back-compat;
64
+ * `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with
65
+ * the configured `basePath`. Prefer the factory for a custom base path.
66
+ */
67
+ const BetterAuthCatchallController = createBetterAuthCatchallController();
68
+ //#endregion
69
+ //#region src/better-auth.tokens.ts
70
+ const BETTER_AUTH_OPTIONS = new InjectionToken("vela.BetterAuthOptions");
71
+ const AUTH_USER_KEY = Symbol.for("vela.better-auth.user");
72
+ const AUTH_SESSION_KEY = Symbol.for("vela.better-auth.session");
73
+ const AUTH_ISSUER_KEY = Symbol.for("vela.better-auth.issuer");
74
+ const AUTH_PRINCIPAL_TYPE_KEY = Symbol.for("vela.better-auth.principal-type");
75
+ //#endregion
76
+ //#region src/auth-request-state.ts
77
+ const ANONYMOUS = Object.freeze({ authenticated: false });
78
+ const stateByRequest = /* @__PURE__ */ new WeakMap();
79
+ /** Clear any state before a guard evaluates a request. */
80
+ const beginAuthRequest = (context) => {
81
+ stateByRequest.set(context.getRequest(), ANONYMOUS);
82
+ };
83
+ /** Publish a fully verified session for downstream guards and parameters. */
84
+ const authenticateRequest = (context, state) => {
85
+ const authenticated = Object.freeze({
86
+ authenticated: true,
87
+ ...state
88
+ });
89
+ stateByRequest.set(context.getRequest(), authenticated);
90
+ return authenticated;
91
+ };
92
+ /** Missing state is anonymous: no guard means no ambient identity. */
93
+ const getAuthRequestState = (context) => context.getType() === "http" ? stateByRequest.get(context.getRequest()) ?? ANONYMOUS : ANONYMOUS;
94
+ //#endregion
95
+ //#region src/decorators/optional-auth.decorator.ts
96
+ const OptionalAuth = Reflector.createDecorator({ key: "vela.auth.optional" });
97
+ const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;
98
+ //#endregion
99
+ //#region src/guards/auth.guard.ts
100
+ let AuthGuard = class AuthGuard {
101
+ auth;
102
+ opts;
103
+ reflector = new Reflector();
104
+ constructor(auth, opts) {
105
+ this.auth = auth;
106
+ this.opts = opts;
107
+ }
108
+ async canActivate(context) {
109
+ if (context.getType() === "ws") {
110
+ if (hasValidWebSocketIdentity(context)) return true;
111
+ throw new AuthenticationRequiredException();
112
+ }
113
+ beginAuthRequest(context);
114
+ mirrorRequestContext(context, { authenticated: false });
115
+ if (this.reflector.getAllAndOverride(Public, context)) return true;
116
+ const request = context.getRequest();
117
+ const data = validateSessionData(await this.auth.api.getSession({ headers: request.headers }));
118
+ if (data) {
119
+ mirrorRequestContext(context, authenticateRequest(context, {
120
+ user: data.user,
121
+ session: data.session,
122
+ issuer: this.opts.issuer ?? "better-auth",
123
+ principalType: "user"
124
+ }));
125
+ return true;
126
+ }
127
+ if (this.reflector.getAllAndOverride(OptionalAuth, context)) return true;
128
+ throw new AuthenticationRequiredException();
129
+ }
130
+ };
131
+ AuthGuard = __decorate([
132
+ Injectable(),
133
+ __decorateParam(0, Inject(BetterAuthService)),
134
+ __decorateParam(1, Inject(BETTER_AUTH_OPTIONS)),
135
+ __decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService, Object])
136
+ ], AuthGuard);
137
+ function hasValidWebSocketIdentity(context) {
138
+ try {
139
+ const data = context.switchToWs().getClient()?.data;
140
+ if (!data || typeof data !== "object") return false;
141
+ const record = data;
142
+ const principal = record.principal;
143
+ if (!principal || typeof principal !== "object") return false;
144
+ const fields = principal;
145
+ return typeof fields.issuer === "string" && fields.issuer.length > 0 && typeof fields.subject === "string" && fields.subject.length > 0 && (fields.principalType === "user" || fields.principalType === "service") && typeof record.tenantId === "string" && record.tenantId.length > 0 && typeof record.expiresAtMs === "number" && Number.isSafeInteger(record.expiresAtMs) && record.expiresAtMs > Date.now();
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ /**
151
+ * Preserve the public REQUEST_CONTEXT symbols for applications that consume
152
+ * them directly. The private Request-keyed state above remains canonical: a
153
+ * missing/duplicated framework token must not prevent a verified guard from
154
+ * publishing identity to its own downstream decorators and guards.
155
+ */
156
+ function mirrorRequestContext(context, state) {
157
+ const request = context.getRequest();
158
+ if (!state.authenticated) clearTrustedRequestIdentity(request);
159
+ else {
160
+ const tenantId = readActiveOrganizationId(state.session);
161
+ setTrustedRequestIdentity(request, {
162
+ principal: {
163
+ issuer: state.issuer,
164
+ subject: state.user.id,
165
+ principalType: state.principalType
166
+ },
167
+ ...tenantId === void 0 ? {} : { tenantId }
168
+ });
169
+ }
170
+ const container = context.getContext().get("container");
171
+ if (!container) return;
172
+ let reqCtx;
173
+ try {
174
+ reqCtx = container.resolve(REQUEST_CONTEXT);
175
+ } catch {
176
+ return;
177
+ }
178
+ if (!state.authenticated) {
179
+ reqCtx.set(AUTH_USER_KEY, void 0);
180
+ reqCtx.set(AUTH_SESSION_KEY, void 0);
181
+ reqCtx.set(AUTH_ISSUER_KEY, void 0);
182
+ reqCtx.set(AUTH_PRINCIPAL_TYPE_KEY, void 0);
183
+ return;
184
+ }
185
+ reqCtx.set(AUTH_USER_KEY, state.user);
186
+ reqCtx.set(AUTH_SESSION_KEY, state.session);
187
+ reqCtx.set(AUTH_ISSUER_KEY, state.issuer);
188
+ reqCtx.set(AUTH_PRINCIPAL_TYPE_KEY, state.principalType);
189
+ }
190
+ function readActiveOrganizationId(session) {
191
+ const descriptor = Object.getOwnPropertyDescriptor(session, "activeOrganizationId");
192
+ if (descriptor === void 0 || !("value" in descriptor)) return void 0;
193
+ const value = descriptor.value;
194
+ return typeof value === "string" && value.length > 0 ? value : void 0;
195
+ }
196
+ /** A test/provider override is trusted code, but its runtime result is not. */
197
+ function validateSessionData(value) {
198
+ if (value === null || typeof value !== "object") return void 0;
199
+ try {
200
+ const userDescriptor = Object.getOwnPropertyDescriptor(value, "user");
201
+ const sessionDescriptor = Object.getOwnPropertyDescriptor(value, "session");
202
+ if (userDescriptor === void 0 || !("value" in userDescriptor) || sessionDescriptor === void 0 || !("value" in sessionDescriptor)) return;
203
+ const user = userDescriptor.value;
204
+ const session = sessionDescriptor.value;
205
+ if (user === null || typeof user !== "object" || session === null || typeof session !== "object") return;
206
+ const userIdDescriptor = Object.getOwnPropertyDescriptor(user, "id");
207
+ const sessionIdDescriptor = Object.getOwnPropertyDescriptor(session, "id");
208
+ const sessionUserIdDescriptor = Object.getOwnPropertyDescriptor(session, "userId");
209
+ const userId = userIdDescriptor !== void 0 && "value" in userIdDescriptor ? userIdDescriptor.value : void 0;
210
+ const sessionId = sessionIdDescriptor !== void 0 && "value" in sessionIdDescriptor ? sessionIdDescriptor.value : void 0;
211
+ const sessionUserId = sessionUserIdDescriptor !== void 0 && "value" in sessionUserIdDescriptor ? sessionUserIdDescriptor.value : void 0;
212
+ if (typeof userId !== "string" || userId.length === 0 || typeof sessionId !== "string" || sessionId.length === 0 || sessionUserId !== userId) return;
213
+ return {
214
+ user,
215
+ session
216
+ };
217
+ } catch {
218
+ return;
219
+ }
220
+ }
221
+ /**
222
+ * `UnauthorizedException` preserves Nest-style direct behavior. The structural
223
+ * VelaError brand also survives test/runtime package duplication, so the
224
+ * central renderer still maps this to 401 rather than treating it as a foreign
225
+ * 500 error.
226
+ */
227
+ var AuthenticationRequiredException = class extends UnauthorizedException {
228
+ type = "VelaError";
229
+ code = "unauthorized";
230
+ status = 401;
231
+ constructor() {
232
+ super("Authentication required");
233
+ }
234
+ };
235
+ //#endregion
236
+ //#region src/decorators/roles.decorator.ts
237
+ const Roles = Reflector.createDecorator({ key: "vela.auth.roles" });
238
+ const ROLES_KEY = Roles.KEY;
239
+ //#endregion
240
+ //#region src/guards/roles.guard.ts
241
+ const ACCESS_DENIED$1 = "Access denied";
242
+ let RolesGuard = class RolesGuard {
243
+ reflector = new Reflector();
244
+ canActivate(context) {
245
+ const required = this.reflector.getAllAndOverride(Roles, context);
246
+ if (!required || required.length === 0) return true;
247
+ const state = getAuthRequestState(context);
248
+ if (!state.authenticated) throw new ForbiddenException(ACCESS_DENIED$1);
249
+ const userRoles = normalizeRoles$1(state.user.role);
250
+ if (!required.some((r) => userRoles.includes(r))) throw new ForbiddenException(ACCESS_DENIED$1);
251
+ return true;
252
+ }
253
+ };
254
+ RolesGuard = __decorate([Injectable()], RolesGuard);
255
+ function normalizeRoles$1(role) {
256
+ if (!role) return [];
257
+ if (Array.isArray(role)) return role;
258
+ return role.split(",").map((r) => r.trim()).filter(Boolean);
259
+ }
260
+ //#endregion
261
+ //#region src/authz-bridge.ts
262
+ /** Stable issuer namespace used for better-auth session principals. */
263
+ const BETTER_AUTH_ISSUER = "better-auth";
264
+ const normalizeRoles = (role) => {
265
+ if (!role) return [];
266
+ if (Array.isArray(role)) return role.filter(Boolean);
267
+ return role.split(",").map((r) => r.trim()).filter(Boolean);
268
+ };
269
+ /**
270
+ * Adapts a better-auth user into a stable `@velajs/authz` {@link Identity}.
271
+ * The issuer scopes `user.id` as both `subject` and the compatibility `userId`;
272
+ * the admin-plugin `role` field supplies local roles.
273
+ *
274
+ * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated
275
+ * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream
276
+ * `can()` checks grant nothing.
277
+ */
278
+ const identityFromUser = (user, issuer = BETTER_AUTH_ISSUER, principalType = "user") => {
279
+ if (!user || typeof user.id !== "string" || user.id.length === 0) return { roles: [] };
280
+ if (issuer.length === 0) throw new Error("@velajs/better-auth: identity issuer must be non-empty");
281
+ return {
282
+ issuer,
283
+ subject: user.id,
284
+ principalType,
285
+ userId: user.id,
286
+ roles: normalizeRoles(user.role)
287
+ };
288
+ };
289
+ /**
290
+ * Flattens a better-auth AC role's `statements` into `resource:action`
291
+ * permission strings — the granted-side format `@velajs/authz` matches
292
+ * (wildcards included).
293
+ */
294
+ const permissionsFromAcRole = (role) => {
295
+ const permissions = [];
296
+ for (const [resource, actions] of Object.entries(role.statements ?? {})) for (const action of actions ?? []) permissions.push(`${resource}:${action}`);
297
+ return permissions;
298
+ };
299
+ /**
300
+ * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a
301
+ * better-auth access-control role table (`{ roleName: acRole }` — the same map
302
+ * shape passed to better-auth's admin/organization plugins). An identity's
303
+ * `roles` are unioned into their granted permission strings; unknown roles
304
+ * contribute nothing.
305
+ *
306
+ * ```ts
307
+ * const ac = createAccessControl({ posts: ['read', 'write'] });
308
+ * const authz = createAuthz({
309
+ * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),
310
+ * });
311
+ * await authz.can(identityFromUser(user), 'posts:write');
312
+ * ```
313
+ */
314
+ const betterAuthAcResolver = (roles) => {
315
+ const grantsByRole = /* @__PURE__ */ new Map();
316
+ for (const [name, role] of Object.entries(roles)) grantsByRole.set(name, permissionsFromAcRole(role));
317
+ return { grants(identity) {
318
+ const out = /* @__PURE__ */ new Set();
319
+ for (const name of identity.roles ?? []) for (const permission of grantsByRole.get(name) ?? []) out.add(permission);
320
+ return out;
321
+ } };
322
+ };
323
+ //#endregion
324
+ //#region src/decorators/require-permission.decorator.ts
325
+ /**
326
+ * Declares the `@velajs/authz` permission(s) required to reach a controller or
327
+ * route handler. Read via `Reflector` in an authorization guard, then checked
328
+ * against the caller's `Identity` with `authz.can(...)`.
329
+ *
330
+ * ```ts
331
+ * @RequirePermission(['posts:write'])
332
+ * @Post()
333
+ * create() { ... }
334
+ * ```
335
+ *
336
+ * The metadata is a plain `string[]` of permission strings in the granted-side
337
+ * format `@velajs/authz` matches (`resource:action`, or wildcards like
338
+ * `posts:*`). Handler-level metadata overrides class-level (standard
339
+ * `Reflector.getAllAndOverride` precedence).
340
+ *
341
+ * Semantics are **require-ALL** (AND): every listed permission must be granted
342
+ * for access — the `PermissionGuard` denies if any one is missing. This
343
+ * contrasts with `@Roles`, which is **OR** (any one of the listed roles
344
+ * suffices).
345
+ */
346
+ const RequirePermission = Reflector.createDecorator({ key: "vela.authz.permissions" });
347
+ const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;
348
+ //#endregion
349
+ //#region src/guards/permission.guard.ts
350
+ const AUTHZ_TOKEN = AUTHZ;
351
+ const ACCESS_DENIED = "Access denied";
352
+ function resolveSingleAuthz(container, moduleId) {
353
+ try {
354
+ if (typeof container.resolveAll === "function") {
355
+ const candidates = container.resolveAll(AUTHZ_TOKEN, moduleId);
356
+ return candidates.length === 1 ? candidates[0] : void 0;
357
+ }
358
+ return container.resolve(AUTHZ_TOKEN, moduleId);
359
+ } catch {
360
+ return;
361
+ }
362
+ }
363
+ let PermissionGuard = class PermissionGuard {
364
+ reflector = new Reflector();
365
+ async canActivate(context) {
366
+ const required = this.reflector.getAllAndOverride(RequirePermission, context);
367
+ if (!required || required.length === 0) return true;
368
+ const container = resolveContextContainer(context);
369
+ const moduleId = context.getModuleId();
370
+ const authz = container === void 0 || moduleId === void 0 ? void 0 : resolveSingleAuthz(container, moduleId);
371
+ if (!authz) throw new ForbiddenException(ACCESS_DENIED);
372
+ const identity = resolveContextIdentity(context);
373
+ if (identity === void 0) throw new ForbiddenException(ACCESS_DENIED);
374
+ for (const permission of required) if (!await authz.can(identity, permission)) throw new ForbiddenException(ACCESS_DENIED);
375
+ return true;
376
+ }
377
+ };
378
+ PermissionGuard = __decorate([Injectable()], PermissionGuard);
379
+ function resolveContextContainer(context) {
380
+ const direct = context.getContainer?.();
381
+ if (direct !== void 0) return direct;
382
+ if (context.getType() !== "http") return void 0;
383
+ try {
384
+ return context.getContext().get("container");
385
+ } catch {
386
+ return;
387
+ }
388
+ }
389
+ function resolveContextIdentity(context) {
390
+ if (context.getType() === "ws") try {
391
+ const data = context.switchToWs().getClient()?.data;
392
+ if (!data || typeof data !== "object") return void 0;
393
+ const record = data;
394
+ const principal = record.principal;
395
+ if (!principal || typeof principal !== "object") return void 0;
396
+ const fields = principal;
397
+ if (typeof fields.issuer !== "string" || fields.issuer.length === 0 || typeof fields.subject !== "string" || fields.subject.length === 0 || fields.principalType !== "user" && fields.principalType !== "service" || typeof record.tenantId !== "string" || record.tenantId.length === 0 || typeof record.expiresAtMs !== "number" || !Number.isSafeInteger(record.expiresAtMs) || record.expiresAtMs <= Date.now()) return;
398
+ return {
399
+ issuer: fields.issuer,
400
+ subject: fields.subject,
401
+ principalType: fields.principalType,
402
+ userId: fields.subject,
403
+ roles: []
404
+ };
405
+ } catch {
406
+ return;
407
+ }
408
+ const state = getAuthRequestState(context);
409
+ return state.authenticated ? identityFromUser(state.user, state.issuer, state.principalType) : void 0;
410
+ }
411
+ //#endregion
412
+ //#region src/better-auth.module.ts
413
+ const referenceIds = /* @__PURE__ */ new WeakMap();
414
+ const explicitKeyClaims = /* @__PURE__ */ new Map();
415
+ let nextReferenceId = 1;
416
+ function referenceId(reference) {
417
+ const existing = referenceIds.get(reference);
418
+ if (existing !== void 0) return existing;
419
+ const id = nextReferenceId++;
420
+ referenceIds.set(reference, id);
421
+ return id;
422
+ }
423
+ function claimExplicitKey(key, kind, reference, shape) {
424
+ if (key.length === 0 || key !== key.trim()) throw new Error("@velajs/better-auth: an explicit module key must be a non-empty string");
425
+ const existing = explicitKeyClaims.get(key);
426
+ if (existing !== void 0 && (existing.kind !== kind || existing.reference !== reference || existing.shape !== shape)) throw new Error(`@velajs/better-auth: explicit module key "${key}" is already bound to a different auth registration`);
427
+ explicitKeyClaims.set(key, {
428
+ kind,
429
+ reference,
430
+ shape
431
+ });
432
+ return `explicit:${key}:ref:${referenceId(reference)}`;
433
+ }
434
+ function normalize(options) {
435
+ const basePath = normalizeBetterAuthBasePath(options.basePath);
436
+ const issuer = options.issuer ?? `better-auth:${basePath}`;
437
+ if (issuer.length === 0 || issuer !== issuer.trim()) throw new Error("@velajs/better-auth: issuer must be a non-empty stable namespace");
438
+ if (options.defaultPolicy !== void 0 && options.defaultPolicy !== "deny") throw new Error("@velajs/better-auth: defaultPolicy is deny-only; mark anonymous routes with @Public() or @OptionalAuth()");
439
+ return {
440
+ basePath,
441
+ issuer,
442
+ isGlobal: options.isGlobal ?? true,
443
+ defaultPolicy: "deny",
444
+ mountHandler: options.mountHandler ?? true
445
+ };
446
+ }
447
+ /** Providers, controllers, and exports shared by both entry points. */
448
+ function commonContributions(n) {
449
+ return {
450
+ providers: [
451
+ BetterAuthService,
452
+ AuthGuard,
453
+ RolesGuard,
454
+ PermissionGuard
455
+ ],
456
+ controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],
457
+ exports: [
458
+ BetterAuthService,
459
+ BETTER_AUTH_OPTIONS,
460
+ AuthGuard,
461
+ RolesGuard,
462
+ PermissionGuard
463
+ ]
464
+ };
465
+ }
466
+ /**
467
+ * The blessed engine generates `forRoot`. `setup` runs once per instance at
468
+ * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,
469
+ * derives the auth builder from those options, mounts the catch-all controller,
470
+ * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.
471
+ *
472
+ * `isGlobal` here means "apply AuthGuard app-wide", NOT "make this a global
473
+ * module", so the default `isGlobal → global: true` extras transform is
474
+ * replaced with identity; the flag reaches `setup` through the options bag.
475
+ */
476
+ const authModuleHost = defineModule({
477
+ name: "BetterAuth",
478
+ optionsToken: BETTER_AUTH_OPTIONS,
479
+ transform: (definition) => definition,
480
+ key: (options) => stableHash(normalize(options)),
481
+ setup: ({ OPTIONS, options }) => {
482
+ const n = normalize(options);
483
+ const common = commonContributions(n);
484
+ const auth = options.auth;
485
+ return {
486
+ providers: [
487
+ {
488
+ provide: OPTIONS,
489
+ useValue: {
490
+ ...n,
491
+ auth
492
+ }
493
+ },
494
+ lazyProvider({
495
+ provide: BETTER_AUTH_BUILDER,
496
+ inject: [OPTIONS],
497
+ useFactory: (o) => o.auth
498
+ }),
499
+ ...common.providers
500
+ ],
501
+ controllers: common.controllers,
502
+ exports: common.exports,
503
+ global: n.isGlobal ? { guards: [AuthGuard] } : void 0
504
+ };
505
+ }
506
+ });
507
+ var BetterAuthModule = class BetterAuthModule {
508
+ /**
509
+ * Synchronous registration. The auth instance is constructed by the consumer
510
+ * at module-load time and passed in directly. Use this when the inputs to
511
+ * `betterAuth({...})` are available at startup (Node apps with a static DB
512
+ * connection, in-memory adapters, etc.).
513
+ */
514
+ static forRoot(options) {
515
+ const shape = stableHash(normalize(options));
516
+ const key = options.key === void 0 ? `${shape}:auth:${referenceId(options.auth)}` : claimExplicitKey(options.key, "auth", options.auth, shape);
517
+ return {
518
+ ...authModuleHost.ConfigurableModuleClass.forRoot({
519
+ ...options,
520
+ key
521
+ }),
522
+ module: BetterAuthModule
523
+ };
524
+ }
525
+ /**
526
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on the
527
+ * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
528
+ * In normal request handling that's `AuthGuard.canActivate` or the catch-all
529
+ * controller's `.handle`. At module load the factory does NOT run — it's only
530
+ * captured behind {@link lazyProvider}'s memoized thunk. This is what makes
531
+ * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but
532
+ * it IS by the time a request flows through and the guard / catch-all reads
533
+ * the service. Inject deps resolve at module load (cheap BindingRef wrappers);
534
+ * their *values* are read at first auth use, inside your factory body.
535
+ */
536
+ static forRootAsync(options) {
537
+ const n = normalize(options);
538
+ const common = commonContributions(n);
539
+ const shape = stableHash({
540
+ ...n,
541
+ inject: options.inject
542
+ });
543
+ const key = options.key === void 0 ? `${shape}:factory:${referenceId(options.useFactory)}` : claimExplicitKey(options.key, "factory", options.useFactory, shape);
544
+ return {
545
+ module: BetterAuthModule,
546
+ key,
547
+ imports: options.imports ?? [],
548
+ providers: [
549
+ {
550
+ provide: BETTER_AUTH_OPTIONS,
551
+ useValue: n
552
+ },
553
+ lazyProvider({
554
+ provide: BETTER_AUTH_BUILDER,
555
+ inject: options.inject,
556
+ useFactory: options.useFactory
557
+ }),
558
+ ...common.providers,
559
+ ...n.isGlobal ? provideGlobal("guard", AuthGuard) : []
560
+ ],
561
+ controllers: common.controllers,
562
+ exports: common.exports
563
+ };
564
+ }
565
+ };
566
+ //#endregion
567
+ //#region src/decorators/current-user.decorator.ts
568
+ const CurrentUser = createParamDecorator((_data, ctx) => {
569
+ const state = getAuthRequestState(ctx);
570
+ return state.authenticated ? state.user : void 0;
571
+ });
572
+ //#endregion
573
+ //#region src/decorators/current-session.decorator.ts
574
+ const CurrentSession = createParamDecorator((_data, ctx) => {
575
+ const state = getAuthRequestState(ctx);
576
+ return state.authenticated ? state.session : void 0;
577
+ });
578
+ //#endregion
579
+ export { AUTH_ISSUER_KEY, AUTH_PRINCIPAL_TYPE_KEY, AUTH_SESSION_KEY, AUTH_USER_KEY, AuthGuard, BETTER_AUTH_ISSUER, BETTER_AUTH_OPTIONS, BetterAuthCatchallController, BetterAuthModule, BetterAuthService, CurrentSession, CurrentUser, OPTIONAL_AUTH_KEY, OptionalAuth, PUBLIC_KEY, PermissionGuard, Public, REQUIRE_PERMISSION_KEY, ROLES_KEY, RequirePermission, Roles, RolesGuard, betterAuthAcResolver, createBetterAuthCatchallController, identityFromUser, permissionsFromAcRole };
580
+
581
+ //# sourceMappingURL=index.js.map