@stratal/framework 0.0.0-canary-ccb3f17 → 0.0.0-canary-e5681b8
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 +655 -0
- package/README.md +166 -24
- package/dist/access-control/index.d.mts +8 -8
- package/dist/access-control/index.d.mts.map +1 -1
- package/dist/access-control/index.mjs +2 -2
- package/dist/{access.service-rsEreT-4.mjs → access.service-BjmnWBEo.mjs} +3 -3
- package/dist/{access.service-rsEreT-4.mjs.map → access.service-BjmnWBEo.mjs.map} +1 -1
- package/dist/auth/index.d.mts +49 -48
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +86 -17
- package/dist/auth/index.mjs.map +1 -1
- package/dist/{auth-context-CE_-TV27.mjs → auth-context-cNSS1rmh.mjs} +2 -2
- package/dist/{auth-context-CE_-TV27.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
- package/dist/context/index.d.mts +3 -3
- package/dist/context/index.d.mts.map +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/database/index.d.mts +3 -3
- package/dist/database/index.mjs +288 -9
- package/dist/database/index.mjs.map +1 -1
- package/dist/{decorate-ZDdbRmsv.mjs → decorate-RQD1h28J.mjs} +1 -1
- package/dist/{decorateParam-DQ95ttaa.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
- package/dist/{decorateParam-DQ95ttaa.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
- package/dist/factory/index.d.mts +3 -4
- package/dist/factory/index.d.mts.map +1 -1
- package/dist/guards/index.d.mts +3 -3
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +4 -4
- package/dist/index-e_u1SRyd.d.mts +921 -0
- package/dist/index-e_u1SRyd.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/{types-DjLXYLlD.d.mts → types-B35g-lXi.d.mts} +18 -3
- package/dist/types-B35g-lXi.d.mts.map +1 -0
- package/package.json +29 -25
- package/dist/index-B8EVn7T5.d.mts +0 -481
- package/dist/index-B8EVn7T5.d.mts.map +0 -1
- package/dist/types-DjLXYLlD.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @stratal/framework
|
|
2
2
|
|
|
3
|
-
Authentication, database ORM,
|
|
3
|
+
Authentication, database ORM, access control, guards, and test factories for [Stratal](https://stratal.dev) applications.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@stratal/framework)
|
|
6
6
|
[](https://github.com/strataljs/stratal/actions/workflows/ci.yml)
|
|
@@ -13,13 +13,13 @@ Authentication, database ORM, RBAC, guards, and test factories for [Stratal](htt
|
|
|
13
13
|
[](https://github.com/strataljs/stratal/pulls)
|
|
14
14
|
[](https://github.com/strataljs/stratal)
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## What's inside
|
|
17
17
|
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **AuthGuard** — Authentication and
|
|
22
|
-
- **AuthContext** — Request-scoped user context for
|
|
18
|
+
- **DatabaseModule** — ZenStack ORM with multiple named connections, plus `db:*` and `migrate:*` Quarry commands
|
|
19
|
+
- **AuthModule** — Better Auth integration with session verification middleware and a `ctx.user()` shorthand
|
|
20
|
+
- **Access control** — Resources, roles and permissions built on Better Auth's `access` plugin
|
|
21
|
+
- **AuthGuard** — Authentication and permission enforcement for routes and controllers
|
|
22
|
+
- **AuthContext** — Request-scoped user context for the authenticated user
|
|
23
23
|
- **Factory** — Test data factories with sequenced attributes powered by Faker.js
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
@@ -32,16 +32,25 @@ yarn add @stratal/framework
|
|
|
32
32
|
|
|
33
33
|
### Peer dependencies
|
|
34
34
|
|
|
35
|
-
| Package |
|
|
35
|
+
| Package | Notes |
|
|
36
36
|
|---|---|
|
|
37
|
-
| `stratal` |
|
|
38
|
-
| `@zenstackhq/orm` |
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
|
|
|
42
|
-
| `@
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
| `stratal` | The core framework |
|
|
38
|
+
| `@zenstackhq/orm` | ORM runtime |
|
|
39
|
+
| `@zenstackhq/schema` | Generated schema types |
|
|
40
|
+
| `pg` | PostgreSQL driver |
|
|
41
|
+
| `better-auth` | Authentication |
|
|
42
|
+
| `@better-auth/core` | Authentication |
|
|
43
|
+
|
|
44
|
+
### Sub-path exports
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { AuthModule } from '@stratal/framework/auth'
|
|
48
|
+
import { DatabaseModule } from '@stratal/framework/database'
|
|
49
|
+
import { createAccessControl } from '@stratal/framework/access-control'
|
|
50
|
+
import { AuthGuard } from '@stratal/framework/guards'
|
|
51
|
+
import type { AuthContext, AuthUser } from '@stratal/framework/context'
|
|
52
|
+
import { Factory } from '@stratal/framework/factory'
|
|
53
|
+
```
|
|
45
54
|
|
|
46
55
|
### AI Agent Skills
|
|
47
56
|
|
|
@@ -53,27 +62,48 @@ npx skills add strataljs/stratal
|
|
|
53
62
|
|
|
54
63
|
| Skill | Description |
|
|
55
64
|
|---|---|
|
|
56
|
-
| `stratal` | Build Cloudflare Workers apps with the Stratal framework — modules, DI, controllers, routing, OpenAPI, queues, cron, events, seeders, CLI, auth, database,
|
|
65
|
+
| `stratal` | Build Cloudflare Workers apps with the Stratal framework — modules, DI, controllers, routing, OpenAPI, queues, cron, events, seeders, CLI, auth, database, access control, testing, and more |
|
|
57
66
|
|
|
58
67
|
## Quick Start
|
|
59
68
|
|
|
60
69
|
```typescript
|
|
61
70
|
import { Stratal } from 'stratal'
|
|
71
|
+
import { DI_TOKENS } from 'stratal/di'
|
|
62
72
|
import { Module } from 'stratal/module'
|
|
63
73
|
import { AuthModule } from '@stratal/framework/auth'
|
|
64
|
-
import { DatabaseModule } from '@stratal/framework/database'
|
|
65
|
-
import {
|
|
74
|
+
import { createPoolFactory, DatabaseModule } from '@stratal/framework/database'
|
|
75
|
+
import { PostgresDialect } from 'kysely'
|
|
76
|
+
import { Pool } from 'pg'
|
|
77
|
+
import { permissions } from './permissions'
|
|
78
|
+
import { schema } from './zenstack/schema'
|
|
66
79
|
|
|
67
80
|
@Module({
|
|
68
81
|
imports: [
|
|
69
|
-
DatabaseModule.
|
|
70
|
-
|
|
82
|
+
DatabaseModule.forRootAsync({
|
|
83
|
+
inject: [DI_TOKENS.CloudflareEnv],
|
|
84
|
+
useFactory: (env) => ({
|
|
85
|
+
default: 'main',
|
|
86
|
+
connections: [
|
|
87
|
+
{
|
|
88
|
+
name: 'main',
|
|
89
|
+
schema,
|
|
90
|
+
dialect: () => new PostgresDialect({
|
|
91
|
+
pool: createPoolFactory(env, () => new Pool({
|
|
92
|
+
connectionString: env.HYPERDRIVE.connectionString,
|
|
93
|
+
})),
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}),
|
|
71
98
|
}),
|
|
72
99
|
AuthModule.forRootAsync({
|
|
73
|
-
inject: [],
|
|
74
|
-
useFactory: () => ({
|
|
100
|
+
inject: [DI_TOKENS.Database],
|
|
101
|
+
useFactory: (db) => ({
|
|
102
|
+
database: db,
|
|
103
|
+
emailAndPassword: { enabled: true },
|
|
104
|
+
accessControl: permissions,
|
|
105
|
+
}),
|
|
75
106
|
}),
|
|
76
|
-
RbacModule.forRoot({ model: MODEL }),
|
|
77
107
|
],
|
|
78
108
|
})
|
|
79
109
|
class AppModule {}
|
|
@@ -81,10 +111,122 @@ class AppModule {}
|
|
|
81
111
|
export default new Stratal({ module: AppModule })
|
|
82
112
|
```
|
|
83
113
|
|
|
114
|
+
A connection takes a generated ZenStack `schema` and a `dialect` factory. `createPoolFactory()` picks the connection topology from the environment — a fresh pool per resolution behind Hyperdrive in production, one shared pool under `@stratal/testing` — so your app config never branches on whether it is running under test.
|
|
115
|
+
|
|
116
|
+
The factory is only called when the module initializes, so a large generated schema can sit behind an `import()` and stay out of the isolate's startup budget.
|
|
117
|
+
|
|
118
|
+
## Access control
|
|
119
|
+
|
|
120
|
+
Define resources and roles once with `createAccessControl()`, then hand the result to Better Auth:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// permissions.ts
|
|
124
|
+
import { createAccessControl } from '@stratal/framework/access-control'
|
|
125
|
+
|
|
126
|
+
export const permissions = createAccessControl({
|
|
127
|
+
resources: {
|
|
128
|
+
posts: ['create', 'read', 'update', 'delete'],
|
|
129
|
+
admin: ['access'],
|
|
130
|
+
} as const,
|
|
131
|
+
roles: {
|
|
132
|
+
user: { posts: ['create', 'read'] },
|
|
133
|
+
editor: { posts: ['create', 'read', 'update'] },
|
|
134
|
+
admin: { posts: ['create', 'read', 'update', 'delete'], admin: ['access'] },
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Compose roles with `extendRole()` — duplicate resource keys are merged, not overwritten:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { extendRole } from '@stratal/framework/access-control'
|
|
143
|
+
|
|
144
|
+
const { ac, roles } = permissions
|
|
145
|
+
const superAdmin = extendRole(ac, roles.admin, { users: ['ban', 'delete'] })
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Guards
|
|
149
|
+
|
|
150
|
+
`AuthGuard()` enforces authentication; pass `permissions` to enforce authorization too. Prefer guards over checking the auth context by hand — they throw the right 401 / 403 automatically.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { AuthGuard } from '@stratal/framework/guards'
|
|
154
|
+
import { UseGuards } from 'stratal/guards'
|
|
155
|
+
import { Controller } from 'stratal/router'
|
|
156
|
+
|
|
157
|
+
@Controller('/profile', { version: '1' })
|
|
158
|
+
@UseGuards(AuthGuard())
|
|
159
|
+
export class ProfileController {}
|
|
160
|
+
|
|
161
|
+
@Controller('/admin', { version: '1' })
|
|
162
|
+
@UseGuards(AuthGuard({ permissions: 'admin:access' }))
|
|
163
|
+
export class AdminController {}
|
|
164
|
+
|
|
165
|
+
// Several permissions — all must be satisfied by one of the user's roles
|
|
166
|
+
@Controller('/posts', { version: '1' })
|
|
167
|
+
@UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))
|
|
168
|
+
export class PostsController {}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Permissions are written `resource:action`; `resource` alone means "any action on that resource". A list is combined with AND — every permission must be granted — while a user holding several roles passes if any one role satisfies the whole list.
|
|
172
|
+
|
|
173
|
+
Guards can also be applied per method, and combined with Stratal's [versioning](https://stratal.dev) so the version never has to be written into the path.
|
|
174
|
+
|
|
175
|
+
## Reading the authenticated user
|
|
176
|
+
|
|
177
|
+
Enforcement belongs on the guard, not in the handler. Once `AuthGuard()` is applied, the route only runs for an authenticated user, so reading the user is just a read:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
@Controller('/profile', { version: '1' })
|
|
181
|
+
@UseGuards(AuthGuard())
|
|
182
|
+
export class ProfileController {
|
|
183
|
+
async show(ctx: RouterContext) {
|
|
184
|
+
return ctx.json(await this.service.forUser(ctx.user().id))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`ctx.user()` is a `RouterContext` shorthand that `AuthModule` adds. Inject `AuthContext` where you don't have the request context to hand:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import type { AuthContext } from '@stratal/framework/context'
|
|
193
|
+
import { DI_TOKENS, inject, Transient } from 'stratal/di'
|
|
194
|
+
|
|
195
|
+
@Transient()
|
|
196
|
+
export class ProfileService {
|
|
197
|
+
constructor(@inject(DI_TOKENS.AuthContext) private auth: AuthContext) {}
|
|
198
|
+
|
|
199
|
+
profile() {
|
|
200
|
+
return this.auth.requireUser()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`requireUser()` throws `UserNotAuthenticatedError` when signed out — a backstop for code reachable outside a guarded route, not a substitute for the guard. `getUser()` returns `AuthUser | undefined` instead of throwing, and `getRoles()` splits `user.role` on commas.
|
|
206
|
+
|
|
207
|
+
Augment `AuthUser` to type your own fields:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
declare module '@stratal/framework/context' {
|
|
211
|
+
interface AuthUser {
|
|
212
|
+
firstName: string
|
|
213
|
+
role: string
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
84
218
|
## Documentation
|
|
85
219
|
|
|
86
220
|
Full guides and examples are available at **[stratal.dev](https://stratal.dev)**.
|
|
87
221
|
|
|
222
|
+
## Support the project
|
|
223
|
+
|
|
224
|
+
If Stratal is useful to you, **[star the repository](https://github.com/strataljs/stratal)** — it is the simplest way to help others find it.
|
|
225
|
+
|
|
226
|
+
## Maintainer
|
|
227
|
+
|
|
228
|
+
Built and maintained by **Temitayo Fadojutimi** — [@adesege_](https://x.com/adesege_).
|
|
229
|
+
|
|
88
230
|
## License
|
|
89
231
|
|
|
90
232
|
MIT
|
|
@@ -5,7 +5,7 @@ import { Middleware, Next, RouterContext } from "stratal/router";
|
|
|
5
5
|
import { AccessControl, Role, Statements } from "better-auth/plugins/access";
|
|
6
6
|
import { BetterAuthPlugin } from "better-auth";
|
|
7
7
|
//#region src/access-control/tokens.d.ts
|
|
8
|
-
declare const AC_TOKENS: {
|
|
8
|
+
export declare const AC_TOKENS: {
|
|
9
9
|
/** Request-scoped access service */
|
|
10
10
|
readonly AccessService: symbol;
|
|
11
11
|
/** Access control module options (ac, roles) */
|
|
@@ -39,7 +39,7 @@ declare const AC_TOKENS: {
|
|
|
39
39
|
* plugins: [admin({ ...permissions })]
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
declare function createAccessControl<const TResources extends Statements, const TRoles extends Record<string, RolePermissions<TResources>>>(config: {
|
|
42
|
+
export declare function createAccessControl<const TResources extends Statements, const TRoles extends Record<string, RolePermissions<TResources>>>(config: {
|
|
43
43
|
resources: TResources;
|
|
44
44
|
roles: TRoles;
|
|
45
45
|
}): AccessControlOptions<TResources, TRoles>;
|
|
@@ -74,7 +74,7 @@ type MergeStatements<A extends Statements, B extends Partial<Record<string, read
|
|
|
74
74
|
* // if userRole had posts: ['create', 'read'], editorRole has posts: ['create', 'read', 'update']
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
|
-
declare function extendRole<TParent extends Statements, TExtra extends Partial<Record<string, readonly string[]>>>(ac: AccessControl<TParent>, parent: Role<TParent>, extra: TExtra): Role<MergeStatements<TParent, TExtra>>;
|
|
77
|
+
export declare function extendRole<TParent extends Statements, TExtra extends Partial<Record<string, readonly string[]>>>(ac: AccessControl<TParent>, parent: Role<TParent>, extra: TExtra): Role<MergeStatements<TParent, TExtra>>;
|
|
78
78
|
//#endregion
|
|
79
79
|
//#region src/access-control/plugin.d.ts
|
|
80
80
|
/**
|
|
@@ -86,7 +86,7 @@ declare function extendRole<TParent extends Statements, TExtra extends Partial<R
|
|
|
86
86
|
* Auto-added to Better Auth options when `accessControl` is provided to
|
|
87
87
|
* `AuthModule.forRootAsync()`. Users never call this directly.
|
|
88
88
|
*/
|
|
89
|
-
declare function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin;
|
|
89
|
+
export declare function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin;
|
|
90
90
|
//#endregion
|
|
91
91
|
//#region src/access-control/services/access.service.d.ts
|
|
92
92
|
/**
|
|
@@ -116,7 +116,7 @@ declare function createStratalAcPlugin(_options: AccessControlOptions): BetterAu
|
|
|
116
116
|
* await accessService.setUserRole(userId, ['editor', 'reviewer'])
|
|
117
117
|
* ```
|
|
118
118
|
*/
|
|
119
|
-
declare class AccessService {
|
|
119
|
+
export declare class AccessService {
|
|
120
120
|
private readonly authContext;
|
|
121
121
|
private readonly authService;
|
|
122
122
|
private readonly options;
|
|
@@ -184,18 +184,18 @@ declare class AccessService {
|
|
|
184
184
|
* Wrapped in `ctx.always()` so partial reloads never drop the prop; the client
|
|
185
185
|
* treats an absent `access` prop as a wiring error, not as "denied".
|
|
186
186
|
*/
|
|
187
|
-
declare class AccessShareMiddleware implements Middleware {
|
|
187
|
+
export declare class AccessShareMiddleware implements Middleware {
|
|
188
188
|
private readonly access?;
|
|
189
189
|
constructor(access?: AccessService | undefined);
|
|
190
190
|
handle(ctx: RouterContext, next: Next): Promise<void>;
|
|
191
191
|
}
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/access-control/errors/insufficient-permissions.error.d.ts
|
|
194
|
-
declare class InsufficientPermissionsError extends HttpException {
|
|
194
|
+
export declare class InsufficientPermissionsError extends HttpException {
|
|
195
195
|
readonly requiredPermissions: string;
|
|
196
196
|
readonly userId?: string;
|
|
197
197
|
constructor(requiredPermissions: string | string[], userId?: string);
|
|
198
198
|
}
|
|
199
199
|
//#endregion
|
|
200
|
-
export {
|
|
200
|
+
export type { AccessControlOptions };
|
|
201
201
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/access-control/tokens.ts","../../src/access-control/create-access-control.ts","../../src/access-control/extend-role.ts","../../src/access-control/plugin.ts","../../src/access-control/services/access.service.ts","../../src/access-control/access-share.middleware.ts","../../src/access-control/errors/insufficient-permissions.error.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/access-control/tokens.ts","../../src/access-control/create-access-control.ts","../../src/access-control/extend-role.ts","../../src/access-control/plugin.ts","../../src/access-control/services/access.service.ts","../../src/access-control/access-share.middleware.ts","../../src/access-control/errors/insufficient-permissions.error.ts"],"mappings":";;;;;;;qBAAa;;WAEX;;WAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBC0Bc,0BACR,mBAAmB,kBACnB,eAAe,eAAe,gBAAgB,cACpD;EACA,WAAW;EACX,OAAO;IACL,qBAAqB,YAAY;;;;;;;;;;;;;;KCvBhC,gBACH,UAAU,YACV,UAAU,QAAQ,yCAEjB,WAAW,UAAU,IAAI,gBAAgB,IACxC,gBAAgB,cACN,EAAE,aAAa,YAAY,EAAE,iBACvC,EAAE,KACF,gBAAgB,IAChB,YAAY,EAAE;;;;;;;;;;;;;;;;;;wBAqBF,WACd,gBAAgB,YAChB,eAAe,QAAQ,oCAEvB,IAAI,cAAc,UAClB,QAAQ,KAAK,UACb,OAAO,SACN,KAAK,gBAAgB,SAAS;;;;;;;;;;;;wBCtCjB,sBAAsB,UAAU,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBC4B1D;mBAGQ;mBAEA;mBAEA;EANnB,YAEmB,aAAa,aAEb,aAAa,aAEb,SAAS;;;;;;;EAStB,aAAa,iBAAiB;;;;;;EAe9B,YAAY,gBAAgB,0BAA0B;;;;;;EActD,cAAc,gBAAgB,aAAa,2BAA2B;;;;;EAStE,sBAAsB,iBAAiB,QAAQ;;;;;EASrD;;;;;EAQA,yBAAyB,aAAa;;;;;EAUtC,6BAA6B;UAKrB;UAqBA;;;;;;;;;;;;;;;;;;;;;;;qBC7GG,iCAAiC;mBAGzB;EAFnB,YAEmB,SAAS;EAGtB,OAAO,KAAK,eAAe,MAAM,OAAO;;;;qBC1CnC,qCAAqC;WAChC;WACA;EAEhB,YAAY,wCAAwC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as AC_TOKENS } from "../decorateParam-
|
|
2
|
-
import { a as AccessShareMiddleware, i as createStratalAcPlugin, t as AccessService } from "../access.service-
|
|
1
|
+
import { n as AC_TOKENS } from "../decorateParam-xwTkq9gO.mjs";
|
|
2
|
+
import { a as AccessShareMiddleware, i as createStratalAcPlugin, t as AccessService } from "../access.service-BjmnWBEo.mjs";
|
|
3
3
|
import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-DeEyZRgy.mjs";
|
|
4
4
|
import { createAccessControl as createAccessControl$1 } from "better-auth/plugins/access";
|
|
5
5
|
//#region src/access-control/create-access-control.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as AC_TOKENS, t as __decorateParam } from "./decorateParam-
|
|
2
|
-
import { t as __decorate } from "./decorate-
|
|
1
|
+
import { n as AC_TOKENS, t as __decorateParam } from "./decorateParam-xwTkq9gO.mjs";
|
|
2
|
+
import { t as __decorate } from "./decorate-RQD1h28J.mjs";
|
|
3
3
|
import { DI_TOKENS, Request, Transient, inject } from "stratal/di";
|
|
4
4
|
//#region src/access-control/access-share.middleware.ts
|
|
5
5
|
let AccessShareMiddleware = class AccessShareMiddleware {
|
|
@@ -157,4 +157,4 @@ AccessService = __decorate([
|
|
|
157
157
|
//#endregion
|
|
158
158
|
export { AccessShareMiddleware as a, createStratalAcPlugin as i, AUTH_OPTIONS as n, AUTH_SERVICE as r, AccessService as t };
|
|
159
159
|
|
|
160
|
-
//# sourceMappingURL=access.service-
|
|
160
|
+
//# sourceMappingURL=access.service-BjmnWBEo.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.service-rsEreT-4.mjs","names":[],"sources":["../src/access-control/access-share.middleware.ts","../src/access-control/plugin.ts","../src/auth/auth.tokens.ts","../src/access-control/services/access.service.ts"],"sourcesContent":["import { inject, Transient } from 'stratal/di'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport type { AccessService } from './services/access.service'\nimport { AC_TOKENS } from './tokens'\n\n/**\n * The Inertia methods this middleware needs, declared structurally.\n *\n * `@stratal/framework` must not depend on `@stratal/inertia` (the dependency\n * runs the other way at the type level, via the generated registry), so the\n * two optional context methods are described here and probed at runtime\n * instead of pulled in through a `/// <reference types=\"...\" />`.\n */\ninterface MaybeInertiaContext {\n share?: (key: string, value: unknown) => void\n always?: <T>(callback: () => T) => unknown\n}\n\n/**\n * Shares the current user's roles and merged permissions as the `access` prop\n * on every Inertia page rendered during the request, so the client can gate\n * rendering with `<Can>` / `<HasRole>` from `@stratal/inertia/react/access`.\n *\n * Both values come from `AuthContext` (populated by\n * `SessionVerificationMiddleware`) — no database hit and no I/O, which is why\n * this is registered globally by `AuthModule` rather than being opt-in the way\n * `FeatureFlagShareMiddleware` is.\n *\n * Inert unless access control is configured and Inertia is installed. Not\n * restricted to `GET`: an Inertia render can legally happen on any method —\n * e.g. `return ctx.inertia('Posts/Create', {...})` from a `POST` handler\n * re-rendering after a validation failure — and every `<Can>` / `useCan` on\n * that page needs `access` just as much as it would on a full `GET` visit.\n *\n * Wrapped in `ctx.always()` so partial reloads never drop the prop; the client\n * treats an absent `access` prop as a wiring error, not as \"denied\".\n */\n@Transient()\nexport class AccessShareMiddleware implements Middleware {\n constructor(\n @inject(AC_TOKENS.AccessService, { isOptional: true })\n private readonly access?: AccessService,\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n const access = this.access\n const inertia = ctx as RouterContext & MaybeInertiaContext\n\n if (\n access\n && typeof inertia.share === 'function'\n && typeof inertia.always === 'function'\n ) {\n inertia.share('access', inertia.always(() => ({\n roles: access.getCurrentUserRoles(),\n permissions: access.getCurrentUserPermissions(),\n })))\n }\n\n await next()\n }\n}\n","import type { BetterAuthPlugin } from 'better-auth'\nimport type { AccessControlOptions } from './types'\n\n/**\n * Creates the Stratal access control Better Auth plugin.\n *\n * Ensures the `user.role` schema field exists.\n * No endpoints are added — all permission logic lives in AccessService.\n *\n * Auto-added to Better Auth options when `accessControl` is provided to\n * `AuthModule.forRootAsync()`. Users never call this directly.\n */\nexport function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin {\n return {\n id: 'stratal-ac',\n schema: {\n user: {\n fields: {\n role: {\n type: 'string',\n required: false,\n input: false,\n defaultValue: 'user',\n },\n },\n },\n },\n }\n}\n","/** Token for AuthService - core authentication service */\nexport const AUTH_SERVICE = Symbol.for('stratal:auth:service')\n\n/** Token for Better Auth options configuration */\nexport const AUTH_OPTIONS = Symbol.for('stratal:auth:options')\n","import { DI_TOKENS, inject, Request } from 'stratal/di'\nimport { AUTH_SERVICE } from '../../auth/auth.tokens'\nimport type { AuthService } from '../../auth/services/auth.service'\nimport type { AuthContext } from '../../context/auth-context'\nimport { AC_TOKENS } from '../tokens'\nimport type { AccessControlOptions } from '../types'\n\nfunction parseRoles(role: string | null | undefined): string[] {\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n}\n\n/**\n * AccessService\n *\n * Request-scoped service for role and permission management.\n *\n * Roles for the current user are read from AuthContext (populated by\n * SessionVerificationMiddleware — no DB hit). Other users are resolved\n * from the database.\n *\n * Permission checks use Better Auth's `role.authorize()` locally with\n * OR logic — access is granted if any of the user's roles allows it.\n *\n * @example\n * ```typescript\n * // Check current user\n * await accessService.currentUserHasPermission({ posts: ['update'] })\n *\n * // Check arbitrary user (e.g. from an admin action)\n * await accessService.hasPermission(userId, { admin: ['access'] })\n *\n * // Assign a role\n * await accessService.setUserRole(userId, 'admin')\n *\n * // Assign multiple roles\n * await accessService.setUserRole(userId, ['editor', 'reviewer'])\n * ```\n */\n@Request(AC_TOKENS.AccessService)\nexport class AccessService {\n constructor(\n @inject(DI_TOKENS.AuthContext)\n private readonly authContext: AuthContext,\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(AC_TOKENS.Options)\n private readonly options: AccessControlOptions\n ) { }\n\n /**\n * Get all roles for a user.\n *\n * Uses AuthContext for the current user (no DB hit).\n * Falls back to DB for other users.\n */\n async getUserRoles(userId: string): Promise<string[]> {\n if (userId === this.authContext.getUserId()) {\n const roles = this.authContext.getRoles()\n if (roles.length > 0) return roles\n }\n const ctx = await this.authService.auth.$context\n const user = await ctx.internalAdapter.findUserById(userId)\n return parseRoles((user as { role?: string | null } | null)?.role)\n }\n\n /**\n * Assign one or more roles to a user.\n *\n * Multiple roles are stored as a comma-separated string in `user.role`.\n */\n async setUserRole(userId: string, role: string | string[]): Promise<void> {\n const roleStr = Array.isArray(role) ? role.join(',') : role\n const ctx = await this.authService.auth.$context\n await ctx.internalAdapter.updateUser(\n userId,\n { role: roleStr } as Parameters<typeof ctx.internalAdapter.updateUser>[1]\n )\n }\n\n /**\n * Check if a user has the required permissions.\n *\n * Returns true if any of the user's roles grants all of the requested permissions.\n */\n async hasPermission(userId: string, permissions: Record<string, string[]>): Promise<boolean> {\n const roles = await this.getUserRoles(userId)\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get the merged permission set for a user across all their roles.\n * Useful for sending to the frontend.\n */\n async getPermissionsForUser(userId: string): Promise<Record<string, string[]>> {\n const roles = await this.getUserRoles(userId)\n return this.mergePermissions(roles)\n }\n\n /**\n * Get all roles for the currently authenticated user.\n * Reads from AuthContext — no DB hit.\n */\n getCurrentUserRoles(): string[] {\n return this.authContext.getRoles()\n }\n\n /**\n * Check if the currently authenticated user has the required permissions.\n * Reads roles from AuthContext — no DB hit.\n */\n currentUserHasPermission(permissions: Record<string, string[]>): boolean {\n const roles = this.authContext.getRoles()\n if (roles.length === 0) return false\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get merged permissions for the currently authenticated user.\n * Reads roles from AuthContext — no DB hit.\n */\n getCurrentUserPermissions(): Record<string, string[]> {\n const roles = this.authContext.getRoles()\n return this.mergePermissions(roles)\n }\n\n private checkPermissions(roles: string[], permissions: Record<string, string[]>): boolean {\n return roles.some(roleName => {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) return false\n\n const specific: Record<string, string[]> = {}\n\n for (const [resource, actions] of Object.entries(permissions)) {\n if (actions.includes('*')) {\n // Wildcard: role must have at least one action defined for this resource\n const roleActions = (roleObj.statements as Record<string, readonly string[]>)[resource]\n if (!roleActions?.length) return false\n } else {\n specific[resource] = actions\n }\n }\n\n return Object.keys(specific).length === 0 || roleObj.authorize(specific).success\n })\n }\n\n private mergePermissions(roles: string[]): Record<string, string[]> {\n const result: Record<string, string[]> = {}\n for (const roleName of roles) {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) continue\n for (const [resource, actions] of Object.entries(roleObj.statements)) {\n result[resource] ??= []\n for (const action of actions as string[]) {\n if (!result[resource].includes(action)) {\n result[resource].push(action)\n }\n }\n }\n }\n return result\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"access.service-BjmnWBEo.mjs","names":[],"sources":["../src/access-control/access-share.middleware.ts","../src/access-control/plugin.ts","../src/auth/auth.tokens.ts","../src/access-control/services/access.service.ts"],"sourcesContent":["import { inject, Transient } from 'stratal/di'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport type { AccessService } from './services/access.service'\nimport { AC_TOKENS } from './tokens'\n\n/**\n * The Inertia methods this middleware needs, declared structurally.\n *\n * `@stratal/framework` must not depend on `@stratal/inertia` (the dependency\n * runs the other way at the type level, via the generated registry), so the\n * two optional context methods are described here and probed at runtime\n * instead of pulled in through a `/// <reference types=\"...\" />`.\n */\ninterface MaybeInertiaContext {\n share?: (key: string, value: unknown) => void\n always?: <T>(callback: () => T) => unknown\n}\n\n/**\n * Shares the current user's roles and merged permissions as the `access` prop\n * on every Inertia page rendered during the request, so the client can gate\n * rendering with `<Can>` / `<HasRole>` from `@stratal/inertia/react/access`.\n *\n * Both values come from `AuthContext` (populated by\n * `SessionVerificationMiddleware`) — no database hit and no I/O, which is why\n * this is registered globally by `AuthModule` rather than being opt-in the way\n * `FeatureFlagShareMiddleware` is.\n *\n * Inert unless access control is configured and Inertia is installed. Not\n * restricted to `GET`: an Inertia render can legally happen on any method —\n * e.g. `return ctx.inertia('Posts/Create', {...})` from a `POST` handler\n * re-rendering after a validation failure — and every `<Can>` / `useCan` on\n * that page needs `access` just as much as it would on a full `GET` visit.\n *\n * Wrapped in `ctx.always()` so partial reloads never drop the prop; the client\n * treats an absent `access` prop as a wiring error, not as \"denied\".\n */\n@Transient()\nexport class AccessShareMiddleware implements Middleware {\n constructor(\n @inject(AC_TOKENS.AccessService, { isOptional: true })\n private readonly access?: AccessService,\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n const access = this.access\n const inertia = ctx as RouterContext & MaybeInertiaContext\n\n if (\n access\n && typeof inertia.share === 'function'\n && typeof inertia.always === 'function'\n ) {\n inertia.share('access', inertia.always(() => ({\n roles: access.getCurrentUserRoles(),\n permissions: access.getCurrentUserPermissions(),\n })))\n }\n\n await next()\n }\n}\n","import type { BetterAuthPlugin } from 'better-auth'\nimport type { AccessControlOptions } from './types'\n\n/**\n * Creates the Stratal access control Better Auth plugin.\n *\n * Ensures the `user.role` schema field exists.\n * No endpoints are added — all permission logic lives in AccessService.\n *\n * Auto-added to Better Auth options when `accessControl` is provided to\n * `AuthModule.forRootAsync()`. Users never call this directly.\n */\nexport function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin {\n return {\n id: 'stratal-ac',\n schema: {\n user: {\n fields: {\n role: {\n type: 'string',\n required: false,\n input: false,\n defaultValue: 'user',\n },\n },\n },\n },\n }\n}\n","/** Token for AuthService - core authentication service */\nexport const AUTH_SERVICE = Symbol.for('stratal:auth:service')\n\n/** Token for Better Auth options configuration */\nexport const AUTH_OPTIONS = Symbol.for('stratal:auth:options')\n","import { DI_TOKENS, inject, Request } from 'stratal/di'\nimport { AUTH_SERVICE } from '../../auth/auth.tokens'\nimport type { AuthService } from '../../auth/services/auth.service'\nimport type { AuthContext } from '../../context/auth-context'\nimport { AC_TOKENS } from '../tokens'\nimport type { AccessControlOptions } from '../types'\n\nfunction parseRoles(role: string | null | undefined): string[] {\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n}\n\n/**\n * AccessService\n *\n * Request-scoped service for role and permission management.\n *\n * Roles for the current user are read from AuthContext (populated by\n * SessionVerificationMiddleware — no DB hit). Other users are resolved\n * from the database.\n *\n * Permission checks use Better Auth's `role.authorize()` locally with\n * OR logic — access is granted if any of the user's roles allows it.\n *\n * @example\n * ```typescript\n * // Check current user\n * await accessService.currentUserHasPermission({ posts: ['update'] })\n *\n * // Check arbitrary user (e.g. from an admin action)\n * await accessService.hasPermission(userId, { admin: ['access'] })\n *\n * // Assign a role\n * await accessService.setUserRole(userId, 'admin')\n *\n * // Assign multiple roles\n * await accessService.setUserRole(userId, ['editor', 'reviewer'])\n * ```\n */\n@Request(AC_TOKENS.AccessService)\nexport class AccessService {\n constructor(\n @inject(DI_TOKENS.AuthContext)\n private readonly authContext: AuthContext,\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(AC_TOKENS.Options)\n private readonly options: AccessControlOptions\n ) { }\n\n /**\n * Get all roles for a user.\n *\n * Uses AuthContext for the current user (no DB hit).\n * Falls back to DB for other users.\n */\n async getUserRoles(userId: string): Promise<string[]> {\n if (userId === this.authContext.getUserId()) {\n const roles = this.authContext.getRoles()\n if (roles.length > 0) return roles\n }\n const ctx = await this.authService.auth.$context\n const user = await ctx.internalAdapter.findUserById(userId)\n return parseRoles((user as { role?: string | null } | null)?.role)\n }\n\n /**\n * Assign one or more roles to a user.\n *\n * Multiple roles are stored as a comma-separated string in `user.role`.\n */\n async setUserRole(userId: string, role: string | string[]): Promise<void> {\n const roleStr = Array.isArray(role) ? role.join(',') : role\n const ctx = await this.authService.auth.$context\n await ctx.internalAdapter.updateUser(\n userId,\n { role: roleStr } as Parameters<typeof ctx.internalAdapter.updateUser>[1]\n )\n }\n\n /**\n * Check if a user has the required permissions.\n *\n * Returns true if any of the user's roles grants all of the requested permissions.\n */\n async hasPermission(userId: string, permissions: Record<string, string[]>): Promise<boolean> {\n const roles = await this.getUserRoles(userId)\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get the merged permission set for a user across all their roles.\n * Useful for sending to the frontend.\n */\n async getPermissionsForUser(userId: string): Promise<Record<string, string[]>> {\n const roles = await this.getUserRoles(userId)\n return this.mergePermissions(roles)\n }\n\n /**\n * Get all roles for the currently authenticated user.\n * Reads from AuthContext — no DB hit.\n */\n getCurrentUserRoles(): string[] {\n return this.authContext.getRoles()\n }\n\n /**\n * Check if the currently authenticated user has the required permissions.\n * Reads roles from AuthContext — no DB hit.\n */\n currentUserHasPermission(permissions: Record<string, string[]>): boolean {\n const roles = this.authContext.getRoles()\n if (roles.length === 0) return false\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get merged permissions for the currently authenticated user.\n * Reads roles from AuthContext — no DB hit.\n */\n getCurrentUserPermissions(): Record<string, string[]> {\n const roles = this.authContext.getRoles()\n return this.mergePermissions(roles)\n }\n\n private checkPermissions(roles: string[], permissions: Record<string, string[]>): boolean {\n return roles.some(roleName => {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) return false\n\n const specific: Record<string, string[]> = {}\n\n for (const [resource, actions] of Object.entries(permissions)) {\n if (actions.includes('*')) {\n // Wildcard: role must have at least one action defined for this resource\n const roleActions = (roleObj.statements as Record<string, readonly string[]>)[resource]\n if (!roleActions?.length) return false\n } else {\n specific[resource] = actions\n }\n }\n\n return Object.keys(specific).length === 0 || roleObj.authorize(specific).success\n })\n }\n\n private mergePermissions(roles: string[]): Record<string, string[]> {\n const result: Record<string, string[]> = {}\n for (const roleName of roles) {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) continue\n for (const [resource, actions] of Object.entries(roleObj.statements)) {\n result[resource] ??= []\n for (const action of actions as string[]) {\n if (!result[resource].includes(action)) {\n result[resource].push(action)\n }\n }\n }\n }\n return result\n }\n}\n"],"mappings":"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"}
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -20,14 +20,14 @@ declare module 'stratal/router' {
|
|
|
20
20
|
}
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/auth/auth.module.d.ts
|
|
23
|
-
interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions> extends AsyncModuleOptions<TOptions> {
|
|
23
|
+
export interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions> extends AsyncModuleOptions<TOptions> {
|
|
24
24
|
/**
|
|
25
25
|
* Optional access control configuration.
|
|
26
26
|
* When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.
|
|
27
27
|
*/
|
|
28
28
|
accessControl?: AccessControlOptions;
|
|
29
29
|
}
|
|
30
|
-
declare class AuthModule implements RouteConfigurable {
|
|
30
|
+
export declare class AuthModule implements RouteConfigurable {
|
|
31
31
|
/**
|
|
32
32
|
* Configure auth middleware globally.
|
|
33
33
|
*
|
|
@@ -62,9 +62,10 @@ declare class AuthModule implements RouteConfigurable {
|
|
|
62
62
|
* **Responsibilities:**
|
|
63
63
|
* - Calls Better Auth's getSession() API
|
|
64
64
|
* - Populates AuthContext with the user record if the session is valid
|
|
65
|
+
* - Carries any cookie that read issued through to the response
|
|
65
66
|
* - Continues request chain regardless of session status
|
|
66
67
|
*/
|
|
67
|
-
declare class SessionVerificationMiddleware implements Middleware {
|
|
68
|
+
export declare class SessionVerificationMiddleware implements Middleware {
|
|
68
69
|
private readonly authService;
|
|
69
70
|
private logger;
|
|
70
71
|
constructor(authService: AuthService, logger: LoggerService);
|
|
@@ -100,139 +101,139 @@ declare class SessionVerificationMiddleware implements Middleware {
|
|
|
100
101
|
* })
|
|
101
102
|
* ```
|
|
102
103
|
*/
|
|
103
|
-
declare const AUTH_GATEWAY_PRIMERS: readonly [typeof SessionVerificationMiddleware];
|
|
104
|
+
export declare const AUTH_GATEWAY_PRIMERS: readonly [typeof SessionVerificationMiddleware];
|
|
104
105
|
//#endregion
|
|
105
106
|
//#region src/auth/auth.tokens.d.ts
|
|
106
107
|
/** Token for AuthService - core authentication service */
|
|
107
|
-
declare const AUTH_SERVICE: unique symbol;
|
|
108
|
+
export declare const AUTH_SERVICE: unique symbol;
|
|
108
109
|
/** Token for Better Auth options configuration */
|
|
109
|
-
declare const AUTH_OPTIONS: unique symbol;
|
|
110
|
+
export declare const AUTH_OPTIONS: unique symbol;
|
|
110
111
|
//#endregion
|
|
111
112
|
//#region src/auth/errors/auth-errors.d.ts
|
|
112
|
-
declare class UserNotFoundError extends HttpException {
|
|
113
|
+
export declare class UserNotFoundError extends HttpException {
|
|
113
114
|
readonly email?: string | undefined;
|
|
114
115
|
constructor(email?: string | undefined);
|
|
115
116
|
}
|
|
116
|
-
declare class InvalidCredentialsError extends HttpException {
|
|
117
|
+
export declare class InvalidCredentialsError extends HttpException {
|
|
117
118
|
constructor();
|
|
118
119
|
}
|
|
119
|
-
declare class InvalidPasswordError extends HttpException {
|
|
120
|
+
export declare class InvalidPasswordError extends HttpException {
|
|
120
121
|
constructor();
|
|
121
122
|
}
|
|
122
|
-
declare class InvalidEmailError extends HttpException {
|
|
123
|
+
export declare class InvalidEmailError extends HttpException {
|
|
123
124
|
readonly email?: string | undefined;
|
|
124
125
|
constructor(email?: string | undefined);
|
|
125
126
|
}
|
|
126
|
-
declare class SessionExpiredError extends HttpException {
|
|
127
|
+
export declare class SessionExpiredError extends HttpException {
|
|
127
128
|
constructor();
|
|
128
129
|
}
|
|
129
|
-
declare class FreshSessionRequiredError extends HttpException {
|
|
130
|
+
export declare class FreshSessionRequiredError extends HttpException {
|
|
130
131
|
constructor();
|
|
131
132
|
}
|
|
132
|
-
declare class EmailNotVerifiedError extends HttpException {
|
|
133
|
+
export declare class EmailNotVerifiedError extends HttpException {
|
|
133
134
|
readonly email?: string | undefined;
|
|
134
135
|
constructor(email?: string | undefined);
|
|
135
136
|
}
|
|
136
|
-
declare class PasswordTooShortError extends HttpException {
|
|
137
|
+
export declare class PasswordTooShortError extends HttpException {
|
|
137
138
|
readonly minLength?: number | undefined;
|
|
138
139
|
constructor(minLength?: number | undefined);
|
|
139
140
|
}
|
|
140
|
-
declare class PasswordTooLongError extends HttpException {
|
|
141
|
+
export declare class PasswordTooLongError extends HttpException {
|
|
141
142
|
readonly maxLength?: number | undefined;
|
|
142
143
|
constructor(maxLength?: number | undefined);
|
|
143
144
|
}
|
|
144
|
-
declare class AccountAlreadyExistsError extends HttpException {
|
|
145
|
+
export declare class AccountAlreadyExistsError extends HttpException {
|
|
145
146
|
readonly email?: string | undefined;
|
|
146
147
|
constructor(email?: string | undefined);
|
|
147
148
|
}
|
|
148
|
-
declare class SocialAccountLinkedError extends HttpException {
|
|
149
|
+
export declare class SocialAccountLinkedError extends HttpException {
|
|
149
150
|
readonly provider?: string | undefined;
|
|
150
151
|
constructor(provider?: string | undefined);
|
|
151
152
|
}
|
|
152
|
-
declare class CannotUnlinkLastAccountError extends HttpException {
|
|
153
|
+
export declare class CannotUnlinkLastAccountError extends HttpException {
|
|
153
154
|
constructor();
|
|
154
155
|
}
|
|
155
|
-
declare class ProviderNotFoundError extends HttpException {
|
|
156
|
+
export declare class ProviderNotFoundError extends HttpException {
|
|
156
157
|
readonly provider?: string | undefined;
|
|
157
158
|
constructor(provider?: string | undefined);
|
|
158
159
|
}
|
|
159
|
-
declare class UserEmailNotFoundError extends HttpException {
|
|
160
|
+
export declare class UserEmailNotFoundError extends HttpException {
|
|
160
161
|
constructor();
|
|
161
162
|
}
|
|
162
|
-
declare class AccountNotFoundError extends HttpException {
|
|
163
|
+
export declare class AccountNotFoundError extends HttpException {
|
|
163
164
|
constructor();
|
|
164
165
|
}
|
|
165
|
-
declare class CredentialAccountNotFoundError extends HttpException {
|
|
166
|
+
export declare class CredentialAccountNotFoundError extends HttpException {
|
|
166
167
|
constructor();
|
|
167
168
|
}
|
|
168
|
-
declare class UserAlreadyHasPasswordError extends HttpException {
|
|
169
|
+
export declare class UserAlreadyHasPasswordError extends HttpException {
|
|
169
170
|
constructor();
|
|
170
171
|
}
|
|
171
|
-
declare class EmailCannotBeUpdatedError extends HttpException {
|
|
172
|
+
export declare class EmailCannotBeUpdatedError extends HttpException {
|
|
172
173
|
readonly reason?: string | undefined;
|
|
173
174
|
constructor(reason?: string | undefined);
|
|
174
175
|
}
|
|
175
|
-
declare class IdTokenNotSupportedError extends HttpException {
|
|
176
|
+
export declare class IdTokenNotSupportedError extends HttpException {
|
|
176
177
|
constructor();
|
|
177
178
|
}
|
|
178
|
-
declare class TokenExpiredError extends HttpException {
|
|
179
|
+
export declare class TokenExpiredError extends HttpException {
|
|
179
180
|
constructor();
|
|
180
181
|
}
|
|
181
|
-
declare class InvalidCallbackUrlError extends HttpException {
|
|
182
|
+
export declare class InvalidCallbackUrlError extends HttpException {
|
|
182
183
|
constructor();
|
|
183
184
|
}
|
|
184
|
-
declare class InvalidOriginError extends HttpException {
|
|
185
|
+
export declare class InvalidOriginError extends HttpException {
|
|
185
186
|
constructor();
|
|
186
187
|
}
|
|
187
|
-
declare class AuthValidationFailedError extends HttpException {
|
|
188
|
+
export declare class AuthValidationFailedError extends HttpException {
|
|
188
189
|
constructor();
|
|
189
190
|
}
|
|
190
|
-
declare class EmailAlreadyVerifiedError extends HttpException {
|
|
191
|
+
export declare class EmailAlreadyVerifiedError extends HttpException {
|
|
191
192
|
constructor();
|
|
192
193
|
}
|
|
193
|
-
declare class EmailMismatchError extends HttpException {
|
|
194
|
+
export declare class EmailMismatchError extends HttpException {
|
|
194
195
|
constructor();
|
|
195
196
|
}
|
|
196
197
|
//#endregion
|
|
197
198
|
//#region src/auth/errors/invalid-token.error.d.ts
|
|
198
|
-
declare class InvalidTokenError extends HttpException {
|
|
199
|
+
export declare class InvalidTokenError extends HttpException {
|
|
199
200
|
constructor();
|
|
200
201
|
}
|
|
201
202
|
//#endregion
|
|
202
203
|
//#region src/auth/errors/organization-errors.d.ts
|
|
203
|
-
declare class OrganizationNotFoundError extends HttpException {
|
|
204
|
+
export declare class OrganizationNotFoundError extends HttpException {
|
|
204
205
|
constructor();
|
|
205
206
|
}
|
|
206
|
-
declare class OrganizationMemberNotFoundError extends HttpException {
|
|
207
|
+
export declare class OrganizationMemberNotFoundError extends HttpException {
|
|
207
208
|
constructor();
|
|
208
209
|
}
|
|
209
|
-
declare class OrganizationInvitationNotFoundError extends HttpException {
|
|
210
|
+
export declare class OrganizationInvitationNotFoundError extends HttpException {
|
|
210
211
|
constructor();
|
|
211
212
|
}
|
|
212
|
-
declare class OrganizationPermissionDeniedError extends HttpException {
|
|
213
|
+
export declare class OrganizationPermissionDeniedError extends HttpException {
|
|
213
214
|
constructor();
|
|
214
215
|
}
|
|
215
|
-
declare class OrganizationInvitationRecipientMismatchError extends HttpException {
|
|
216
|
+
export declare class OrganizationInvitationRecipientMismatchError extends HttpException {
|
|
216
217
|
constructor();
|
|
217
218
|
}
|
|
218
|
-
declare class OrganizationConflictError extends HttpException {
|
|
219
|
+
export declare class OrganizationConflictError extends HttpException {
|
|
219
220
|
constructor();
|
|
220
221
|
}
|
|
221
|
-
declare class OrganizationLimitReachedError extends HttpException {
|
|
222
|
+
export declare class OrganizationLimitReachedError extends HttpException {
|
|
222
223
|
constructor();
|
|
223
224
|
}
|
|
224
|
-
declare class OrganizationMembershipError extends HttpException {
|
|
225
|
+
export declare class OrganizationMembershipError extends HttpException {
|
|
225
226
|
constructor();
|
|
226
227
|
}
|
|
227
|
-
declare class OrganizationTeamNotFoundError extends HttpException {
|
|
228
|
+
export declare class OrganizationTeamNotFoundError extends HttpException {
|
|
228
229
|
constructor();
|
|
229
230
|
}
|
|
230
|
-
declare class OrganizationRoleNotFoundError extends HttpException {
|
|
231
|
+
export declare class OrganizationRoleNotFoundError extends HttpException {
|
|
231
232
|
constructor();
|
|
232
233
|
}
|
|
233
234
|
//#endregion
|
|
234
235
|
//#region src/auth/errors/token-required.error.d.ts
|
|
235
|
-
declare class TokenRequiredError extends HttpException {
|
|
236
|
+
export declare class TokenRequiredError extends HttpException {
|
|
236
237
|
constructor();
|
|
237
238
|
}
|
|
238
239
|
//#endregion
|
|
@@ -241,23 +242,23 @@ declare class TokenRequiredError extends HttpException {
|
|
|
241
242
|
* Get shared Better Auth error handler configuration.
|
|
242
243
|
* Use this in Better Auth config's onAPIError option.
|
|
243
244
|
*/
|
|
244
|
-
declare function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'];
|
|
245
|
+
export declare function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'];
|
|
245
246
|
/**
|
|
246
247
|
* Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.
|
|
247
248
|
*/
|
|
248
|
-
declare const wrapBetterAuth: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
249
|
+
export declare const wrapBetterAuth: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
249
250
|
//#endregion
|
|
250
251
|
//#region src/auth/utils/better-auth-error-handler.d.ts
|
|
251
252
|
/**
|
|
252
253
|
* Maps Better Auth API error codes to ApplicationError instances.
|
|
253
254
|
*/
|
|
254
|
-
declare function mapBetterAuthError(error: APIError): ApplicationError;
|
|
255
|
+
export declare function mapBetterAuthError(error: APIError): ApplicationError;
|
|
255
256
|
/**
|
|
256
257
|
* Type guard to check if an error is a Better Auth APIError.
|
|
257
258
|
* Uses duck typing to handle bundler environments (e.g. Vite)
|
|
258
259
|
* where instanceof may fail across module boundaries.
|
|
259
260
|
*/
|
|
260
|
-
declare function isAPIError(error: unknown): error is APIError;
|
|
261
|
+
export declare function isAPIError(error: unknown): error is APIError;
|
|
261
262
|
//#endregion
|
|
262
|
-
export {
|
|
263
|
+
export { AuthService };
|
|
263
264
|
//# sourceMappingURL=index.d.mts.map
|