@velajs/better-auth 0.6.1 → 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.
- package/CHANGELOG.md +6 -0
- package/README.md +19 -14
- package/dist/index.d.ts +25 -15
- package/dist/index.js +264 -47
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- a468b57: Run guards before ordinary identity parameters, install authentication globally and deny application routes by default, remove the insecure `defaultPolicy: 'allow'` mode and implicit auth-path bypass, enforce canonical auth mount paths, reject ambiguous authorization engines, and key module instances by the actual auth/factory reference. Anonymous routes must now use explicit `@Public()` or `@OptionalAuth()` metadata. Verified sessions now publish Vela's framework-owned principal identity and, when present, the Better Auth organization plugin's `activeOrganizationId` so downstream throttling can partition by principal and tenant before IP fallback.
|
|
8
|
+
|
|
3
9
|
## 0.6.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ pnpm add @velajs/better-auth better-auth
|
|
|
8
8
|
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
11
|
-
Construct your better-auth instance once
|
|
11
|
+
Construct your better-auth instance once and hand it to `BetterAuthModule.forRoot`. The module installs `AuthGuard` application-wide by default; use `@CurrentUser()` on authenticated routes and mark the small anonymous surface explicitly.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import { betterAuth } from 'better-auth';
|
|
15
|
-
import { Module, Controller, Get,
|
|
15
|
+
import { Module, Controller, Get, VelaFactory } from '@velajs/vela';
|
|
16
16
|
import {
|
|
17
|
-
BetterAuthModule,
|
|
17
|
+
BetterAuthModule, CurrentUser, Public,
|
|
18
18
|
} from '@velajs/better-auth';
|
|
19
19
|
|
|
20
20
|
const auth = betterAuth({
|
|
@@ -24,7 +24,6 @@ const auth = betterAuth({
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
@Controller('/me')
|
|
27
|
-
@UseGuards(AuthGuard)
|
|
28
27
|
class MeController {
|
|
29
28
|
@Get() me(@CurrentUser() user: { id: string; email: string }) {
|
|
30
29
|
return { id: user.id, email: user.email };
|
|
@@ -35,7 +34,7 @@ class MeController {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
@Module({
|
|
38
|
-
imports: [BetterAuthModule.forRoot({ auth
|
|
37
|
+
imports: [BetterAuthModule.forRoot({ auth })],
|
|
39
38
|
controllers: [MeController],
|
|
40
39
|
})
|
|
41
40
|
class AppModule {}
|
|
@@ -51,13 +50,15 @@ export default app; // edge-compatible (.fetch)
|
|
|
51
50
|
```ts
|
|
52
51
|
BetterAuthModule.forRoot({
|
|
53
52
|
auth, // pre-constructed betterAuth({ ... }) instance
|
|
53
|
+
issuer: 'my-app:better-auth', // stable namespace paired with user ids
|
|
54
54
|
basePath: '/api/auth', // default — must match your better-auth config
|
|
55
|
-
isGlobal:
|
|
56
|
-
defaultPolicy: 'deny', // 'deny' | 'allow' for unauthenticated requests
|
|
55
|
+
isGlobal: true, // default — register AuthGuard as APP_GUARD
|
|
57
56
|
mountHandler: true, // mount /api/auth/* catch-all controller
|
|
58
57
|
});
|
|
59
58
|
```
|
|
60
59
|
|
|
60
|
+
Authentication has no allow-by-default compatibility mode. Use `@Public(true)` for routes that intentionally skip authentication, or `@OptionalAuth(true)` when the route accepts an anonymous identity. `isGlobal: false` is intended only for applications that install an equivalent global authentication guard themselves.
|
|
61
|
+
|
|
61
62
|
## Three composition patterns
|
|
62
63
|
|
|
63
64
|
### Pattern A — inline (simplest)
|
|
@@ -170,26 +171,30 @@ Under `forRootAsync`, the underlying `betterAuth({...})` instance is constructed
|
|
|
170
171
|
|
|
171
172
|
| Decorator | Purpose |
|
|
172
173
|
| ------------------- | ------------------------------------------------------------------------ |
|
|
173
|
-
| `@CurrentUser()` |
|
|
174
|
-
| `@CurrentSession()` |
|
|
174
|
+
| `@CurrentUser()` | Better-auth `User` from the request after guards run |
|
|
175
|
+
| `@CurrentSession()` | Better-auth `Session` after guards run |
|
|
175
176
|
| `@Public(true)` | Class or method — bypass AuthGuard entirely |
|
|
176
177
|
| `@OptionalAuth(true)` | Class or method — populate user if present, never throw 401 |
|
|
177
178
|
| `@Roles(['admin'])` | Method — read by `RolesGuard`. Compares against `user.role`. |
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
Optional identities are ordinary values: an anonymous caller receives the actual `undefined`, so normal truthiness checks are safe.
|
|
180
181
|
|
|
181
182
|
```ts
|
|
182
183
|
handle(@CurrentUser() user: User | undefined) {
|
|
183
|
-
return { hasUser: user
|
|
184
|
+
return { hasUser: Boolean(user) };
|
|
184
185
|
}
|
|
185
186
|
```
|
|
186
187
|
|
|
187
188
|
## Guards
|
|
188
189
|
|
|
189
|
-
- **`AuthGuard`** — singleton. Reads `Authorization` header / cookies via `auth.api.getSession`, populates `REQUEST_CONTEXT
|
|
190
|
+
- **`AuthGuard`** — singleton. Reads `Authorization` header / cookies via `auth.api.getSession`, populates `REQUEST_CONTEXT`, and publishes Vela's trusted principal/tenant identity for downstream security components. The Better Auth organization plugin's verified `activeOrganizationId` becomes the tenant partition when present. The guard honors only explicit `@Public()` / `@OptionalAuth()` metadata. The generated Better Auth catch-all controller is explicitly public; sharing its URL prefix never makes an application controller public.
|
|
190
191
|
- **`RolesGuard`** — singleton. Reads `@Roles([...])` metadata, compares against `user.role`. Use with `@UseGuards(AuthGuard, RolesGuard)` — order matters.
|
|
191
192
|
|
|
192
|
-
Global registration
|
|
193
|
+
Global registration is the default: installing the module binds `AuthGuard` to `APP_GUARD`. Routes are deny-by-default; mark public ones with `@Public(true)`. The generated Better Auth controller is already marked public.
|
|
194
|
+
|
|
195
|
+
When using `ThrottlerModule`, import Better Auth first. Vela then rate-limits by
|
|
196
|
+
the verified issuer, subject, principal type, and active organization before it
|
|
197
|
+
falls back to a platform-attested client address.
|
|
193
198
|
|
|
194
199
|
## Edge-safe DB adapters
|
|
195
200
|
|
|
@@ -223,7 +228,7 @@ class CustomCatchallController {
|
|
|
223
228
|
}
|
|
224
229
|
```
|
|
225
230
|
|
|
226
|
-
Pass `basePath: '/auth'` to `BetterAuthModule.forRoot`
|
|
231
|
+
Pass `basePath: '/auth'` to `BetterAuthModule.forRoot` to mount the generated controller there, and keep your `betterAuth({ basePath: '/auth' })` config in sync. A custom catch-all must carry `@Public(true)` itself. Base paths must be canonical absolute paths: no root mount, trailing slash, wildcards, query/fragment, backslashes, or dot segments.
|
|
227
232
|
|
|
228
233
|
## License
|
|
229
234
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,9 +5,16 @@ import { Identity, PermissionResolver } from "@velajs/authz";
|
|
|
5
5
|
type BetterAuthInstance = Auth<any>;
|
|
6
6
|
interface BetterAuthModuleOptions {
|
|
7
7
|
auth: BetterAuthInstance;
|
|
8
|
+
/** Stable namespace paired with user ids in authorization identities. */
|
|
9
|
+
issuer?: string;
|
|
8
10
|
basePath?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Register AuthGuard application-wide. Defaults to `true`; opt out only when
|
|
13
|
+
* the application installs an equivalent global authentication guard itself.
|
|
14
|
+
*/
|
|
9
15
|
isGlobal?: boolean;
|
|
10
|
-
|
|
16
|
+
/** @deprecated Authentication is deny-by-default. Only `'deny'` is accepted. */
|
|
17
|
+
defaultPolicy?: 'deny';
|
|
11
18
|
mountHandler?: boolean;
|
|
12
19
|
}
|
|
13
20
|
type User = User$1;
|
|
@@ -36,7 +43,9 @@ interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonl
|
|
|
36
43
|
isGlobal?: boolean;
|
|
37
44
|
mountHandler?: boolean;
|
|
38
45
|
basePath?: string;
|
|
39
|
-
|
|
46
|
+
issuer?: string;
|
|
47
|
+
/** @deprecated Authentication is deny-by-default. Only `'deny'` is accepted. */
|
|
48
|
+
defaultPolicy?: 'deny';
|
|
40
49
|
key?: string;
|
|
41
50
|
}
|
|
42
51
|
declare class BetterAuthModule {
|
|
@@ -124,6 +133,8 @@ declare const BetterAuthCatchallController: Type;
|
|
|
124
133
|
declare const BETTER_AUTH_OPTIONS: InjectionToken<BetterAuthModuleOptions>;
|
|
125
134
|
declare const AUTH_USER_KEY: unique symbol;
|
|
126
135
|
declare const AUTH_SESSION_KEY: unique symbol;
|
|
136
|
+
declare const AUTH_ISSUER_KEY: unique symbol;
|
|
137
|
+
declare const AUTH_PRINCIPAL_TYPE_KEY: unique symbol;
|
|
127
138
|
//#endregion
|
|
128
139
|
//#region src/guards/auth.guard.d.ts
|
|
129
140
|
declare class AuthGuard implements CanActivate {
|
|
@@ -146,22 +157,18 @@ declare class RolesGuard implements CanActivate {
|
|
|
146
157
|
* engine. For each required permission it calls `authz.can(identity, perm)`,
|
|
147
158
|
* requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which
|
|
148
159
|
* is OR over roles). The caller's `Identity` is derived from the better-auth
|
|
149
|
-
* user that {@link AuthGuard} placed in
|
|
160
|
+
* user that {@link AuthGuard} placed in canonical request-local auth state, so this guard must
|
|
150
161
|
* run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).
|
|
151
162
|
*
|
|
152
|
-
* `AUTHZ` is resolved at
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* not `AuthzModule` is registered as global — a present-but-non-global
|
|
156
|
-
* `AuthzModule` resolves fine and, crucially, never crashes bootstrap. If
|
|
157
|
-
* `AuthzModule` is not registered at all the resolve fails and the guard fails
|
|
158
|
-
* closed (403) rather than granting access.
|
|
163
|
+
* `AUTHZ` is resolved at request time from the per-request container. Exactly
|
|
164
|
+
* one reachable engine is required; zero or multiple registrations deny rather
|
|
165
|
+
* than selecting one by import order.
|
|
159
166
|
*
|
|
160
167
|
* Fail-closed on every abnormal path — no branch grants access on missing
|
|
161
168
|
* wiring or a missing caller:
|
|
162
169
|
* - no required permissions → allow (nothing to enforce);
|
|
163
170
|
* - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);
|
|
164
|
-
* - no authenticated user in
|
|
171
|
+
* - no authenticated user in request-local auth state → deny;
|
|
165
172
|
* - any single required permission not granted → deny.
|
|
166
173
|
*
|
|
167
174
|
* The guard is stateless (no injected dependencies), so it is safe to register
|
|
@@ -224,15 +231,18 @@ declare const REQUIRE_PERMISSION_KEY: string;
|
|
|
224
231
|
type AuthUser = User & {
|
|
225
232
|
role?: string | string[] | null;
|
|
226
233
|
};
|
|
234
|
+
/** Stable issuer namespace used for better-auth session principals. */
|
|
235
|
+
declare const BETTER_AUTH_ISSUER = "better-auth";
|
|
227
236
|
/**
|
|
228
|
-
* Adapts a better-auth user into a `@velajs/authz` {@link Identity}.
|
|
229
|
-
* `user.id`
|
|
237
|
+
* Adapts a better-auth user into a stable `@velajs/authz` {@link Identity}.
|
|
238
|
+
* The issuer scopes `user.id` as both `subject` and the compatibility `userId`;
|
|
239
|
+
* the admin-plugin `role` field supplies local roles.
|
|
230
240
|
*
|
|
231
241
|
* Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated
|
|
232
242
|
* request) maps to the zero-privilege identity `{ roles: [] }`, so downstream
|
|
233
243
|
* `can()` checks grant nothing.
|
|
234
244
|
*/
|
|
235
|
-
declare const identityFromUser: (user: AuthUser | null | undefined) => Identity;
|
|
245
|
+
declare const identityFromUser: (user: AuthUser | null | undefined, issuer?: string, principalType?: 'user' | 'service') => Identity;
|
|
236
246
|
/**
|
|
237
247
|
* The minimal slice of a better-auth access-control role consumed here. Both
|
|
238
248
|
* `createAccessControl(...).newRole(...)` and the standalone `role(...)` return
|
|
@@ -266,5 +276,5 @@ declare const permissionsFromAcRole: (role: BetterAuthAcRole) => string[];
|
|
|
266
276
|
*/
|
|
267
277
|
declare const betterAuthAcResolver: (roles: Readonly<Record<string, BetterAuthAcRole>>) => PermissionResolver;
|
|
268
278
|
//#endregion
|
|
269
|
-
export { AUTH_SESSION_KEY, AUTH_USER_KEY, AuthGuard, type AuthUser, BETTER_AUTH_OPTIONS, type BetterAuthAcRole, BetterAuthCatchallController, type BetterAuthInstance, BetterAuthModule, type BetterAuthModuleOptions, BetterAuthService, CurrentSession, CurrentUser, OPTIONAL_AUTH_KEY, OptionalAuth, PUBLIC_KEY, PermissionGuard, Public, REQUIRE_PERMISSION_KEY, ROLES_KEY, RequirePermission, Roles, RolesGuard, type Session, type User, betterAuthAcResolver, createBetterAuthCatchallController, identityFromUser, permissionsFromAcRole };
|
|
279
|
+
export { AUTH_ISSUER_KEY, AUTH_PRINCIPAL_TYPE_KEY, AUTH_SESSION_KEY, AUTH_USER_KEY, AuthGuard, type AuthUser, BETTER_AUTH_ISSUER, BETTER_AUTH_OPTIONS, type BetterAuthAcRole, BetterAuthCatchallController, type BetterAuthInstance, BetterAuthModule, type BetterAuthModuleOptions, BetterAuthService, CurrentSession, CurrentUser, OPTIONAL_AUTH_KEY, OptionalAuth, PUBLIC_KEY, PermissionGuard, Public, REQUIRE_PERMISSION_KEY, ROLES_KEY, RequirePermission, Roles, RolesGuard, type Session, type User, betterAuthAcResolver, createBetterAuthCatchallController, identityFromUser, permissionsFromAcRole };
|
|
270
280
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
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,
|
|
2
|
+
import { All, Controller, ForbiddenException, Inject, Injectable, InjectionToken, REQUEST_CONTEXT, Reflector, Req, UnauthorizedException, clearTrustedRequestIdentity, createParamDecorator, defineModule, lazyProvider, provideGlobal, setTrustedRequestIdentity, stableHash } from "@velajs/vela";
|
|
3
3
|
import { AUTHZ } from "@velajs/authz/vela";
|
|
4
4
|
//#region src/decorators/public.decorator.ts
|
|
5
5
|
const Public = Reflector.createDecorator({ key: "vela.auth.public" });
|
|
6
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
|
+
}
|
|
7
20
|
//#endregion
|
|
8
21
|
//#region src/better-auth.controller.ts
|
|
9
22
|
/**
|
|
@@ -20,6 +33,7 @@ const PUBLIC_KEY = Public.KEY;
|
|
|
20
33
|
* Both default to `/api/auth`, so the no-prefix / no-config case just works.
|
|
21
34
|
*/
|
|
22
35
|
function createBetterAuthCatchallController(basePath = "/api/auth") {
|
|
36
|
+
const normalizedBasePath = normalizeBetterAuthBasePath(basePath);
|
|
23
37
|
let BetterAuthCatchallController = class BetterAuthCatchallController {
|
|
24
38
|
auth;
|
|
25
39
|
constructor(auth) {
|
|
@@ -38,7 +52,7 @@ function createBetterAuthCatchallController(basePath = "/api/auth") {
|
|
|
38
52
|
], BetterAuthCatchallController.prototype, "handle", null);
|
|
39
53
|
BetterAuthCatchallController = __decorate([
|
|
40
54
|
Public(true),
|
|
41
|
-
Controller(
|
|
55
|
+
Controller(normalizedBasePath),
|
|
42
56
|
Injectable(),
|
|
43
57
|
__decorateParam(0, Inject(BetterAuthService)),
|
|
44
58
|
__decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService])
|
|
@@ -56,6 +70,27 @@ const BetterAuthCatchallController = createBetterAuthCatchallController();
|
|
|
56
70
|
const BETTER_AUTH_OPTIONS = new InjectionToken("vela.BetterAuthOptions");
|
|
57
71
|
const AUTH_USER_KEY = Symbol.for("vela.better-auth.user");
|
|
58
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;
|
|
59
94
|
//#endregion
|
|
60
95
|
//#region src/decorators/optional-auth.decorator.ts
|
|
61
96
|
const OptionalAuth = Reflector.createDecorator({ key: "vela.auth.optional" });
|
|
@@ -71,20 +106,26 @@ let AuthGuard = class AuthGuard {
|
|
|
71
106
|
this.opts = opts;
|
|
72
107
|
}
|
|
73
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 });
|
|
74
115
|
if (this.reflector.getAllAndOverride(Public, context)) return true;
|
|
75
116
|
const request = context.getRequest();
|
|
76
|
-
const
|
|
77
|
-
const basePath = this.opts.basePath ?? "/api/auth";
|
|
78
|
-
if (path === basePath || path.startsWith(`${basePath}/`)) return true;
|
|
79
|
-
const data = await this.auth.api.getSession({ headers: request.headers });
|
|
117
|
+
const data = validateSessionData(await this.auth.api.getSession({ headers: request.headers }));
|
|
80
118
|
if (data) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
mirrorRequestContext(context, authenticateRequest(context, {
|
|
120
|
+
user: data.user,
|
|
121
|
+
session: data.session,
|
|
122
|
+
issuer: this.opts.issuer ?? "better-auth",
|
|
123
|
+
principalType: "user"
|
|
124
|
+
}));
|
|
84
125
|
return true;
|
|
85
126
|
}
|
|
86
|
-
if (this.
|
|
87
|
-
throw new
|
|
127
|
+
if (this.reflector.getAllAndOverride(OptionalAuth, context)) return true;
|
|
128
|
+
throw new AuthenticationRequiredException();
|
|
88
129
|
}
|
|
89
130
|
};
|
|
90
131
|
AuthGuard = __decorate([
|
|
@@ -93,24 +134,120 @@ AuthGuard = __decorate([
|
|
|
93
134
|
__decorateParam(1, Inject(BETTER_AUTH_OPTIONS)),
|
|
94
135
|
__decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService, Object])
|
|
95
136
|
], AuthGuard);
|
|
96
|
-
function
|
|
97
|
-
|
|
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
|
+
}
|
|
98
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
|
+
};
|
|
99
235
|
//#endregion
|
|
100
236
|
//#region src/decorators/roles.decorator.ts
|
|
101
237
|
const Roles = Reflector.createDecorator({ key: "vela.auth.roles" });
|
|
102
238
|
const ROLES_KEY = Roles.KEY;
|
|
103
239
|
//#endregion
|
|
104
240
|
//#region src/guards/roles.guard.ts
|
|
241
|
+
const ACCESS_DENIED$1 = "Access denied";
|
|
105
242
|
let RolesGuard = class RolesGuard {
|
|
106
243
|
reflector = new Reflector();
|
|
107
244
|
canActivate(context) {
|
|
108
245
|
const required = this.reflector.getAllAndOverride(Roles, context);
|
|
109
246
|
if (!required || required.length === 0) return true;
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
112
|
-
const userRoles = normalizeRoles$1(user.role);
|
|
113
|
-
if (!required.some((r) => userRoles.includes(r))) throw new ForbiddenException(
|
|
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);
|
|
114
251
|
return true;
|
|
115
252
|
}
|
|
116
253
|
};
|
|
@@ -122,22 +259,29 @@ function normalizeRoles$1(role) {
|
|
|
122
259
|
}
|
|
123
260
|
//#endregion
|
|
124
261
|
//#region src/authz-bridge.ts
|
|
262
|
+
/** Stable issuer namespace used for better-auth session principals. */
|
|
263
|
+
const BETTER_AUTH_ISSUER = "better-auth";
|
|
125
264
|
const normalizeRoles = (role) => {
|
|
126
265
|
if (!role) return [];
|
|
127
266
|
if (Array.isArray(role)) return role.filter(Boolean);
|
|
128
267
|
return role.split(",").map((r) => r.trim()).filter(Boolean);
|
|
129
268
|
};
|
|
130
269
|
/**
|
|
131
|
-
* Adapts a better-auth user into a `@velajs/authz` {@link Identity}.
|
|
132
|
-
* `user.id`
|
|
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.
|
|
133
273
|
*
|
|
134
274
|
* Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated
|
|
135
275
|
* request) maps to the zero-privilege identity `{ roles: [] }`, so downstream
|
|
136
276
|
* `can()` checks grant nothing.
|
|
137
277
|
*/
|
|
138
|
-
const identityFromUser = (user) => {
|
|
139
|
-
if (!user) return { roles: [] };
|
|
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");
|
|
140
281
|
return {
|
|
282
|
+
issuer,
|
|
283
|
+
subject: user.id,
|
|
284
|
+
principalType,
|
|
141
285
|
userId: user.id,
|
|
142
286
|
roles: normalizeRoles(user.role)
|
|
143
287
|
};
|
|
@@ -204,35 +348,99 @@ const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;
|
|
|
204
348
|
//#endregion
|
|
205
349
|
//#region src/guards/permission.guard.ts
|
|
206
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
|
+
}
|
|
207
363
|
let PermissionGuard = class PermissionGuard {
|
|
208
364
|
reflector = new Reflector();
|
|
209
365
|
async canActivate(context) {
|
|
210
366
|
const required = this.reflector.getAllAndOverride(RequirePermission, context);
|
|
211
367
|
if (!required || required.length === 0) return true;
|
|
212
|
-
const container = context
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!authz) throw new ForbiddenException("Authorization is not configured");
|
|
220
|
-
const user = container.resolve(REQUEST_CONTEXT).get(AUTH_USER_KEY);
|
|
221
|
-
if (!user) throw new ForbiddenException("Permission check requires authentication");
|
|
222
|
-
const identity = identityFromUser(user);
|
|
223
|
-
for (const permission of required) if (!await authz.can(identity, permission)) throw new ForbiddenException(`Missing permission: ${permission}`);
|
|
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);
|
|
224
375
|
return true;
|
|
225
376
|
}
|
|
226
377
|
};
|
|
227
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
|
+
}
|
|
228
411
|
//#endregion
|
|
229
412
|
//#region src/better-auth.module.ts
|
|
230
|
-
const
|
|
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
|
+
}
|
|
231
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()");
|
|
232
439
|
return {
|
|
233
|
-
basePath
|
|
234
|
-
|
|
235
|
-
|
|
440
|
+
basePath,
|
|
441
|
+
issuer,
|
|
442
|
+
isGlobal: options.isGlobal ?? true,
|
|
443
|
+
defaultPolicy: "deny",
|
|
236
444
|
mountHandler: options.mountHandler ?? true
|
|
237
445
|
};
|
|
238
446
|
}
|
|
@@ -304,8 +512,13 @@ var BetterAuthModule = class BetterAuthModule {
|
|
|
304
512
|
* connection, in-memory adapters, etc.).
|
|
305
513
|
*/
|
|
306
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);
|
|
307
517
|
return {
|
|
308
|
-
...authModuleHost.ConfigurableModuleClass.forRoot(
|
|
518
|
+
...authModuleHost.ConfigurableModuleClass.forRoot({
|
|
519
|
+
...options,
|
|
520
|
+
key
|
|
521
|
+
}),
|
|
309
522
|
module: BetterAuthModule
|
|
310
523
|
};
|
|
311
524
|
}
|
|
@@ -323,12 +536,14 @@ var BetterAuthModule = class BetterAuthModule {
|
|
|
323
536
|
static forRootAsync(options) {
|
|
324
537
|
const n = normalize(options);
|
|
325
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);
|
|
326
544
|
return {
|
|
327
545
|
module: BetterAuthModule,
|
|
328
|
-
key
|
|
329
|
-
...n,
|
|
330
|
-
inject: options.inject
|
|
331
|
-
}),
|
|
546
|
+
key,
|
|
332
547
|
imports: options.imports ?? [],
|
|
333
548
|
providers: [
|
|
334
549
|
{
|
|
@@ -350,15 +565,17 @@ var BetterAuthModule = class BetterAuthModule {
|
|
|
350
565
|
};
|
|
351
566
|
//#endregion
|
|
352
567
|
//#region src/decorators/current-user.decorator.ts
|
|
353
|
-
const CurrentUser =
|
|
354
|
-
|
|
568
|
+
const CurrentUser = createParamDecorator((_data, ctx) => {
|
|
569
|
+
const state = getAuthRequestState(ctx);
|
|
570
|
+
return state.authenticated ? state.user : void 0;
|
|
355
571
|
});
|
|
356
572
|
//#endregion
|
|
357
573
|
//#region src/decorators/current-session.decorator.ts
|
|
358
|
-
const CurrentSession =
|
|
359
|
-
|
|
574
|
+
const CurrentSession = createParamDecorator((_data, ctx) => {
|
|
575
|
+
const state = getAuthRequestState(ctx);
|
|
576
|
+
return state.authenticated ? state.session : void 0;
|
|
360
577
|
});
|
|
361
578
|
//#endregion
|
|
362
|
-
export { AUTH_SESSION_KEY, AUTH_USER_KEY, AuthGuard, 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 };
|
|
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 };
|
|
363
580
|
|
|
364
581
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["normalizeRoles"],"sources":["../src/decorators/public.decorator.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/decorators/roles.decorator.ts","../src/guards/roles.guard.ts","../src/authz-bridge.ts","../src/decorators/require-permission.decorator.ts","../src/guards/permission.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","import { All, Controller, Inject, Injectable, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n @Public(true)\n @Controller(basePath)\n @Injectable()\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n\n/**\n * Default-path (`/api/auth`) catch-all controller. Retained for back-compat;\n * `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with\n * the configured `basePath`. Prefer the factory for a custom base path.\n */\nexport const BetterAuthCatchallController = createBetterAuthCatchallController();\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthModuleOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthModuleOptions>(\n 'vela.BetterAuthOptions',\n);\n\nexport const AUTH_USER_KEY = Symbol.for('vela.better-auth.user');\nexport const AUTH_SESSION_KEY = Symbol.for('vela.better-auth.session');\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n REQUEST_CONTEXT,\n Reflector,\n UnauthorizedException,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_SESSION_KEY, AUTH_USER_KEY, BETTER_AUTH_OPTIONS } from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthModuleOptions } from '../better-auth.types';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n constructor(\n // Inject BetterAuthService rather than the raw better-auth instance.\n // The service's lazy `.auth` getter defers construction to first use, so\n // forRootAsync factories that depend on values only available at\n // request time (Cloudflare D1/KV bindings, etc.) build safely on the\n // first canActivate — not at module-load bootstrap.\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthModuleOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const request = context.getRequest();\n const path = new URL(request.url).pathname;\n const basePath = this.opts.basePath ?? '/api/auth';\n if (path === basePath || path.startsWith(`${basePath}/`)) return true;\n\n const data = await this.auth.api.getSession({ headers: request.headers });\n\n if (data) {\n const reqCtx = resolveRequestContext(context);\n reqCtx.set(AUTH_USER_KEY, data.user);\n reqCtx.set(AUTH_SESSION_KEY, data.session);\n return true;\n }\n\n if (\n this.opts.defaultPolicy === 'allow' ||\n this.reflector.getAllAndOverride(OptionalAuth, context)\n ) {\n return true;\n }\n\n throw new UnauthorizedException('Authentication required');\n }\n}\n\ninterface ContainerLike {\n resolve<T>(token: unknown): T;\n}\n\nfunction resolveRequestContext(context: ExecutionContext): RequestContext {\n const honoCtx = context.getContext() as { get: (k: string) => ContainerLike };\n const container = honoCtx.get('container');\n return container.resolve<RequestContext>(REQUEST_CONTEXT);\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const Roles = Reflector.createDecorator<string[]>({ key: 'vela.auth.roles' });\nexport const ROLES_KEY = Roles.KEY;\n","import {\n ForbiddenException,\n Injectable,\n REQUEST_CONTEXT,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\nimport { Roles } from '../decorators/roles.decorator';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n canActivate(context: ExecutionContext): boolean {\n const required = this.reflector.getAllAndOverride(Roles, context);\n if (!required || required.length === 0) return true;\n\n const honoCtx = context.getContext() as {\n get: (k: string) => { resolve<T>(t: unknown): T };\n };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n const user = reqCtx.get<User & { role?: string | string[] }>(AUTH_USER_KEY);\n if (!user) {\n throw new ForbiddenException('Role check requires authentication');\n }\n\n const userRoles = normalizeRoles(user.role);\n const ok = required.some((r) => userRoles.includes(r));\n if (!ok) {\n throw new ForbiddenException(`Insufficient role; one of [${required.join(', ')}] required`);\n }\n return true;\n }\n}\n\nfunction normalizeRoles(role: string | string[] | undefined): string[] {\n if (!role) return [];\n if (Array.isArray(role)) return role;\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n}\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = User & { role?: string | string[] | null };\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Adapts a better-auth user into a `@velajs/authz` {@link Identity}. Maps\n * `user.id` → `userId` and the admin-plugin `role` field → `roles`.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (user: AuthUser | null | undefined): Identity => {\n if (!user) return { roles: [] };\n return {\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n","import { Reflector } from '@velajs/vela';\n\n/**\n * Declares the `@velajs/authz` permission(s) required to reach a controller or\n * route handler. Read via `Reflector` in an authorization guard, then checked\n * against the caller's `Identity` with `authz.can(...)`.\n *\n * ```ts\n * @RequirePermission(['posts:write'])\n * @Post()\n * create() { ... }\n * ```\n *\n * The metadata is a plain `string[]` of permission strings in the granted-side\n * format `@velajs/authz` matches (`resource:action`, or wildcards like\n * `posts:*`). Handler-level metadata overrides class-level (standard\n * `Reflector.getAllAndOverride` precedence).\n *\n * Semantics are **require-ALL** (AND): every listed permission must be granted\n * for access — the `PermissionGuard` denies if any one is missing. This\n * contrasts with `@Roles`, which is **OR** (any one of the listed roles\n * suffices).\n */\nexport const RequirePermission = Reflector.createDecorator<string[]>({\n key: 'vela.authz.permissions',\n});\n\nexport const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;\n","import {\n ForbiddenException,\n Injectable,\n InjectionToken,\n REQUEST_CONTEXT,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTHZ } from '@velajs/authz/vela';\nimport type { Authz } from '@velajs/authz';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\nimport { identityFromUser } from '../authz-bridge';\nimport { RequirePermission } from '../decorators/require-permission.decorator';\n\n// The linked `@velajs/authz` is built against its own (newer) `@velajs/vela`\n// copy, so the `AUTHZ` token's `InjectionToken` type is nominally distinct from\n// this package's `InjectionToken` — even though it is the very same runtime\n// token object (the DI container matches tokens by object identity). Re-type it\n// to the local `InjectionToken` so the request-time `container.resolve(...)`\n// accepts it without a structural clash. This is purely a compile-time alias;\n// it changes nothing at runtime. (Version-skew workaround until both publish.)\nconst AUTHZ_TOKEN = AUTHZ as unknown as InjectionToken<Authz>;\n\n/**\n * Enforces the `@RequirePermission(...)` metadata against the `@velajs/authz`\n * engine. For each required permission it calls `authz.can(identity, perm)`,\n * requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which\n * is OR over roles). The caller's `Identity` is derived from the better-auth\n * user that {@link AuthGuard} placed in the request context, so this guard must\n * run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).\n *\n * `AUTHZ` is resolved at **request time** from the per-request container (the\n * same container `REQUEST_CONTEXT` is resolved from), not constructor-injected.\n * This deliberately avoids DI visibility coupling: the guard works whether or\n * not `AuthzModule` is registered as global — a present-but-non-global\n * `AuthzModule` resolves fine and, crucially, never crashes bootstrap. If\n * `AuthzModule` is not registered at all the resolve fails and the guard fails\n * closed (403) rather than granting access.\n *\n * Fail-closed on every abnormal path — no branch grants access on missing\n * wiring or a missing caller:\n * - no required permissions → allow (nothing to enforce);\n * - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);\n * - no authenticated user in the request context → deny;\n * - any single required permission not granted → deny.\n *\n * The guard is stateless (no injected dependencies), so it is safe to register\n * as a plain provided guard.\n */\n@Injectable()\nexport class PermissionGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const required = this.reflector.getAllAndOverride(RequirePermission, context);\n if (!required || required.length === 0) return true;\n\n const honoCtx = context.getContext() as {\n get: (k: string) => { resolve<T>(t: unknown): T };\n };\n const container = honoCtx.get('container');\n\n // Resolve AUTHZ at request time from the per-request container — the SAME\n // container REQUEST_CONTEXT resolves from. Because this lookup carries no\n // requesting module, it matches AUTHZ by its exporter, so a non-global\n // `AuthzModule` is reachable without forcing the app to declare it global.\n // `resolve` throws (or, defensively, could yield undefined) for an\n // unregistered token, so wrap it and fail closed on any failure.\n let authz: Authz | undefined;\n try {\n authz = container.resolve<Authz>(AUTHZ_TOKEN);\n } catch {\n authz = undefined;\n }\n if (!authz) {\n throw new ForbiddenException('Authorization is not configured');\n }\n\n const reqCtx = container.resolve<RequestContext>(REQUEST_CONTEXT);\n const user = reqCtx.get<User & { id?: string; role?: string | string[] }>(AUTH_USER_KEY);\n if (!user) {\n throw new ForbiddenException('Permission check requires authentication');\n }\n\n const identity = identityFromUser(user);\n for (const permission of required) {\n if (!(await authz.can(identity, permission))) {\n throw new ForbiddenException(`Missing permission: ${permission}`);\n }\n }\n return true;\n }\n}\n","import {\n defineModule,\n lazyProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type ProviderOptions,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { RolesGuard } from './guards/roles.guard';\nimport { PermissionGuard } from './guards/permission.guard';\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n isGlobal: boolean;\n defaultPolicy: 'deny' | 'allow';\n mountHandler: boolean;\n}\n\nfunction normalize(options: Partial<BetterAuthModuleOptions>): NormalizedOptions {\n return {\n basePath: options.basePath ?? DEFAULT_BASE_PATH,\n isGlobal: options.isGlobal ?? false,\n defaultPolicy: options.defaultPolicy ?? 'deny',\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: Array<Type | ProviderOptions>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard, RolesGuard, PermissionGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard, RolesGuard, PermissionGuard],\n };\n}\n\n/**\n * The blessed engine generates `forRoot`. `setup` runs once per instance at\n * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,\n * derives the auth builder from those options, mounts the catch-all controller,\n * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.\n *\n * `isGlobal` here means \"apply AuthGuard app-wide\", NOT \"make this a global\n * module\", so the default `isGlobal → global: true` extras transform is\n * replaced with identity; the flag reaches `setup` through the options bag.\n */\nconst authModuleHost = defineModule<BetterAuthModuleOptions>({\n name: 'BetterAuth',\n optionsToken: BETTER_AUTH_OPTIONS,\n transform: (definition) => definition,\n // The auth instance is a stateful value — key off the structural subset only.\n key: (options) => stableHash(normalize(options)),\n setup: ({ OPTIONS, options }) => {\n const n = normalize(options);\n const common = commonContributions(n);\n const auth = (options as BetterAuthModuleOptions).auth;\n return {\n providers: [\n // Override the auto-provided raw bag with the normalized shape so\n // BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.\n { provide: OPTIONS, useValue: { ...n, auth } },\n // Eager auth: the builder hands back the instance the caller passed in.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: [OPTIONS],\n useFactory: (o: BetterAuthModuleOptions) => o.auth,\n }),\n ...common.providers,\n ],\n controllers: common.controllers,\n exports: common.exports,\n global: n.isGlobal ? { guards: [AuthGuard] } : undefined,\n };\n },\n});\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [D1Service, ConfigService], // captured as readonly tuple\n * useFactory: (d1, config) => // d1: D1Service, config: ConfigService\n * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<\n Inject extends readonly Token<unknown>[] = readonly Token<unknown>[],\n> {\n inject?: Inject;\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n defaultPolicy?: 'deny' | 'allow';\n key?: string;\n}\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n // Delegate to the generated static, then rebrand the module identity so the\n // public `BetterAuthModule` class is the one registered (consistent with\n // `forRootAsync` and better diagnostics).\n return { ...authModuleHost.ConfigurableModuleClass.forRoot(options), module: BetterAuthModule };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured behind {@link lazyProvider}'s memoized thunk. This is what makes\n * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but\n * it IS by the time a request flows through and the guard / catch-all reads\n * the service. Inject deps resolve at module load (cheap BindingRef wrappers);\n * their *values* are read at first auth use, inside your factory body.\n */\n static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n return {\n module: BetterAuthModule,\n key: options.key ?? stableHash({ ...n, inject: options.inject }),\n imports: options.imports ?? [],\n providers: [\n { provide: BETTER_AUTH_OPTIONS, useValue: n },\n // The deferred auth builder: `lazyProvider` wraps the user factory in a\n // memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: options.inject,\n useFactory: options.useFactory,\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import {\n createLazyParamDecorator,\n REQUEST_CONTEXT,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_USER_KEY } from '../better-auth.tokens';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createLazyParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const honoCtx = ctx.getContext() as { get: (k: string) => { resolve<T>(t: unknown): T } };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n return reqCtx.get<User>(AUTH_USER_KEY);\n});\n","import {\n createLazyParamDecorator,\n REQUEST_CONTEXT,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport { AUTH_SESSION_KEY } from '../better-auth.tokens';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createLazyParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const honoCtx = ctx.getContext() as { get: (k: string) => { resolve<T>(t: unknown): T } };\n const reqCtx = honoCtx.get('container').resolve<RequestContext>(REQUEST_CONTEXT);\n return reqCtx.get<Session>(AUTH_SESSION_KEY);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ACCESS_DENIED","normalizeRoles"],"sources":["../src/decorators/public.decorator.ts","../src/base-path.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/auth-request-state.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/decorators/roles.decorator.ts","../src/guards/roles.guard.ts","../src/authz-bridge.ts","../src/decorators/require-permission.decorator.ts","../src/guards/permission.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","export const DEFAULT_BETTER_AUTH_BASE_PATH = '/api/auth';\n\n/** Validate and canonicalize the route prefix used by the public auth controller. */\nexport function normalizeBetterAuthBasePath(value?: string): string {\n const basePath = value ?? DEFAULT_BETTER_AUTH_BASE_PATH;\n if (\n basePath.length === 0 ||\n basePath !== basePath.trim() ||\n !basePath.startsWith('/') ||\n basePath.startsWith('//') ||\n basePath === '/' ||\n basePath.endsWith('/') ||\n /[\\\\?#*]/u.test(basePath) ||\n /%(?:2e|2f|5c)/iu.test(basePath)\n ) {\n throw new Error(\n '@velajs/better-auth: basePath must be a canonical absolute path such as \"/api/auth\"',\n );\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(basePath);\n } catch {\n throw new Error('@velajs/better-auth: basePath contains invalid percent encoding');\n }\n if (decoded.split('/').some((segment) => segment === '.' || segment === '..')) {\n throw new Error('@velajs/better-auth: basePath must not contain dot segments');\n }\n return basePath;\n}\n","import { All, Controller, Inject, Injectable, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n const normalizedBasePath = normalizeBetterAuthBasePath(basePath);\n @Public(true)\n @Controller(normalizedBasePath)\n @Injectable()\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n\n/**\n * Default-path (`/api/auth`) catch-all controller. Retained for back-compat;\n * `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with\n * the configured `basePath`. Prefer the factory for a custom base path.\n */\nexport const BetterAuthCatchallController = createBetterAuthCatchallController();\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthModuleOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthModuleOptions>(\n 'vela.BetterAuthOptions',\n);\n\nexport const AUTH_USER_KEY = Symbol.for('vela.better-auth.user');\nexport const AUTH_SESSION_KEY = Symbol.for('vela.better-auth.session');\nexport const AUTH_ISSUER_KEY = Symbol.for('vela.better-auth.issuer');\nexport const AUTH_PRINCIPAL_TYPE_KEY = Symbol.for('vela.better-auth.principal-type');\n","import type { ExecutionContext } from '@velajs/vela';\nimport type { Session, User } from './better-auth.types';\n\nexport interface AuthenticatedRequestState {\n readonly authenticated: true;\n readonly user: User;\n readonly session: Session;\n readonly issuer: string;\n readonly principalType: 'user';\n}\n\ninterface AnonymousRequestState {\n readonly authenticated: false;\n}\n\nexport type AuthRequestState = AuthenticatedRequestState | AnonymousRequestState;\n\nconst ANONYMOUS: AnonymousRequestState = Object.freeze({ authenticated: false });\n\n// Canonical authentication state is request-local and unforgeable by upstream\n// Hono middleware. It deliberately does not depend on resolving a DI token:\n// guards execute before parameter extraction and test harnesses may load Vela's\n// public/internal entry points as separate module instances. Both execution\n// contexts still expose the same raw Request object.\nconst stateByRequest = new WeakMap<Request, AuthRequestState>();\n\n/** Clear any state before a guard evaluates a request. */\nexport const beginAuthRequest = (context: ExecutionContext): void => {\n stateByRequest.set(context.getRequest(), ANONYMOUS);\n};\n\n/** Publish a fully verified session for downstream guards and parameters. */\nexport const authenticateRequest = (\n context: ExecutionContext,\n state: Omit<AuthenticatedRequestState, 'authenticated'>,\n): AuthenticatedRequestState => {\n const authenticated: AuthenticatedRequestState = Object.freeze({\n authenticated: true,\n ...state,\n });\n stateByRequest.set(context.getRequest(), authenticated);\n return authenticated;\n};\n\n/** Missing state is anonymous: no guard means no ambient identity. */\nexport const getAuthRequestState = (context: ExecutionContext): AuthRequestState =>\n context.getType() === 'http'\n ? (stateByRequest.get(context.getRequest()) ?? ANONYMOUS)\n : ANONYMOUS;\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n REQUEST_CONTEXT,\n Reflector,\n UnauthorizedException,\n clearTrustedRequestIdentity,\n setTrustedRequestIdentity,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport {\n AUTH_ISSUER_KEY,\n AUTH_PRINCIPAL_TYPE_KEY,\n AUTH_SESSION_KEY,\n AUTH_USER_KEY,\n BETTER_AUTH_OPTIONS,\n} from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthModuleOptions, Session, User } from '../better-auth.types';\nimport {\n authenticateRequest,\n beginAuthRequest,\n type AuthRequestState,\n} from '../auth-request-state';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n constructor(\n // Inject BetterAuthService rather than the raw better-auth instance.\n // The service's lazy `.auth` getter defers construction to first use, so\n // forRootAsync factories that depend on values only available at\n // request time (Cloudflare D1/KV bindings, etc.) build safely on the\n // first canActivate — not at module-load bootstrap.\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthModuleOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // WebSocket upgrades are authenticated before allocation. Frames reuse\n // only that normalized attachment identity; never call HTTP-only context\n // accessors or re-read ambient cookies after the connection is established.\n if (context.getType() === 'ws') {\n if (hasValidWebSocketIdentity(context)) return true;\n throw new AuthenticationRequiredException();\n }\n\n // The guard owns this request's identity epoch. Clear first so public,\n // optional, malformed, and throwing auth paths can never inherit state.\n beginAuthRequest(context);\n mirrorRequestContext(context, { authenticated: false });\n\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const request = context.getRequest();\n const raw = await this.auth.api.getSession({ headers: request.headers });\n const data = validateSessionData(raw);\n\n if (data) {\n const state = authenticateRequest(context, {\n user: data.user,\n session: data.session,\n issuer: this.opts.issuer ?? 'better-auth',\n principalType: 'user',\n });\n mirrorRequestContext(context, state);\n return true;\n }\n\n if (this.reflector.getAllAndOverride(OptionalAuth, context)) {\n return true;\n }\n\n throw new AuthenticationRequiredException();\n }\n}\n\nfunction hasValidWebSocketIdentity(context: ExecutionContext): boolean {\n try {\n const client = context.switchToWs().getClient<{ data?: unknown }>();\n const data = client?.data;\n if (!data || typeof data !== 'object') return false;\n const record = data as Record<string, unknown>;\n const principal = record.principal;\n if (!principal || typeof principal !== 'object') return false;\n const fields = principal as Record<string, unknown>;\n return (\n typeof fields.issuer === 'string' &&\n fields.issuer.length > 0 &&\n typeof fields.subject === 'string' &&\n fields.subject.length > 0 &&\n (fields.principalType === 'user' || fields.principalType === 'service') &&\n typeof record.tenantId === 'string' &&\n record.tenantId.length > 0 &&\n typeof record.expiresAtMs === 'number' &&\n Number.isSafeInteger(record.expiresAtMs) &&\n record.expiresAtMs > Date.now()\n );\n } catch {\n return false;\n }\n}\n\ninterface ContainerLike {\n resolve<T>(token: unknown): T;\n}\n\n/**\n * Preserve the public REQUEST_CONTEXT symbols for applications that consume\n * them directly. The private Request-keyed state above remains canonical: a\n * missing/duplicated framework token must not prevent a verified guard from\n * publishing identity to its own downstream decorators and guards.\n */\nfunction mirrorRequestContext(context: ExecutionContext, state: AuthRequestState): void {\n const request = context.getRequest();\n if (!state.authenticated) {\n clearTrustedRequestIdentity(request);\n } else {\n const tenantId = readActiveOrganizationId(state.session);\n setTrustedRequestIdentity(request, {\n principal: {\n issuer: state.issuer,\n subject: state.user.id,\n principalType: state.principalType,\n },\n ...(tenantId === undefined ? {} : { tenantId }),\n });\n }\n\n const honoCtx = context.getContext() as { get: (k: string) => ContainerLike | undefined };\n const container = honoCtx.get('container');\n if (!container) return;\n\n let reqCtx: RequestContext;\n try {\n reqCtx = container.resolve<RequestContext>(REQUEST_CONTEXT);\n } catch {\n return;\n }\n\n if (!state.authenticated) {\n reqCtx.set<User | undefined>(AUTH_USER_KEY, undefined);\n reqCtx.set<Session | undefined>(AUTH_SESSION_KEY, undefined);\n reqCtx.set<string | undefined>(AUTH_ISSUER_KEY, undefined);\n reqCtx.set<'user' | undefined>(AUTH_PRINCIPAL_TYPE_KEY, undefined);\n return;\n }\n\n reqCtx.set(AUTH_USER_KEY, state.user);\n reqCtx.set(AUTH_SESSION_KEY, state.session);\n reqCtx.set(AUTH_ISSUER_KEY, state.issuer);\n reqCtx.set(AUTH_PRINCIPAL_TYPE_KEY, state.principalType);\n}\n\nfunction readActiveOrganizationId(session: Session): string | undefined {\n const descriptor = Object.getOwnPropertyDescriptor(session, 'activeOrganizationId');\n if (descriptor === undefined || !('value' in descriptor)) return undefined;\n const value: unknown = descriptor.value;\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\ninterface SessionData {\n user: User;\n session: Session;\n}\n\n/** A test/provider override is trusted code, but its runtime result is not. */\nfunction validateSessionData(value: unknown): SessionData | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n\n try {\n const userDescriptor = Object.getOwnPropertyDescriptor(value, 'user');\n const sessionDescriptor = Object.getOwnPropertyDescriptor(value, 'session');\n if (\n userDescriptor === undefined ||\n !('value' in userDescriptor) ||\n sessionDescriptor === undefined ||\n !('value' in sessionDescriptor)\n ) {\n return undefined;\n }\n\n const user: unknown = userDescriptor.value;\n const session: unknown = sessionDescriptor.value;\n if (\n user === null ||\n typeof user !== 'object' ||\n session === null ||\n typeof session !== 'object'\n ) {\n return undefined;\n }\n\n const userIdDescriptor = Object.getOwnPropertyDescriptor(user, 'id');\n const sessionIdDescriptor = Object.getOwnPropertyDescriptor(session, 'id');\n const sessionUserIdDescriptor = Object.getOwnPropertyDescriptor(session, 'userId');\n const userId =\n userIdDescriptor !== undefined && 'value' in userIdDescriptor\n ? userIdDescriptor.value\n : undefined;\n const sessionId =\n sessionIdDescriptor !== undefined && 'value' in sessionIdDescriptor\n ? sessionIdDescriptor.value\n : undefined;\n const sessionUserId =\n sessionUserIdDescriptor !== undefined && 'value' in sessionUserIdDescriptor\n ? sessionUserIdDescriptor.value\n : undefined;\n\n if (\n typeof userId !== 'string' ||\n userId.length === 0 ||\n typeof sessionId !== 'string' ||\n sessionId.length === 0 ||\n sessionUserId !== userId\n ) {\n return undefined;\n }\n\n // The identity-bearing fields above are validated as own data properties.\n // Remaining Better Auth/plugin fields stay intact for typed consumers.\n return { user: user as User, session: session as Session };\n } catch {\n return undefined;\n }\n}\n\n/**\n * `UnauthorizedException` preserves Nest-style direct behavior. The structural\n * VelaError brand also survives test/runtime package duplication, so the\n * central renderer still maps this to 401 rather than treating it as a foreign\n * 500 error.\n */\nclass AuthenticationRequiredException extends UnauthorizedException {\n readonly type = 'VelaError' as const;\n readonly code = 'unauthorized';\n readonly status = 401;\n\n constructor() {\n super('Authentication required');\n }\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const Roles = Reflector.createDecorator<string[]>({ key: 'vela.auth.roles' });\nexport const ROLES_KEY = Roles.KEY;\n","import {\n ForbiddenException,\n Injectable,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n} from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport { Roles } from '../decorators/roles.decorator';\n\nconst ACCESS_DENIED = 'Access denied';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n canActivate(context: ExecutionContext): boolean {\n const required = this.reflector.getAllAndOverride(Roles, context);\n if (!required || required.length === 0) return true;\n\n const state = getAuthRequestState(context);\n if (!state.authenticated) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n\n const userRoles = normalizeRoles((state.user as { role?: string | string[] }).role);\n const ok = required.some((r) => userRoles.includes(r));\n if (!ok) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n return true;\n }\n}\n\nfunction normalizeRoles(role: string | string[] | undefined): string[] {\n if (!role) return [];\n if (Array.isArray(role)) return role;\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n}\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = User & { role?: string | string[] | null };\n\n/** Stable issuer namespace used for better-auth session principals. */\nexport const BETTER_AUTH_ISSUER = 'better-auth';\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Adapts a better-auth user into a stable `@velajs/authz` {@link Identity}.\n * The issuer scopes `user.id` as both `subject` and the compatibility `userId`;\n * the admin-plugin `role` field supplies local roles.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (\n user: AuthUser | null | undefined,\n issuer: string = BETTER_AUTH_ISSUER,\n principalType: 'user' | 'service' = 'user',\n): Identity => {\n if (!user || typeof user.id !== 'string' || user.id.length === 0) return { roles: [] };\n if (issuer.length === 0)\n throw new Error('@velajs/better-auth: identity issuer must be non-empty');\n return {\n issuer,\n subject: user.id,\n principalType,\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n","import { Reflector } from '@velajs/vela';\n\n/**\n * Declares the `@velajs/authz` permission(s) required to reach a controller or\n * route handler. Read via `Reflector` in an authorization guard, then checked\n * against the caller's `Identity` with `authz.can(...)`.\n *\n * ```ts\n * @RequirePermission(['posts:write'])\n * @Post()\n * create() { ... }\n * ```\n *\n * The metadata is a plain `string[]` of permission strings in the granted-side\n * format `@velajs/authz` matches (`resource:action`, or wildcards like\n * `posts:*`). Handler-level metadata overrides class-level (standard\n * `Reflector.getAllAndOverride` precedence).\n *\n * Semantics are **require-ALL** (AND): every listed permission must be granted\n * for access — the `PermissionGuard` denies if any one is missing. This\n * contrasts with `@Roles`, which is **OR** (any one of the listed roles\n * suffices).\n */\nexport const RequirePermission = Reflector.createDecorator<string[]>({\n key: 'vela.authz.permissions',\n});\n\nexport const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;\n","import {\n ForbiddenException,\n Injectable,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type InjectionToken,\n} from '@velajs/vela';\nimport { AUTHZ } from '@velajs/authz/vela';\nimport type { Authz, Identity } from '@velajs/authz';\nimport { getAuthRequestState } from '../auth-request-state';\nimport { identityFromUser } from '../authz-bridge';\nimport { RequirePermission } from '../decorators/require-permission.decorator';\n\n// The linked `@velajs/authz` is built against its own (newer) `@velajs/vela`\n// copy, so the `AUTHZ` token's `InjectionToken` type is nominally distinct from\n// this package's `InjectionToken` — even though it is the very same runtime\n// token object (the DI container matches tokens by object identity). Re-type it\n// to the local `InjectionToken` so the request-time `container.resolve(...)`\n// accepts it without a structural clash. This is purely a compile-time alias;\n// it changes nothing at runtime. (Version-skew workaround until both publish.)\nconst AUTHZ_TOKEN = AUTHZ as unknown as InjectionToken<Authz>;\nconst ACCESS_DENIED = 'Access denied';\n\ninterface ContainerLike {\n resolve<T>(token: unknown, requestingModuleId?: string): T;\n resolveAll?<T>(token: unknown, requestingModuleId?: string): T[];\n}\n\nfunction resolveSingleAuthz(container: ContainerLike, moduleId: string): Authz | undefined {\n try {\n if (typeof container.resolveAll === 'function') {\n const candidates = container.resolveAll<Authz>(AUTHZ_TOKEN, moduleId);\n return candidates.length === 1 ? candidates[0] : undefined;\n }\n return container.resolve<Authz>(AUTHZ_TOKEN, moduleId);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Enforces the `@RequirePermission(...)` metadata against the `@velajs/authz`\n * engine. For each required permission it calls `authz.can(identity, perm)`,\n * requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which\n * is OR over roles). The caller's `Identity` is derived from the better-auth\n * user that {@link AuthGuard} placed in canonical request-local auth state, so this guard must\n * run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).\n *\n * `AUTHZ` is resolved at request time from the per-request container. Exactly\n * one reachable engine is required; zero or multiple registrations deny rather\n * than selecting one by import order.\n *\n * Fail-closed on every abnormal path — no branch grants access on missing\n * wiring or a missing caller:\n * - no required permissions → allow (nothing to enforce);\n * - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);\n * - no authenticated user in request-local auth state → deny;\n * - any single required permission not granted → deny.\n *\n * The guard is stateless (no injected dependencies), so it is safe to register\n * as a plain provided guard.\n */\n@Injectable()\nexport class PermissionGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const required = this.reflector.getAllAndOverride(RequirePermission, context);\n if (!required || required.length === 0) return true;\n\n const container = resolveContextContainer(context);\n const moduleId = context.getModuleId();\n\n // Resolve all visible AUTHZ registrations and accept only an unambiguous\n // single engine. This prevents import order from selecting another tenant's\n // or feature module's authorization policy.\n const authz =\n container === undefined || moduleId === undefined\n ? undefined\n : resolveSingleAuthz(container, moduleId);\n if (!authz) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n\n const identity = resolveContextIdentity(context);\n if (identity === undefined) throw new ForbiddenException(ACCESS_DENIED);\n for (const permission of required) {\n if (!(await authz.can(identity, permission))) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n }\n return true;\n }\n}\n\nfunction resolveContextContainer(context: ExecutionContext): ContainerLike | undefined {\n const direct = context.getContainer?.<ContainerLike>();\n if (direct !== undefined) return direct;\n if (context.getType() !== 'http') return undefined;\n try {\n const honoCtx = context.getContext() as { get: (key: string) => ContainerLike | undefined };\n return honoCtx.get('container');\n } catch {\n return undefined;\n }\n}\n\nfunction resolveContextIdentity(context: ExecutionContext): Identity | undefined {\n if (context.getType() === 'ws') {\n try {\n const data = context.switchToWs().getClient<{ data?: unknown }>()?.data;\n if (!data || typeof data !== 'object') return undefined;\n const record = data as Record<string, unknown>;\n const principal = record.principal;\n if (!principal || typeof principal !== 'object') return undefined;\n const fields = principal as Record<string, unknown>;\n if (\n typeof fields.issuer !== 'string' ||\n fields.issuer.length === 0 ||\n typeof fields.subject !== 'string' ||\n fields.subject.length === 0 ||\n (fields.principalType !== 'user' && fields.principalType !== 'service') ||\n typeof record.tenantId !== 'string' ||\n record.tenantId.length === 0 ||\n typeof record.expiresAtMs !== 'number' ||\n !Number.isSafeInteger(record.expiresAtMs) ||\n record.expiresAtMs <= Date.now()\n ) {\n return undefined;\n }\n return {\n issuer: fields.issuer,\n subject: fields.subject,\n principalType: fields.principalType,\n userId: fields.subject,\n roles: [],\n };\n } catch {\n return undefined;\n }\n }\n\n const state = getAuthRequestState(context);\n return state.authenticated\n ? identityFromUser(state.user, state.issuer, state.principalType)\n : undefined;\n}\n","import {\n defineModule,\n lazyProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type ProviderOptions,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { RolesGuard } from './guards/roles.guard';\nimport { PermissionGuard } from './guards/permission.guard';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\nconst referenceIds = new WeakMap<object, number>();\nconst explicitKeyClaims = new Map<\n string,\n { readonly kind: 'auth' | 'factory'; readonly reference: object; readonly shape: string }\n>();\nlet nextReferenceId = 1;\n\nfunction referenceId(reference: object): number {\n const existing = referenceIds.get(reference);\n if (existing !== undefined) return existing;\n const id = nextReferenceId++;\n referenceIds.set(reference, id);\n return id;\n}\n\nfunction claimExplicitKey(\n key: string,\n kind: 'auth' | 'factory',\n reference: object,\n shape: string,\n): string {\n if (key.length === 0 || key !== key.trim()) {\n throw new Error('@velajs/better-auth: an explicit module key must be a non-empty string');\n }\n const existing = explicitKeyClaims.get(key);\n if (\n existing !== undefined &&\n (existing.kind !== kind || existing.reference !== reference || existing.shape !== shape)\n ) {\n throw new Error(\n `@velajs/better-auth: explicit module key \"${key}\" is already bound to a different auth registration`,\n );\n }\n explicitKeyClaims.set(key, { kind, reference, shape });\n return `explicit:${key}:ref:${referenceId(reference)}`;\n}\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n issuer: string;\n isGlobal: boolean;\n defaultPolicy: 'deny';\n mountHandler: boolean;\n}\n\nfunction normalize(options: Partial<BetterAuthModuleOptions>): NormalizedOptions {\n const basePath = normalizeBetterAuthBasePath(options.basePath);\n const issuer = options.issuer ?? `better-auth:${basePath}`;\n if (issuer.length === 0 || issuer !== issuer.trim()) {\n throw new Error('@velajs/better-auth: issuer must be a non-empty stable namespace');\n }\n if (options.defaultPolicy !== undefined && options.defaultPolicy !== 'deny') {\n throw new Error(\n '@velajs/better-auth: defaultPolicy is deny-only; mark anonymous routes with @Public() or @OptionalAuth()',\n );\n }\n return {\n basePath,\n issuer,\n isGlobal: options.isGlobal ?? true,\n defaultPolicy: 'deny',\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: Array<Type | ProviderOptions>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard, RolesGuard, PermissionGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard, RolesGuard, PermissionGuard],\n };\n}\n\n/**\n * The blessed engine generates `forRoot`. `setup` runs once per instance at\n * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,\n * derives the auth builder from those options, mounts the catch-all controller,\n * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.\n *\n * `isGlobal` here means \"apply AuthGuard app-wide\", NOT \"make this a global\n * module\", so the default `isGlobal → global: true` extras transform is\n * replaced with identity; the flag reaches `setup` through the options bag.\n */\nconst authModuleHost = defineModule<BetterAuthModuleOptions>({\n name: 'BetterAuth',\n optionsToken: BETTER_AUTH_OPTIONS,\n transform: (definition) => definition,\n // Public entry points always supply an identity-aware key. Keep this fallback\n // for direct host use in tests and future refactors.\n key: (options) => stableHash(normalize(options)),\n setup: ({ OPTIONS, options }) => {\n const n = normalize(options);\n const common = commonContributions(n);\n const auth = (options as BetterAuthModuleOptions).auth;\n return {\n providers: [\n // Override the auto-provided raw bag with the normalized shape so\n // BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.\n { provide: OPTIONS, useValue: { ...n, auth } },\n // Eager auth: the builder hands back the instance the caller passed in.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: [OPTIONS],\n useFactory: (o: BetterAuthModuleOptions) => o.auth,\n }),\n ...common.providers,\n ],\n controllers: common.controllers,\n exports: common.exports,\n global: n.isGlobal ? { guards: [AuthGuard] } : undefined,\n };\n },\n});\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [D1Service, ConfigService], // captured as readonly tuple\n * useFactory: (d1, config) => // d1: D1Service, config: ConfigService\n * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<\n Inject extends readonly Token<unknown>[] = readonly Token<unknown>[],\n> {\n inject?: Inject;\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n issuer?: string;\n /** @deprecated Authentication is deny-by-default. Only `'deny'` is accepted. */\n defaultPolicy?: 'deny';\n key?: string;\n}\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n const normalized = normalize(options);\n const shape = stableHash(normalized);\n const key =\n options.key === undefined\n ? `${shape}:auth:${referenceId(options.auth)}`\n : claimExplicitKey(options.key, 'auth', options.auth, shape);\n // Delegate to the generated static, then rebrand the module identity so the\n // public `BetterAuthModule` class is the one registered (consistent with\n // `forRootAsync` and better diagnostics).\n return {\n ...authModuleHost.ConfigurableModuleClass.forRoot({ ...options, key }),\n module: BetterAuthModule,\n };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured behind {@link lazyProvider}'s memoized thunk. This is what makes\n * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but\n * it IS by the time a request flows through and the guard / catch-all reads\n * the service. Inject deps resolve at module load (cheap BindingRef wrappers);\n * their *values* are read at first auth use, inside your factory body.\n */\n static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n const shape = stableHash({ ...n, inject: options.inject });\n const key =\n options.key === undefined\n ? `${shape}:factory:${referenceId(options.useFactory)}`\n : claimExplicitKey(options.key, 'factory', options.useFactory, shape);\n return {\n module: BetterAuthModule,\n key,\n imports: options.imports ?? [],\n providers: [\n { provide: BETTER_AUTH_OPTIONS, useValue: n },\n // The deferred auth builder: `lazyProvider` wraps the user factory in a\n // memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: options.inject,\n useFactory: options.useFactory,\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): User | undefined => {\n const state = getAuthRequestState(ctx);\n return state.authenticated ? state.user : undefined;\n },\n);\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): Session | undefined => {\n const state = getAuthRequestState(ctx);\n return state.authenticated ? state.session : undefined;\n },\n);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "better-auth integration for the Vela framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@velajs/authz": "^1.
|
|
46
|
+
"@velajs/authz": "^1.1.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
50
50
|
"@changesets/cli": "^2.31.0",
|
|
51
51
|
"@swc/core": "^1.15.43",
|
|
52
|
-
"@velajs/testing": "^0.
|
|
53
|
-
"@velajs/vela": "^1.
|
|
52
|
+
"@velajs/testing": "^1.0.0",
|
|
53
|
+
"@velajs/vela": "^1.21.0",
|
|
54
54
|
"better-auth": "^1.6.20",
|
|
55
55
|
"hono": "^4.12.26",
|
|
56
56
|
"oxfmt": "^0.58.0",
|
|
@@ -59,10 +59,11 @@
|
|
|
59
59
|
"tsdown": "^0.22.4",
|
|
60
60
|
"typescript": "^7.0.2",
|
|
61
61
|
"unplugin-swc": "^1.5.9",
|
|
62
|
+
"vite": "^8.0.16",
|
|
62
63
|
"vitest": "^4.1.10"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
|
-
"@velajs/vela": ">=1.
|
|
66
|
+
"@velajs/vela": ">=1.21.0 <2",
|
|
66
67
|
"better-auth": ">=1.2.0",
|
|
67
68
|
"hono": ">=4"
|
|
68
69
|
},
|
|
@@ -80,7 +81,9 @@
|
|
|
80
81
|
"attw": "attw --pack . --profile esm-only",
|
|
81
82
|
"changeset": "changeset",
|
|
82
83
|
"version-packages": "changeset version",
|
|
83
|
-
"release": "
|
|
84
|
+
"release:preflight": "npm view @velajs/vela@1.21.0 version && npm view @velajs/authz@1.1.0 version && npm view @velajs/testing@1.0.0 version",
|
|
85
|
+
"release:check": "node scripts/check-release-lock.mjs && pnpm verify && pnpm audit --audit-level=high",
|
|
86
|
+
"release": "pnpm release:preflight && pnpm release:check && changeset publish",
|
|
84
87
|
"verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
|
|
85
88
|
}
|
|
86
89
|
}
|