@opensaas/stack-auth 0.36.0 → 0.38.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +146 -0
- package/CLAUDE.md +153 -9
- package/README.md +18 -7
- package/dist/config/adopt-better-auth-tables.d.ts +47 -0
- package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
- package/dist/config/adopt-better-auth-tables.js +29 -1
- package/dist/config/adopt-better-auth-tables.js.map +1 -1
- package/dist/config/derive-auth-lists.d.ts +7 -5
- package/dist/config/derive-auth-lists.d.ts.map +1 -1
- package/dist/config/derive-auth-lists.js +33 -34
- package/dist/config/derive-auth-lists.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +41 -11
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts.map +1 -1
- package/dist/config/plugin.js +39 -27
- package/dist/config/plugin.js.map +1 -1
- package/dist/config/types.d.ts +143 -28
- package/dist/config/types.d.ts.map +1 -1
- package/dist/server/build-better-auth-options.test.d.ts +2 -0
- package/dist/server/build-better-auth-options.test.d.ts.map +1 -0
- package/dist/server/build-better-auth-options.test.js +29 -0
- package/dist/server/build-better-auth-options.test.js.map +1 -0
- package/dist/server/index.d.ts +112 -8
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +284 -96
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema-converter.d.ts +3 -3
- package/dist/server/schema-converter.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/config/adopt-better-auth-tables.ts +70 -1
- package/src/config/derive-auth-lists.ts +37 -38
- package/src/config/index.ts +47 -12
- package/src/config/plugin.ts +40 -28
- package/src/config/types.ts +144 -27
- package/src/server/build-better-auth-options.test.ts +59 -0
- package/src/server/index.ts +470 -106
- package/src/server/schema-converter.ts +3 -3
- package/tests/adopt-better-auth-tables.test.ts +99 -0
- package/tests/config.test.ts +66 -8
- package/tests/derive-auth-lists.test.ts +79 -5
- package/tests/generated-fk-shape.test.ts +65 -0
- package/tests/plugin-derived-keys.test.ts +48 -0
- package/tests/server.test.ts +723 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -1
package/dist/config/types.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { ListConfig } from '@opensaas/stack-core';
|
|
2
|
+
import type { BetterAuthOptions, BetterAuthPlugin, User } from 'better-auth';
|
|
2
3
|
import type { ExtendUserListConfig } from '../lists/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* better-auth's own callback shape for `sendVerificationEmail`/
|
|
6
|
+
* `sendResetPassword` — `data` is exactly what better-auth passes (no stack
|
|
7
|
+
* abstraction layered on top), and `request` is the raw request that
|
|
8
|
+
* triggered it.
|
|
9
|
+
*/
|
|
10
|
+
export type SendAuthEmail = (data: {
|
|
11
|
+
user: User;
|
|
12
|
+
url: string;
|
|
13
|
+
token: string;
|
|
14
|
+
}, request?: Request) => Promise<void>;
|
|
3
15
|
/**
|
|
4
16
|
* OAuth provider configuration
|
|
5
17
|
*/
|
|
@@ -29,10 +41,36 @@ export type EmailPasswordConfig = {
|
|
|
29
41
|
*/
|
|
30
42
|
minPasswordLength?: number;
|
|
31
43
|
/**
|
|
32
|
-
* Require password confirmation
|
|
44
|
+
* Require password confirmation (a second "confirm password" field).
|
|
45
|
+
*
|
|
46
|
+
* There is no better-auth server-side equivalent — this is purely a UI
|
|
47
|
+
* concern. `createAuth()` does not read it. Pass it directly to the
|
|
48
|
+
* pre-built forms instead: `<SignUpForm requirePasswordConfirmation={...} />`
|
|
49
|
+
* / `<ResetPasswordForm requirePasswordConfirmation={...} />` (both default
|
|
50
|
+
* to `true`). Setting it here has no effect and `createAuth()` warns if it
|
|
51
|
+
* is configured.
|
|
52
|
+
*
|
|
33
53
|
* @default true
|
|
34
54
|
*/
|
|
35
55
|
requireConfirmation?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Send a password reset email. Passed straight through to better-auth's own
|
|
58
|
+
* `emailAndPassword.sendResetPassword` — the stack does not wrap or
|
|
59
|
+
* reshape it. If not provided, reset emails are logged to console instead
|
|
60
|
+
* of sent.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* sendResetPassword: async ({ user, url }) => {
|
|
65
|
+
* await resend.emails.send({
|
|
66
|
+
* to: user.email,
|
|
67
|
+
* subject: 'Reset your password',
|
|
68
|
+
* html: `<a href="${url}">Reset your password</a>`,
|
|
69
|
+
* })
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
sendResetPassword?: SendAuthEmail;
|
|
36
74
|
};
|
|
37
75
|
/**
|
|
38
76
|
* Email verification configuration
|
|
@@ -49,6 +87,24 @@ export type EmailVerificationConfig = {
|
|
|
49
87
|
* @default 86400 (24 hours)
|
|
50
88
|
*/
|
|
51
89
|
tokenExpiration?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Send a verification email. Passed straight through to better-auth's own
|
|
92
|
+
* `emailVerification.sendVerificationEmail` — the stack does not wrap or
|
|
93
|
+
* reshape it. If not provided, verification emails are logged to console
|
|
94
|
+
* instead of sent.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* sendVerificationEmail: async ({ user, url }) => {
|
|
99
|
+
* await resend.emails.send({
|
|
100
|
+
* to: user.email,
|
|
101
|
+
* subject: 'Verify your email',
|
|
102
|
+
* html: `<a href="${url}">Verify your email</a>`,
|
|
103
|
+
* })
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
sendVerificationEmail?: SendAuthEmail;
|
|
52
108
|
};
|
|
53
109
|
/**
|
|
54
110
|
* Password reset configuration
|
|
@@ -71,10 +127,12 @@ export type SessionConfig = {
|
|
|
71
127
|
*/
|
|
72
128
|
expiresIn?: number;
|
|
73
129
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
130
|
+
* How often the session should be refreshed, in seconds. Passed straight
|
|
131
|
+
* through to better-auth's own `session.updateAge`. Set `false` to disable
|
|
132
|
+
* refresh entirely (the session expiry is then fixed at creation time).
|
|
133
|
+
* @default 86400 (1 day, matching better-auth's own default)
|
|
76
134
|
*/
|
|
77
|
-
updateAge?:
|
|
135
|
+
updateAge?: number | false;
|
|
78
136
|
};
|
|
79
137
|
/**
|
|
80
138
|
* Per-model better-auth configuration block.
|
|
@@ -138,10 +196,30 @@ export type AuthAccessConfig = {
|
|
|
138
196
|
export type AuthModelConfig = {
|
|
139
197
|
/**
|
|
140
198
|
* The table/list name for this model.
|
|
141
|
-
* Becomes the OpenSaaS list key
|
|
199
|
+
* Becomes the OpenSaaS list key and Prisma model name.
|
|
142
200
|
* @default the default better-auth model name (e.g. 'User', 'Session')
|
|
143
201
|
*/
|
|
144
202
|
modelName?: string;
|
|
203
|
+
/**
|
|
204
|
+
* The physical database table name for this model, independent of
|
|
205
|
+
* `modelName`. Generates a `@@map("...")` on the derived list.
|
|
206
|
+
*
|
|
207
|
+
* Lets a renamed list key (e.g. `modelName: 'AuthUser'`, to avoid colliding
|
|
208
|
+
* with an app's own domain `User`) still adopt a live table under a
|
|
209
|
+
* different name — most commonly better-auth's own default lowercase
|
|
210
|
+
* table names (`user`, `session`, `account`, `verification`).
|
|
211
|
+
*
|
|
212
|
+
* @default `modelName` when it differs from the better-auth default model
|
|
213
|
+
* name, otherwise unset (no `@@map`) — i.e. today's behaviour when this
|
|
214
|
+
* option is not set.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* // List key AuthUser, but the live table is still called `user`.
|
|
219
|
+
* user: { modelName: 'AuthUser', tableName: 'user' }
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
tableName?: string;
|
|
145
223
|
/**
|
|
146
224
|
* Map better-auth field names to database column names.
|
|
147
225
|
* Each entry generates a `@map("column")` on the derived field.
|
|
@@ -243,8 +321,21 @@ export type AuthConfig = {
|
|
|
243
321
|
*/
|
|
244
322
|
schema?: string;
|
|
245
323
|
/**
|
|
246
|
-
* Which fields to include in the session object
|
|
247
|
-
*
|
|
324
|
+
* Which fields to include in the session object passed to access control
|
|
325
|
+
* functions — a **flattened projection** of the resolved better-auth
|
|
326
|
+
* session, not the session's own shape. `getSessionFromAuth` (the
|
|
327
|
+
* implementation the scaffolded `getSession()` calls) resolves each name
|
|
328
|
+
* against a fixed precedence: a top-level key on the resolved session
|
|
329
|
+
* object, then the `user` object, then the `session` sub-object.
|
|
330
|
+
* `userId` is special-cased to the authenticated user's `id`.
|
|
331
|
+
*
|
|
332
|
+
* A `customSession` better-auth plugin fully replaces the resolved shape
|
|
333
|
+
* (it can nest fields anywhere, e.g. under its own custom key) —
|
|
334
|
+
* reconciling that shape against `sessionFields` is the application's job.
|
|
335
|
+
* A listed name that can't be resolved is omitted and warns once per
|
|
336
|
+
* field per process, naming what was checked, rather than silently
|
|
337
|
+
* becoming `undefined` in an access control function.
|
|
338
|
+
*
|
|
248
339
|
* @default ['userId', 'email', 'name']
|
|
249
340
|
*
|
|
250
341
|
* @example
|
|
@@ -284,22 +375,6 @@ export type AuthConfig = {
|
|
|
284
375
|
* narrower, User-specific override.
|
|
285
376
|
*/
|
|
286
377
|
access?: AuthAccessConfig;
|
|
287
|
-
/**
|
|
288
|
-
* Custom email sending function for verification and password reset
|
|
289
|
-
* If not provided, emails will be logged to console
|
|
290
|
-
*
|
|
291
|
-
* @example
|
|
292
|
-
* ```typescript
|
|
293
|
-
* sendEmail: async ({ to, subject, html }) => {
|
|
294
|
-
* await resend.emails.send({ to, subject, html })
|
|
295
|
-
* }
|
|
296
|
-
* ```
|
|
297
|
-
*/
|
|
298
|
-
sendEmail?: (params: {
|
|
299
|
-
to: string;
|
|
300
|
-
subject: string;
|
|
301
|
-
html: string;
|
|
302
|
-
}) => Promise<void>;
|
|
303
378
|
/**
|
|
304
379
|
* Additional Better Auth plugins to enable
|
|
305
380
|
* Allows integrating any Better Auth plugin (MCP, 2FA, etc.)
|
|
@@ -313,7 +388,7 @@ export type AuthConfig = {
|
|
|
313
388
|
* ]
|
|
314
389
|
* ```
|
|
315
390
|
*/
|
|
316
|
-
betterAuthPlugins?:
|
|
391
|
+
betterAuthPlugins?: BetterAuthPlugin[];
|
|
317
392
|
/**
|
|
318
393
|
* Rate limiting configuration
|
|
319
394
|
* Controls rate limiting for authentication endpoints
|
|
@@ -346,16 +421,56 @@ export type AuthConfig = {
|
|
|
346
421
|
*/
|
|
347
422
|
max?: number;
|
|
348
423
|
};
|
|
424
|
+
/**
|
|
425
|
+
* Escape hatch for better-auth options the stack doesn't model — typed as
|
|
426
|
+
* better-auth's own `BetterAuthOptions` so it stays in step with
|
|
427
|
+
* better-auth's surface without the stack re-declaring it. Deep-merged into
|
|
428
|
+
* the options `createAuth()` builds, applied LAST: a plain-object value at a
|
|
429
|
+
* given key merges recursively with whatever the stack already set there
|
|
430
|
+
* (so e.g. `session: { cookieCache: {...} }` adds alongside the stack's own
|
|
431
|
+
* `session.expiresIn`/`updateAge` rather than clobbering them); any other
|
|
432
|
+
* value (including arrays) replaces the stack's value outright. On a genuine
|
|
433
|
+
* key collision, this option wins.
|
|
434
|
+
*
|
|
435
|
+
* `database` and `plugins` are rejected — they're already the dedicated
|
|
436
|
+
* seams (the stack's `db` config, and `betterAuthPlugins` respectively) and
|
|
437
|
+
* accepting them here would create two unranked ways to set the same thing.
|
|
438
|
+
* So is `additionalFields` under `user`/`session`/`account`/`verification` —
|
|
439
|
+
* it has schema consequences (new columns) that a passthrough can't also
|
|
440
|
+
* apply to the generated Prisma schema; add fields to the derived list
|
|
441
|
+
* instead (`extendUserList` for the user model, or declare the list
|
|
442
|
+
* yourself for the others — see `packages/auth/CLAUDE.md`).
|
|
443
|
+
*
|
|
444
|
+
* The same options object is available standalone via
|
|
445
|
+
* `buildBetterAuthOptions()` (`@opensaas/stack-auth/server`) for apps that
|
|
446
|
+
* still need to hand-wire their own `betterAuth()` instance.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```typescript
|
|
450
|
+
* authPlugin({
|
|
451
|
+
* betterAuthOptions: {
|
|
452
|
+
* databaseHooks: { user: { create: { after: syncDomainUser } } },
|
|
453
|
+
* session: { cookieCache: { enabled: true, maxAge: 300 } },
|
|
454
|
+
* verification: { storeIdentifier: 'hashed' },
|
|
455
|
+
* baseURL: process.env.BETTER_AUTH_URL,
|
|
456
|
+
* },
|
|
457
|
+
* })
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
betterAuthOptions?: Partial<BetterAuthOptions>;
|
|
349
461
|
};
|
|
350
462
|
/**
|
|
351
463
|
* Resolved per-model auth configuration after normalization.
|
|
352
464
|
* Always carries a concrete `modelName` (the developer's override or the
|
|
353
|
-
* better-auth default) and a (possibly empty) `fields` column map. `
|
|
354
|
-
*
|
|
355
|
-
* the
|
|
465
|
+
* better-auth default) and a (possibly empty) `fields` column map. `tableName`
|
|
466
|
+
* is the resolved physical table name — `undefined` means no `@@map` is
|
|
467
|
+
* emitted (the list key doubles as the table name). `schema` carries the
|
|
468
|
+
* resolved Postgres schema for the model (per-model override, else the
|
|
469
|
+
* plugin-level schema, else `undefined` for the default `public` schema).
|
|
356
470
|
*/
|
|
357
471
|
export type NormalizedAuthModelConfig = {
|
|
358
472
|
modelName: string;
|
|
473
|
+
tableName?: string;
|
|
359
474
|
fields: Record<string, string>;
|
|
360
475
|
schema?: string;
|
|
361
476
|
};
|
|
@@ -387,7 +502,7 @@ export type NormalizedAuthConfig = Required<Omit<AuthConfig, 'emailAndPassword'
|
|
|
387
502
|
* default (used to wire the datasource `schemas` array during generation).
|
|
388
503
|
*/
|
|
389
504
|
schema?: string;
|
|
390
|
-
betterAuthPlugins:
|
|
505
|
+
betterAuthPlugins: BetterAuthPlugin[];
|
|
391
506
|
rateLimit?: {
|
|
392
507
|
enabled: boolean;
|
|
393
508
|
window?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE7D;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;CACzC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-better-auth-options.test.d.ts","sourceRoot":"","sources":["../../src/server/build-better-auth-options.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
+
import { buildBetterAuthOptions } from './index.js';
|
|
3
|
+
// `ReturnType<typeof buildBetterAuthOptions>` on the overloaded export itself
|
|
4
|
+
// resolves against its last (generic) signature, not the call-site-selected
|
|
5
|
+
// one — wrapping each call shape in its own ordinary function and reading
|
|
6
|
+
// `ReturnType<typeof wrapper>` instead forces TS to resolve overloads exactly
|
|
7
|
+
// as a real call site would.
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced only via `typeof` below
|
|
9
|
+
function callWithNoPlugins(config, context) {
|
|
10
|
+
return buildBetterAuthOptions(config, context);
|
|
11
|
+
}
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced only via `typeof` below
|
|
13
|
+
function callWithPlugins(config, context, plugins) {
|
|
14
|
+
return buildBetterAuthOptions(config, context, plugins);
|
|
15
|
+
}
|
|
16
|
+
describe('buildBetterAuthOptions plugin-tuple typing', () => {
|
|
17
|
+
it('back-compat: the no-argument call still returns the widened BetterAuthOptions', () => {
|
|
18
|
+
expectTypeOf().toEqualTypeOf();
|
|
19
|
+
});
|
|
20
|
+
it('preserves plugin-derived auth.api.* endpoints and a customSession shape', () => {
|
|
21
|
+
// emailOTP() endpoints exist on the constructed Auth's `api` — erased entirely
|
|
22
|
+
// when constructed from the widened `BetterAuthOptions` (see #876).
|
|
23
|
+
expectTypeOf().not.toBeNever();
|
|
24
|
+
expectTypeOf().not.toBeNever();
|
|
25
|
+
expectTypeOf().not.toBeNever();
|
|
26
|
+
expectTypeOf().toEqualTypeOf();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=build-better-auth-options.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-better-auth-options.test.js","sourceRoot":"","sources":["../../src/server/build-better-auth-options.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAKnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAWnD,8EAA8E;AAC9E,4EAA4E;AAC5E,0EAA0E;AAC1E,8EAA8E;AAC9E,6BAA6B;AAC7B,mGAAmG;AACnG,SAAS,iBAAiB,CACxB,MAAgD,EAChD,OAA+C;IAE/C,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAChD,CAAC;AAED,mGAAmG;AACnG,SAAS,eAAe,CACtB,MAAgD,EAChD,OAA+C,EAC/C,OAAoB;IAEpB,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AACzD,CAAC;AAMD,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,YAAY,EAAe,CAAC,aAAa,EAAqB,CAAA;IAChE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,+EAA+E;QAC/E,oEAAoE;QACpE,YAAY,EAA4C,CAAC,GAAG,CAAC,SAAS,EAAE,CAAA;QACxE,YAAY,EAAiD,CAAC,GAAG,CAAC,SAAS,EAAE,CAAA;QAC7E,YAAY,EAAkD,CAAC,GAAG,CAAC,SAAS,EAAE,CAAA;QAI9E,YAAY,EAAoB,CAAC,aAAa,EAAqB,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,25 +1,129 @@
|
|
|
1
1
|
import { betterAuth } from 'better-auth';
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
2
|
+
import { nextCookies } from 'better-auth/next-js';
|
|
3
|
+
import type { Auth, BetterAuthOptions, BetterAuthPlugin } from 'better-auth';
|
|
4
|
+
import type { OpenSaasConfig, AccessContext, Session } from '@opensaas/stack-core';
|
|
5
|
+
/**
|
|
6
|
+
* The `BetterAuthOptions` shape produced when an app's own plugin tuple is
|
|
7
|
+
* passed to `buildBetterAuthOptions()`/`createAuth()` — the tuple plus the
|
|
8
|
+
* `nextCookies()` plugin the stack always appends last. Carrying the literal
|
|
9
|
+
* tuple type (rather than the widened `BetterAuthPlugin[]`) is what lets
|
|
10
|
+
* `betterAuth()` re-infer plugin endpoints (e.g. `emailOTP()`'s
|
|
11
|
+
* `api.signInEmailOTP`) and a `customSession()` plugin's replaced session
|
|
12
|
+
* shape from the resulting options object.
|
|
13
|
+
*/
|
|
14
|
+
type ResolvedBetterAuthOptions<TPlugins extends readonly BetterAuthPlugin[]> = Omit<BetterAuthOptions, 'plugins'> & {
|
|
15
|
+
plugins: [...TPlugins, ReturnType<typeof nextCookies>];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Build the `BetterAuthOptions` a better-auth instance for this OpenSaas
|
|
19
|
+
* config should be constructed with — the same options `createAuth()` uses
|
|
20
|
+
* internally, available standalone for an app that still needs to hand-wire
|
|
21
|
+
* its own `betterAuth()` instance (e.g. a third-party contract that requires
|
|
22
|
+
* a resolved instance at module-init time). Keeps the auth plugin
|
|
23
|
+
* authoritative for everything it models; the app's additions on top become
|
|
24
|
+
* an explicit, reviewable diff instead of a parallel, hand-duplicated config.
|
|
25
|
+
*
|
|
26
|
+
* Called with just `(config, context)`, the return type is the widened
|
|
27
|
+
* `BetterAuthOptions` — `betterAuth()` infers its plugin/session types from
|
|
28
|
+
* the *literal* type of the options object, so constructing from this
|
|
29
|
+
* widened return erases plugin endpoints (e.g. `emailOTP()`'s
|
|
30
|
+
* `api.signInEmailOTP`) and a `customSession()` plugin's replaced session
|
|
31
|
+
* shape. **If your app reads `auth.api.*` in typed code and uses either of
|
|
32
|
+
* those, pass its plugin tuple as the third argument** — the exact same
|
|
33
|
+
* array already passed to `authPlugin({ betterAuthPlugins })` — so the
|
|
34
|
+
* return type carries the literal tuple instead:
|
|
35
|
+
*
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { betterAuth } from 'better-auth'
|
|
38
|
+
* import { emailOTP } from 'better-auth/plugins'
|
|
39
|
+
* import { buildBetterAuthOptions } from '@opensaas/stack-auth/server'
|
|
40
|
+
*
|
|
41
|
+
* export const appBetterAuthPlugins = [emailOTP()] // same array passed to authPlugin({ betterAuthPlugins })
|
|
42
|
+
*
|
|
43
|
+
* export const auth = betterAuth({
|
|
44
|
+
* ...(await buildBetterAuthOptions(config, context, appBetterAuthPlugins)),
|
|
45
|
+
* databaseHooks: { user: { create: { after: syncDomainUser } } },
|
|
46
|
+
* })
|
|
47
|
+
* // auth.api.signInEmailOTP / auth.api.getSession()'s customSession shape are now typed.
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* The supplied tuple is for typing only — the array actually used at runtime
|
|
51
|
+
* is always the one resolved from `authPlugin({ betterAuthPlugins })`, with
|
|
52
|
+
* exactly one `nextCookies()` appended last. Passing a tuple that isn't the
|
|
53
|
+
* same plugin instances in the same order throws, so the two can't silently
|
|
54
|
+
* drift apart.
|
|
55
|
+
*
|
|
56
|
+
* Note `createAuth()`'s lazy Proxy does not behave identically to a real
|
|
57
|
+
* `Auth` instance for every property (see its own doc comment) — reach for
|
|
58
|
+
* this builder plus `betterAuth()` instead when the app reads `auth.api.*`
|
|
59
|
+
* in typed code.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildBetterAuthOptions(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>): Promise<BetterAuthOptions>;
|
|
62
|
+
export declare function buildBetterAuthOptions<const TPlugins extends readonly BetterAuthPlugin[]>(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>, plugins: TPlugins): Promise<ResolvedBetterAuthOptions<TPlugins>>;
|
|
4
63
|
/**
|
|
5
64
|
* Create a better-auth instance from OpenSaas config
|
|
6
65
|
* This should be called once at app startup
|
|
7
66
|
*
|
|
8
|
-
*
|
|
67
|
+
* Returns a lazy `Proxy` (see the caveat below), typed as `Auth<BetterAuthOptions>`
|
|
68
|
+
* when called with just `(config, context)` — the widened type, same erasure
|
|
69
|
+
* caveat as {@link buildBetterAuthOptions}'s no-argument form. **If your app
|
|
70
|
+
* reads `auth.api.*` in typed code and relies on a plugin's endpoints (e.g.
|
|
71
|
+
* `emailOTP()`) or a `customSession()`'s replaced session shape, pass its
|
|
72
|
+
* plugin tuple as the third argument** — the exact same array already passed
|
|
73
|
+
* to `authPlugin({ betterAuthPlugins })` — so the declared type carries the
|
|
74
|
+
* literal tuple instead:
|
|
75
|
+
*
|
|
9
76
|
* ```typescript
|
|
10
77
|
* // lib/auth.ts
|
|
11
78
|
* import { createAuth } from '@opensaas/stack-auth/server'
|
|
12
79
|
* import config from '../opensaas.config'
|
|
13
80
|
* import { rawOpensaasContext } from '@/.opensaas/context'
|
|
14
81
|
*
|
|
15
|
-
* export const
|
|
82
|
+
* export const appBetterAuthPlugins = [emailOTP()] // same array passed to authPlugin({ betterAuthPlugins })
|
|
83
|
+
*
|
|
84
|
+
* export const auth = createAuth(config, rawOpensaasContext, appBetterAuthPlugins)
|
|
16
85
|
* ```
|
|
86
|
+
*
|
|
87
|
+
* As with the builder, the supplied tuple is for typing only, and a tuple
|
|
88
|
+
* that isn't the same plugin instances in the same order throws.
|
|
89
|
+
*
|
|
90
|
+
* **Proxy caveat:** the lazy `Proxy` this returns does not behave identically
|
|
91
|
+
* to a real `Auth` instance for every property — every access, including a
|
|
92
|
+
* non-function property, is surfaced through an `async` wrapper (so e.g.
|
|
93
|
+
* `auth.options` reads back as a `Promise`, not the plain object a real
|
|
94
|
+
* instance would return synchronously). The declared type does not model
|
|
95
|
+
* this difference; where it matters, reach for {@link buildBetterAuthOptions}
|
|
96
|
+
* plus `betterAuth()` instead, which constructs a real instance.
|
|
17
97
|
*/
|
|
18
|
-
export declare function createAuth(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>):
|
|
98
|
+
export declare function createAuth(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>): Auth<BetterAuthOptions>;
|
|
99
|
+
export declare function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>, plugins: TPlugins): Auth<ResolvedBetterAuthOptions<TPlugins>>;
|
|
19
100
|
/**
|
|
20
|
-
* Get session from better-auth and transform it to OpenSaas session format
|
|
21
|
-
*
|
|
101
|
+
* Get session from better-auth and transform it to OpenSaas session format —
|
|
102
|
+
* a flattened projection of `sessionFields` off the *resolved* session
|
|
103
|
+
* object, not just its `user` sub-object. This is what makes a
|
|
104
|
+
* `customSession` plugin's fields (added at the top level, or a
|
|
105
|
+
* session-only field like the admin plugin's `impersonatedBy`) reachable.
|
|
106
|
+
* See the `sessionFields` reference for the resolution precedence.
|
|
107
|
+
*
|
|
108
|
+
* Returns `null` only when there is genuinely no session — a resolved
|
|
109
|
+
* session with no `user` key (a `customSession` plugin that dropped it) is
|
|
110
|
+
* still a session and still gets projected, never misreported as anonymous.
|
|
111
|
+
* A listed field that can't be resolved from the session shape is omitted
|
|
112
|
+
* and warns once per field per process (see `warnUnresolvedSessionField`)
|
|
113
|
+
* instead of silently vanishing into an access-control function reading
|
|
114
|
+
* `undefined`.
|
|
115
|
+
*
|
|
116
|
+
* Errors from the underlying `auth.api.getSession()` call propagate rather
|
|
117
|
+
* than becoming `null` — collapsing a lookup failure (e.g. a session-store
|
|
118
|
+
* outage) into "anonymous" is indistinguishable from a mass sign-out under
|
|
119
|
+
* fail-closed access control, so the caller must see it.
|
|
120
|
+
*
|
|
121
|
+
* Not called by any generated code before this helper existed — apps used to
|
|
122
|
+
* hand-roll this same transform against `auth.api.getSession({ headers:
|
|
123
|
+
* await headers() })`. Exported as the single reusable implementation; pass
|
|
124
|
+
* the caller's request headers (e.g. Next.js `await headers()` in a Server
|
|
125
|
+
* Component/action) so a session cookie can actually be resolved.
|
|
22
126
|
*/
|
|
23
|
-
export declare function getSessionFromAuth(auth: ReturnType<typeof betterAuth>, sessionFields: string[]): Promise<
|
|
127
|
+
export declare function getSessionFromAuth(auth: ReturnType<typeof betterAuth>, sessionFields: string[], headers: Headers): Promise<Session | null>;
|
|
24
128
|
export type { BetterAuthOptions };
|
|
25
129
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAIlF;;;;;;;;GAQG;AACH,KAAK,yBAAyB,CAAC,QAAQ,SAAS,SAAS,gBAAgB,EAAE,IAAI,IAAI,CACjF,iBAAiB,EACjB,SAAS,CACV,GAAG;IACF,OAAO,EAAE,CAAC,GAAG,QAAQ,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CACvD,CAAA;AA+ID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,sBAAsB,CAC1C,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAC9C,OAAO,CAAC,iBAAiB,CAAC,CAAA;AAC7B,wBAAsB,sBAAsB,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,gBAAgB,EAAE,EAC7F,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,EAC/C,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAA;AA2I/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,UAAU,CACxB,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAC9C,IAAI,CAAC,iBAAiB,CAAC,CAAA;AAC1B,wBAAgB,UAAU,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,gBAAgB,EAAE,EAC3E,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,EACxD,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,EAC/C,OAAO,EAAE,QAAQ,GAChB,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAA;AA8I5C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EACnC,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAoBzB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|