@dunx/auth 2.4.0 → 3.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/README.md CHANGED
@@ -1,251 +1,83 @@
1
1
  # @dunx/auth
2
2
 
3
- [Better Auth](https://better-auth.com) for dunx. **This package is not an
4
- authentication system** - better-auth is, and it is very good at it. This is the
5
- wiring: a module that builds the instance from your `ConfigService`, five routes that
6
- mount its handler, a guard that composes with the `@Public()` and `@Roles()` metadata
7
- `@dunx/http` already carries, and two adapters that let it drive Bun's own APIs.
3
+ [Better Auth](https://better-auth.com) for
4
+ [dunx](https://github.com/petarzarkov/dunx).
8
5
 
9
- `better-auth` is a **required peer dependency**. Install it yourself and own its
10
- version - dunx does not bundle it - but it is not optional, because this package
11
- imports `betterAuth` as a value and cannot load without it. Marking it optional would
12
- trade an install-time warning for a module-resolution crash.
6
+ **This package is not an authentication system.** better-auth is, and it is very
7
+ good at it. This is the wiring: a module that builds the instance from your
8
+ `ConfigService`, five routes that mount its handler, a guard that composes with
9
+ the `@Public()` and `@Roles()` metadata `@dunx/http` already carries, and two
10
+ adapters that let it drive Bun's own APIs.
11
+
12
+ There is no dunx sign-in flow, no dunx session table and no dunx OAuth. Each is
13
+ a better-auth feature reached through `AuthModule.forRoot`'s options, which
14
+ **are** better-auth's `BetterAuthOptions`.
15
+
16
+ ## Install
13
17
 
14
18
  ```bash
15
19
  bun add @dunx/auth better-auth
16
20
  ```
17
21
 
18
- `drizzle-orm` **is** an optional peer, needed only by `@dunx/auth/drizzle` - which is
19
- its own subpath precisely so that a Prisma, Kysely or MongoDB app never loads it.
20
- `dist/index.js` contains no reference to drizzle, the test a peer has to
21
- pass to be called optional.
22
-
23
- There is no dunx sign-in flow, no dunx session table, no dunx password reset and no
24
- dunx OAuth. Every one of those is a better-auth feature reached through
25
- `AuthModule.forRoot`'s options, which **are** better-auth's `BetterAuthOptions`. Its
26
- documentation is the documentation.
27
-
28
- ## What dunx adds
29
-
30
- | Export | What it is |
31
- | --------------------- | --------------------------------------------------------------------- |
32
- | `AuthModule` | `forRoot` / `forRootAsync`, binding the instance and mounting it |
33
- | `Auth` | The injection token for the better-auth instance |
34
- | `SessionGuard` | Middleware: authenticates, then reads `@Public()` and `@Roles()` |
35
- | `AuthContext` | The authenticated caller, reachable from any service in the request |
36
- | `Principal` | `{ session, user }` - better-auth's own inferred session type |
37
- | `bunPassword` | `Bun.password` bcrypt in place of better-auth's JavaScript scrypt |
38
- | `redisStorage` | `secondaryStorage` over `Bun.RedisClient` |
39
- | `drizzleDatabase` | `database` over the drizzle handle `@dunx/infra/db` already opened |
40
- | `rolesOf` | The `admin` plugin's `role` column read as a list |
41
- | `AuthOptions` | The resolved options, the `basePath` and where the handler mounted |
42
-
43
- ## Getting started
22
+ `better-auth` is a **required** peer: this package imports `betterAuth` as a
23
+ value and cannot load without it. `drizzle-orm` is an optional peer, needed only
24
+ by `@dunx/auth/drizzle` - its own subpath so a Prisma, Kysely or MongoDB app
25
+ never loads it.
26
+
27
+ ## Usage
44
28
 
45
29
  ```ts
46
30
  import { AuthModule } from '@dunx/auth';
47
31
  import { drizzleDatabase } from '@dunx/auth/drizzle';
48
- import { Module } from '@dunx/core';
49
32
  import { DbConnection } from '@dunx/infra/db';
50
- import { admin, bearer } from 'better-auth/plugins';
51
33
 
52
34
  @Module({
53
35
  imports: [
54
36
  AuthModule.forRootAsync({
37
+ imports: [DatabaseModule],
38
+ inject: [AppConfigService, DbConnection],
55
39
  useFactory: (config: AppConfigService, connection: DbConnection) => ({
56
40
  secret: config.get('auth').secret,
57
- baseURL: config.get('appUrl'),
41
+ basePath: '/api/auth',
58
42
  database: drizzleDatabase(connection),
59
43
  emailAndPassword: { enabled: true },
60
- plugins: [admin(), bearer()],
61
44
  }),
62
- inject: [AppConfigService, DbConnection] as const,
63
45
  }),
64
46
  ],
65
47
  })
66
- export class AccountsModule {}
67
- ```
68
-
69
- That is the whole integration. `forRoot(options)` is the same thing without a factory,
70
- for when the secret is not behind config.
71
-
72
- `forRootAsync` exists for the one reason it exists on `LoggerModule`, `DbModule` and
73
- the rest: a zero-argument function cannot read `ConfigService`. It is not a second
74
- mechanism - dunx settles every async factory before the first constructor runs, so
75
- the instance is built and the connection handshaked before anything can ask for
76
- either.
77
-
78
- ### The database tables
79
-
80
- **dunx ships no schema for better-auth's tables.** They are better-auth's, they change
81
- with the plugins you enable, and its own CLI generates them:
82
-
83
- ```bash
84
- bunx @better-auth/cli generate
85
- ```
86
-
87
- Put the result in the schema object you already hand `@dunx/infra/db`, and
88
- `drizzleDatabase(connection)` needs no schema argument - `@dunx/infra/db` builds its
89
- handle with `drizzle({ client, schema })`, and better-auth's adapter reads
90
- `db._.fullSchema` off it. `examples/full/src/database/auth.schema.ts` is a
91
- generated schema in place.
92
-
93
- A framework carrying its own copy of a library's tables is a copy that rots against
94
- the library that reads them.
95
-
96
- ## Mounting
97
-
98
- `AuthHandler` puts better-auth's `(request: Request) => Promise<Response>` behind five
99
- wildcard routes - `GET`, `POST`, `PUT`, `PATCH` and `DELETE` at `<basePath>/*`.
100
- `Bun.serve` matches a wildcard natively, so **Bun is still the router**: dunx does not
101
- restate, wrap or re-dispatch a single better-auth endpoint, and the `Response` comes
102
- back untouched, `Set-Cookie` headers and redirects included.
103
-
104
- `basePath` is better-auth's own option, defaulting to `/api/auth`.
105
-
106
- ### With `setGlobalPrefix`
107
-
108
- better-auth resolves an endpoint by comparing the **whole pathname** to its
109
- `basePath`, so a global prefix makes the mount and the base path two different
110
- strings for one URL:
111
-
112
- ```ts
113
- // app.setGlobalPrefix('api') turns the `/auth` route into `/api/auth`.
114
- AuthModule.forRootAsync({ useFactory: () => ({ basePath: '/api/auth', ... }) }, '/auth');
115
- ```
116
-
117
- The second argument is the **route** path; `basePath` is what the browser sees. Get it
118
- wrong and the first request through the handler fails with an `AuthError` naming both
119
- paths, rather than better-auth quietly answering 404 to everything.
120
-
121
- ## The guard
122
-
123
- ```ts
124
- // Global - every route needs a session unless it says otherwise.
125
- HttpFactory.create(root, { middleware: [SessionGuard] });
126
-
127
- // or scoped - this controller needs one, nothing else does.
128
- @UseGuards(SessionGuard)
129
- @Controller('profile')
130
- class ProfileController {}
131
- ```
132
-
133
- `AuthModule` registers `SessionGuard` as a provider either way. It resolves the
134
- session through better-auth's own `api.getSession`, so a cookie and the `bearer`
135
- plugin's `Authorization: Bearer <token>` both work, and then reads the metadata
136
- `@dunx/http` already had:
48
+ export class AuthFeatureModule {}
137
49
 
138
- - **`@Public()`** - skipped outright. No session lookup, no rejection, no role check.
139
- The guard is safe to install globally for that reason: `AuthHandler` is `@Public()`,
140
- and a sign-in endpoint that required a session could never be reached.
141
- - **`@Roles('admin', 'editor')`** - a 403 unless the caller holds one of them.
142
- `@dunx/openapi` already reads the same key for its security schemes.
143
-
144
- A public route that wants to *adapt* to an optional caller asks better-auth itself:
145
-
146
- ```ts
147
- const principal = await this.auth.api.getSession({ headers: req.headers });
148
- ```
149
-
150
- One line, and it keeps a session lookup off every public request in the app.
151
-
152
- ## Reaching the caller
153
-
154
- `AuthContext` is `AsyncLocalStorage`, so a service three constructor hops from the
155
- route sees the principal without it being threaded through a signature:
156
-
157
- ```ts
158
- export class Audit {
159
- constructor(private readonly auth: AuthContext) {}
160
-
161
- entries(): readonly string[] {
162
- const { user } = this.auth.require(); // 401 if there is none
163
- return this.log.forUser(user.id);
164
- }
165
- }
166
- ```
167
-
168
- `current()` returns `Principal | undefined`; `require()` throws a 401.
169
-
170
- Two alternatives were rejected. Request-scoped DI was measured and turned down
171
- (`docs/ARCHITECTURE.md`), and hanging the principal off `req` reaches a route handler
172
- but nothing a route handler calls. `AsyncLocalStorage` is a Node built-in Bun
173
- implements natively, and it is already how `@dunx/core` carries request state.
174
-
175
- It is a **second** store rather than a key in `RequestContext`, because that one is
176
- the log record - every field in it is serialized into every line the request writes,
177
- so a session object there would be noise on each entry and a redaction hazard in the
178
- ones that matter. `userId` does go there, so every log line inside a
179
- guarded request is already correlated to the user.
180
-
181
- ### Plugin types
182
-
183
- `Auth` is generic over the options it was built from, the same trick
184
- `@dunx/infra/db` uses for drizzle's schema: the token is the erased class, the type
185
- argument rides on the annotation.
186
-
187
- ```ts
188
- export const authOptions = { plugins: [admin()], ... } as const;
189
-
190
- // `api` here has the admin plugin's endpoints on it.
191
- constructor(private readonly auth: Auth<typeof authOptions>) {}
192
- ```
193
-
194
- Written bare, `Auth` carries better-auth's core endpoints only.
195
-
196
- ## Password hashing
197
-
198
- better-auth's default hasher is **pure-JavaScript scrypt**. `AuthModule` replaces it
199
- with `bunPassword` - native bcrypt through `Bun.password` - whenever
200
- `emailAndPassword` is enabled and you did not supply a `password` of your own. That is
201
- The rule is simple: if Bun ships it, use Bun.
202
-
203
- Bun pre-hashes the input, so bcrypt's 72-byte cap is a non-issue even for a
204
- maximum-length multibyte password, and `verify` reads a hash from another algorithm as
205
- a clean authentication failure rather than a 500.
206
-
207
- **Migrating an existing user table?** Those users' scrypt hashes will no longer verify
208
- and they will have to reset their passwords. Pass your own `emailAndPassword.password`
209
- to keep the old hasher, or a hybrid that tries both.
210
-
211
- ## Sessions in Redis
212
-
213
- ```ts
214
- import { redisStorage } from '@dunx/auth';
215
-
216
- AuthModule.forRootAsync({
217
- useFactory: (redis: RedisConnection) => ({
218
- secondaryStorage: redisStorage(redis),
219
- ...
220
- }),
221
- inject: [RedisConnection] as const,
222
- });
50
+ // Globally, and opt routes out with @Public():
51
+ HttpFactory.create(AppModule, { middleware: [SessionGuard] });
223
52
  ```
224
53
 
225
- Sessions, verification values and rate-limit counters then live in Redis instead of
226
- costing a database round trip per request.
54
+ ## What is here
227
55
 
228
- All five methods are implemented, beyond the three that are mandatory. `getAndDelete` and
229
- `increment` are optional in better-auth's interface because most clients cannot do
230
- them atomically - `Bun.RedisClient` can, through `GETDEL` and `INCR`. Without them
231
- better-auth falls back to read-then-delete for single-use credentials, which is a
232
- race, and to a non-atomic rate-limit counter.
56
+ The [Authentication guide](../../docs/guide/17-authentication.md) is canonical.
233
57
 
234
- `redisStorage` takes a `RedisStore`, which is six methods restated rather than
235
- imported - an `@dunx/infra/redis` `RedisConnection` satisfies it structurally, and so
236
- does anything else shaped like `Bun.RedisClient`.
58
+ | Export | What it does |
59
+ | --------------------- | ----------------------------------------------------------------- |
60
+ | `AuthModule` | Builds the instance, mounts the handler, binds the guard |
61
+ | `Auth` | The better-auth instance, injectable |
62
+ | `SessionGuard` | Authenticates, honours `@Public()` and `@Roles()` |
63
+ | `AuthContext` | The authenticated caller, anywhere in the request |
64
+ | `betterAuthDocument` | better-auth's own paths merged into the OpenAPI document |
65
+ | `bunPassword` | `Bun.password` native bcrypt, applied by default |
66
+ | `@dunx/auth/drizzle` | better-auth over the connection the app already opened |
67
+ | `@dunx/auth/redis` | `secondaryStorage` over `Bun.RedisClient` |
237
68
 
238
- ## What is bound
69
+ ## Notes
239
70
 
240
- `AuthModule` binds four things and mounts one controller:
71
+ - dunx ships no schema for better-auth's tables. They are better-auth's, they
72
+ change with its plugins, and `bunx @better-auth/cli generate` writes them.
73
+ Export them under the singular model names the adapter looks up.
74
+ - Under `setGlobalPrefix`, `basePath` is what better-auth matches and `mountAt`
75
+ is where the route is mounted. Omitting `mountAt` with a non-default
76
+ `basePath` is a boot error.
77
+ - `AuthContext` is a second `AsyncLocalStorage` store rather than a key in
78
+ `RequestContext`, because everything in that store is serialized into every
79
+ log line the request writes.
241
80
 
242
- | Token | Resolves to |
243
- | ------------- | --------------------------------------------------------------- |
244
- | `AuthOptions` | The resolved options, the `basePath`, and the mount path |
245
- | `Auth` | The better-auth instance |
246
- | `AuthContext` | The per-request principal store |
247
- | `SessionGuard`| The guard, ready for `middleware: [...]` or `@UseGuards` |
81
+ ## License
248
82
 
249
- Every one of them declares its own `inject` list, so none of it needs
250
- `@dunx/transform`'s transform to have run - `@dunx/auth` works in an app with no
251
- preload.
83
+ MIT
package/dist/auth.d.ts CHANGED
@@ -1,20 +1,13 @@
1
1
  import type { Auth as Instance, BetterAuthOptions } from 'better-auth';
2
2
  /**
3
3
  * The injection token for the better-auth instance, and the whole of dunx's
4
- * contract with the library.
4
+ * contract with the library. `betterAuth()` returns a plain object, so there is no
5
+ * class to use: this is an abstract class whose members alias better-auth's own,
6
+ * which a real instance satisfies structurally.
5
7
  *
6
- * `betterAuth()` returns a plain object, so there is no class to use as a token.
7
- * This is the same trick `Logger` and `RequestContext` use in `@dunx/core`: an
8
- * abstract class whose members are **aliases of better-auth's own** - not
9
- * restatements - which a real instance satisfies structurally. That is what makes
10
- * `constructor(private readonly auth: Auth)` work, since `@dunx/transform` records
11
- * the bare type name and the container resolves it.
12
- *
13
- * The type argument is the `DbModule` trick from `@dunx/infra/db`: the token is the
14
- * erased class, so `Auth<typeof authOptions>` at an injection site keeps the
15
- * plugin-widened `api` while still resolving the one binding. Written bare, `Auth`
16
- * carries better-auth's core endpoints only - a plugin's endpoints are on the
17
- * annotation, not on the token.
8
+ * The type argument is `DbModule`'s trick - the token is the erased class, so
9
+ * `Auth<typeof authOptions>` keeps the plugin-widened `api` while resolving the
10
+ * one binding. Written bare it carries better-auth's core endpoints only.
18
11
  */
19
12
  export declare abstract class Auth<O extends BetterAuthOptions = BetterAuthOptions> {
20
13
  /**
@@ -71,6 +71,3 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
71
71
  };
72
72
 
73
73
  export { __privateGet, __privateAdd, __privateSet, __privateMethod, __decoratorStart, __decoratorMetadata, __runInitializers, __decorateElement };
74
-
75
- //# debugId=B8D015D42BC8A4C864756E2164756E21
76
- //# sourceMappingURL=chunk-dtq1p1zv.js.map
package/dist/context.d.ts CHANGED
@@ -2,21 +2,16 @@ import { RequestContext } from '@dunx/core';
2
2
  import type { BetterAuthOptions } from 'better-auth';
3
3
  import type { Principal } from './auth.js';
4
4
  /**
5
- * How the authenticated caller reaches a handler - and anything the handler calls,
6
- * however deep.
5
+ * How the authenticated caller reaches a handler, and anything the handler calls.
7
6
  *
8
- * `AsyncLocalStorage`, for the same reason `@dunx/core`'s `RequestContext` is: it is
9
- * a Node built-in Bun implements natively, and it is the only mechanism that gets a
10
- * value from middleware to a service three constructor hops away without passing
11
- * it. The alternatives were both worse - request-scoped DI was measured and rejected
12
- * (docs/ARCHITECTURE.md), and hanging the principal off `req` reaches a route
13
- * handler but nothing a route handler calls.
7
+ * `AsyncLocalStorage`, the only mechanism that gets a value from middleware to a
8
+ * service three constructor hops away without passing it. Request-scoped DI was
9
+ * measured and rejected; hanging the principal off `req` reaches a route handler
10
+ * but nothing it calls.
14
11
  *
15
- * It is a **second** store rather than a key in `RequestContext`. That store is the
16
- * log record: every field in it is serialized into every line the request writes, so
17
- * a session object there would be noise on each entry and a redaction hazard in the
18
- * ones that matter. What does go there is `userId` - a well-known `RequestFields`
19
- * key - so the log lines are correlated without carrying the principal.
12
+ * A second store rather than a key in `RequestContext`: that one is the log
13
+ * record, so a session object there would be noise on every line and a redaction
14
+ * hazard. `userId` does go there, so lines correlate without the principal.
20
15
  */
21
16
  export declare class AuthContext {
22
17
  #private;
package/dist/drizzle.d.ts CHANGED
@@ -14,9 +14,9 @@ export interface DrizzleSource {
14
14
  readonly db: unknown;
15
15
  }
16
16
  /**
17
- * better-auth's `database` option over a connection the app already opened. Nothing
18
- * here connects: the point is that the app keeps **one** pool, one SQLite handle and
19
- * one shutdown path, instead of better-auth opening a second.
17
+ * better-auth's `database` option over a connection the app already opened, so the
18
+ * app keeps one pool and one shutdown path rather than better-auth opening a
19
+ * second. Nothing here connects.
20
20
  *
21
21
  * ```ts
22
22
  * AuthModule.forRootAsync({
@@ -27,29 +27,12 @@ export interface DrizzleSource {
27
27
  * });
28
28
  * ```
29
29
  *
30
- * The `provider` comes from the connection's own dialect, so swapping `bun:sqlite`
31
- * for `Bun.SQL` needs no edit at the call site. The schema does not have to be passed
32
- * either - `@dunx/infra/db` builds its handle with `drizzle({ client, schema })` and
33
- * the adapter reads `db._.fullSchema`.
34
- *
35
- * **The tables have to be exported under the names better-auth looks up**, which is
36
- * the singular model name and not the table name. The adapter does `fullSchema['user']`,
37
- * so a barrel exporting `users` fails on the first query rather than at boot:
38
- *
39
- * ```
40
- * BetterAuthError: [# Drizzle Adapter]: The model "user" was not found in the schema object.
41
- * ```
42
- *
43
- * Either name the exports `user`, `session`, `account` and `verification`, or map them
44
- * where the schema is assembled:
30
+ * The `provider` comes from the connection's dialect and the schema off
31
+ * `db._.fullSchema`. Tables must be exported under better-auth's singular model
32
+ * names, so a barrel exporting `users` fails on first query:
45
33
  *
46
34
  * ```ts
47
35
  * schema: { user: users, session: sessions, account: accounts, verification: verifications }
48
36
  * ```
49
- *
50
- * dunx ships **no** schema for those tables. They are better-auth's, they change with
51
- * its plugins, and its own CLI generates them: `bunx @better-auth/cli generate`. A
52
- * copy of them inside a framework is a copy that silently rots against the library
53
- * that reads it.
54
37
  */
55
38
  export declare const drizzleDatabase: (connection: DrizzleSource, config?: Omit<DrizzleAdapterConfig, 'provider'>) => ReturnType<typeof drizzleAdapter>;
package/dist/drizzle.js CHANGED
@@ -18,6 +18,3 @@ var drizzleDatabase = (connection, config = {}) => drizzleAdapter(connection.db,
18
18
  export {
19
19
  drizzleDatabase
20
20
  };
21
-
22
- //# debugId=F599C8B7916586AE64756E2164756E21
23
- //# sourceMappingURL=drizzle.js.map
package/dist/guard.d.ts CHANGED
@@ -10,20 +10,16 @@ import { AuthContext } from './context.js';
10
10
  */
11
11
  export declare const rolesOf: (user: object) => readonly string[];
12
12
  /**
13
- * Authenticates every request it sees through better-auth's own session lookup, and
14
- * composes with the metadata `@dunx/http` already carries:
13
+ * Authenticates every request through better-auth's own session lookup, composing
14
+ * with the metadata `@dunx/http` carries:
15
15
  *
16
- * - `@Public()` - skipped outright. No session lookup, no rejection, no role check.
17
- * That is what makes it safe to install globally: better-auth's own endpoints are
18
- * `@Public()`, and a sign-in route that needed a session could never be reached.
19
- * A public route that wants to *adapt* to an optional caller injects `Auth` and
20
- * calls `auth.api.getSession({ headers: req.headers })` itself - one line, and it
21
- * does not put a lookup on every public request in the app.
22
- * - `@Roles('admin')` - a 403 unless the caller holds one of them.
16
+ * - `@Public()` - skipped outright, which is what makes it safe to install
17
+ * globally: better-auth's own endpoints are public. A public route adapting to
18
+ * an optional caller injects `Auth` and looks the session up itself.
19
+ * - `@Roles('admin')` - a 403 unless the caller holds one.
23
20
  *
24
- * Install it globally with `HttpFactory.create(root, { middleware: [SessionGuard] })`
25
- * and opt routes out with `@Public()`, or scope it with `@UseGuards(SessionGuard)`
26
- * and leave the rest of the app open. `AuthModule` registers it either way.
21
+ * Install it in `HttpFactory.create(root, { middleware: [SessionGuard] })`, or
22
+ * scope it with `@UseGuards(SessionGuard)`. `AuthModule` registers it either way.
27
23
  */
28
24
  export declare class SessionGuard implements Middleware {
29
25
  private readonly auth;
package/dist/handler.d.ts CHANGED
@@ -2,25 +2,14 @@ import { type Ctor } from '@dunx/core';
2
2
  import { type Input, type RouteSchemas } from '@dunx/http';
3
3
  /**
4
4
  * better-auth's handler is a plain `(request: Request) => Promise<Response>`, so
5
- * mounting it is five one-line routes and nothing else. Every endpoint the library
6
- * and its plugins declare lives under one wildcard - dunx does not restate, wrap or
7
- * re-dispatch a single one of them.
5
+ * mounting it is five one-line routes. Every endpoint it and its plugins declare
6
+ * lives under one wildcard, which `Bun.serve` matches natively, so Bun is still
7
+ * the router. All five verbs, because a plugin may declare any of them.
8
8
  *
9
- * `Bun.serve` matches `<basePath>/*` natively (verified on Bun 1.3.14), so Bun is
10
- * still the router. All five verbs are mounted because a plugin may declare any of
11
- * them; better-auth's own endpoints are `GET` and `POST`.
12
- *
13
- * The `Response` is returned untouched - `buildRoutes` passes one straight through,
14
- * which is what keeps better-auth's `Set-Cookie` headers and redirects intact.
15
- *
16
- * `@Public()` at class scope, so all five inherit it - `mergeMeta` reads the class's
17
- * record under the handler's. Without it a globally installed `SessionGuard` would
18
- * demand a session from the sign-in endpoint, and no session could ever be created.
19
- *
20
- * `inject(Auth)` in a field rather than a constructor parameter, because a bare
21
- * class in `controllers` is bound as a class provider and would then need
22
- * `@dunx/transform`'s transform to have run. This way mounting works in an app that
23
- * never added the preload.
9
+ * The `Response` is returned untouched, keeping `Set-Cookie` and redirects intact.
10
+ * `@Public()` at class scope, or a global `SessionGuard` would demand a session
11
+ * from the sign-in endpoint. `inject(Auth)` in a field rather than a constructor
12
+ * parameter, so mounting works without the transform preload.
24
13
  */
25
14
  export declare class AuthHandler {
26
15
  #private;
@@ -33,17 +22,12 @@ export declare class AuthHandler {
33
22
  /**
34
23
  * The controller `AuthModule` registers, prefixed with `AuthOptions.mountAt`.
35
24
  *
36
- * A subclass rather than `@Controller(...)` on {@link AuthHandler} itself: the prefix
37
- * is only known once the module is configured, and mutating the shared class from a
38
- * factory would make two configurations fight over one prefix. The subclass declares
39
- * nothing of its own and inherits everything - `discoverRoutes` walks the prototype
40
- * chain for the routes, and `metaOf` and `prefixOf` are plain lookups, so `@Public()`
41
- * comes down from the base while the prefix stays own to the subclass.
25
+ * A subclass rather than `@Controller(...)` on {@link AuthHandler}: the prefix is
26
+ * only known once the module is configured, and mutating the shared class would
27
+ * make two configurations fight over one. The subclass inherits the routes and
28
+ * `@Public()` off the prototype chain while owning the prefix.
42
29
  *
43
- * `@ApiHidden()` because the mount is a wildcard. The route is real and has to be
44
- * served, but `*` is not an OpenAPI path template, so documenting it produced an
45
- * invalid entry tagged with this class's internal name - alongside the paths
46
- * `betterAuthDocument` describes properly, which is where the auth surface should
47
- * be read from.
30
+ * `@ApiHidden()` because `*` is not an OpenAPI path template;
31
+ * `betterAuthDocument` describes the auth surface properly.
48
32
  */
49
33
  export declare const mountHandler: (mountAt: string) => Ctor<AuthHandler>;
package/dist/index.js CHANGED
@@ -51,9 +51,7 @@ class AuthContext {
51
51
  return this.#storage.run(principal, callback);
52
52
  }
53
53
  }
54
- Object.defineProperty(AuthContext, Symbol.for("dunx.deps"), {
55
- value: () => [RequestContext]
56
- });
54
+ Object.defineProperty(AuthContext, Symbol.for("dunx.deps"), { value: () => [RequestContext] });
57
55
  // src/guard.ts
58
56
  import {
59
57
  HttpError as HttpError2,
@@ -98,9 +96,7 @@ class SessionGuard {
98
96
  return this.context.run(principal, next);
99
97
  }
100
98
  }
101
- Object.defineProperty(SessionGuard, Symbol.for("dunx.deps"), {
102
- value: () => [Auth, AuthContext]
103
- });
99
+ Object.defineProperty(SessionGuard, Symbol.for("dunx.deps"), { value: () => [Auth, AuthContext] });
104
100
  // src/handler.ts
105
101
  import { inject } from "@dunx/core";
106
102
  import {
@@ -158,9 +154,7 @@ class AuthOptions {
158
154
  this.options = withBunPassword({ ...init, basePath: this.basePath });
159
155
  }
160
156
  }
161
- Object.defineProperty(AuthOptions, Symbol.for("dunx.deps"), {
162
- value: () => [{ unresolved: "init: O" }, { unresolved: "mountAt?: string" }]
163
- });
157
+ Object.defineProperty(AuthOptions, Symbol.for("dunx.deps"), { value: () => [{ unresolved: "init: O" }, { unresolved: "mountAt?: string" }] });
164
158
 
165
159
  // src/handler.ts
166
160
  var _dec = [
@@ -337,6 +331,3 @@ export {
337
331
  redisStorage,
338
332
  rolesOf
339
333
  };
340
-
341
- //# debugId=B662BCE4255C137264756E2164756E21
342
- //# sourceMappingURL=index.js.map
package/dist/module.d.ts CHANGED
@@ -1,17 +1,11 @@
1
1
  import { type Deps, type DynamicModule, type AsyncModuleConfig } from '@dunx/core';
2
2
  import { type BetterAuthOptions } from 'better-auth';
3
3
  /**
4
- * Binds three tokens and one controller:
4
+ * Binds `AuthOptions`, `Auth` and `AuthContext`, plus a prefixed `AuthHandler`
5
+ * serving every better-auth endpoint under `basePath`.
5
6
  *
6
- * - `AuthOptions` - what `betterAuth()` was called with, and where it is mounted.
7
- * - `Auth` - the better-auth instance itself.
8
- * - `AuthContext` - the authenticated caller, per request.
9
- * - a prefixed `AuthHandler`, serving every better-auth endpoint under `basePath`.
10
- *
11
- * `SessionGuard` is registered as a provider rather than installed as global
12
- * middleware, because whether it guards the whole app or one controller is the app's
13
- * decision - pass it to `HttpFactory.create(root, { middleware: [SessionGuard] })`
14
- * or to `@UseGuards(SessionGuard)`.
7
+ * `SessionGuard` is a provider rather than global middleware: whether it guards
8
+ * the whole app or one controller is the app's decision.
15
9
  */
16
10
  export declare class AuthModule {
17
11
  /**
@@ -33,9 +27,8 @@ export declare class AuthModule {
33
27
  */
34
28
  static forRoot<const O extends BetterAuthOptions>(options: O, mountAt?: string): DynamicModule;
35
29
  /**
36
- * `forRoot` with the options behind a factory that may await and may inject -
37
- * which is the only way the secret, the base URL and the database can come from
38
- * `ConfigService` rather than from module scope:
30
+ * `forRoot` with the options behind a factory that may await and inject, so the
31
+ * secret, base URL and database can come from `ConfigService`:
39
32
  *
40
33
  * ```ts
41
34
  * AuthModule.forRootAsync({
@@ -49,13 +42,10 @@ export declare class AuthModule {
49
42
  * });
50
43
  * ```
51
44
  *
52
- * `mountAt` is a second, **synchronous** argument for the same reason
53
- * `DbModule.forRootAsync` takes its token positionally: the mount is a route in
54
- * Bun's table, and that table is built before any factory has run. It is only
55
- * needed under a global prefix - see {@link AuthOptions.mountAt}. Omitting it while
56
- * the factory returns a non-default `basePath` is a boot error, because that
57
- * combination could only ever have mounted the handler where better-auth is not
58
- * looking.
45
+ * `mountAt` is a second, synchronous argument: the mount is a route in Bun's
46
+ * table, built before any factory has run. Only needed under a global prefix.
47
+ * Omitting it while the factory returns a non-default `basePath` is a boot
48
+ * error, since that would mount the handler where better-auth is not looking.
59
49
  */
60
50
  static forRootAsync<const D extends Deps>(provider: AsyncModuleConfig<BetterAuthOptions, D>, mountAt?: string): DynamicModule;
61
51
  }
package/dist/openapi.d.ts CHANGED
@@ -38,16 +38,12 @@ export interface AuthDocumentOptions {
38
38
  readonly tag?: string;
39
39
  }
40
40
  /**
41
- * Better Auth's own endpoints, as a contribution to the app's OpenAPI document.
41
+ * better-auth's own endpoints, contributed to the app's OpenAPI document. It
42
+ * serves `<basePath>/*` from its own handler, so route discovery sees none of it
43
+ * and the document would omit the whole authentication surface.
42
44
  *
43
- * Better Auth serves `<basePath>/*` from its own handler rather than from dunx
44
- * controllers, so route discovery cannot see any of it and the document would
45
- * describe an API missing its entire authentication surface. This asks the
46
- * library for its schema and hands it over:
47
- *
48
- * **`forRootAsync`, not `forRoot`.** `forRoot` is evaluated while the module graph
49
- * is being described, before there is a container, so there is nowhere for the
50
- * `Auth` instance to come from. The async pair injects it:
45
+ * `forRootAsync`, not `forRoot`: the latter is evaluated while the module graph is
46
+ * described, before there is a container to take `Auth` from.
51
47
  *
52
48
  * ```ts
53
49
  * OpenApiModule.forRootAsync({
@@ -61,16 +57,7 @@ export interface AuthDocumentOptions {
61
57
  * });
62
58
  * ```
63
59
  *
64
- * Building a second `betterAuth()` purely to generate the schema is the workaround
65
- * this replaces, and it is not needed.
66
- *
67
- * **Better Auth only generates a schema when the `openAPI()` plugin is enabled.**
68
- * Without it `generateOpenAPISchema` is absent and this contributes nothing rather
69
- * than throwing, because a missing plugin should cost documentation and not boot.
70
- * Pass `openAPI({ disableDefaultReference: true })` if you want the schema without
71
- * Better Auth also mounting its own reference page next to the dunx one.
72
- *
73
- * Paths are rewritten to sit under `basePath`, since the library reports them
74
- * relative to its own mount.
60
+ * A schema exists only with the `openAPI()` plugin enabled; without it this
61
+ * contributes nothing rather than throwing. Paths are rewritten under `basePath`.
75
62
  */
76
63
  export declare const betterAuthDocument: (auth: OpenApiCapableAuth, options: AuthDocumentOptions) => () => Promise<AuthDocumentFragment>;
@@ -1,19 +1,12 @@
1
1
  /**
2
- * better-auth's `emailAndPassword.password`, backed by `Bun.password`.
2
+ * better-auth's `emailAndPassword.password`, backed by `Bun.password`. Applied by
3
+ * `AuthModule` when `emailAndPassword` is enabled and no hasher is given;
4
+ * better-auth's own default is JavaScript scrypt. Bun pre-hashes the input, so
5
+ * bcrypt's 72-byte cap is a non-issue.
3
6
  *
4
- * Applied by `AuthModule` whenever `emailAndPassword` is enabled and no `password`
5
- * of your own is given. better-auth's default is a **pure-JavaScript scrypt**;
6
- * `Bun.password` is native bcrypt, and the rule is simple - if Bun ships it,
7
- * use Bun.
8
- *
9
- * Bun pre-hashes the input, so bcrypt's 72-byte cap is a non-issue even for a
10
- * maximum-length multibyte password.
11
- *
12
- * `verify` swallows Bun's `UnsupportedAlgorithm` throw, so a hash produced by a
13
- * *different* algorithm - a scrypt hash written before this was in place - is a
14
- * clean authentication failure rather than a 500. Those users must reset their
15
- * password to get a bcrypt hash; pass your own `password` implementation instead
16
- * if you are migrating an existing user table and cannot.
7
+ * `verify` swallows Bun's `UnsupportedAlgorithm` throw, so a hash from a different
8
+ * algorithm is a clean authentication failure rather than a 500. Those users must
9
+ * reset; pass your own implementation if you are migrating a table and cannot.
17
10
  */
18
11
  export declare const bunPassword: {
19
12
  hash: (password: string) => Promise<string>;
package/dist/redis.d.ts CHANGED
@@ -17,25 +17,17 @@ export interface RedisStore {
17
17
  del(key: string): Promise<number>;
18
18
  }
19
19
  /**
20
- * better-auth's `secondaryStorage` over `Bun.RedisClient`, so sessions, verification
21
- * values and rate-limit counters live in Redis instead of costing a database round
22
- * trip on every request.
20
+ * better-auth's `secondaryStorage` over `Bun.RedisClient`, so sessions and
21
+ * rate-limit counters cost no database round trip.
23
22
  *
24
- * All five methods are implemented, not the three that are mandatory.
25
- * `getAndDelete` and `increment` are optional in better-auth's interface because most
26
- * clients cannot do them atomically - `Bun.RedisClient` can, through `GETDEL` and
27
- * `INCR`, both already on `@dunx/infra/redis`'s contract. Without them better-auth
28
- * falls back to read-then-delete for single-use credentials, which is a race, and to
29
- * a non-atomic rate-limit counter.
23
+ * All five methods, not the three that are mandatory: `getAndDelete` and
24
+ * `increment` are optional because most clients cannot do them atomically, and
25
+ * `Bun.RedisClient` can through `GETDEL` and `INCR`. Without them better-auth
26
+ * falls back to a read-then-delete race and a non-atomic counter.
30
27
  *
31
- * `increment`'s TTL applies on creation only, which is what makes the counter expire
32
- * a fixed window after the first hit rather than sliding forever: `INCR` returning
33
- * `1` is the signal that this call created the key.
34
- *
35
- * Redis being unreachable is deliberately **not** softened here. Bun's client
36
- * connects lazily and queues, so a command against a down server rejects and
37
- * better-auth's own error path is what should see it - a swallowed `null` from `get`
38
- * would read as "no session" and sign every user out.
28
+ * `increment`'s TTL applies on creation only, so the window is fixed from the
29
+ * first hit. An unreachable Redis is not softened: a swallowed `null` would read
30
+ * as "no session" and sign every user out.
39
31
  */
40
32
  export declare const redisStorage: (connection: RedisStore) => SecondaryStorage;
41
33
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/auth",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "Better Auth for dunx: its handler mounted on Bun.serve, a session guard reading @Public() and @Roles(), the caller in async context, and Bun.password hashing",
5
5
  "keywords": [
6
6
  "auth",
@@ -58,8 +58,8 @@
58
58
  "drizzle-orm": "^0.45.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@dunx/core": "^2.4.0",
62
- "@dunx/http": "^2.4.0",
61
+ "@dunx/core": "^3.0.0",
62
+ "@dunx/http": "^3.0.0",
63
63
  "@types/bun": ">=1.3.0",
64
64
  "better-auth": "^1.6.25",
65
65
  "drizzle-orm": "^0.45.2"
@@ -73,6 +73,6 @@
73
73
  }
74
74
  },
75
75
  "engines": {
76
- "bun": ">=1.3.0"
76
+ "bun": ">=1.4.0"
77
77
  }
78
78
  }
@@ -1,9 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [
5
- ],
6
- "mappings": "",
7
- "debugId": "B8D015D42BC8A4C864756E2164756E21",
8
- "names": []
9
- }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/drizzle.ts"],
4
- "sourcesContent": [
5
- "import {\n drizzleAdapter,\n type DB,\n type DrizzleAdapterConfig,\n} from 'better-auth/adapters/drizzle';\n\n/**\n * The two members of `@dunx/infra/db`'s `DbConnection` this reads.\n *\n * Restated structurally rather than imported, for the same reason `@dunx/http`\n * restates Standard Schema: it keeps `@dunx/auth`'s dependency list at `@dunx/core`\n * and `@dunx/http`, and it means a bare `drizzle({ client, schema })` handle works\n * here too. An `@dunx/infra/db` connection satisfies it with no adapter in between -\n * `dialect` is exactly that union and `db` is exactly `unknown`.\n */\nexport interface DrizzleSource {\n readonly dialect: 'postgres' | 'mysql' | 'mariadb' | 'sqlite';\n /** The drizzle handle - `BunSQLiteDatabase` or `BunSQLDatabase`. */\n readonly db: unknown;\n}\n\n/** What better-auth's drizzle adapter calls each of the dialects `@dunx/infra/db` reports. */\nconst PROVIDERS: Readonly<\n Record<DrizzleSource['dialect'], DrizzleAdapterConfig['provider']>\n> = Object.freeze({\n postgres: 'pg',\n mysql: 'mysql',\n mariadb: 'mysql',\n sqlite: 'sqlite',\n});\n\n/**\n * better-auth's `database` option over a connection the app already opened. Nothing\n * here connects: the point is that the app keeps **one** pool, one SQLite handle and\n * one shutdown path, instead of better-auth opening a second.\n *\n * ```ts\n * AuthModule.forRootAsync({\n * useFactory: (connection: DbConnection) => ({\n * database: drizzleDatabase(connection),\n * }),\n * inject: [DbConnection],\n * });\n * ```\n *\n * The `provider` comes from the connection's own dialect, so swapping `bun:sqlite`\n * for `Bun.SQL` needs no edit at the call site. The schema does not have to be passed\n * either - `@dunx/infra/db` builds its handle with `drizzle({ client, schema })` and\n * the adapter reads `db._.fullSchema`.\n *\n * **The tables have to be exported under the names better-auth looks up**, which is\n * the singular model name and not the table name. The adapter does `fullSchema['user']`,\n * so a barrel exporting `users` fails on the first query rather than at boot:\n *\n * ```\n * BetterAuthError: [# Drizzle Adapter]: The model \"user\" was not found in the schema object.\n * ```\n *\n * Either name the exports `user`, `session`, `account` and `verification`, or map them\n * where the schema is assembled:\n *\n * ```ts\n * schema: { user: users, session: sessions, account: accounts, verification: verifications }\n * ```\n *\n * dunx ships **no** schema for those tables. They are better-auth's, they change with\n * its plugins, and its own CLI generates them: `bunx @better-auth/cli generate`. A\n * copy of them inside a framework is a copy that silently rots against the library\n * that reads it.\n */\nexport const drizzleDatabase = (\n connection: DrizzleSource,\n config: Omit<DrizzleAdapterConfig, 'provider'> = {},\n): ReturnType<typeof drizzleAdapter> =>\n // `db` is `unknown` on the contract because it cannot promise either backend's\n // handle. Narrowing it is what that contract documents.\n drizzleAdapter(connection.db as DB, {\n ...config,\n provider: PROVIDERS[connection.dialect],\n });\n"
6
- ],
7
- "mappings": ";;;;AAAA;AAAA;AAAA;AAsBA,IAAM,YAEF,OAAO,OAAO;AAAA,EAChB,UAAU;AAAA,EACV,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AACV,CAAC;AAyCM,IAAM,kBAAkB,CAC7B,YACA,SAAiD,CAAC,MAIlD,eAAe,WAAW,IAAU;AAAA,KAC/B;AAAA,EACH,UAAU,UAAU,WAAW;AACjC,CAAC;",
8
- "debugId": "F599C8B7916586AE64756E2164756E21",
9
- "names": []
10
- }
package/dist/index.js.map DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/errors.ts", "../src/auth.ts", "../src/context.ts", "../src/guard.ts", "../src/handler.ts", "../src/password.ts", "../src/options.ts", "../src/module.ts", "../src/redis.ts", "../src/openapi.ts"],
4
- "sourcesContent": [
5
- "import { AppError } from '@dunx/core';\n\n/**\n * Raised by `@dunx/auth`'s own wiring. better-auth's failures propagate as its\n * `APIError`, and a rejected request is an `HttpError` from `@dunx/http`.\n */\nexport class AuthError extends AppError {\n override name = 'AuthError';\n}\n",
6
- "import type { Auth as Instance, BetterAuthOptions } from 'better-auth';\nimport { AuthError } from './errors.js';\n\n/**\n * The injection token for the better-auth instance, and the whole of dunx's\n * contract with the library.\n *\n * `betterAuth()` returns a plain object, so there is no class to use as a token.\n * This is the same trick `Logger` and `RequestContext` use in `@dunx/core`: an\n * abstract class whose members are **aliases of better-auth's own** - not\n * restatements - which a real instance satisfies structurally. That is what makes\n * `constructor(private readonly auth: Auth)` work, since `@dunx/transform` records\n * the bare type name and the container resolves it.\n *\n * The type argument is the `DbModule` trick from `@dunx/infra/db`: the token is the\n * erased class, so `Auth<typeof authOptions>` at an injection site keeps the\n * plugin-widened `api` while still resolving the one binding. Written bare, `Auth`\n * carries better-auth's core endpoints only - a plugin's endpoints are on the\n * annotation, not on the token.\n */\nexport abstract class Auth<O extends BetterAuthOptions = BetterAuthOptions> {\n /**\n * `abstract` stops TypeScript constructing this, but the container works on\n * runtime values and every class self-binds - so `get(Auth)` with nothing bound\n * would hand back a bare instance whose every member is `undefined`, and the\n * first symptom would be `auth.handler is not a function` deep in a request.\n */\n constructor() {\n if (new.target === Auth) {\n throw new AuthError(\n 'Auth is a contract, not an implementation. Bind one with ' +\n 'AuthModule.forRoot({ ... }) or AuthModule.forRootAsync({ useFactory }).',\n );\n }\n }\n\n /** better-auth's framework-agnostic handler. `AuthHandler` mounts it. */\n abstract readonly handler: Instance<O>['handler'];\n /** Every endpoint as a callable - `api.getSession`, `api.signUpEmail`, ... */\n abstract readonly api: Instance<O>['api'];\n /** The options `betterAuth()` was called with, dunx's defaults already applied. */\n abstract readonly options: Instance<O>['options'];\n abstract readonly $ERROR_CODES: Instance<O>['$ERROR_CODES'];\n abstract readonly $context: Instance<O>['$context'];\n abstract readonly $Infer: Instance<O>['$Infer'];\n}\n\n/**\n * `{ session, user }` for an authenticated caller - better-auth's own inferred\n * session type, so a plugin's extra user fields (the `admin` plugin's `role` and\n * `banned`, say) are typed without dunx naming a single one of them.\n */\nexport type Principal<O extends BetterAuthOptions = BetterAuthOptions> =\n Instance<O>['$Infer']['Session'];\n",
7
- "import { AsyncLocalStorage } from 'node:async_hooks';\nimport { RequestContext } from '@dunx/core';\nimport { HttpError, HttpStatusCode } from '@dunx/http';\nimport type { BetterAuthOptions } from 'better-auth';\nimport type { Principal } from './auth.js';\n\n/**\n * How the authenticated caller reaches a handler - and anything the handler calls,\n * however deep.\n *\n * `AsyncLocalStorage`, for the same reason `@dunx/core`'s `RequestContext` is: it is\n * a Node built-in Bun implements natively, and it is the only mechanism that gets a\n * value from middleware to a service three constructor hops away without passing\n * it. The alternatives were both worse - request-scoped DI was measured and rejected\n * (docs/ARCHITECTURE.md), and hanging the principal off `req` reaches a route\n * handler but nothing a route handler calls.\n *\n * It is a **second** store rather than a key in `RequestContext`. That store is the\n * log record: every field in it is serialized into every line the request writes, so\n * a session object there would be noise on each entry and a redaction hazard in the\n * ones that matter. What does go there is `userId` - a well-known `RequestFields`\n * key - so the log lines are correlated without carrying the principal.\n */\nexport class AuthContext {\n readonly #storage = new AsyncLocalStorage<Principal>();\n\n constructor(private readonly context: RequestContext) {}\n\n /**\n * The caller, or `undefined` on an anonymous request. The type argument is the\n * options object `AuthModule` was configured with, and is how a plugin's extra\n * user fields become visible:\n *\n * ```ts\n * const principal = this.auth.current<typeof authOptions>();\n * ```\n */\n current<O extends BetterAuthOptions = BetterAuthOptions>():\n | Principal<O>\n | undefined {\n return this.#storage.getStore() as Principal<O> | undefined;\n }\n\n /** The caller, or a 401. For a handler behind `SessionGuard` that is not `@Public()`. */\n require<O extends BetterAuthOptions = BetterAuthOptions>(): Principal<O> {\n const principal = this.current<O>();\n if (!principal) {\n throw new HttpError(HttpStatusCode.UNAUTHORIZED, 'UNAUTHENTICATED');\n }\n return principal;\n }\n\n /**\n * Runs `callback` with `principal` as the caller. `SessionGuard` is what calls\n * this; a job or a socket handler that resolved a session itself can too.\n *\n * `userId` is written to `RequestContext` as well, which is what puts it on every\n * log line the callback produces.\n */\n run<T>(principal: Principal, callback: () => T): T {\n this.context.updateContext({ userId: principal.user.id });\n return this.#storage.run(principal, callback);\n }\n}\nObject.defineProperty(AuthContext, Symbol.for('dunx.deps'), {\n value: () => [RequestContext],\n});\n",
8
- "import {\n HttpError,\n HttpStatusCode,\n PUBLIC,\n ROLES,\n type Middleware,\n type Next,\n type RouteContext,\n} from '@dunx/http';\nimport type { BunRequest } from 'bun';\nimport { Auth, type Principal } from './auth.js';\nimport { AuthContext } from './context.js';\n\ninterface Roled {\n readonly role?: unknown;\n}\n\n/**\n * What roles a user holds. better-auth's `admin` plugin stores them in a single\n * `role` column, comma-separated for more than one; a custom plugin may use an\n * array. Both read the same here, and a user with none reads as `[]` rather than\n * throwing - an app may well not use roles at all.\n */\nexport const rolesOf = (user: object): readonly string[] => {\n const role = (user as Roled).role;\n\n if (typeof role === 'string') {\n return role\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0);\n }\n if (Array.isArray(role)) {\n return role.filter((entry): entry is string => typeof entry === 'string');\n }\n return [];\n};\n\n/**\n * Authenticates every request it sees through better-auth's own session lookup, and\n * composes with the metadata `@dunx/http` already carries:\n *\n * - `@Public()` - skipped outright. No session lookup, no rejection, no role check.\n * That is what makes it safe to install globally: better-auth's own endpoints are\n * `@Public()`, and a sign-in route that needed a session could never be reached.\n * A public route that wants to *adapt* to an optional caller injects `Auth` and\n * calls `auth.api.getSession({ headers: req.headers })` itself - one line, and it\n * does not put a lookup on every public request in the app.\n * - `@Roles('admin')` - a 403 unless the caller holds one of them.\n *\n * Install it globally with `HttpFactory.create(root, { middleware: [SessionGuard] })`\n * and opt routes out with `@Public()`, or scope it with `@UseGuards(SessionGuard)`\n * and leave the rest of the app open. `AuthModule` registers it either way.\n */\nexport class SessionGuard implements Middleware {\n constructor(\n private readonly auth: Auth,\n private readonly context: AuthContext,\n ) {}\n\n async handle(\n req: BunRequest,\n ctx: RouteContext,\n next: Next,\n ): Promise<Response> {\n if (ctx.get(PUBLIC)) return next();\n\n const principal: Principal | null = await this.auth.api.getSession({\n headers: req.headers,\n });\n if (!principal) {\n throw new HttpError(HttpStatusCode.UNAUTHORIZED, 'UNAUTHENTICATED');\n }\n\n const required = ctx.get(ROLES);\n if (required !== undefined && required.length > 0) {\n const held = rolesOf(principal.user);\n if (!required.some((role) => held.includes(role))) {\n throw new HttpError(\n HttpStatusCode.FORBIDDEN,\n `Requires one of: ${required.join(', ')}`,\n );\n }\n }\n\n return this.context.run(principal, next);\n }\n}\nObject.defineProperty(SessionGuard, Symbol.for('dunx.deps'), {\n value: () => [Auth, AuthContext],\n});\n",
9
- "import { inject, type Ctor } from '@dunx/core';\nimport {\n ApiHidden,\n Controller,\n Delete,\n Get,\n Patch,\n Post,\n Public,\n Put,\n type Input,\n type RouteSchemas,\n} from '@dunx/http';\nimport type { BunRequest } from 'bun';\nimport { Auth } from './auth.js';\nimport { AuthError } from './errors.js';\nimport { DEFAULT_BASE_PATH } from './options.js';\n\n/**\n * better-auth's handler is a plain `(request: Request) => Promise<Response>`, so\n * mounting it is five one-line routes and nothing else. Every endpoint the library\n * and its plugins declare lives under one wildcard - dunx does not restate, wrap or\n * re-dispatch a single one of them.\n *\n * `Bun.serve` matches `<basePath>/*` natively (verified on Bun 1.3.14), so Bun is\n * still the router. All five verbs are mounted because a plugin may declare any of\n * them; better-auth's own endpoints are `GET` and `POST`.\n *\n * The `Response` is returned untouched - `buildRoutes` passes one straight through,\n * which is what keeps better-auth's `Set-Cookie` headers and redirects intact.\n *\n * `@Public()` at class scope, so all five inherit it - `mergeMeta` reads the class's\n * record under the handler's. Without it a globally installed `SessionGuard` would\n * demand a session from the sign-in endpoint, and no session could ever be created.\n *\n * `inject(Auth)` in a field rather than a constructor parameter, because a bare\n * class in `controllers` is bound as a class provider and would then need\n * `@dunx/transform`'s transform to have run. This way mounting works in an app that\n * never added the preload.\n */\n@Public()\nexport class AuthHandler {\n readonly #auth = inject(Auth);\n #verified = false;\n\n @Get('/*') get({ req }: Input<RouteSchemas>): Promise<Response> {\n return this.#dispatch(req);\n }\n\n @Post('/*') post({ req }: Input<RouteSchemas>): Promise<Response> {\n return this.#dispatch(req);\n }\n\n @Put('/*') put({ req }: Input<RouteSchemas>): Promise<Response> {\n return this.#dispatch(req);\n }\n\n @Patch('/*') patch({ req }: Input<RouteSchemas>): Promise<Response> {\n return this.#dispatch(req);\n }\n\n @Delete('/*') delete({ req }: Input<RouteSchemas>): Promise<Response> {\n return this.#dispatch(req);\n }\n\n /**\n * better-auth resolves an endpoint by comparing the whole pathname to its own\n * `basePath`, so a handler mounted somewhere else answers 404 to everything with no\n * hint as to why. The final path is only knowable once `listen()` has applied the\n * global prefix, which is after this module was configured - so it is checked on\n * the **first** request and never again.\n */\n #dispatch(req: BunRequest): Promise<Response> {\n if (!this.#verified) {\n this.#verified = true;\n const basePath = this.#auth.options.basePath ?? DEFAULT_BASE_PATH;\n const { pathname } = new URL(req.url);\n if (!pathname.startsWith(`${basePath}/`)) {\n throw new AuthError(\n `${pathname} reached the auth handler, but better-auth is configured with ` +\n `basePath ${basePath} and would answer 404 to everything under it. A ` +\n 'global prefix is the usual cause: mount the handler at the path without ' +\n 'the prefix and give better-auth the full one - see AuthOptions.mountAt.',\n );\n }\n }\n return this.#auth.handler(req);\n }\n}\n\n/**\n * The controller `AuthModule` registers, prefixed with `AuthOptions.mountAt`.\n *\n * A subclass rather than `@Controller(...)` on {@link AuthHandler} itself: the prefix\n * is only known once the module is configured, and mutating the shared class from a\n * factory would make two configurations fight over one prefix. The subclass declares\n * nothing of its own and inherits everything - `discoverRoutes` walks the prototype\n * chain for the routes, and `metaOf` and `prefixOf` are plain lookups, so `@Public()`\n * comes down from the base while the prefix stays own to the subclass.\n *\n * `@ApiHidden()` because the mount is a wildcard. The route is real and has to be\n * served, but `*` is not an OpenAPI path template, so documenting it produced an\n * invalid entry tagged with this class's internal name - alongside the paths\n * `betterAuthDocument` describes properly, which is where the auth surface should\n * be read from.\n */\nexport const mountHandler = (mountAt: string): Ctor<AuthHandler> =>\n ApiHidden()(\n Controller(mountAt)(class MountedAuthHandler extends AuthHandler {}),\n );\n",
10
- "/**\n * better-auth's `emailAndPassword.password`, backed by `Bun.password`.\n *\n * Applied by `AuthModule` whenever `emailAndPassword` is enabled and no `password`\n * of your own is given. better-auth's default is a **pure-JavaScript scrypt**;\n * `Bun.password` is native bcrypt, and the rule is simple - if Bun ships it,\n * use Bun.\n *\n * Bun pre-hashes the input, so bcrypt's 72-byte cap is a non-issue even for a\n * maximum-length multibyte password.\n *\n * `verify` swallows Bun's `UnsupportedAlgorithm` throw, so a hash produced by a\n * *different* algorithm - a scrypt hash written before this was in place - is a\n * clean authentication failure rather than a 500. Those users must reset their\n * password to get a bcrypt hash; pass your own `password` implementation instead\n * if you are migrating an existing user table and cannot.\n */\nexport const bunPassword = {\n hash: (password: string): Promise<string> =>\n Bun.password.hash(password, { algorithm: 'bcrypt', cost: 10 }),\n verify: async ({\n hash,\n password,\n }: {\n hash: string;\n password: string;\n }): Promise<boolean> => {\n try {\n return await Bun.password.verify(password, hash);\n } catch {\n return false;\n }\n },\n};\n",
11
- "import type { BetterAuthOptions } from 'better-auth';\nimport { AuthError } from './errors.js';\nimport { bunPassword } from './password.js';\n\n/** better-auth's own default, and where `AuthHandler` mounts unless told otherwise. */\nexport const DEFAULT_BASE_PATH = '/api/auth';\n\n/**\n * One leading slash, no trailing one - the shape `@dunx/http`'s route paths take,\n * so the mount and better-auth's own URL building agree character for character.\n *\n * The root is rejected: the mount is `<basePath>/*`, and at `/` that wildcard would\n * claim every path in the app.\n */\nexport const normalizeBasePath = (basePath: string): string => {\n const normalized = `/${basePath}`.replace(/\\/{2,}/g, '/').replace(/\\/$/, '');\n\n if (normalized.length < 2) {\n throw new AuthError(\n `\"${basePath}\" is not a usable basePath. The handler mounts at ` +\n '<basePath>/*, so at the root it would claim every route in the app. ' +\n `Use something like ${DEFAULT_BASE_PATH}.`,\n );\n }\n return normalized;\n};\n\n/**\n * Bun's native bcrypt in place of better-auth's pure-JavaScript scrypt, unless a\n * `password` of your own is already there. See {@link bunPassword} for the\n * migration caveat.\n */\nconst withBunPassword = <O extends BetterAuthOptions>(options: O): O => {\n const email = options.emailAndPassword;\n if (!email?.enabled || email.password) return options;\n\n // The one cast in the package: TypeScript cannot prove a spread of a generic with\n // one key replaced is still that generic, and widening the return to\n // `BetterAuthOptions` would lose the plugin types `betterAuth()` infers from it.\n return {\n ...options,\n emailAndPassword: { ...email, password: bunPassword },\n } as O;\n};\n\n/**\n * What `betterAuth()` gets called with, where the handler mounts, and the difference\n * between the two. Bound in the container so all of it is readable, and constructed\n * by `AuthModule` rather than by the app.\n */\nexport class AuthOptions<O extends BetterAuthOptions = BetterAuthOptions> {\n readonly options: O;\n\n /**\n * What better-auth matches an incoming pathname against, and builds its URLs from.\n * Normalized here and written back into `options`, so the two cannot drift.\n */\n readonly basePath: string;\n\n /**\n * The **route** path `AuthHandler` is mounted at, which is `basePath` unless the\n * app calls `setGlobalPrefix`. better-auth compares the whole pathname to\n * `basePath`, so with `setGlobalPrefix('api')` the two are different strings for\n * the same URL: mount at `/auth`, and tell better-auth `basePath: '/api/auth'`.\n */\n readonly mountAt: string;\n\n constructor(init: O, mountAt?: string) {\n this.basePath = normalizeBasePath(init.basePath ?? DEFAULT_BASE_PATH);\n this.mountAt =\n mountAt === undefined ? this.basePath : normalizeBasePath(mountAt);\n this.options = withBunPassword({ ...init, basePath: this.basePath });\n }\n}\nObject.defineProperty(AuthOptions, Symbol.for('dunx.deps'), {\n value: () => [{ unresolved: \"init: O\" }, { unresolved: \"mountAt?: string\" }],\n});\n",
12
- "import {\n provide,\n type AbstractCtor,\n type Deps,\n type DynamicModule,\n type AsyncModuleConfig,\n type ModuleRef,\n type Registration,\n RequestContext,\n} from '@dunx/core';\nimport { betterAuth, type BetterAuthOptions } from 'better-auth';\nimport { Auth } from './auth.js';\nimport { AuthContext } from './context.js';\nimport { AuthError } from './errors.js';\nimport { SessionGuard } from './guard.js';\nimport { mountHandler } from './handler.js';\nimport {\n AuthOptions,\n DEFAULT_BASE_PATH,\n normalizeBasePath,\n} from './options.js';\n\nconst build = (\n options: Registration,\n mountAt: string,\n imports?: readonly ModuleRef[],\n): DynamicModule => {\n // Instantiated to the token type rather than left as `typeof Auth`, which is what\n // lets the factory below hand back a plain better-auth instance with no cast.\n const auth: AbstractCtor<Auth> = Auth;\n\n return {\n module: AuthModule,\n ...(imports === undefined ? {} : { imports }),\n /**\n * The public surface. `AuthOptions` is how an app reports where the handler is\n * mounted, `Auth` is better-auth itself, `AuthContext` is the caller per request\n * and `SessionGuard` is what an app lists in its middleware or `@UseGuards`.\n */\n exports: [AuthOptions, auth, AuthContext, SessionGuard],\n controllers: [mountHandler(mountAt)],\n // Every binding declares its own `inject`, so nothing here needs\n // `@dunx/transform`'s transform to have run - the same reason\n // `RequestLoggingMiddleware` and `@dunx/infra/redis`'s `Redis` are bound this way.\n providers: [\n options,\n provide(auth, {\n useFactory: (resolved: AuthOptions) => betterAuth(resolved.options),\n inject: [AuthOptions] as const,\n }),\n provide(AuthContext, {\n useFactory: (context: RequestContext) => new AuthContext(context),\n inject: [RequestContext] as const,\n }),\n provide(SessionGuard, {\n useFactory: (instance: Auth, context: AuthContext) =>\n new SessionGuard(instance, context),\n inject: [auth, AuthContext] as const,\n }),\n ],\n };\n};\n\n/**\n * Binds three tokens and one controller:\n *\n * - `AuthOptions` - what `betterAuth()` was called with, and where it is mounted.\n * - `Auth` - the better-auth instance itself.\n * - `AuthContext` - the authenticated caller, per request.\n * - a prefixed `AuthHandler`, serving every better-auth endpoint under `basePath`.\n *\n * `SessionGuard` is registered as a provider rather than installed as global\n * middleware, because whether it guards the whole app or one controller is the app's\n * decision - pass it to `HttpFactory.create(root, { middleware: [SessionGuard] })`\n * or to `@UseGuards(SessionGuard)`.\n */\nexport class AuthModule {\n /**\n * ```ts\n * AuthModule.forRoot({\n * secret: process.env.BETTER_AUTH_SECRET,\n * baseURL: 'http://localhost:3000',\n * database: drizzleDatabase(connection),\n * emailAndPassword: { enabled: true },\n * plugins: [admin(), bearer()],\n * });\n * ```\n *\n * `const O` is load-bearing: it keeps the literal `plugins` tuple, which is what\n * `betterAuth()` infers the plugin endpoints from - and therefore what\n * `Auth<typeof options>` resolves to at an injection site.\n *\n * `mountAt` only matters under a global prefix - see {@link AuthOptions.mountAt}.\n */\n static forRoot<const O extends BetterAuthOptions>(\n options: O,\n mountAt?: string,\n ): DynamicModule {\n const resolved = new AuthOptions(options, mountAt);\n return build(\n provide(AuthOptions, { useValue: resolved }),\n resolved.mountAt,\n );\n }\n\n /**\n * `forRoot` with the options behind a factory that may await and may inject -\n * which is the only way the secret, the base URL and the database can come from\n * `ConfigService` rather than from module scope:\n *\n * ```ts\n * AuthModule.forRootAsync({\n * useFactory: (config: AppConfigService, connection: DbConnection) => ({\n * secret: config.get('authSecret'),\n * baseURL: config.get('appUrl'),\n * database: drizzleDatabase(connection),\n * emailAndPassword: { enabled: true },\n * }),\n * inject: [AppConfigService, DbConnection],\n * });\n * ```\n *\n * `mountAt` is a second, **synchronous** argument for the same reason\n * `DbModule.forRootAsync` takes its token positionally: the mount is a route in\n * Bun's table, and that table is built before any factory has run. It is only\n * needed under a global prefix - see {@link AuthOptions.mountAt}. Omitting it while\n * the factory returns a non-default `basePath` is a boot error, because that\n * combination could only ever have mounted the handler where better-auth is not\n * looking.\n */\n static forRootAsync<const D extends Deps>(\n provider: AsyncModuleConfig<BetterAuthOptions, D>,\n mountAt?: string,\n ): DynamicModule;\n static forRootAsync(\n provider: AsyncModuleConfig<BetterAuthOptions, Deps>,\n mountAt?: string,\n ): DynamicModule {\n const mounted = normalizeBasePath(mountAt ?? DEFAULT_BASE_PATH);\n\n return build(\n provide(AuthOptions, {\n useFactory: async (\n ...deps: readonly unknown[]\n ): Promise<AuthOptions> => {\n const resolved = new AuthOptions(\n await provider.useFactory(...deps),\n mounted,\n );\n if (mountAt === undefined && resolved.basePath !== mounted) {\n throw new AuthError(\n `The factory returned basePath ${resolved.basePath}, but the handler ` +\n `is mounted at ${mounted} - the table was built before the factory ` +\n 'ran, so it could not follow. Pass the route path as ' +\n \"forRootAsync's second argument.\",\n );\n }\n return resolved;\n },\n inject: provider.inject ?? [],\n }),\n mounted,\n provider.imports,\n );\n }\n}\n",
13
- "import type { BetterAuthOptions } from 'better-auth';\n\ntype SecondaryStorage = NonNullable<BetterAuthOptions['secondaryStorage']>;\n\n/**\n * The six commands this needs, restated rather than imported from\n * `@dunx/infra/redis` - same reasoning as {@link DrizzleSource}. A `RedisConnection`\n * satisfies it structurally (its parameters are wider, which is the assignable\n * direction), and a test double is six methods instead of the whole surface.\n */\nexport interface RedisStore {\n get(key: string): Promise<string | null>;\n getdel(key: string): Promise<string | null>;\n incr(key: string): Promise<number>;\n expire(key: string, seconds: number): Promise<boolean>;\n set(\n key: string,\n value: string,\n options?: { readonly ex?: number },\n ): Promise<string | null>;\n del(key: string): Promise<number>;\n}\n\n/**\n * better-auth's `secondaryStorage` over `Bun.RedisClient`, so sessions, verification\n * values and rate-limit counters live in Redis instead of costing a database round\n * trip on every request.\n *\n * All five methods are implemented, not the three that are mandatory.\n * `getAndDelete` and `increment` are optional in better-auth's interface because most\n * clients cannot do them atomically - `Bun.RedisClient` can, through `GETDEL` and\n * `INCR`, both already on `@dunx/infra/redis`'s contract. Without them better-auth\n * falls back to read-then-delete for single-use credentials, which is a race, and to\n * a non-atomic rate-limit counter.\n *\n * `increment`'s TTL applies on creation only, which is what makes the counter expire\n * a fixed window after the first hit rather than sliding forever: `INCR` returning\n * `1` is the signal that this call created the key.\n *\n * Redis being unreachable is deliberately **not** softened here. Bun's client\n * connects lazily and queues, so a command against a down server rejects and\n * better-auth's own error path is what should see it - a swallowed `null` from `get`\n * would read as \"no session\" and sign every user out.\n */\nexport const redisStorage = (connection: RedisStore): SecondaryStorage => ({\n get: (key) => connection.get(key),\n getAndDelete: (key) => connection.getdel(key),\n increment: async (key, ttl) => {\n const value = await connection.incr(key);\n if (value === 1) await connection.expire(key, ttl);\n return value;\n },\n set: (key, value, ttl) =>\n connection.set(key, value, ttl === undefined ? {} : { ex: ttl }),\n delete: async (key) => {\n await connection.del(key);\n },\n});\n",
14
- "import { normalizeBasePath } from './options.js';\n\n/**\n * The shape `@dunx/openapi` accepts as a contribution. Restated here rather than\n * imported, for the same reason `DrizzleSource` restates `DbConnection`:\n * `@dunx/auth` must not depend on `@dunx/openapi`. An app that documents nothing\n * still uses this package, and an app that never mounts auth still uses that one.\n */\nexport interface AuthDocumentFragment {\n readonly paths: Readonly<Record<string, Record<string, unknown>>>;\n readonly schemas: Readonly<Record<string, unknown>>;\n readonly tags: readonly {\n readonly name: string;\n readonly description?: string;\n }[];\n}\n\n/**\n * Just enough of a Better Auth instance to ask it for its schema.\n *\n * `api` is `object` with the method optional on top, rather than an interface\n * whose only member is optional. Every-property-optional triggers TypeScript's\n * weak-type check, which rejects any argument sharing no property with it - so an\n * instance built without the `openAPI()` plugin failed to compile, and the doc\n * below promising it \"contributes nothing rather than throwing\" described a path\n * that could not be written.\n */\nexport interface OpenApiCapableAuth {\n readonly api: object & {\n generateOpenAPISchema?: () => Promise<unknown>;\n };\n}\n\nexport interface AuthDocumentOptions {\n /**\n * Where the handler is mounted. Matches `AuthOptions.basePath`, including the\n * global prefix if there is one: these paths go into the document as-is and\n * are not moved again by `setGlobalPrefix()`.\n */\n readonly basePath: string;\n /** Tag every contributed operation carries. Default `auth`. */\n readonly tag?: string;\n}\n\nconst METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];\n\ninterface RawSchema {\n paths?: Record<string, Record<string, unknown>>;\n components?: { schemas?: Record<string, unknown> };\n}\n\n/**\n * Better Auth's own endpoints, as a contribution to the app's OpenAPI document.\n *\n * Better Auth serves `<basePath>/*` from its own handler rather than from dunx\n * controllers, so route discovery cannot see any of it and the document would\n * describe an API missing its entire authentication surface. This asks the\n * library for its schema and hands it over:\n *\n * **`forRootAsync`, not `forRoot`.** `forRoot` is evaluated while the module graph\n * is being described, before there is a container, so there is nowhere for the\n * `Auth` instance to come from. The async pair injects it:\n *\n * ```ts\n * OpenApiModule.forRootAsync({\n * root: AppModule,\n * useFactory: (auth: Auth) => ({\n * title: 'API',\n * version: '1.0.0',\n * contribute: [betterAuthDocument(auth, { basePath: '/api/auth' })],\n * }),\n * inject: [Auth],\n * });\n * ```\n *\n * Building a second `betterAuth()` purely to generate the schema is the workaround\n * this replaces, and it is not needed.\n *\n * **Better Auth only generates a schema when the `openAPI()` plugin is enabled.**\n * Without it `generateOpenAPISchema` is absent and this contributes nothing rather\n * than throwing, because a missing plugin should cost documentation and not boot.\n * Pass `openAPI({ disableDefaultReference: true })` if you want the schema without\n * Better Auth also mounting its own reference page next to the dunx one.\n *\n * Paths are rewritten to sit under `basePath`, since the library reports them\n * relative to its own mount.\n */\nexport const betterAuthDocument =\n (auth: OpenApiCapableAuth, options: AuthDocumentOptions) =>\n async (): Promise<AuthDocumentFragment> => {\n const empty: AuthDocumentFragment = { paths: {}, schemas: {}, tags: [] };\n if (typeof auth.api.generateOpenAPISchema !== 'function') return empty;\n\n const raw = (await auth.api.generateOpenAPISchema()) as RawSchema;\n const prefix = normalizeBasePath(options.basePath);\n const tag = options.tag ?? 'auth';\n\n const paths: Record<string, Record<string, unknown>> = {};\n for (const [path, item] of Object.entries(raw.paths ?? {})) {\n // Tagged so the explorer groups them, instead of scattering a dozen auth\n // endpoints through the rest of the API.\n for (const method of METHODS) {\n const operation = item[method];\n if (operation && typeof operation === 'object') {\n (operation as { tags?: string[] }).tags = [tag];\n }\n }\n paths[path.startsWith(prefix) ? path : `${prefix}${path}`] = item;\n }\n\n return {\n paths,\n schemas: raw.components?.schemas ?? {},\n tags: [{ name: tag, description: 'Served by Better Auth' }],\n };\n };\n"
15
- ],
16
- "mappings": "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",
17
- "debugId": "B662BCE4255C137264756E2164756E21",
18
- "names": []
19
- }