@velajs/better-auth 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/better-auth.module.d.ts +21 -27
- package/dist/better-auth.module.js +116 -122
- package/dist/testing/acting-as.d.ts +46 -0
- package/dist/testing/acting-as.js +70 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -0
- package/package.json +13 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-07-04)
|
|
4
|
+
|
|
5
|
+
- Rebuilt on vela 1.11 `defineModule` + `lazyProvider` + `provideGlobal` (lazy auth-builder deferral preserved; public API unchanged). Requires `@velajs/vela >=1.11.0`.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
All notable changes to `@velajs/better-auth` are documented here. The format
|
|
4
9
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
10
|
|
|
@@ -3,22 +3,17 @@ import type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.
|
|
|
3
3
|
/**
|
|
4
4
|
* Options for {@link BetterAuthModule.forRootAsync}.
|
|
5
5
|
*
|
|
6
|
-
* The `Inject` type parameter captures the literal `inject` tuple at the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* No `as const`, no `(...deps: any[])` workaround:
|
|
6
|
+
* The `Inject` type parameter captures the literal `inject` tuple at the call
|
|
7
|
+
* site (via `const` inference) so `useFactory` parameters are typed from the
|
|
8
|
+
* inject array, position-by-position — no `as const`, no `(...deps: any[])`:
|
|
10
9
|
*
|
|
11
10
|
* ```ts
|
|
12
11
|
* BetterAuthModule.forRootAsync({
|
|
13
12
|
* inject: [D1Service, ConfigService], // captured as readonly tuple
|
|
14
|
-
* useFactory: (d1, config) =>
|
|
13
|
+
* useFactory: (d1, config) => // d1: D1Service, config: ConfigService
|
|
15
14
|
* betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),
|
|
16
15
|
* });
|
|
17
16
|
* ```
|
|
18
|
-
*
|
|
19
|
-
* When `inject` isn't a literal tuple (or is omitted), `Inject` falls back
|
|
20
|
-
* to `readonly Token<unknown>[]` and `useFactory` accepts variadic
|
|
21
|
-
* `unknown[]` — the historical loose-typing behavior.
|
|
22
17
|
*/
|
|
23
18
|
interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]> {
|
|
24
19
|
inject?: Inject;
|
|
@@ -32,26 +27,25 @@ interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonl
|
|
|
32
27
|
}
|
|
33
28
|
export declare class BetterAuthModule {
|
|
34
29
|
/**
|
|
35
|
-
* Synchronous registration. The auth instance is constructed by the
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
30
|
+
* Synchronous registration. The auth instance is constructed by the consumer
|
|
31
|
+
* at module-load time and passed in directly. Use this when the inputs to
|
|
32
|
+
* `betterAuth({...})` are available at startup (Node apps with a static DB
|
|
33
|
+
* connection, in-memory adapters, etc.).
|
|
39
34
|
*/
|
|
40
|
-
static forRoot(options: BetterAuthModuleOptions
|
|
35
|
+
static forRoot(options: BetterAuthModuleOptions & {
|
|
36
|
+
isGlobal?: boolean;
|
|
37
|
+
key?: string;
|
|
38
|
+
}): DynamicModule;
|
|
41
39
|
/**
|
|
42
|
-
* Deferred / DI-driven registration. The user factory runs **lazily**, on
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Inject deps are resolved at module load (cheap; e.g. D1Service is a
|
|
52
|
-
* thin BindingRef wrapper). Their *values* are read at first auth use,
|
|
53
|
-
* inside your factory body — `(d1: D1Service) => betterAuth({ database:
|
|
54
|
-
* drizzleAdapter(drizzle(d1.database), ...) })`.
|
|
40
|
+
* Deferred / DI-driven registration. The user factory runs **lazily**, on the
|
|
41
|
+
* first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
|
|
42
|
+
* In normal request handling that's `AuthGuard.canActivate` or the catch-all
|
|
43
|
+
* controller's `.handle`. At module load the factory does NOT run — it's only
|
|
44
|
+
* captured behind {@link lazyProvider}'s memoized thunk. This is what makes
|
|
45
|
+
* Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but
|
|
46
|
+
* it IS by the time a request flows through and the guard / catch-all reads
|
|
47
|
+
* the service. Inject deps resolve at module load (cheap BindingRef wrappers);
|
|
48
|
+
* their *values* are read at first auth use, inside your factory body.
|
|
55
49
|
*/
|
|
56
50
|
static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(options: ForRootAsyncOptions<Inject>): DynamicModule;
|
|
57
51
|
}
|
|
@@ -1,144 +1,138 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineModule, lazyProvider, provideGlobal, stableHash } from "@velajs/vela";
|
|
2
2
|
import { createBetterAuthCatchallController } from "./better-auth.controller.js";
|
|
3
3
|
import { BetterAuthService, BETTER_AUTH_BUILDER } from "./better-auth.service.js";
|
|
4
4
|
import { BETTER_AUTH_OPTIONS } from "./better-auth.tokens.js";
|
|
5
5
|
import { AuthGuard } from "./guards/auth.guard.js";
|
|
6
6
|
import { RolesGuard } from "./guards/roles.guard.js";
|
|
7
7
|
const DEFAULT_BASE_PATH = '/api/auth';
|
|
8
|
+
function normalize(options) {
|
|
9
|
+
return {
|
|
10
|
+
basePath: options.basePath ?? DEFAULT_BASE_PATH,
|
|
11
|
+
isGlobal: options.isGlobal ?? false,
|
|
12
|
+
defaultPolicy: options.defaultPolicy ?? 'deny',
|
|
13
|
+
mountHandler: options.mountHandler ?? true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Providers, controllers, and exports shared by both entry points. */ function commonContributions(n) {
|
|
17
|
+
return {
|
|
18
|
+
providers: [
|
|
19
|
+
BetterAuthService,
|
|
20
|
+
AuthGuard,
|
|
21
|
+
RolesGuard
|
|
22
|
+
],
|
|
23
|
+
controllers: n.mountHandler ? [
|
|
24
|
+
createBetterAuthCatchallController(n.basePath)
|
|
25
|
+
] : [],
|
|
26
|
+
exports: [
|
|
27
|
+
BetterAuthService,
|
|
28
|
+
BETTER_AUTH_OPTIONS,
|
|
29
|
+
AuthGuard,
|
|
30
|
+
RolesGuard
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The blessed engine generates `forRoot`. `setup` runs once per instance at
|
|
36
|
+
* call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,
|
|
37
|
+
* derives the auth builder from those options, mounts the catch-all controller,
|
|
38
|
+
* and — via the `global:` slot — registers the app-wide guard when `isGlobal`.
|
|
39
|
+
*
|
|
40
|
+
* `isGlobal` here means "apply AuthGuard app-wide", NOT "make this a global
|
|
41
|
+
* module", so the default `isGlobal → global: true` extras transform is
|
|
42
|
+
* replaced with identity; the flag reaches `setup` through the options bag.
|
|
43
|
+
*/ const authModuleHost = defineModule({
|
|
44
|
+
name: 'BetterAuth',
|
|
45
|
+
optionsToken: BETTER_AUTH_OPTIONS,
|
|
46
|
+
transform: (definition)=>definition,
|
|
47
|
+
// The auth instance is a stateful value — key off the structural subset only.
|
|
48
|
+
key: (options)=>stableHash(normalize(options)),
|
|
49
|
+
setup: ({ OPTIONS, options })=>{
|
|
50
|
+
const n = normalize(options);
|
|
51
|
+
const common = commonContributions(n);
|
|
52
|
+
const auth = options.auth;
|
|
53
|
+
return {
|
|
54
|
+
providers: [
|
|
55
|
+
// Override the auto-provided raw bag with the normalized shape so
|
|
56
|
+
// BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.
|
|
57
|
+
{
|
|
58
|
+
provide: OPTIONS,
|
|
59
|
+
useValue: {
|
|
60
|
+
...n,
|
|
61
|
+
auth
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
// Eager auth: the builder hands back the instance the caller passed in.
|
|
65
|
+
lazyProvider({
|
|
66
|
+
provide: BETTER_AUTH_BUILDER,
|
|
67
|
+
inject: [
|
|
68
|
+
OPTIONS
|
|
69
|
+
],
|
|
70
|
+
useFactory: (o)=>o.auth
|
|
71
|
+
}),
|
|
72
|
+
...common.providers
|
|
73
|
+
],
|
|
74
|
+
controllers: common.controllers,
|
|
75
|
+
exports: common.exports,
|
|
76
|
+
global: n.isGlobal ? {
|
|
77
|
+
guards: [
|
|
78
|
+
AuthGuard
|
|
79
|
+
]
|
|
80
|
+
} : undefined
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
8
84
|
export class BetterAuthModule {
|
|
9
85
|
/**
|
|
10
|
-
* Synchronous registration. The auth instance is constructed by the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
86
|
+
* Synchronous registration. The auth instance is constructed by the consumer
|
|
87
|
+
* at module-load time and passed in directly. Use this when the inputs to
|
|
88
|
+
* `betterAuth({...})` are available at startup (Node apps with a static DB
|
|
89
|
+
* connection, in-memory adapters, etc.).
|
|
14
90
|
*/ static forRoot(options) {
|
|
15
|
-
|
|
16
|
-
|
|
91
|
+
// Delegate to the generated static, then rebrand the module identity so the
|
|
92
|
+
// public `BetterAuthModule` class is the one registered (consistent with
|
|
93
|
+
// `forRootAsync` and better diagnostics).
|
|
17
94
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
basePath: normalized.basePath,
|
|
21
|
-
defaultPolicy: normalized.defaultPolicy,
|
|
22
|
-
isGlobal: !!options.isGlobal,
|
|
23
|
-
mountHandler: !!normalized.mountHandler
|
|
24
|
-
}),
|
|
25
|
-
providers,
|
|
26
|
-
controllers: normalized.mountHandler ? [
|
|
27
|
-
createBetterAuthCatchallController(normalized.basePath)
|
|
28
|
-
] : [],
|
|
29
|
-
exports: [
|
|
30
|
-
BetterAuthService,
|
|
31
|
-
BETTER_AUTH_OPTIONS,
|
|
32
|
-
AuthGuard,
|
|
33
|
-
RolesGuard
|
|
34
|
-
]
|
|
95
|
+
...authModuleHost.ConfigurableModuleClass.forRoot(options),
|
|
96
|
+
module: BetterAuthModule
|
|
35
97
|
};
|
|
36
98
|
}
|
|
37
99
|
/**
|
|
38
|
-
* Deferred / DI-driven registration. The user factory runs **lazily**, on
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* Inject deps are resolved at module load (cheap; e.g. D1Service is a
|
|
48
|
-
* thin BindingRef wrapper). Their *values* are read at first auth use,
|
|
49
|
-
* inside your factory body — `(d1: D1Service) => betterAuth({ database:
|
|
50
|
-
* drizzleAdapter(drizzle(d1.database), ...) })`.
|
|
100
|
+
* Deferred / DI-driven registration. The user factory runs **lazily**, on the
|
|
101
|
+
* first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
|
|
102
|
+
* In normal request handling that's `AuthGuard.canActivate` or the catch-all
|
|
103
|
+
* controller's `.handle`. At module load the factory does NOT run — it's only
|
|
104
|
+
* captured behind {@link lazyProvider}'s memoized thunk. This is what makes
|
|
105
|
+
* Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but
|
|
106
|
+
* it IS by the time a request flows through and the guard / catch-all reads
|
|
107
|
+
* the service. Inject deps resolve at module load (cheap BindingRef wrappers);
|
|
108
|
+
* their *values* are read at first auth use, inside your factory body.
|
|
51
109
|
*/ static forRootAsync(options) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const basePath = options.basePath ?? DEFAULT_BASE_PATH;
|
|
55
|
-
const defaultPolicy = options.defaultPolicy ?? 'deny';
|
|
56
|
-
const providers = [
|
|
57
|
-
{
|
|
58
|
-
provide: BETTER_AUTH_OPTIONS,
|
|
59
|
-
useValue: {
|
|
60
|
-
basePath,
|
|
61
|
-
defaultPolicy,
|
|
62
|
-
isGlobal,
|
|
63
|
-
mountHandler
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
// The builder is a zero-arg closure capturing the DI'd deps. It is
|
|
68
|
-
// invoked lazily by BetterAuthService.auth on first access; vela
|
|
69
|
-
// can resolve this provider at module load without running the user
|
|
70
|
-
// factory — the factory body lives behind the closure.
|
|
71
|
-
//
|
|
72
|
-
// The inner factory is typed `unknown[]` (vela's runtime contract:
|
|
73
|
-
// deps are resolved by token order). The outer user-facing
|
|
74
|
-
// `options.useFactory` is statically typed via `InferTokens<Inject>`.
|
|
75
|
-
// We narrow at the boundary with a single cast — runtime order
|
|
76
|
-
// matches the declared `inject` order by construction.
|
|
77
|
-
provide: BETTER_AUTH_BUILDER,
|
|
78
|
-
useFactory: (...deps)=>()=>options.useFactory(...deps),
|
|
79
|
-
inject: options.inject ?? []
|
|
80
|
-
},
|
|
81
|
-
BetterAuthService,
|
|
82
|
-
AuthGuard,
|
|
83
|
-
RolesGuard
|
|
84
|
-
];
|
|
85
|
-
if (isGlobal) {
|
|
86
|
-
providers.push({
|
|
87
|
-
provide: APP_GUARD,
|
|
88
|
-
useExisting: AuthGuard
|
|
89
|
-
});
|
|
90
|
-
}
|
|
110
|
+
const n = normalize(options);
|
|
111
|
+
const common = commonContributions(n);
|
|
91
112
|
return {
|
|
92
113
|
module: BetterAuthModule,
|
|
93
114
|
key: options.key ?? stableHash({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
mountHandler,
|
|
97
|
-
basePath,
|
|
98
|
-
defaultPolicy
|
|
115
|
+
...n,
|
|
116
|
+
inject: options.inject
|
|
99
117
|
}),
|
|
100
118
|
imports: options.imports ?? [],
|
|
101
|
-
providers
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
providers: [
|
|
120
|
+
{
|
|
121
|
+
provide: BETTER_AUTH_OPTIONS,
|
|
122
|
+
useValue: n
|
|
123
|
+
},
|
|
124
|
+
// The deferred auth builder: `lazyProvider` wraps the user factory in a
|
|
125
|
+
// memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.
|
|
126
|
+
lazyProvider({
|
|
127
|
+
provide: BETTER_AUTH_BUILDER,
|
|
128
|
+
inject: options.inject,
|
|
129
|
+
useFactory: options.useFactory
|
|
130
|
+
}),
|
|
131
|
+
...common.providers,
|
|
132
|
+
...n.isGlobal ? provideGlobal('guard', AuthGuard) : []
|
|
133
|
+
],
|
|
134
|
+
controllers: common.controllers,
|
|
135
|
+
exports: common.exports
|
|
111
136
|
};
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
|
-
function normalize(options) {
|
|
115
|
-
return {
|
|
116
|
-
auth: options.auth,
|
|
117
|
-
basePath: options.basePath ?? DEFAULT_BASE_PATH,
|
|
118
|
-
isGlobal: options.isGlobal ?? false,
|
|
119
|
-
defaultPolicy: options.defaultPolicy ?? 'deny',
|
|
120
|
-
mountHandler: options.mountHandler ?? true
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
function buildProviders(normalized, builder) {
|
|
124
|
-
const providers = [
|
|
125
|
-
{
|
|
126
|
-
provide: BETTER_AUTH_OPTIONS,
|
|
127
|
-
useValue: normalized
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
provide: BETTER_AUTH_BUILDER,
|
|
131
|
-
useValue: builder
|
|
132
|
-
},
|
|
133
|
-
BetterAuthService,
|
|
134
|
-
AuthGuard,
|
|
135
|
-
RolesGuard
|
|
136
|
-
];
|
|
137
|
-
if (normalized.isGlobal) {
|
|
138
|
-
providers.push({
|
|
139
|
-
provide: APP_GUARD,
|
|
140
|
-
useExisting: AuthGuard
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
return providers;
|
|
144
|
-
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.
|
|
3
|
+
* Declared structurally so `@velajs/better-auth/testing` carries NO runtime
|
|
4
|
+
* (or type) dependency on `@velajs/testing` — a real `TestingModule` satisfies
|
|
5
|
+
* it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.
|
|
6
|
+
*/
|
|
7
|
+
export interface TestModuleLike {
|
|
8
|
+
get<T>(token: unknown): T;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A test principal. Opaque `Record<string, unknown>` to stay compatible with
|
|
12
|
+
* `@velajs/testing`'s `TestPrincipal`. Recognized fields:
|
|
13
|
+
*
|
|
14
|
+
* - `id` — reuse the user with this id if it already exists.
|
|
15
|
+
* - `email` — reuse the user with this email, else create one.
|
|
16
|
+
* - `name` — display name for a created user (defaults to the email).
|
|
17
|
+
*
|
|
18
|
+
* Any other fields are forwarded to `internalAdapter.createUser` (e.g. `role`)
|
|
19
|
+
* when a new user is minted, so role-guarded routes can be exercised.
|
|
20
|
+
*/
|
|
21
|
+
export type ActingAsPrincipal = Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* actingAs — a `@velajs/testing` auth resolver for better-auth.
|
|
24
|
+
*
|
|
25
|
+
* Resolves {@link BetterAuthService} from the module, mints a REAL better-auth
|
|
26
|
+
* session for `principal` through `auth.$context.internalAdapter`, and returns
|
|
27
|
+
* a `Headers` carrying a properly signed session cookie. Guarded routes
|
|
28
|
+
* (`AuthGuard`) then accept requests carrying those headers because
|
|
29
|
+
* `auth.api.getSession` validates the cookie against the same session store.
|
|
30
|
+
*
|
|
31
|
+
* The signature `(module, principal) => Promise<Headers>` is exactly
|
|
32
|
+
* `@velajs/testing`'s `ActingAsResolver`, so it plugs straight in:
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { actingAs } from '@velajs/better-auth/testing';
|
|
37
|
+
*
|
|
38
|
+
* // As the default resolver for the module:
|
|
39
|
+
* module.setAuthResolver(actingAs);
|
|
40
|
+
* await module.http.get('/me').actingAs({ email: 'ada@example.com' }).send();
|
|
41
|
+
*
|
|
42
|
+
* // Or passed per-request:
|
|
43
|
+
* await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function actingAs(module: TestModuleLike, principal: ActingAsPrincipal): Promise<Headers>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Mechanics adapted from @stratal/testing (MIT, © Temitayo Fadojutimi):
|
|
2
|
+
// mint a real better-auth session via `$context.internalAdapter` and hand back
|
|
3
|
+
// a signed session-cookie header. Adapted to better-auth >=1.6: the session
|
|
4
|
+
// cookie is signed with the package's own `makeSignature` (better-auth/crypto)
|
|
5
|
+
// — the same primitive better-auth's built-in test cookie builder uses — so no
|
|
6
|
+
// endpoint-context shim or `setSessionCookie` mock is needed.
|
|
7
|
+
import { makeSignature } from "better-auth/crypto";
|
|
8
|
+
import { BetterAuthService } from "../better-auth.service.js";
|
|
9
|
+
/**
|
|
10
|
+
* actingAs — a `@velajs/testing` auth resolver for better-auth.
|
|
11
|
+
*
|
|
12
|
+
* Resolves {@link BetterAuthService} from the module, mints a REAL better-auth
|
|
13
|
+
* session for `principal` through `auth.$context.internalAdapter`, and returns
|
|
14
|
+
* a `Headers` carrying a properly signed session cookie. Guarded routes
|
|
15
|
+
* (`AuthGuard`) then accept requests carrying those headers because
|
|
16
|
+
* `auth.api.getSession` validates the cookie against the same session store.
|
|
17
|
+
*
|
|
18
|
+
* The signature `(module, principal) => Promise<Headers>` is exactly
|
|
19
|
+
* `@velajs/testing`'s `ActingAsResolver`, so it plugs straight in:
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { actingAs } from '@velajs/better-auth/testing';
|
|
24
|
+
*
|
|
25
|
+
* // As the default resolver for the module:
|
|
26
|
+
* module.setAuthResolver(actingAs);
|
|
27
|
+
* await module.http.get('/me').actingAs({ email: 'ada@example.com' }).send();
|
|
28
|
+
*
|
|
29
|
+
* // Or passed per-request:
|
|
30
|
+
* await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();
|
|
31
|
+
* ```
|
|
32
|
+
*/ export async function actingAs(module, principal) {
|
|
33
|
+
const auth = module.get(BetterAuthService).auth;
|
|
34
|
+
const ctx = await auth.$context;
|
|
35
|
+
const internalAdapter = ctx.internalAdapter;
|
|
36
|
+
const id = typeof principal.id === 'string' ? principal.id : undefined;
|
|
37
|
+
const email = typeof principal.email === 'string' ? principal.email : undefined;
|
|
38
|
+
const name = typeof principal.name === 'string' ? principal.name : undefined;
|
|
39
|
+
// `findUserById`/`createUser` yield a bare user; `findUserByEmail` nests it
|
|
40
|
+
// under `{ user, accounts }` — normalize to the id we need.
|
|
41
|
+
let user = null;
|
|
42
|
+
if (id) user = await internalAdapter.findUserById(id);
|
|
43
|
+
if (!user && email) {
|
|
44
|
+
const found = await internalAdapter.findUserByEmail(email);
|
|
45
|
+
user = found?.user ?? null;
|
|
46
|
+
}
|
|
47
|
+
if (!user) {
|
|
48
|
+
if (!email) {
|
|
49
|
+
throw new Error('actingAs: principal must carry an `email` (to create a user) or an ' + '`id` matching an existing user.');
|
|
50
|
+
}
|
|
51
|
+
const { id: _id, email: _email, name: _name, ...extra } = principal;
|
|
52
|
+
user = await internalAdapter.createUser({
|
|
53
|
+
...extra,
|
|
54
|
+
email,
|
|
55
|
+
name: name ?? email,
|
|
56
|
+
...id ? {
|
|
57
|
+
id
|
|
58
|
+
} : {}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const session = await internalAdapter.createSession(user.id, false, {
|
|
62
|
+
ipAddress: '127.0.0.1',
|
|
63
|
+
userAgent: 'vela-test'
|
|
64
|
+
});
|
|
65
|
+
const cookieName = ctx.authCookies.sessionToken.name;
|
|
66
|
+
const signedToken = `${session.token}.${await makeSignature(session.token, ctx.secret)}`;
|
|
67
|
+
const headers = new Headers();
|
|
68
|
+
headers.set('Cookie', `${cookieName}=${signedToken}`);
|
|
69
|
+
return headers;
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "better-auth integration for the Vela framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./dist/testing/index.d.ts",
|
|
15
|
+
"import": "./dist/testing/index.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
@@ -17,13 +21,6 @@
|
|
|
17
21
|
"LICENSE",
|
|
18
22
|
"CHANGELOG.md"
|
|
19
23
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
|
|
22
|
-
"test": "vitest run",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"prepare": "swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
|
|
25
|
-
"prepublishOnly": "pnpm run typecheck && pnpm test"
|
|
26
|
-
},
|
|
27
24
|
"sideEffects": false,
|
|
28
25
|
"keywords": [
|
|
29
26
|
"vela",
|
|
@@ -49,26 +46,24 @@
|
|
|
49
46
|
"node": ">=20"
|
|
50
47
|
},
|
|
51
48
|
"peerDependencies": {
|
|
52
|
-
"@velajs/vela": ">=1.
|
|
49
|
+
"@velajs/vela": ">=1.11.0",
|
|
53
50
|
"better-auth": ">=1.2.0",
|
|
54
51
|
"hono": ">=4"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
|
57
54
|
"@swc/cli": "^0.8.1",
|
|
58
55
|
"@swc/core": "^1.15.41",
|
|
59
|
-
"@velajs/testing": "^0.
|
|
60
|
-
"@velajs/vela": "^1.
|
|
56
|
+
"@velajs/testing": "^0.4.0",
|
|
57
|
+
"@velajs/vela": "^1.12.0",
|
|
61
58
|
"better-auth": "^1.6.20",
|
|
62
59
|
"hono": "^4.12.26",
|
|
63
60
|
"typescript": "^6.0.3",
|
|
64
61
|
"unplugin-swc": "^1.5.9",
|
|
65
62
|
"vitest": "^4.1.9"
|
|
66
63
|
},
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
"esbuild"
|
|
72
|
-
]
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"typecheck": "tsc --noEmit"
|
|
73
68
|
}
|
|
74
|
-
}
|
|
69
|
+
}
|