@opensaas/stack-auth 0.37.0 → 0.39.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 +60 -0
- package/CLAUDE.md +50 -3
- package/dist/config/adopt-better-auth-tables.d.ts +23 -3
- package/dist/config/adopt-better-auth-tables.d.ts.map +1 -1
- package/dist/config/adopt-better-auth-tables.js +7 -2
- package/dist/config/adopt-better-auth-tables.js.map +1 -1
- package/dist/config/derive-auth-lists.d.ts +6 -1
- package/dist/config/derive-auth-lists.d.ts.map +1 -1
- package/dist/config/derive-auth-lists.js +63 -16
- package/dist/config/derive-auth-lists.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +12 -5
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin.d.ts.map +1 -1
- package/dist/config/plugin.js +7 -2
- package/dist/config/plugin.js.map +1 -1
- package/dist/config/types.d.ts +62 -7
- 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/get-session-from-auth.test.d.ts +2 -0
- package/dist/server/get-session-from-auth.test.d.ts.map +1 -0
- package/dist/server/get-session-from-auth.test.js +25 -0
- package/dist/server/get-session-from-auth.test.js.map +1 -0
- package/dist/server/index.d.ts +108 -15
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +151 -63
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema-converter.d.ts +15 -6
- package/dist/server/schema-converter.d.ts.map +1 -1
- package/dist/server/schema-converter.js +14 -2
- package/dist/server/schema-converter.js.map +1 -1
- package/package.json +5 -5
- package/src/config/adopt-better-auth-tables.ts +31 -4
- package/src/config/derive-auth-lists.ts +82 -21
- package/src/config/index.ts +18 -5
- package/src/config/plugin.ts +7 -2
- package/src/config/types.ts +63 -9
- package/src/server/build-better-auth-options.test.ts +59 -0
- package/src/server/get-session-from-auth.test.ts +52 -0
- package/src/server/index.ts +273 -41
- package/src/server/schema-converter.ts +29 -8
- package/tests/adopt-better-auth-tables.test.ts +73 -0
- package/tests/config.test.ts +161 -0
- package/tests/derive-auth-lists.test.ts +104 -0
- package/tests/generated-fk-shape.test.ts +81 -0
- package/tests/plugin-schema-placement.test.ts +39 -0
- package/tests/rate-limit-e2e.test.ts +239 -0
- package/tests/schema-converter.test.ts +58 -0
- package/tests/server.test.ts +310 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -1
package/dist/config/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ListConfig } from '@opensaas/stack-core';
|
|
2
|
-
import type { BetterAuthOptions, User } from 'better-auth';
|
|
2
|
+
import type { BetterAuthOptions, BetterAuthPlugin, User } from 'better-auth';
|
|
3
3
|
import type { ExtendUserListConfig } from '../lists/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* better-auth's own callback shape for `sendVerificationEmail`/
|
|
@@ -192,6 +192,13 @@ export type AuthAccessConfig = {
|
|
|
192
192
|
session?: ListConfig<any>['access'];
|
|
193
193
|
account?: ListConfig<any>['access'];
|
|
194
194
|
verification?: ListConfig<any>['access'];
|
|
195
|
+
/**
|
|
196
|
+
* Access control for the `RateLimit` list — only meaningful when
|
|
197
|
+
* `rateLimit.storage: 'database'` derives it. Per ADR-0013 the list ships
|
|
198
|
+
* closed like the other four; grant access here (e.g. to inspect throttled
|
|
199
|
+
* keys in the Admin UI).
|
|
200
|
+
*/
|
|
201
|
+
rateLimit?: ListConfig<any>['access'];
|
|
195
202
|
};
|
|
196
203
|
export type AuthModelConfig = {
|
|
197
204
|
/**
|
|
@@ -321,8 +328,21 @@ export type AuthConfig = {
|
|
|
321
328
|
*/
|
|
322
329
|
schema?: string;
|
|
323
330
|
/**
|
|
324
|
-
* Which fields to include in the session object
|
|
325
|
-
*
|
|
331
|
+
* Which fields to include in the session object passed to access control
|
|
332
|
+
* functions — a **flattened projection** of the resolved better-auth
|
|
333
|
+
* session, not the session's own shape. `getSessionFromAuth` (the
|
|
334
|
+
* implementation the scaffolded `getSession()` calls) resolves each name
|
|
335
|
+
* against a fixed precedence: a top-level key on the resolved session
|
|
336
|
+
* object, then the `user` object, then the `session` sub-object.
|
|
337
|
+
* `userId` is special-cased to the authenticated user's `id`.
|
|
338
|
+
*
|
|
339
|
+
* A `customSession` better-auth plugin fully replaces the resolved shape
|
|
340
|
+
* (it can nest fields anywhere, e.g. under its own custom key) —
|
|
341
|
+
* reconciling that shape against `sessionFields` is the application's job.
|
|
342
|
+
* A listed name that can't be resolved is omitted and warns once per
|
|
343
|
+
* field per process, naming what was checked, rather than silently
|
|
344
|
+
* becoming `undefined` in an access control function.
|
|
345
|
+
*
|
|
326
346
|
* @default ['userId', 'email', 'name']
|
|
327
347
|
*
|
|
328
348
|
* @example
|
|
@@ -375,11 +395,27 @@ export type AuthConfig = {
|
|
|
375
395
|
* ]
|
|
376
396
|
* ```
|
|
377
397
|
*/
|
|
378
|
-
betterAuthPlugins?:
|
|
398
|
+
betterAuthPlugins?: BetterAuthPlugin[];
|
|
379
399
|
/**
|
|
380
400
|
* Rate limiting configuration
|
|
381
401
|
* Controls rate limiting for authentication endpoints
|
|
382
402
|
*
|
|
403
|
+
* `storage` mirrors better-auth's own `rateLimit.storage` option
|
|
404
|
+
* (`'memory' | 'database' | 'secondary-storage'`, default `'memory'`). Set
|
|
405
|
+
* it to `'database'` to persist the limiter across restarts/instances — the
|
|
406
|
+
* plugin then derives a fifth `RateLimit` Auth list (per ADR-0007) so the
|
|
407
|
+
* required table exists in the generated Prisma schema, following the same
|
|
408
|
+
* adoption knobs (`modelName`/`fields`/`tableName`/`schema`) the other four
|
|
409
|
+
* models carry. Derivation keys off `storage` alone — `enabled: false` with
|
|
410
|
+
* `storage: 'database'` still produces the list, since better-auth still
|
|
411
|
+
* expects the table regardless of whether the limiter is currently active
|
|
412
|
+
* (`enabled` is routinely environment-driven; tying the schema to it would
|
|
413
|
+
* make dev/prod schemas differ).
|
|
414
|
+
*
|
|
415
|
+
* Setting `storage` via `betterAuthOptions.rateLimit.storage` is rejected —
|
|
416
|
+
* use this option instead, since it also has schema consequences the
|
|
417
|
+
* passthrough can't apply.
|
|
418
|
+
*
|
|
383
419
|
* @example
|
|
384
420
|
* ```typescript
|
|
385
421
|
* // Disable rate limiting for testing
|
|
@@ -393,6 +429,12 @@ export type AuthConfig = {
|
|
|
393
429
|
* window: 60, // 60 seconds
|
|
394
430
|
* max: 100, // 100 requests per window
|
|
395
431
|
* }
|
|
432
|
+
*
|
|
433
|
+
* // Persist the limiter in the database (derives a RateLimit list)
|
|
434
|
+
* rateLimit: {
|
|
435
|
+
* enabled: true,
|
|
436
|
+
* storage: 'database',
|
|
437
|
+
* }
|
|
396
438
|
* ```
|
|
397
439
|
*/
|
|
398
440
|
rateLimit?: {
|
|
@@ -407,7 +449,12 @@ export type AuthConfig = {
|
|
|
407
449
|
* @default 100
|
|
408
450
|
*/
|
|
409
451
|
max?: number;
|
|
410
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Where better-auth persists the rate limiter.
|
|
454
|
+
* @default 'memory'
|
|
455
|
+
*/
|
|
456
|
+
storage?: 'memory' | 'database' | 'secondary-storage';
|
|
457
|
+
} & AuthModelConfig;
|
|
411
458
|
/**
|
|
412
459
|
* Escape hatch for better-auth options the stack doesn't model — typed as
|
|
413
460
|
* better-auth's own `BetterAuthOptions` so it stays in step with
|
|
@@ -462,7 +509,8 @@ export type NormalizedAuthModelConfig = {
|
|
|
462
509
|
schema?: string;
|
|
463
510
|
};
|
|
464
511
|
/**
|
|
465
|
-
* Resolved auth model configuration for all four better-auth models
|
|
512
|
+
* Resolved auth model configuration for all four better-auth models, plus an
|
|
513
|
+
* optional fifth for the database-backed rate limiter.
|
|
466
514
|
* Consumed by the Auth-list derivation and the runtime user-key resolution.
|
|
467
515
|
*/
|
|
468
516
|
export type NormalizedAuthModels = {
|
|
@@ -470,6 +518,12 @@ export type NormalizedAuthModels = {
|
|
|
470
518
|
session: NormalizedAuthModelConfig;
|
|
471
519
|
account: NormalizedAuthModelConfig;
|
|
472
520
|
verification: NormalizedAuthModelConfig;
|
|
521
|
+
/**
|
|
522
|
+
* Present only when `rateLimit.storage === 'database'` — derives a fifth
|
|
523
|
+
* `RateLimit` Auth list. Absent (not `undefined`-valued) otherwise, so
|
|
524
|
+
* `Object.values(models)` never yields a model-less entry.
|
|
525
|
+
*/
|
|
526
|
+
rateLimit?: NormalizedAuthModelConfig;
|
|
473
527
|
};
|
|
474
528
|
/**
|
|
475
529
|
* Internal normalized auth configuration
|
|
@@ -489,11 +543,12 @@ export type NormalizedAuthConfig = Required<Omit<AuthConfig, 'emailAndPassword'
|
|
|
489
543
|
* default (used to wire the datasource `schemas` array during generation).
|
|
490
544
|
*/
|
|
491
545
|
schema?: string;
|
|
492
|
-
betterAuthPlugins:
|
|
546
|
+
betterAuthPlugins: BetterAuthPlugin[];
|
|
493
547
|
rateLimit?: {
|
|
494
548
|
enabled: boolean;
|
|
495
549
|
window?: number;
|
|
496
550
|
max?: number;
|
|
551
|
+
storage?: 'memory' | 'database' | 'secondary-storage';
|
|
497
552
|
};
|
|
498
553
|
};
|
|
499
554
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -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,iBAAiB,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-session-from-auth.test.d.ts","sourceRoot":"","sources":["../../src/server/get-session-from-auth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
+
import { createAuth, getSessionFromAuth } from './index.js';
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced only via `typeof` below
|
|
4
|
+
function callWithNoPlugins(config, context) {
|
|
5
|
+
return createAuth(config, context);
|
|
6
|
+
}
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced only via `typeof` below
|
|
8
|
+
function callWithPlugins(config, context, plugins) {
|
|
9
|
+
return createAuth(config, context, plugins);
|
|
10
|
+
}
|
|
11
|
+
describe('getSessionFromAuth accepts either createAuth() overload (#906)', () => {
|
|
12
|
+
it('accepts the widened Auth<BetterAuthOptions> instance with no cast', () => {
|
|
13
|
+
expectTypeOf().toExtend();
|
|
14
|
+
});
|
|
15
|
+
it('accepts the narrowed, plugin-typed Auth instance with no cast', () => {
|
|
16
|
+
// This is the exact case #906 reported as a type error: a `createAuth`
|
|
17
|
+
// instance narrowed by a plugin tuple, passed straight into
|
|
18
|
+
// `getSessionFromAuth` without widening it back to `Auth<BetterAuthOptions>`.
|
|
19
|
+
expectTypeOf().toExtend();
|
|
20
|
+
});
|
|
21
|
+
it('return type stays Promise<Session | null>, unchanged by the widened/narrowed instance', () => {
|
|
22
|
+
expectTypeOf(getSessionFromAuth).returns.toEqualTypeOf();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=get-session-from-auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-session-from-auth.test.js","sourceRoot":"","sources":["../../src/server/get-session-from-auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAGnD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAa3D,mGAAmG;AACnG,SAAS,iBAAiB,CACxB,MAAgD,EAChD,OAA+C;IAE/C,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACpC,CAAC;AAED,mGAAmG;AACnG,SAAS,eAAe,CACtB,MAAgD,EAChD,OAA+C,EAC/C,OAAoB;IAEpB,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AAC7C,CAAC;AAKD,QAAQ,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC9E,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,YAAY,EAAe,CAAC,QAAQ,EAA4C,CAAA;IAClF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,uEAAuE;QACvE,4DAA4D;QAC5D,8EAA8E;QAC9E,YAAY,EAAgB,CAAC,QAAQ,EAA4C,CAAA;IACnF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uFAAuF,EAAE,GAAG,EAAE;QAC/F,YAAY,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,aAAa,EAA2B,CAAA;IACnF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { BetterAuthOptions } from 'better-auth';
|
|
3
|
-
import type { OpenSaasConfig, AccessContext } from '@opensaas/stack-core';
|
|
1
|
+
import { nextCookies } from 'better-auth/next-js';
|
|
2
|
+
import type { Auth, BetterAuthOptions, BetterAuthPlugin } from 'better-auth';
|
|
3
|
+
import type { OpenSaasConfig, AccessContext, Session } from '@opensaas/stack-core';
|
|
4
|
+
/**
|
|
5
|
+
* The `BetterAuthOptions` shape produced when an app's own plugin tuple is
|
|
6
|
+
* passed to `buildBetterAuthOptions()`/`createAuth()` — the tuple plus the
|
|
7
|
+
* `nextCookies()` plugin the stack always appends last. Carrying the literal
|
|
8
|
+
* tuple type (rather than the widened `BetterAuthPlugin[]`) is what lets
|
|
9
|
+
* `betterAuth()` re-infer plugin endpoints (e.g. `emailOTP()`'s
|
|
10
|
+
* `api.signInEmailOTP`) and a `customSession()` plugin's replaced session
|
|
11
|
+
* shape from the resulting options object.
|
|
12
|
+
*/
|
|
13
|
+
type ResolvedBetterAuthOptions<TPlugins extends readonly BetterAuthPlugin[]> = Omit<BetterAuthOptions, 'plugins'> & {
|
|
14
|
+
plugins: [...TPlugins, ReturnType<typeof nextCookies>];
|
|
15
|
+
};
|
|
4
16
|
/**
|
|
5
17
|
* Build the `BetterAuthOptions` a better-auth instance for this OpenSaas
|
|
6
18
|
* config should be constructed with — the same options `createAuth()` uses
|
|
@@ -10,42 +22,123 @@ import type { OpenSaasConfig, AccessContext } from '@opensaas/stack-core';
|
|
|
10
22
|
* authoritative for everything it models; the app's additions on top become
|
|
11
23
|
* an explicit, reviewable diff instead of a parallel, hand-duplicated config.
|
|
12
24
|
*
|
|
13
|
-
*
|
|
25
|
+
* Called with just `(config, context)`, the return type is the widened
|
|
26
|
+
* `BetterAuthOptions` — `betterAuth()` infers its plugin/session types from
|
|
27
|
+
* the *literal* type of the options object, so constructing from this
|
|
28
|
+
* widened return erases plugin endpoints (e.g. `emailOTP()`'s
|
|
29
|
+
* `api.signInEmailOTP`) and a `customSession()` plugin's replaced session
|
|
30
|
+
* shape. **If your app reads `auth.api.*` in typed code and uses either of
|
|
31
|
+
* those, pass its plugin tuple as the third argument** — the exact same
|
|
32
|
+
* array already passed to `authPlugin({ betterAuthPlugins })` — so the
|
|
33
|
+
* return type carries the literal tuple instead:
|
|
34
|
+
*
|
|
14
35
|
* ```typescript
|
|
15
36
|
* import { betterAuth } from 'better-auth'
|
|
37
|
+
* import { emailOTP } from 'better-auth/plugins'
|
|
16
38
|
* import { buildBetterAuthOptions } from '@opensaas/stack-auth/server'
|
|
17
39
|
*
|
|
40
|
+
* export const appBetterAuthPlugins = [emailOTP()] // same array passed to authPlugin({ betterAuthPlugins })
|
|
41
|
+
*
|
|
18
42
|
* export const auth = betterAuth({
|
|
19
|
-
* ...(await buildBetterAuthOptions(config, context)),
|
|
43
|
+
* ...(await buildBetterAuthOptions(config, context, appBetterAuthPlugins)),
|
|
20
44
|
* databaseHooks: { user: { create: { after: syncDomainUser } } },
|
|
21
45
|
* })
|
|
46
|
+
* // auth.api.signInEmailOTP / auth.api.getSession()'s customSession shape are now typed.
|
|
22
47
|
* ```
|
|
48
|
+
*
|
|
49
|
+
* The supplied tuple is for typing only — the array actually used at runtime
|
|
50
|
+
* is always the one resolved from `authPlugin({ betterAuthPlugins })`, with
|
|
51
|
+
* exactly one `nextCookies()` appended last. Passing a tuple that isn't the
|
|
52
|
+
* same plugin instances in the same order throws, so the two can't silently
|
|
53
|
+
* drift apart.
|
|
54
|
+
*
|
|
55
|
+
* Note `createAuth()`'s lazy Proxy does not behave identically to a real
|
|
56
|
+
* `Auth` instance for every property (see its own doc comment) — reach for
|
|
57
|
+
* this builder plus `betterAuth()` instead when the app reads `auth.api.*`
|
|
58
|
+
* in typed code.
|
|
23
59
|
*/
|
|
24
60
|
export declare function buildBetterAuthOptions(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>): Promise<BetterAuthOptions>;
|
|
61
|
+
export declare function buildBetterAuthOptions<const TPlugins extends readonly BetterAuthPlugin[]>(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>, plugins: TPlugins): Promise<ResolvedBetterAuthOptions<TPlugins>>;
|
|
25
62
|
/**
|
|
26
63
|
* Create a better-auth instance from OpenSaas config
|
|
27
64
|
* This should be called once at app startup
|
|
28
65
|
*
|
|
29
|
-
*
|
|
66
|
+
* Returns a lazy `Proxy` (see the caveat below), typed as `Auth<BetterAuthOptions>`
|
|
67
|
+
* when called with just `(config, context)` — the widened type, same erasure
|
|
68
|
+
* caveat as {@link buildBetterAuthOptions}'s no-argument form. **If your app
|
|
69
|
+
* reads `auth.api.*` in typed code and relies on a plugin's endpoints (e.g.
|
|
70
|
+
* `emailOTP()`) or a `customSession()`'s replaced session shape, pass its
|
|
71
|
+
* plugin tuple as the third argument** — the exact same array already passed
|
|
72
|
+
* to `authPlugin({ betterAuthPlugins })` — so the declared type carries the
|
|
73
|
+
* literal tuple instead:
|
|
74
|
+
*
|
|
30
75
|
* ```typescript
|
|
31
76
|
* // lib/auth.ts
|
|
32
77
|
* import { createAuth } from '@opensaas/stack-auth/server'
|
|
33
78
|
* import config from '../opensaas.config'
|
|
34
79
|
* import { rawOpensaasContext } from '@/.opensaas/context'
|
|
35
80
|
*
|
|
36
|
-
* export const
|
|
81
|
+
* export const appBetterAuthPlugins = [emailOTP()] // same array passed to authPlugin({ betterAuthPlugins })
|
|
82
|
+
*
|
|
83
|
+
* export const auth = createAuth(config, rawOpensaasContext, appBetterAuthPlugins)
|
|
37
84
|
* ```
|
|
85
|
+
*
|
|
86
|
+
* As with the builder, the supplied tuple is for typing only, and a tuple
|
|
87
|
+
* that isn't the same plugin instances in the same order throws.
|
|
88
|
+
*
|
|
89
|
+
* **Proxy caveat:** the lazy `Proxy` this returns does not behave identically
|
|
90
|
+
* to a real `Auth` instance for every property — every access, including a
|
|
91
|
+
* non-function property, is surfaced through an `async` wrapper (so e.g.
|
|
92
|
+
* `auth.options` reads back as a `Promise`, not the plain object a real
|
|
93
|
+
* instance would return synchronously). The declared type does not model
|
|
94
|
+
* this difference; where it matters, reach for {@link buildBetterAuthOptions}
|
|
95
|
+
* plus `betterAuth()` instead, which constructs a real instance.
|
|
38
96
|
*/
|
|
39
|
-
export declare function createAuth(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>):
|
|
97
|
+
export declare function createAuth(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>): Auth<BetterAuthOptions>;
|
|
98
|
+
export declare function createAuth<const TPlugins extends readonly BetterAuthPlugin[]>(opensaasConfig: OpenSaasConfig | Promise<OpenSaasConfig>, context: AccessContext | Promise<AccessContext>, plugins: TPlugins): Auth<ResolvedBetterAuthOptions<TPlugins>>;
|
|
40
99
|
/**
|
|
41
|
-
* Get session from better-auth and transform it to OpenSaas session format
|
|
100
|
+
* Get session from better-auth and transform it to OpenSaas session format —
|
|
101
|
+
* a flattened projection of `sessionFields` off the *resolved* session
|
|
102
|
+
* object, not just its `user` sub-object. This is what makes a
|
|
103
|
+
* `customSession` plugin's fields (added at the top level, or a
|
|
104
|
+
* session-only field like the admin plugin's `impersonatedBy`) reachable.
|
|
105
|
+
* See the `sessionFields` reference for the resolution precedence.
|
|
106
|
+
*
|
|
107
|
+
* Returns `null` only when there is genuinely no session — a resolved
|
|
108
|
+
* session with no `user` key (a `customSession` plugin that dropped it) is
|
|
109
|
+
* still a session and still gets projected, never misreported as anonymous.
|
|
110
|
+
* A listed field that can't be resolved from the session shape is omitted
|
|
111
|
+
* and warns once per field per process (see `warnUnresolvedSessionField`)
|
|
112
|
+
* instead of silently vanishing into an access-control function reading
|
|
113
|
+
* `undefined`.
|
|
114
|
+
*
|
|
115
|
+
* Errors from the underlying `auth.api.getSession()` call propagate rather
|
|
116
|
+
* than becoming `null` — collapsing a lookup failure (e.g. a session-store
|
|
117
|
+
* outage) into "anonymous" is indistinguishable from a mass sign-out under
|
|
118
|
+
* fail-closed access control, so the caller must see it.
|
|
119
|
+
*
|
|
120
|
+
* Not called by any generated code before this helper existed — apps used to
|
|
121
|
+
* hand-roll this same transform against `auth.api.getSession({ headers:
|
|
122
|
+
* await headers() })`. Exported as the single reusable implementation; pass
|
|
123
|
+
* the caller's request headers (e.g. Next.js `await headers()` in a Server
|
|
124
|
+
* Component/action) so a session cookie can actually be resolved.
|
|
42
125
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
126
|
+
* `auth` is typed structurally over just the one member this function reads
|
|
127
|
+
* — `api.getSession` — rather than a single concrete `Auth<Options>`
|
|
128
|
+
* instantiation, so it accepts an instance from either `createAuth()`
|
|
129
|
+
* overload: the widened `Auth<BetterAuthOptions>`, or the narrowed
|
|
130
|
+
* `Auth<ResolvedBetterAuthOptions<TPlugins>>` returned when a plugin tuple is
|
|
131
|
+
* passed. `TResolvedSession` is inferred from whatever `auth.api.getSession`
|
|
132
|
+
* actually returns (the default `{ session, user }` shape, or a
|
|
133
|
+
* `customSession` plugin's replaced shape) — it is not constrained, so no
|
|
134
|
+
* `any`/`unknown` is introduced at the call boundary.
|
|
48
135
|
*/
|
|
49
|
-
export declare function getSessionFromAuth(auth:
|
|
136
|
+
export declare function getSessionFromAuth<TResolvedSession>(auth: {
|
|
137
|
+
api: {
|
|
138
|
+
getSession: (args: {
|
|
139
|
+
headers: Headers;
|
|
140
|
+
}) => Promise<TResolvedSession>;
|
|
141
|
+
};
|
|
142
|
+
}, sessionFields: string[], headers: Headers): Promise<Session | null>;
|
|
50
143
|
export type { BetterAuthOptions };
|
|
51
144
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,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;AA8JD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AAkJ/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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,kBAAkB,CAAC,gBAAgB,EACvD,IAAI,EAAE;IAAE,GAAG,EAAE;QAAE,UAAU,EAAE,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;KAAE,CAAA;CAAE,EACxF,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAoBzB;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|