@velajs/better-auth 0.6.1 → 1.22.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 +31 -0
- package/README.md +42 -32
- package/dist/{better-auth.service-BMkyFX-w.js → better-auth.service-DurQ4JQf.js} +4 -4
- package/dist/better-auth.service-DurQ4JQf.js.map +1 -0
- package/dist/better-auth.service-NOlWXxd4.d.ts +68 -0
- package/dist/index.d.ts +33 -152
- package/dist/index.js +255 -230
- package/dist/index.js.map +1 -1
- package/dist/testing/index.d.ts +4 -3
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +25 -23
- package/dist/better-auth.service-BMkyFX-w.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Continue the coordinated framework release on the 1.x line with the current checked provider, endpoint, identity, CRUD, live, and Studio APIs. Breaking API changes are accepted during this development phase; maintained applications use the current contracts.
|
|
8
|
+
- Publish from the pnpm packages workspace with TypeScript 7 and GitHub OIDC. Obsolete standalone examples have been removed; runnable applications live in apps/.
|
|
9
|
+
- Updated workspace dependencies
|
|
10
|
+
- @velajs/authz@1.22.0
|
|
11
|
+
- @velajs/vela@1.22.0
|
|
12
|
+
|
|
13
|
+
## 2.0.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Publish from the unified packages workspace with corrected repository paths, shared native tooling, TypeScript 7 checks, and npm OIDC releases. Runnable examples now live in apps/.
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @velajs/vela@2.0.1
|
|
20
|
+
- @velajs/authz@2.0.1
|
|
21
|
+
|
|
22
|
+
## 2.0.0
|
|
23
|
+
|
|
24
|
+
Native environment configuration and one immutable verified identity shared with provider-independent authorization.
|
|
25
|
+
|
|
26
|
+
Requires the coordinated Vela 2.0 package set. See the workspace migration guide.
|
|
27
|
+
|
|
28
|
+
## 1.0.0
|
|
29
|
+
|
|
30
|
+
### Major Changes
|
|
31
|
+
|
|
32
|
+
- 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.
|
|
33
|
+
|
|
3
34
|
## 0.6.1
|
|
4
35
|
|
|
5
36
|
### 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)
|
|
@@ -73,22 +74,19 @@ imports: [
|
|
|
73
74
|
|
|
74
75
|
### Pattern B — DI'd plugin construction
|
|
75
76
|
|
|
76
|
-
`forRootAsync` lets
|
|
77
|
+
`forRootAsync` requires an explicit `inject` tuple (use `inject: []` when there are no dependencies), so factory parameter types always have matching runtime tokens. It lets Vela services participate in your Better Auth configuration. On Workers, declare `WORKER_ENV = new InjectionToken<WorkerEnv>('app.Env')` and pass that token to `createCloudflareWorker(AppModule, { envToken: WORKER_ENV })`; the native event environment is available before DI factories run.
|
|
77
78
|
|
|
78
79
|
```ts
|
|
79
80
|
imports: [
|
|
80
|
-
D1Module.forRoot({ binding: 'DB' }),
|
|
81
81
|
BetterAuthModule.forRootAsync({
|
|
82
|
-
inject: [
|
|
83
|
-
useFactory: (
|
|
84
|
-
|
|
85
|
-
database: drizzleAdapter(drizzle(d1.binding), { provider: 'sqlite' }),
|
|
82
|
+
inject: [WORKER_ENV, EmailService],
|
|
83
|
+
useFactory: (env, email) => betterAuth({
|
|
84
|
+
database: drizzleAdapter(drizzle(env.DB), { provider: 'sqlite' }),
|
|
86
85
|
plugins: [
|
|
87
86
|
magicLink({ sendMagicLink: (data) => email.send(data) }), // DI'd EmailService
|
|
88
87
|
apiKey(),
|
|
89
88
|
twoFactor(),
|
|
90
89
|
],
|
|
91
|
-
}),
|
|
92
90
|
}),
|
|
93
91
|
isGlobal: true,
|
|
94
92
|
}),
|
|
@@ -101,7 +99,7 @@ Each feature ships a self-contained vela module that exports a plugin token. App
|
|
|
101
99
|
|
|
102
100
|
```ts
|
|
103
101
|
// magic-link-auth.module.ts
|
|
104
|
-
import { Module, InjectionToken } from '@velajs/vela';
|
|
102
|
+
import { Module, InjectionToken, defineProvider } from '@velajs/vela';
|
|
105
103
|
import { magicLink } from 'better-auth/plugins';
|
|
106
104
|
import { EmailService } from './email.service';
|
|
107
105
|
|
|
@@ -112,12 +110,11 @@ export const MAGIC_LINK_PLUGIN = new InjectionToken<ReturnType<typeof magicLink>
|
|
|
112
110
|
@Module({
|
|
113
111
|
providers: [
|
|
114
112
|
EmailService,
|
|
115
|
-
{
|
|
116
|
-
provide: MAGIC_LINK_PLUGIN,
|
|
113
|
+
defineProvider(MAGIC_LINK_PLUGIN, {
|
|
117
114
|
inject: [EmailService],
|
|
118
115
|
useFactory: (email: EmailService) =>
|
|
119
116
|
magicLink({ sendMagicLink: (d) => email.send({ to: d.email, link: d.url }) }),
|
|
120
|
-
},
|
|
117
|
+
}),
|
|
121
118
|
],
|
|
122
119
|
exports: [MAGIC_LINK_PLUGIN],
|
|
123
120
|
})
|
|
@@ -133,9 +130,7 @@ export class MagicLinkAuthModule {}
|
|
|
133
130
|
BetterAuthModule.forRootAsync({
|
|
134
131
|
imports: [MagicLinkAuthModule, OAuthAuthModule],
|
|
135
132
|
inject: [MAGIC_LINK_PLUGIN, OAUTH_PLUGIN],
|
|
136
|
-
useFactory: (magicLink, oauth) => ({
|
|
137
|
-
auth: betterAuth({ database, plugins: [magicLink, oauth] }),
|
|
138
|
-
}),
|
|
133
|
+
useFactory: (magicLink, oauth) => betterAuth({ database, plugins: [magicLink, oauth] }),
|
|
139
134
|
isGlobal: true,
|
|
140
135
|
}),
|
|
141
136
|
],
|
|
@@ -153,7 +148,7 @@ import { BetterAuthService } from '@velajs/better-auth';
|
|
|
153
148
|
|
|
154
149
|
@Injectable()
|
|
155
150
|
class AdminUserService {
|
|
156
|
-
constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}
|
|
151
|
+
constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService<typeof auth>) {}
|
|
157
152
|
|
|
158
153
|
listSessions(userId: string) {
|
|
159
154
|
return this.auth.api.listUserSessions({ userId });
|
|
@@ -164,32 +159,47 @@ class AdminUserService {
|
|
|
164
159
|
}
|
|
165
160
|
```
|
|
166
161
|
|
|
167
|
-
Under `forRootAsync`, the underlying `betterAuth({...})` instance is constructed
|
|
162
|
+
Under `forRootAsync`, the underlying `betterAuth({...})` instance is constructed lazily on first `.auth` / `.api` / `.handler` access. The Workers adapter supplies the typed environment before DI and owns a separate application for each environment, so a cached auth instance never crosses environments.
|
|
168
163
|
|
|
169
164
|
## Decorators
|
|
170
165
|
|
|
171
166
|
| Decorator | Purpose |
|
|
172
167
|
| ------------------- | ------------------------------------------------------------------------ |
|
|
173
|
-
| `@CurrentUser()` |
|
|
174
|
-
| `@CurrentSession()` |
|
|
168
|
+
| `@CurrentUser()` | Better-auth `User` from the request after guards run |
|
|
169
|
+
| `@CurrentSession()` | Better-auth `Session` after guards run |
|
|
175
170
|
| `@Public(true)` | Class or method — bypass AuthGuard entirely |
|
|
176
171
|
| `@OptionalAuth(true)` | Class or method — populate user if present, never throw 401 |
|
|
177
|
-
| `@Roles(['admin'])` | Method — read by `RolesGuard`. Compares against `user.role`. |
|
|
178
172
|
|
|
179
|
-
|
|
173
|
+
Optional identities are ordinary values: an anonymous caller receives the actual `undefined`, so normal truthiness checks are safe.
|
|
180
174
|
|
|
181
175
|
```ts
|
|
182
176
|
handle(@CurrentUser() user: User | undefined) {
|
|
183
|
-
return { hasUser: user
|
|
177
|
+
return { hasUser: Boolean(user) };
|
|
184
178
|
}
|
|
185
179
|
```
|
|
186
180
|
|
|
187
181
|
## Guards
|
|
188
182
|
|
|
189
|
-
- **`AuthGuard`** — singleton. Reads `Authorization` header / cookies via `auth.api.getSession`,
|
|
190
|
-
|
|
183
|
+
- **`AuthGuard`** — singleton. Reads `Authorization` header / cookies via `auth.api.getSession`, validates the full base user/session models, and publishes Vela's trusted principal, tenant, roles, and session expiry 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.
|
|
184
|
+
Permission and role guards live in `@velajs/authz/vela`: import `PermissionGuard`, `RequirePermission`, `RolesGuard`, and `Roles` there. They read the same trusted identity for Better Auth and Cloudflare Access. Run authentication before authorization.
|
|
185
|
+
|
|
186
|
+
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.
|
|
187
|
+
|
|
188
|
+
When using `ThrottlerModule`, import Better Auth first. Vela then rate-limits by
|
|
189
|
+
the verified issuer, subject, principal type, and active organization before it
|
|
190
|
+
falls back to a platform-attested client address.
|
|
191
|
+
|
|
192
|
+
`BETTER_AUTH_OPTIONS` now contains only runtime configuration; read the auth instance from `BetterAuthService`. The obsolete `defaultPolicy` option and default-path controller constant were removed. Use `createBetterAuthCatchallController()` for a manually mounted catch-all.
|
|
193
|
+
|
|
194
|
+
## Trusted identity and typing
|
|
195
|
+
|
|
196
|
+
`@CurrentUser()` and `@CurrentSession()` expose only validated Better Auth data tied to the exact current trusted identity. Public routes, missing/rejected sessions, logout, expiry, and another provider replacing the identity invalidate those values. Hono user variables and removed compatibility symbols cannot grant roles or permissions.
|
|
197
|
+
|
|
198
|
+
Core `getTrustedRequestIdentity(request)` and authz `@CurrentIdentity()` return the verified issuer/subject/type, optional tenant, explicit roles, and credential expiry. WebSocket guards consume only the normalized server connection attachment and do not consult HTTP cookies or arbitrary socket role metadata.
|
|
199
|
+
|
|
200
|
+
The integration accepts the minimal `BetterAuthInstance` contract instead of `Auth<any>`. `new BetterAuthService(() => auth)` infers the concrete instance and retains plugin API/result types. For an injected service, annotate `BetterAuthService<typeof auth>` with the same configured instance type. The unparameterized service intentionally exposes only the operations the framework itself requires.
|
|
191
201
|
|
|
192
|
-
|
|
202
|
+
Migration: identity symbols (`AUTH_USER_KEY`, `AUTH_SESSION_KEY`, issuer/type keys) were removed. Use validated parameter decorators or core's trusted identity reader. Authorization imports moved to `@velajs/authz/vela`; the module no longer registers duplicate provider-specific permission guards.
|
|
193
203
|
|
|
194
204
|
## Edge-safe DB adapters
|
|
195
205
|
|
|
@@ -223,7 +233,7 @@ class CustomCatchallController {
|
|
|
223
233
|
}
|
|
224
234
|
```
|
|
225
235
|
|
|
226
|
-
Pass `basePath: '/auth'` to `BetterAuthModule.forRoot`
|
|
236
|
+
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
237
|
|
|
228
238
|
## License
|
|
229
239
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Inject, Injectable, InjectionToken } from "@velajs/vela";
|
|
2
|
-
//#region \0@oxc-project+runtime@0.
|
|
2
|
+
//#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorateMetadata.js
|
|
3
3
|
function __decorateMetadata(k, v) {
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
5
5
|
}
|
|
6
6
|
//#endregion
|
|
7
|
-
//#region \0@oxc-project+runtime@0.
|
|
7
|
+
//#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorateParam.js
|
|
8
8
|
function __decorateParam(paramIndex, decorator) {
|
|
9
9
|
return function(target, key) {
|
|
10
10
|
decorator(target, key, paramIndex);
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
|
-
//#region \0@oxc-project+runtime@0.
|
|
14
|
+
//#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
|
|
15
15
|
function __decorate(decorators, target, key, desc) {
|
|
16
16
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
17
17
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -60,4 +60,4 @@ BetterAuthService = __decorate([
|
|
|
60
60
|
//#endregion
|
|
61
61
|
export { __decorateMetadata as a, __decorateParam as i, BetterAuthService as n, __decorate as r, BETTER_AUTH_BUILDER as t };
|
|
62
62
|
|
|
63
|
-
//# sourceMappingURL=better-auth.service-
|
|
63
|
+
//# sourceMappingURL=better-auth.service-DurQ4JQf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-auth.service-DurQ4JQf.js","names":[],"sources":["../src/better-auth.service.ts"],"sourcesContent":["import { Inject, Injectable, InjectionToken } from '@velajs/vela';\nimport type { BetterAuthInstance } from './better-auth.types';\n\n/**\n * Internal token holding the auth-construction closure with its inject deps\n * closed over. Resolves cheaply at module load (just captures references);\n * the inner call happens lazily on first auth use (see `BetterAuthService`).\n *\n * Not exported from the public surface — only the service consumes it.\n */\nexport const BETTER_AUTH_BUILDER = new InjectionToken<() => BetterAuthInstance>(\n 'vela.better-auth.Builder',\n);\n\n/**\n * The single injectable consumers reach for to interact with better-auth.\n * Wraps the underlying `betterAuth({...})` instance with lazy construction:\n *\n * - `forRoot({ auth })` — the builder returns the eagerly-provided instance,\n * so the first `.auth` / `.api` / `.handler` access is effectively a\n * read-and-cache.\n * - `forRootAsync({ inject, useFactory })` — the builder wraps the user's\n * factory + inject deps. First access triggers `useFactory(...deps)`. This\n * is what makes Cloudflare D1/KV bindings work: at module load the factory\n * doesn't run; on first request (when AuthGuard or the catch-all calls\n * `service.api` / `service.handler`), the native environment is registered and the\n * factory can read them safely.\n *\n * Used directly by AuthGuard and the catch-all controller. Consumers in\n * application code inject the same way: `@Inject(BetterAuthService)`.\n */\n@Injectable()\nexport class BetterAuthService<TAuth extends BetterAuthInstance = BetterAuthInstance> {\n private cached: TAuth | undefined;\n\n constructor(@Inject(BETTER_AUTH_BUILDER) private readonly build: () => TAuth) {}\n\n /**\n * The underlying better-auth instance. Constructed once on first access.\n * Safe to call from any request-time code path (guards, controllers,\n * services invoked from handlers).\n */\n get auth(): TAuth {\n if (!this.cached) this.cached = this.build();\n return this.cached;\n }\n\n /** Convenience accessor — equivalent to `service.auth.api`. */\n get api(): TAuth['api'] {\n return this.auth.api;\n }\n\n /** Convenience accessor — equivalent to `service.auth.handler`. */\n get handler(): TAuth['handler'] {\n return this.auth.handler;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,MAAa,sBAAsB,IAAI,eACrC,0BACF;AAoBO,IAAM,oBAAN,MAAM,kBAAyE;CAG1B;CAF1D;CAEA,YAAY,OAAkE;EAApB,KAAA,QAAA;CAAqB;;;;;;CAO/E,IAAI,OAAc;EAChB,IAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,MAAM;EAC3C,OAAO,KAAK;CACd;;CAGA,IAAI,MAAoB;EACtB,OAAO,KAAK,KAAK;CACnB;;CAGA,IAAI,UAA4B;EAC9B,OAAO,KAAK,KAAK;CACnB;AACF;;CAzBC,WAAW;CAIG,gBAAA,GAAA,OAAO,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { InjectionToken } from "@velajs/vela";
|
|
2
|
+
import { Auth, Session, User } from "better-auth";
|
|
3
|
+
//#region src/better-auth.types.d.ts
|
|
4
|
+
/** The runtime surface required by the integration; concrete instances retain their generics. */
|
|
5
|
+
interface BetterAuthInstance {
|
|
6
|
+
readonly api: {
|
|
7
|
+
getSession(input: {
|
|
8
|
+
headers: Headers;
|
|
9
|
+
}): Promise<unknown>;
|
|
10
|
+
};
|
|
11
|
+
readonly handler: (request: Request) => Promise<Response>;
|
|
12
|
+
/** Used only by the real-session testing helper. */
|
|
13
|
+
readonly $context?: Promise<Pick<Awaited<Auth['$context']>, 'internalAdapter' | 'authCookies' | 'secret'>>;
|
|
14
|
+
}
|
|
15
|
+
interface BetterAuthModuleOptions<TAuth extends BetterAuthInstance = BetterAuthInstance> extends BetterAuthRuntimeOptions {
|
|
16
|
+
auth: TAuth;
|
|
17
|
+
}
|
|
18
|
+
/** Provider configuration, separate from the lazily constructed auth instance. */
|
|
19
|
+
interface BetterAuthRuntimeOptions {
|
|
20
|
+
/** Stable namespace paired with user ids in authorization identities. */
|
|
21
|
+
issuer?: string;
|
|
22
|
+
basePath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Register AuthGuard application-wide. Defaults to `true`; opt out only when
|
|
25
|
+
* the application installs an equivalent global authentication guard itself.
|
|
26
|
+
*/
|
|
27
|
+
isGlobal?: boolean;
|
|
28
|
+
mountHandler?: boolean;
|
|
29
|
+
}
|
|
30
|
+
type User$1 = User;
|
|
31
|
+
type Session$1 = Session;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/better-auth.service.d.ts
|
|
34
|
+
/**
|
|
35
|
+
* The single injectable consumers reach for to interact with better-auth.
|
|
36
|
+
* Wraps the underlying `betterAuth({...})` instance with lazy construction:
|
|
37
|
+
*
|
|
38
|
+
* - `forRoot({ auth })` — the builder returns the eagerly-provided instance,
|
|
39
|
+
* so the first `.auth` / `.api` / `.handler` access is effectively a
|
|
40
|
+
* read-and-cache.
|
|
41
|
+
* - `forRootAsync({ inject, useFactory })` — the builder wraps the user's
|
|
42
|
+
* factory + inject deps. First access triggers `useFactory(...deps)`. This
|
|
43
|
+
* is what makes Cloudflare D1/KV bindings work: at module load the factory
|
|
44
|
+
* doesn't run; on first request (when AuthGuard or the catch-all calls
|
|
45
|
+
* `service.api` / `service.handler`), the native environment is registered and the
|
|
46
|
+
* factory can read them safely.
|
|
47
|
+
*
|
|
48
|
+
* Used directly by AuthGuard and the catch-all controller. Consumers in
|
|
49
|
+
* application code inject the same way: `@Inject(BetterAuthService)`.
|
|
50
|
+
*/
|
|
51
|
+
declare class BetterAuthService<TAuth extends BetterAuthInstance = BetterAuthInstance> {
|
|
52
|
+
private readonly build;
|
|
53
|
+
private cached;
|
|
54
|
+
constructor(build: () => TAuth);
|
|
55
|
+
/**
|
|
56
|
+
* The underlying better-auth instance. Constructed once on first access.
|
|
57
|
+
* Safe to call from any request-time code path (guards, controllers,
|
|
58
|
+
* services invoked from handlers).
|
|
59
|
+
*/
|
|
60
|
+
get auth(): TAuth;
|
|
61
|
+
/** Convenience accessor — equivalent to `service.auth.api`. */
|
|
62
|
+
get api(): TAuth['api'];
|
|
63
|
+
/** Convenience accessor — equivalent to `service.auth.handler`. */
|
|
64
|
+
get handler(): TAuth['handler'];
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { Session$1 as a, BetterAuthRuntimeOptions as i, BetterAuthInstance as n, User$1 as o, BetterAuthModuleOptions as r, BetterAuthService as t };
|
|
68
|
+
//# sourceMappingURL=better-auth.service-NOlWXxd4.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
|
+
import { a as Session, i as BetterAuthRuntimeOptions, n as BetterAuthInstance, o as User, r as BetterAuthModuleOptions, t as BetterAuthService } from "./better-auth.service-NOlWXxd4.js";
|
|
1
2
|
import { CanActivate, DynamicModule, ExecutionContext, InferTokens, InjectionToken, Token, Type } from "@velajs/vela";
|
|
2
|
-
import { Auth, Session as Session$1, User as User$1 } from "better-auth";
|
|
3
3
|
import { Identity, PermissionResolver } from "@velajs/authz";
|
|
4
|
-
//#region src/better-auth.types.d.ts
|
|
5
|
-
type BetterAuthInstance = Auth<any>;
|
|
6
|
-
interface BetterAuthModuleOptions {
|
|
7
|
-
auth: BetterAuthInstance;
|
|
8
|
-
basePath?: string;
|
|
9
|
-
isGlobal?: boolean;
|
|
10
|
-
defaultPolicy?: 'deny' | 'allow';
|
|
11
|
-
mountHandler?: boolean;
|
|
12
|
-
}
|
|
13
|
-
type User = User$1;
|
|
14
|
-
type Session = Session$1;
|
|
15
|
-
//#endregion
|
|
16
4
|
//#region src/better-auth.module.d.ts
|
|
17
5
|
/**
|
|
18
6
|
* Options for {@link BetterAuthModule.forRootAsync}.
|
|
@@ -23,23 +11,23 @@ type Session = Session$1;
|
|
|
23
11
|
*
|
|
24
12
|
* ```ts
|
|
25
13
|
* BetterAuthModule.forRootAsync({
|
|
26
|
-
* inject: [
|
|
27
|
-
* useFactory: (
|
|
28
|
-
* betterAuth({ database: drizzleAdapter(drizzle(
|
|
14
|
+
* inject: [WORKER_ENV, ConfigService], // captured as readonly tuple
|
|
15
|
+
* useFactory: (env, config) => // inferred from the tokens
|
|
16
|
+
* betterAuth({ database: drizzleAdapter(drizzle(env.DB), ...) }),
|
|
29
17
|
* });
|
|
30
18
|
* ```
|
|
31
19
|
*/
|
|
32
|
-
interface ForRootAsyncOptions<Inject extends readonly Token
|
|
33
|
-
inject
|
|
20
|
+
interface ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> {
|
|
21
|
+
inject: Inject;
|
|
34
22
|
imports?: DynamicModule['imports'];
|
|
35
23
|
useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;
|
|
36
24
|
isGlobal?: boolean;
|
|
37
25
|
mountHandler?: boolean;
|
|
38
26
|
basePath?: string;
|
|
39
|
-
|
|
27
|
+
issuer?: string;
|
|
40
28
|
key?: string;
|
|
41
29
|
}
|
|
42
|
-
declare class BetterAuthModule {
|
|
30
|
+
export declare class BetterAuthModule {
|
|
43
31
|
/**
|
|
44
32
|
* Synchronous registration. The auth instance is constructed by the consumer
|
|
45
33
|
* at module-load time and passed in directly. Use this when the inputs to
|
|
@@ -55,47 +43,11 @@ declare class BetterAuthModule {
|
|
|
55
43
|
* first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
|
|
56
44
|
* In normal request handling that's `AuthGuard.canActivate` or the catch-all
|
|
57
45
|
* controller's `.handle`. At module load the factory does NOT run — it's only
|
|
58
|
-
* captured
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* the service. Inject deps resolve at module load (cheap BindingRef wrappers);
|
|
62
|
-
* their *values* are read at first auth use, inside your factory body.
|
|
63
|
-
*/
|
|
64
|
-
static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(options: ForRootAsyncOptions<Inject>): DynamicModule;
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/better-auth.service.d.ts
|
|
68
|
-
/**
|
|
69
|
-
* The single injectable consumers reach for to interact with better-auth.
|
|
70
|
-
* Wraps the underlying `betterAuth({...})` instance with lazy construction:
|
|
71
|
-
*
|
|
72
|
-
* - `forRoot({ auth })` — the builder returns the eagerly-provided instance,
|
|
73
|
-
* so the first `.auth` / `.api` / `.handler` access is effectively a
|
|
74
|
-
* read-and-cache.
|
|
75
|
-
* - `forRootAsync({ inject, useFactory })` — the builder wraps the user's
|
|
76
|
-
* factory + inject deps. First access triggers `useFactory(...deps)`. This
|
|
77
|
-
* is what makes Cloudflare D1/KV bindings work: at module load the factory
|
|
78
|
-
* doesn't run; on first request (when AuthGuard or the catch-all calls
|
|
79
|
-
* `service.api` / `service.handler`), the bindings are populated and the
|
|
80
|
-
* factory can read them safely.
|
|
81
|
-
*
|
|
82
|
-
* Used directly by AuthGuard and the catch-all controller. Consumers in
|
|
83
|
-
* application code inject the same way: `@Inject(BetterAuthService)`.
|
|
84
|
-
*/
|
|
85
|
-
declare class BetterAuthService {
|
|
86
|
-
private readonly build;
|
|
87
|
-
private cached;
|
|
88
|
-
constructor(build: () => BetterAuthInstance);
|
|
89
|
-
/**
|
|
90
|
-
* The underlying better-auth instance. Constructed once on first access.
|
|
91
|
-
* Safe to call from any request-time code path (guards, controllers,
|
|
92
|
-
* services invoked from handlers).
|
|
46
|
+
* captured in the checked builder provider. The Workers adapter supplies its
|
|
47
|
+
* native environment before DI; the lazily constructed auth instance belongs
|
|
48
|
+
* to that environment's application and never captures another app's bindings.
|
|
93
49
|
*/
|
|
94
|
-
|
|
95
|
-
/** Convenience accessor — equivalent to `service.auth.api`. */
|
|
96
|
-
get api(): BetterAuthInstance['api'];
|
|
97
|
-
/** Convenience accessor — equivalent to `service.auth.handler`. */
|
|
98
|
-
get handler(): BetterAuthInstance['handler'];
|
|
50
|
+
static forRootAsync<const Inject extends readonly Token[] = readonly Token[]>(options: ForRootAsyncOptions<Inject>): DynamicModule;
|
|
99
51
|
}
|
|
100
52
|
//#endregion
|
|
101
53
|
//#region src/better-auth.controller.d.ts
|
|
@@ -112,108 +64,33 @@ declare class BetterAuthService {
|
|
|
112
64
|
*
|
|
113
65
|
* Both default to `/api/auth`, so the no-prefix / no-config case just works.
|
|
114
66
|
*/
|
|
115
|
-
declare function createBetterAuthCatchallController(basePath?: string): Type;
|
|
116
|
-
/**
|
|
117
|
-
* Default-path (`/api/auth`) catch-all controller. Retained for back-compat;
|
|
118
|
-
* `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with
|
|
119
|
-
* the configured `basePath`. Prefer the factory for a custom base path.
|
|
120
|
-
*/
|
|
121
|
-
declare const BetterAuthCatchallController: Type;
|
|
67
|
+
export declare function createBetterAuthCatchallController(basePath?: string): Type;
|
|
122
68
|
//#endregion
|
|
123
69
|
//#region src/better-auth.tokens.d.ts
|
|
124
|
-
declare const BETTER_AUTH_OPTIONS: InjectionToken<
|
|
125
|
-
declare const AUTH_USER_KEY: unique symbol;
|
|
126
|
-
declare const AUTH_SESSION_KEY: unique symbol;
|
|
70
|
+
export declare const BETTER_AUTH_OPTIONS: InjectionToken<BetterAuthRuntimeOptions>;
|
|
127
71
|
//#endregion
|
|
128
72
|
//#region src/guards/auth.guard.d.ts
|
|
129
|
-
declare class AuthGuard implements CanActivate {
|
|
73
|
+
export declare class AuthGuard implements CanActivate {
|
|
130
74
|
private readonly auth;
|
|
131
75
|
private readonly opts;
|
|
132
76
|
private readonly reflector;
|
|
133
|
-
constructor(auth: BetterAuthService, opts:
|
|
134
|
-
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/guards/roles.guard.d.ts
|
|
138
|
-
declare class RolesGuard implements CanActivate {
|
|
139
|
-
private readonly reflector;
|
|
140
|
-
canActivate(context: ExecutionContext): boolean;
|
|
141
|
-
}
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region src/guards/permission.guard.d.ts
|
|
144
|
-
/**
|
|
145
|
-
* Enforces the `@RequirePermission(...)` metadata against the `@velajs/authz`
|
|
146
|
-
* engine. For each required permission it calls `authz.can(identity, perm)`,
|
|
147
|
-
* requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which
|
|
148
|
-
* is OR over roles). The caller's `Identity` is derived from the better-auth
|
|
149
|
-
* user that {@link AuthGuard} placed in the request context, so this guard must
|
|
150
|
-
* run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).
|
|
151
|
-
*
|
|
152
|
-
* `AUTHZ` is resolved at **request time** from the per-request container (the
|
|
153
|
-
* same container `REQUEST_CONTEXT` is resolved from), not constructor-injected.
|
|
154
|
-
* This deliberately avoids DI visibility coupling: the guard works whether or
|
|
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.
|
|
159
|
-
*
|
|
160
|
-
* Fail-closed on every abnormal path — no branch grants access on missing
|
|
161
|
-
* wiring or a missing caller:
|
|
162
|
-
* - no required permissions → allow (nothing to enforce);
|
|
163
|
-
* - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);
|
|
164
|
-
* - no authenticated user in the request context → deny;
|
|
165
|
-
* - any single required permission not granted → deny.
|
|
166
|
-
*
|
|
167
|
-
* The guard is stateless (no injected dependencies), so it is safe to register
|
|
168
|
-
* as a plain provided guard.
|
|
169
|
-
*/
|
|
170
|
-
declare class PermissionGuard implements CanActivate {
|
|
171
|
-
private readonly reflector;
|
|
77
|
+
constructor(auth: BetterAuthService, opts: BetterAuthRuntimeOptions);
|
|
172
78
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
173
79
|
}
|
|
174
80
|
//#endregion
|
|
175
81
|
//#region src/decorators/current-user.decorator.d.ts
|
|
176
|
-
declare const CurrentUser: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
|
|
82
|
+
export declare const CurrentUser: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
|
|
177
83
|
//#endregion
|
|
178
84
|
//#region src/decorators/current-session.decorator.d.ts
|
|
179
|
-
declare const CurrentSession: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
|
|
85
|
+
export declare const CurrentSession: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
|
|
180
86
|
//#endregion
|
|
181
87
|
//#region src/decorators/public.decorator.d.ts
|
|
182
|
-
declare const Public: import("@velajs/vela").ReflectableDecorator<boolean>;
|
|
183
|
-
declare const PUBLIC_KEY: string;
|
|
88
|
+
export declare const Public: import("@velajs/vela").ReflectableDecorator<boolean>;
|
|
89
|
+
export declare const PUBLIC_KEY: string;
|
|
184
90
|
//#endregion
|
|
185
91
|
//#region src/decorators/optional-auth.decorator.d.ts
|
|
186
|
-
declare const OptionalAuth: import("@velajs/vela").ReflectableDecorator<boolean>;
|
|
187
|
-
declare const OPTIONAL_AUTH_KEY: string;
|
|
188
|
-
//#endregion
|
|
189
|
-
//#region src/decorators/roles.decorator.d.ts
|
|
190
|
-
declare const Roles: import("@velajs/vela").ReflectableDecorator<string[]>;
|
|
191
|
-
declare const ROLES_KEY: string;
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/decorators/require-permission.decorator.d.ts
|
|
194
|
-
/**
|
|
195
|
-
* Declares the `@velajs/authz` permission(s) required to reach a controller or
|
|
196
|
-
* route handler. Read via `Reflector` in an authorization guard, then checked
|
|
197
|
-
* against the caller's `Identity` with `authz.can(...)`.
|
|
198
|
-
*
|
|
199
|
-
* ```ts
|
|
200
|
-
* @RequirePermission(['posts:write'])
|
|
201
|
-
* @Post()
|
|
202
|
-
* create() { ... }
|
|
203
|
-
* ```
|
|
204
|
-
*
|
|
205
|
-
* The metadata is a plain `string[]` of permission strings in the granted-side
|
|
206
|
-
* format `@velajs/authz` matches (`resource:action`, or wildcards like
|
|
207
|
-
* `posts:*`). Handler-level metadata overrides class-level (standard
|
|
208
|
-
* `Reflector.getAllAndOverride` precedence).
|
|
209
|
-
*
|
|
210
|
-
* Semantics are **require-ALL** (AND): every listed permission must be granted
|
|
211
|
-
* for access — the `PermissionGuard` denies if any one is missing. This
|
|
212
|
-
* contrasts with `@Roles`, which is **OR** (any one of the listed roles
|
|
213
|
-
* suffices).
|
|
214
|
-
*/
|
|
215
|
-
declare const RequirePermission: import("@velajs/vela").ReflectableDecorator<string[]>;
|
|
216
|
-
declare const REQUIRE_PERMISSION_KEY: string;
|
|
92
|
+
export declare const OptionalAuth: import("@velajs/vela").ReflectableDecorator<boolean>;
|
|
93
|
+
export declare const OPTIONAL_AUTH_KEY: string;
|
|
217
94
|
//#endregion
|
|
218
95
|
//#region src/authz-bridge.d.ts
|
|
219
96
|
/**
|
|
@@ -221,18 +98,22 @@ declare const REQUIRE_PERMISSION_KEY: string;
|
|
|
221
98
|
* admin plugin. `role` may be a single role, a comma-separated list, or an
|
|
222
99
|
* array — {@link identityFromUser} normalizes all three.
|
|
223
100
|
*/
|
|
224
|
-
type AuthUser = User & {
|
|
101
|
+
type AuthUser = Pick<User, 'id'> & {
|
|
225
102
|
role?: string | string[] | null;
|
|
226
103
|
};
|
|
104
|
+
/** Stable issuer namespace used for better-auth session principals. */
|
|
105
|
+
export declare const BETTER_AUTH_ISSUER = "better-auth";
|
|
227
106
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
107
|
+
* Pure authorization projection; this does not authenticate or publish trusted state.
|
|
108
|
+
* Adapts an already verified better-auth user into a stable `@velajs/authz` {@link Identity}.
|
|
109
|
+
* The issuer scopes `user.id` as both `subject` and the compatibility `userId`;
|
|
110
|
+
* the admin-plugin `role` field supplies local roles.
|
|
230
111
|
*
|
|
231
112
|
* Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated
|
|
232
113
|
* request) maps to the zero-privilege identity `{ roles: [] }`, so downstream
|
|
233
114
|
* `can()` checks grant nothing.
|
|
234
115
|
*/
|
|
235
|
-
declare const identityFromUser: (user: AuthUser | null | undefined) => Identity;
|
|
116
|
+
export declare const identityFromUser: (user: AuthUser | null | undefined, issuer?: string, principalType?: 'user' | 'service') => Identity;
|
|
236
117
|
/**
|
|
237
118
|
* The minimal slice of a better-auth access-control role consumed here. Both
|
|
238
119
|
* `createAccessControl(...).newRole(...)` and the standalone `role(...)` return
|
|
@@ -248,7 +129,7 @@ interface BetterAuthAcRole {
|
|
|
248
129
|
* permission strings — the granted-side format `@velajs/authz` matches
|
|
249
130
|
* (wildcards included).
|
|
250
131
|
*/
|
|
251
|
-
declare const permissionsFromAcRole: (role: BetterAuthAcRole) => string[];
|
|
132
|
+
export declare const permissionsFromAcRole: (role: BetterAuthAcRole) => string[];
|
|
252
133
|
/**
|
|
253
134
|
* Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a
|
|
254
135
|
* better-auth access-control role table (`{ roleName: acRole }` — the same map
|
|
@@ -264,7 +145,7 @@ declare const permissionsFromAcRole: (role: BetterAuthAcRole) => string[];
|
|
|
264
145
|
* await authz.can(identityFromUser(user), 'posts:write');
|
|
265
146
|
* ```
|
|
266
147
|
*/
|
|
267
|
-
declare const betterAuthAcResolver: (roles: Readonly<Record<string, BetterAuthAcRole>>) => PermissionResolver;
|
|
148
|
+
export declare const betterAuthAcResolver: (roles: Readonly<Record<string, BetterAuthAcRole>>) => PermissionResolver;
|
|
268
149
|
//#endregion
|
|
269
|
-
export {
|
|
150
|
+
export { type AuthUser, type BetterAuthAcRole, type BetterAuthInstance, type BetterAuthModuleOptions, type BetterAuthRuntimeOptions, BetterAuthService, type Session, type User };
|
|
270
151
|
//# sourceMappingURL=index.d.ts.map
|