@nestjs-kit/better-auth 0.0.0-stage → 2.1.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/LICENSE +7 -0
- package/README.md +1078 -2
- package/dist/admin.cjs +88 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +14 -0
- package/dist/admin.d.cts.map +1 -0
- package/dist/admin.d.mts +14 -0
- package/dist/admin.d.mts.map +1 -0
- package/dist/admin.mjs +85 -0
- package/dist/admin.mjs.map +1 -0
- package/dist/api-key.cjs +164 -0
- package/dist/api-key.cjs.map +1 -0
- package/dist/api-key.d.cts +42 -0
- package/dist/api-key.d.cts.map +1 -0
- package/dist/api-key.d.mts +42 -0
- package/dist/api-key.d.mts.map +1 -0
- package/dist/api-key.mjs +160 -0
- package/dist/api-key.mjs.map +1 -0
- package/dist/auth-contracts-Brh_Qko9.d.cts +1654 -0
- package/dist/auth-contracts-Brh_Qko9.d.cts.map +1 -0
- package/dist/auth-contracts-Cz7C4_Ch.d.mts +1654 -0
- package/dist/auth-contracts-Cz7C4_Ch.d.mts.map +1 -0
- package/dist/auth-decorators-B1mFnGJ6.mjs +284 -0
- package/dist/auth-decorators-B1mFnGJ6.mjs.map +1 -0
- package/dist/auth-decorators-Cu_nxWnF.cjs +421 -0
- package/dist/auth-decorators-Cu_nxWnF.cjs.map +1 -0
- package/dist/auth-errors-C3rbTV9p.mjs +301 -0
- package/dist/auth-errors-C3rbTV9p.mjs.map +1 -0
- package/dist/auth-errors-DmiKl0L_.cjs +378 -0
- package/dist/auth-errors-DmiKl0L_.cjs.map +1 -0
- package/dist/auth-guard-CLyhAEEj.cjs +136 -0
- package/dist/auth-guard-CLyhAEEj.cjs.map +1 -0
- package/dist/auth-guard-h9QB_l11.mjs +125 -0
- package/dist/auth-guard-h9QB_l11.mjs.map +1 -0
- package/dist/auth-module-BgcY2HpA.cjs +2809 -0
- package/dist/auth-module-BgcY2HpA.cjs.map +1 -0
- package/dist/auth-module-CtHx3gRU.mjs +2708 -0
- package/dist/auth-module-CtHx3gRU.mjs.map +1 -0
- package/dist/auth-scope-interceptor-BjxAL1-S.cjs +290 -0
- package/dist/auth-scope-interceptor-BjxAL1-S.cjs.map +1 -0
- package/dist/auth-scope-interceptor-DYPn3b8M.mjs +231 -0
- package/dist/auth-scope-interceptor-DYPn3b8M.mjs.map +1 -0
- package/dist/bridge-protocol-BDw8opAG.cjs +25 -0
- package/dist/bridge-protocol-BDw8opAG.cjs.map +1 -0
- package/dist/bridge-protocol-c66aEkr5.mjs +8 -0
- package/dist/bridge-protocol-c66aEkr5.mjs.map +1 -0
- package/dist/decorate-Cr4Q7bNE.cjs +319 -0
- package/dist/decorate-Cr4Q7bNE.cjs.map +1 -0
- package/dist/decorate-Dt0e9K6R.mjs +80 -0
- package/dist/decorate-Dt0e9K6R.mjs.map +1 -0
- package/dist/express.cjs +256 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +37 -0
- package/dist/express.d.cts.map +1 -0
- package/dist/express.d.mts +37 -0
- package/dist/express.d.mts.map +1 -0
- package/dist/express.mjs +254 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.cjs +218 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +28 -0
- package/dist/fastify.d.cts.map +1 -0
- package/dist/fastify.d.mts +28 -0
- package/dist/fastify.d.mts.map +1 -0
- package/dist/fastify.mjs +216 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/graphql.cjs +509 -0
- package/dist/graphql.cjs.map +1 -0
- package/dist/graphql.d.cts +38 -0
- package/dist/graphql.d.cts.map +1 -0
- package/dist/graphql.d.mts +38 -0
- package/dist/graphql.d.mts.map +1 -0
- package/dist/graphql.mjs +503 -0
- package/dist/graphql.mjs.map +1 -0
- package/dist/index.cjs +86 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +8 -0
- package/dist/microservices.cjs +231 -0
- package/dist/microservices.cjs.map +1 -0
- package/dist/microservices.d.cts +39 -0
- package/dist/microservices.d.cts.map +1 -0
- package/dist/microservices.d.mts +39 -0
- package/dist/microservices.d.mts.map +1 -0
- package/dist/microservices.mjs +222 -0
- package/dist/microservices.mjs.map +1 -0
- package/dist/organization.cjs +191 -0
- package/dist/organization.cjs.map +1 -0
- package/dist/organization.d.cts +40 -0
- package/dist/organization.d.cts.map +1 -0
- package/dist/organization.d.mts +40 -0
- package/dist/organization.d.mts.map +1 -0
- package/dist/organization.mjs +177 -0
- package/dist/organization.mjs.map +1 -0
- package/dist/platform-CZl7TY8j.mjs +414 -0
- package/dist/platform-CZl7TY8j.mjs.map +1 -0
- package/dist/platform-YICqqYv6.cjs +473 -0
- package/dist/platform-YICqqYv6.cjs.map +1 -0
- package/dist/platform.cjs +10 -0
- package/dist/platform.d.cts +43 -0
- package/dist/platform.d.cts.map +1 -0
- package/dist/platform.d.mts +43 -0
- package/dist/platform.d.mts.map +1 -0
- package/dist/platform.mjs +1 -0
- package/dist/plugin.cjs +323 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +24 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.mts +24 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +321 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/principal-readings-Chmt6rQV.cjs +208 -0
- package/dist/principal-readings-Chmt6rQV.cjs.map +1 -0
- package/dist/principal-readings-Ied2staw.mjs +191 -0
- package/dist/principal-readings-Ied2staw.mjs.map +1 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/testing/conformance.cjs +6214 -0
- package/dist/testing/conformance.cjs.map +1 -0
- package/dist/testing/conformance.d.cts +528 -0
- package/dist/testing/conformance.d.cts.map +1 -0
- package/dist/testing/conformance.d.mts +528 -0
- package/dist/testing/conformance.d.mts.map +1 -0
- package/dist/testing/conformance.mjs +6204 -0
- package/dist/testing/conformance.mjs.map +1 -0
- package/dist/testing.cjs +221 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +64 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +64 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +214 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/websockets.cjs +396 -0
- package/dist/websockets.cjs.map +1 -0
- package/dist/websockets.d.cts +67 -0
- package/dist/websockets.d.cts.map +1 -0
- package/dist/websockets.d.mts +67 -0
- package/dist/websockets.d.mts.map +1 -0
- package/dist/websockets.mjs +383 -0
- package/dist/websockets.mjs.map +1 -0
- package/package.json +327 -3
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_auth_errors = require("./auth-errors-DmiKl0L_.cjs");
|
|
3
|
+
const require_decorate = require("./decorate-Cr4Q7bNE.cjs");
|
|
4
|
+
const require_auth_scope_interceptor = require("./auth-scope-interceptor-BjxAL1-S.cjs");
|
|
5
|
+
const require_bridge_protocol = require("./bridge-protocol-BDw8opAG.cjs");
|
|
6
|
+
const require_principal_readings = require("./principal-readings-Chmt6rQV.cjs");
|
|
7
|
+
let rxjs = require("rxjs");
|
|
8
|
+
//#region src/testing.ts
|
|
9
|
+
const TEST_APP = Symbol.for("@nestjs-kit/better-auth:test-app");
|
|
10
|
+
const TEST_APP_CLOSE = Symbol.for("@nestjs-kit/better-auth:test-app-close");
|
|
11
|
+
const TEST_OVERRIDES = Symbol.for("@nestjs-kit/better-auth:test-overrides");
|
|
12
|
+
const PLUGIN_ID = "@nestjs-kit/better-auth";
|
|
13
|
+
const TEST_PRINCIPAL_SOURCE = "@nestjs-kit/better-auth:test-principal";
|
|
14
|
+
/**
|
|
15
|
+
* TestingModuleBuilder keys overrides by token, so repeated helper calls share one provider and its configuration.
|
|
16
|
+
* The configuration lives on the builder under a global symbol, so the ESM and CommonJS copies of this entry compose.
|
|
17
|
+
*/
|
|
18
|
+
function overridesOf(builder) {
|
|
19
|
+
let overrides = Reflect.get(builder, TEST_OVERRIDES);
|
|
20
|
+
if (!overrides) {
|
|
21
|
+
overrides = {
|
|
22
|
+
principals: /* @__PURE__ */ new Map(),
|
|
23
|
+
deciders: []
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(builder, TEST_OVERRIDES, { value: overrides });
|
|
26
|
+
}
|
|
27
|
+
return overrides;
|
|
28
|
+
}
|
|
29
|
+
/** Make app.close() run Nest's shutdown once, whoever calls it: initTestApp, a registered hook or the test. */
|
|
30
|
+
function closeOnce(app) {
|
|
31
|
+
const existing = Reflect.get(app, TEST_APP_CLOSE);
|
|
32
|
+
if (existing) return existing;
|
|
33
|
+
const close = app.close.bind(app);
|
|
34
|
+
let pending;
|
|
35
|
+
const once = () => {
|
|
36
|
+
pending ??= close();
|
|
37
|
+
return pending;
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(app, TEST_APP_CLOSE, { value: once });
|
|
40
|
+
app.close = once;
|
|
41
|
+
return once;
|
|
42
|
+
}
|
|
43
|
+
async function bridgeOf(auth) {
|
|
44
|
+
const plugin = (await auth.$context).getPlugin?.(PLUGIN_ID);
|
|
45
|
+
return plugin && typeof plugin === "object" ? Reflect.get(plugin, require_bridge_protocol.BRIDGE_HANDLE) : void 0;
|
|
46
|
+
}
|
|
47
|
+
function registryOf(app) {
|
|
48
|
+
try {
|
|
49
|
+
return app.get(require_decorate.INSTANCE_REGISTRY, { strict: false });
|
|
50
|
+
} catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* init() plus Fastify ready() when the adapter exposes it. Before init, closes the previous application that
|
|
56
|
+
* initTestApp initialized for the same Better Auth instance and that is still bound to it: an instance binds
|
|
57
|
+
* exclusively to one application (B06). A failed init() leaves its binding for the next application, which takes it
|
|
58
|
+
* over with W_INSTANCE_TAKEN_OVER. app.close() becomes idempotent, and `closeWith` receives it: a per-test cleanup such as
|
|
59
|
+
* Vitest's onTestFinished or node:test's t.after, or a list that a suite-level afterEach drains (Jest registers no hooks
|
|
60
|
+
* while a test runs). Without `closeWith`, close the last application yourself. On Express, compile one TestingModule per application: a
|
|
61
|
+
* second application of one container fails init() with APP_ADAPTER_CHANGED.
|
|
62
|
+
*/
|
|
63
|
+
async function initTestApp(app, options = {}) {
|
|
64
|
+
const close = closeOnce(app);
|
|
65
|
+
const bridges = [];
|
|
66
|
+
for (const registration of registryOf(app)?.registrations() ?? []) {
|
|
67
|
+
const bridge = registration.instance ? await bridgeOf(registration.instance) : void 0;
|
|
68
|
+
if (!bridge) continue;
|
|
69
|
+
const previous = Reflect.get(bridge, TEST_APP);
|
|
70
|
+
if (previous && previous !== app && bridge.state === "bound") await previous.close();
|
|
71
|
+
Object.defineProperty(bridge, TEST_APP, {
|
|
72
|
+
value: void 0,
|
|
73
|
+
configurable: true,
|
|
74
|
+
writable: true
|
|
75
|
+
});
|
|
76
|
+
bridges.push(bridge);
|
|
77
|
+
}
|
|
78
|
+
await app.init();
|
|
79
|
+
const server = (app.getHttpAdapter?.())?.getInstance?.();
|
|
80
|
+
if (typeof server?.ready === "function") await server.ready();
|
|
81
|
+
for (const bridge of bridges) Object.defineProperty(bridge, TEST_APP, {
|
|
82
|
+
value: app,
|
|
83
|
+
configurable: true,
|
|
84
|
+
writable: true
|
|
85
|
+
});
|
|
86
|
+
options.closeWith?.(close);
|
|
87
|
+
return app;
|
|
88
|
+
}
|
|
89
|
+
/** Real session headers for `userId` through Better Auth's testUtils() plugin, which the instance must include. */
|
|
90
|
+
async function authHeadersFor(auth, userId) {
|
|
91
|
+
const context = await auth.$context;
|
|
92
|
+
if (typeof context.test?.getAuthHeaders !== "function") throw new require_auth_errors.BetterAuthConfigurationError("TEST_UTILS_MISSING", "authHeadersFor requires Better Auth's testUtils() plugin on the instance.", "Add testUtils() from better-auth/plugins to the test instance.");
|
|
93
|
+
return context.test.getAuthHeaders({ userId });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Replace principal resolution for one instance (default: 'default') without Better Auth I/O. Acceptance, the
|
|
97
|
+
* origin check and policies still run. A policy's re-classification read of a generic 401 answers absent,
|
|
98
|
+
* because the request carries no session; other instances keep the real resolver.
|
|
99
|
+
*/
|
|
100
|
+
function overridePrincipal(builder, principal, options = {}) {
|
|
101
|
+
const configured = overridesOf(builder).principals;
|
|
102
|
+
configured.set(options.instance || "default", principal);
|
|
103
|
+
builder.overrideProvider(require_decorate.PRINCIPAL_RESOLVER).useFactory({
|
|
104
|
+
inject: [
|
|
105
|
+
require_decorate.INSTANCE_REGISTRY,
|
|
106
|
+
require_decorate.REQUEST_SCOPE,
|
|
107
|
+
require_decorate.TRANSPORT_REGISTRY
|
|
108
|
+
],
|
|
109
|
+
factory: (instances, scope, transports) => {
|
|
110
|
+
const real = new require_auth_scope_interceptor.ChainPrincipalResolver(instances, scope, transports);
|
|
111
|
+
return { resolve(call, request) {
|
|
112
|
+
if (!configured.has(request.auth.name)) return real.resolve(call, request);
|
|
113
|
+
if (request.reclassify) return Promise.resolve(require_auth_scope_interceptor.absent());
|
|
114
|
+
const fixed = configured.get(request.auth.name);
|
|
115
|
+
const value = typeof fixed === "function" ? fixed(call) : fixed;
|
|
116
|
+
return Promise.resolve(value ? require_auth_scope_interceptor.authenticated(value) : require_auth_scope_interceptor.absent());
|
|
117
|
+
} };
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return builder;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Replace the policy invoker: `decide` answers per requirement, and undefined runs the real policy. Acceptance,
|
|
124
|
+
* the delegation gate, the origin check and the decision memo still run. Later calls are consulted after earlier ones.
|
|
125
|
+
*/
|
|
126
|
+
function overrideDecisions(builder, decide) {
|
|
127
|
+
const configured = overridesOf(builder).deciders;
|
|
128
|
+
configured.push(decide);
|
|
129
|
+
builder.overrideProvider(require_decorate.POLICY_INVOKER).useValue({ async invoke(policy, params, context) {
|
|
130
|
+
for (const decider of configured) {
|
|
131
|
+
const decision = await decider(policy.id, params, context.principal);
|
|
132
|
+
if (decision !== void 0) return decision;
|
|
133
|
+
}
|
|
134
|
+
return policy.evaluate(params, context);
|
|
135
|
+
} });
|
|
136
|
+
return builder;
|
|
137
|
+
}
|
|
138
|
+
/** A principal source that answers a fixed principal (or null for absent), for test module composition. */
|
|
139
|
+
function testPrincipal(principal, options = {}) {
|
|
140
|
+
const fixed = typeof principal === "function" ? void 0 : principal;
|
|
141
|
+
const id = fixed?.source ?? TEST_PRINCIPAL_SOURCE;
|
|
142
|
+
return {
|
|
143
|
+
id,
|
|
144
|
+
kinds: options.kinds ?? (fixed ? [fixed.kind] : ["session"]),
|
|
145
|
+
acceptance: options.acceptance ?? "default",
|
|
146
|
+
delegates: options.delegates ?? Boolean(fixed?.delegation),
|
|
147
|
+
async resolve(request) {
|
|
148
|
+
const value = typeof principal === "function" ? principal(request) : principal;
|
|
149
|
+
return value ? require_auth_scope_interceptor.authenticated({
|
|
150
|
+
...value,
|
|
151
|
+
source: id
|
|
152
|
+
}) : require_auth_scope_interceptor.absent();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Publish a principal for this invocation from a hand-written guard. The stamp lives on ctx.getArgs() and its
|
|
158
|
+
* object elements. Readers accept it from the same args array (WS, RPC and GraphQL share it between guards and
|
|
159
|
+
* interceptors) or from another array with the same elements when at least two elements are objects (an HTTP request
|
|
160
|
+
* and its response); a lone shared element such as a socket never identifies an invocation. Pass `instance` for
|
|
161
|
+
* handlers of a named instance.
|
|
162
|
+
*/
|
|
163
|
+
function stampPrincipal(context, principal, options = {}) {
|
|
164
|
+
require_principal_readings.stampResult(context.getArgs(), options.instance || "default", principal ? require_auth_scope_interceptor.authenticated(principal) : require_auth_scope_interceptor.absent());
|
|
165
|
+
}
|
|
166
|
+
var StampFirstReadings = class extends require_principal_readings.PrincipalReadings {
|
|
167
|
+
read(input) {
|
|
168
|
+
return (input.plan && input.plan.access !== "public" ? require_principal_readings.stampedReading(input.args, input.plan.instance) : void 0) ?? super.read(input);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function planInstance(planner, context) {
|
|
172
|
+
try {
|
|
173
|
+
return planner.forContext(context).instance;
|
|
174
|
+
} catch {
|
|
175
|
+
return "default";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Replace the guard and scope bodies everywhere with one override each of GUARD_CORE and SCOPE_CORE: the APP_GUARD
|
|
180
|
+
* alias and every @UseBetterAuth()/@UseGuards(BetterAuthGuard) site delegate to the same collaborator, which
|
|
181
|
+
* overrideProvider(BetterAuthGuard) plus overrideGuard(BetterAuthGuard) cannot achieve. With `{ principal }` the
|
|
182
|
+
* stand-in stamps the principal (for the handler's instance) and allows; with a CanActivate, that guard runs as the
|
|
183
|
+
* whole guard body and may call stampPrincipal(); an object with a canActivate method is always treated as a guard,
|
|
184
|
+
* even when it also has a `principal` property. The stand-in interceptor opens the same transport-described scope
|
|
185
|
+
* as the real one, reading the test stamp first, so readers and BetterAuthService perform no Better Auth I/O.
|
|
186
|
+
*/
|
|
187
|
+
function overrideAuthGuard(builder, impl) {
|
|
188
|
+
builder.overrideProvider(require_decorate.GUARD_CORE).useFactory({
|
|
189
|
+
inject: [require_decorate.ROUTE_PLANNER],
|
|
190
|
+
factory: (planner) => ({ async canActivate(context) {
|
|
191
|
+
if (typeof impl.canActivate === "function") {
|
|
192
|
+
const result = impl.canActivate(context);
|
|
193
|
+
return (0, rxjs.isObservable)(result) ? (0, rxjs.lastValueFrom)(result) : result;
|
|
194
|
+
}
|
|
195
|
+
const { principal } = impl;
|
|
196
|
+
const value = typeof principal === "function" ? principal(context) : principal;
|
|
197
|
+
require_principal_readings.stampResult(context.getArgs(), planInstance(planner, context), value ? require_auth_scope_interceptor.authenticated(value) : require_auth_scope_interceptor.absent());
|
|
198
|
+
return true;
|
|
199
|
+
} })
|
|
200
|
+
});
|
|
201
|
+
builder.overrideProvider(require_decorate.SCOPE_CORE).useFactory({
|
|
202
|
+
inject: [
|
|
203
|
+
require_decorate.INSTANCE_REGISTRY,
|
|
204
|
+
require_decorate.ROUTE_PLANNER,
|
|
205
|
+
require_decorate.TRANSPORT_REGISTRY,
|
|
206
|
+
require_decorate.REQUEST_SCOPE
|
|
207
|
+
],
|
|
208
|
+
factory: (instances, planner, transports, scope) => new require_auth_scope_interceptor.ScopeCore(instances, planner, transports, scope, new StampFirstReadings(scope))
|
|
209
|
+
});
|
|
210
|
+
return builder;
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
exports.authHeadersFor = authHeadersFor;
|
|
214
|
+
exports.initTestApp = initTestApp;
|
|
215
|
+
exports.overrideAuthGuard = overrideAuthGuard;
|
|
216
|
+
exports.overrideDecisions = overrideDecisions;
|
|
217
|
+
exports.overridePrincipal = overridePrincipal;
|
|
218
|
+
exports.stampPrincipal = stampPrincipal;
|
|
219
|
+
exports.testPrincipal = testPrincipal;
|
|
220
|
+
|
|
221
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":["BRIDGE_HANDLE","INSTANCE_REGISTRY","BetterAuthConfigurationError","PRINCIPAL_RESOLVER","REQUEST_SCOPE","TRANSPORT_REGISTRY","ChainPrincipalResolver","absent","authenticated","POLICY_INVOKER","PrincipalReadings","stampedReading","GUARD_CORE","ROUTE_PLANNER","isObservable","lastValueFrom","SCOPE_CORE","ScopeCore"],"sources":["../src/testing.ts"],"sourcesContent":["import type {\n CanActivate,\n ExecutionContext,\n INestApplication,\n} from \"@nestjs/common\";\nimport type { TestingModuleBuilder } from \"@nestjs/testing\";\nimport { isObservable, lastValueFrom } from \"rxjs\";\nimport type {\n AuthorizationDecision,\n PolicyInvoker,\n PrincipalRequest,\n PrincipalResolver,\n PrincipalResult,\n PrincipalSource,\n TransportCall,\n} from \"./auth-contracts.js\";\nimport { BetterAuthConfigurationError } from \"./auth-errors.js\";\nimport { ScopeCore } from \"./auth-scope-interceptor.js\";\nimport {\n GUARD_CORE,\n INSTANCE_REGISTRY,\n POLICY_INVOKER,\n PRINCIPAL_RESOLVER,\n REQUEST_SCOPE,\n ROUTE_PLANNER,\n SCOPE_CORE,\n TRANSPORT_REGISTRY,\n} from \"./auth-tokens.js\";\nimport type { AuthLike, AuthPrincipal } from \"./auth-types.js\";\nimport { BRIDGE_HANDLE, type BridgeHandle } from \"./bridge-protocol.js\";\nimport type { InstanceRegistry } from \"./instance-registry.js\";\nimport {\n PrincipalReadings,\n stampedReading,\n stampResult,\n type ReadingInput,\n} from \"./principal-readings.js\";\nimport {\n absent,\n authenticated,\n ChainPrincipalResolver,\n} from \"./principal-resolver.js\";\nimport type { RequestScope } from \"./request-scope.js\";\nimport type { RoutePlanner } from \"./route-planner.js\";\nimport type { TransportRegistry } from \"./transport-registry.js\";\n\nconst TEST_APP = Symbol.for(\"@nestjs-kit/better-auth:test-app\");\nconst TEST_APP_CLOSE = Symbol.for(\"@nestjs-kit/better-auth:test-app-close\");\nconst TEST_OVERRIDES = Symbol.for(\"@nestjs-kit/better-auth:test-overrides\");\nconst PLUGIN_ID = \"@nestjs-kit/better-auth\";\nconst TEST_PRINCIPAL_SOURCE = \"@nestjs-kit/better-auth:test-principal\";\n\ntype FixedPrincipal =\n | AuthPrincipal\n | null\n | ((call: TransportCall) => AuthPrincipal | null);\ntype Decide = (\n policyId: string,\n params: unknown,\n principal: AuthPrincipal,\n) =>\n | AuthorizationDecision\n | undefined\n | Promise<AuthorizationDecision | undefined>;\n\ninterface TestOverrides {\n readonly principals: Map<string, FixedPrincipal>;\n readonly deciders: Decide[];\n}\n\n/**\n * TestingModuleBuilder keys overrides by token, so repeated helper calls share one provider and its configuration.\n * The configuration lives on the builder under a global symbol, so the ESM and CommonJS copies of this entry compose.\n */\nfunction overridesOf(builder: TestingModuleBuilder): TestOverrides {\n let overrides = Reflect.get(builder, TEST_OVERRIDES) as\n | TestOverrides\n | undefined;\n if (!overrides) {\n overrides = { principals: new Map(), deciders: [] };\n Object.defineProperty(builder, TEST_OVERRIDES, { value: overrides });\n }\n return overrides;\n}\n\n/** Make app.close() run Nest's shutdown once, whoever calls it: initTestApp, a registered hook or the test. */\nfunction closeOnce(app: INestApplication): () => Promise<void> {\n const existing = Reflect.get(app, TEST_APP_CLOSE) as\n | (() => Promise<void>)\n | undefined;\n if (existing) {\n return existing;\n }\n const close = app.close.bind(app);\n let pending: Promise<void> | undefined;\n const once = () => {\n pending ??= close();\n return pending;\n };\n Object.defineProperty(app, TEST_APP_CLOSE, { value: once });\n app.close = once;\n return once;\n}\n\nasync function bridgeOf(auth: AuthLike): Promise<BridgeHandle | undefined> {\n const context = (await auth.$context) as {\n getPlugin?(id: string): unknown;\n };\n const plugin = context.getPlugin?.(PLUGIN_ID);\n return plugin && typeof plugin === \"object\"\n ? (Reflect.get(plugin, BRIDGE_HANDLE) as BridgeHandle | undefined)\n : undefined;\n}\n\nfunction registryOf(app: INestApplication): InstanceRegistry | undefined {\n try {\n return app.get<InstanceRegistry>(INSTANCE_REGISTRY, { strict: false });\n } catch {\n return undefined;\n }\n}\n\n/**\n * init() plus Fastify ready() when the adapter exposes it. Before init, closes the previous application that\n * initTestApp initialized for the same Better Auth instance and that is still bound to it: an instance binds\n * exclusively to one application (B06). A failed init() leaves its binding for the next application, which takes it\n * over with W_INSTANCE_TAKEN_OVER. app.close() becomes idempotent, and `closeWith` receives it: a per-test cleanup such as\n * Vitest's onTestFinished or node:test's t.after, or a list that a suite-level afterEach drains (Jest registers no hooks\n * while a test runs). Without `closeWith`, close the last application yourself. On Express, compile one TestingModule per application: a\n * second application of one container fails init() with APP_ADAPTER_CHANGED.\n */\nexport async function initTestApp<T extends INestApplication>(\n app: T,\n options: { closeWith?: (close: () => Promise<void>) => void } = {},\n): Promise<T> {\n const close = closeOnce(app);\n const bridges: BridgeHandle[] = [];\n for (const registration of registryOf(app)?.registrations() ?? []) {\n const bridge = registration.instance\n ? await bridgeOf(registration.instance)\n : undefined;\n if (!bridge) {\n continue;\n }\n const previous = Reflect.get(bridge, TEST_APP) as\n | INestApplication\n | undefined;\n if (previous && previous !== app && bridge.state === \"bound\") {\n // initTestApp made previous.close() idempotent, so a registered close of the same app does nothing later.\n await previous.close();\n }\n Object.defineProperty(bridge, TEST_APP, {\n value: undefined,\n configurable: true,\n writable: true,\n });\n bridges.push(bridge);\n }\n await app.init();\n const server = (\n app.getHttpAdapter?.() as { getInstance?(): unknown } | undefined\n )?.getInstance?.() as { ready?: () => unknown } | undefined;\n if (typeof server?.ready === \"function\") {\n await server.ready();\n }\n for (const bridge of bridges) {\n Object.defineProperty(bridge, TEST_APP, {\n value: app,\n configurable: true,\n writable: true,\n });\n }\n options.closeWith?.(close);\n return app;\n}\n\n/** Real session headers for `userId` through Better Auth's testUtils() plugin, which the instance must include. */\nexport async function authHeadersFor(\n auth: AuthLike,\n userId: string,\n): Promise<Headers> {\n const context = (await auth.$context) as {\n test?: { getAuthHeaders?(options: { userId: string }): Promise<Headers> };\n };\n if (typeof context.test?.getAuthHeaders !== \"function\") {\n throw new BetterAuthConfigurationError(\n \"TEST_UTILS_MISSING\",\n \"authHeadersFor requires Better Auth's testUtils() plugin on the instance.\",\n \"Add testUtils() from better-auth/plugins to the test instance.\",\n );\n }\n return context.test.getAuthHeaders({ userId });\n}\n\n/**\n * Replace principal resolution for one instance (default: 'default') without Better Auth I/O. Acceptance, the\n * origin check and policies still run. A policy's re-classification read of a generic 401 answers absent,\n * because the request carries no session; other instances keep the real resolver.\n */\nexport function overridePrincipal(\n builder: TestingModuleBuilder,\n principal: FixedPrincipal,\n options: { instance?: string } = {},\n): TestingModuleBuilder {\n const configured = overridesOf(builder).principals;\n configured.set(options.instance || \"default\", principal);\n builder.overrideProvider(PRINCIPAL_RESOLVER).useFactory({\n inject: [INSTANCE_REGISTRY, REQUEST_SCOPE, TRANSPORT_REGISTRY],\n factory: (\n instances: InstanceRegistry,\n scope: RequestScope,\n transports: TransportRegistry,\n ): PrincipalResolver => {\n const real = new ChainPrincipalResolver(instances, scope, transports);\n return {\n resolve(call, request): Promise<PrincipalResult> {\n if (!configured.has(request.auth.name)) {\n return real.resolve(call, request);\n }\n if (request.reclassify) {\n return Promise.resolve(absent());\n }\n const fixed = configured.get(request.auth.name);\n const value = typeof fixed === \"function\" ? fixed(call) : fixed;\n return Promise.resolve(value ? authenticated(value) : absent());\n },\n };\n },\n });\n return builder;\n}\n\n/**\n * Replace the policy invoker: `decide` answers per requirement, and undefined runs the real policy. Acceptance,\n * the delegation gate, the origin check and the decision memo still run. Later calls are consulted after earlier ones.\n */\nexport function overrideDecisions(\n builder: TestingModuleBuilder,\n decide: Decide,\n): TestingModuleBuilder {\n const configured = overridesOf(builder).deciders;\n configured.push(decide);\n const invoker: PolicyInvoker = {\n async invoke(policy, params, context) {\n for (const decider of configured) {\n const decision = await decider(policy.id, params, context.principal);\n if (decision !== undefined) {\n return decision;\n }\n }\n return policy.evaluate(params, context);\n },\n };\n builder.overrideProvider(POLICY_INVOKER).useValue(invoker);\n return builder;\n}\n\n/** A principal source that answers a fixed principal (or null for absent), for test module composition. */\nexport function testPrincipal(\n principal: AuthPrincipal | ((req: PrincipalRequest) => AuthPrincipal | null),\n options: {\n kinds?: readonly string[];\n acceptance?: \"default\" | \"explicit\";\n delegates?: boolean;\n } = {},\n): PrincipalSource {\n const fixed = typeof principal === \"function\" ? undefined : principal;\n const id = fixed?.source ?? TEST_PRINCIPAL_SOURCE;\n const kinds = options.kinds ?? (fixed ? [fixed.kind] : [\"session\"]);\n return {\n id,\n kinds: kinds as PrincipalSource[\"kinds\"],\n acceptance: options.acceptance ?? \"default\",\n delegates: options.delegates ?? Boolean(fixed?.delegation),\n async resolve(request) {\n const value =\n typeof principal === \"function\" ? principal(request) : principal;\n return value\n ? authenticated({ ...value, source: id } as AuthPrincipal)\n : absent();\n },\n };\n}\n\n/**\n * Publish a principal for this invocation from a hand-written guard. The stamp lives on ctx.getArgs() and its\n * object elements. Readers accept it from the same args array (WS, RPC and GraphQL share it between guards and\n * interceptors) or from another array with the same elements when at least two elements are objects (an HTTP request\n * and its response); a lone shared element such as a socket never identifies an invocation. Pass `instance` for\n * handlers of a named instance.\n */\nexport function stampPrincipal(\n context: ExecutionContext,\n principal: AuthPrincipal | null,\n options: { instance?: string } = {},\n): void {\n stampResult(\n context.getArgs(),\n options.instance || \"default\",\n principal ? authenticated(principal) : absent(),\n );\n}\n\nclass StampFirstReadings extends PrincipalReadings {\n override read(input: ReadingInput) {\n const stamped =\n input.plan && input.plan.access !== \"public\"\n ? stampedReading(input.args, input.plan.instance)\n : undefined;\n return stamped ?? super.read(input);\n }\n}\n\nfunction planInstance(planner: RoutePlanner, context: ExecutionContext) {\n try {\n return planner.forContext(context).instance;\n } catch {\n return \"default\";\n }\n}\n\n/**\n * Replace the guard and scope bodies everywhere with one override each of GUARD_CORE and SCOPE_CORE: the APP_GUARD\n * alias and every @UseBetterAuth()/@UseGuards(BetterAuthGuard) site delegate to the same collaborator, which\n * overrideProvider(BetterAuthGuard) plus overrideGuard(BetterAuthGuard) cannot achieve. With `{ principal }` the\n * stand-in stamps the principal (for the handler's instance) and allows; with a CanActivate, that guard runs as the\n * whole guard body and may call stampPrincipal(); an object with a canActivate method is always treated as a guard,\n * even when it also has a `principal` property. The stand-in interceptor opens the same transport-described scope\n * as the real one, reading the test stamp first, so readers and BetterAuthService perform no Better Auth I/O.\n */\nexport function overrideAuthGuard(\n builder: TestingModuleBuilder,\n impl:\n | CanActivate\n | {\n principal:\n | AuthPrincipal\n | null\n | ((ctx: ExecutionContext) => AuthPrincipal | null);\n },\n): TestingModuleBuilder {\n builder.overrideProvider(GUARD_CORE).useFactory({\n inject: [ROUTE_PLANNER],\n factory: (planner: RoutePlanner) => ({\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // A CanActivate may hold its own `principal` field; its canActivate always runs.\n if (typeof (impl as Partial<CanActivate>).canActivate === \"function\") {\n const result = (impl as CanActivate).canActivate(context);\n return isObservable(result) ? lastValueFrom(result) : result;\n }\n const { principal } = impl as {\n principal:\n | AuthPrincipal\n | null\n | ((ctx: ExecutionContext) => AuthPrincipal | null);\n };\n const value =\n typeof principal === \"function\" ? principal(context) : principal;\n stampResult(\n context.getArgs(),\n planInstance(planner, context),\n value ? authenticated(value) : absent(),\n );\n return true;\n },\n }),\n });\n builder.overrideProvider(SCOPE_CORE).useFactory({\n inject: [\n INSTANCE_REGISTRY,\n ROUTE_PLANNER,\n TRANSPORT_REGISTRY,\n REQUEST_SCOPE,\n ],\n factory: (\n instances: InstanceRegistry,\n planner: RoutePlanner,\n transports: TransportRegistry,\n scope: RequestScope,\n ) =>\n new ScopeCore(\n instances,\n planner,\n transports,\n scope,\n new StampFirstReadings(scope),\n ),\n });\n return builder;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Ct as AuthPrincipal, ht as TransportCall, it as PrincipalSource, tt as PrincipalRequest, u as AuthorizationDecision, xt as AuthLike } from "./auth-contracts-Brh_Qko9.cjs";
|
|
2
|
+
import { CanActivate, ExecutionContext, INestApplication } from "@nestjs/common";
|
|
3
|
+
import { TestingModuleBuilder } from "@nestjs/testing";
|
|
4
|
+
//#region src/testing.d.ts
|
|
5
|
+
type FixedPrincipal = AuthPrincipal | null | ((call: TransportCall) => AuthPrincipal | null);
|
|
6
|
+
type Decide = (policyId: string, params: unknown, principal: AuthPrincipal) => AuthorizationDecision | undefined | Promise<AuthorizationDecision | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* init() plus Fastify ready() when the adapter exposes it. Before init, closes the previous application that
|
|
9
|
+
* initTestApp initialized for the same Better Auth instance and that is still bound to it: an instance binds
|
|
10
|
+
* exclusively to one application (B06). A failed init() leaves its binding for the next application, which takes it
|
|
11
|
+
* over with W_INSTANCE_TAKEN_OVER. app.close() becomes idempotent, and `closeWith` receives it: a per-test cleanup such as
|
|
12
|
+
* Vitest's onTestFinished or node:test's t.after, or a list that a suite-level afterEach drains (Jest registers no hooks
|
|
13
|
+
* while a test runs). Without `closeWith`, close the last application yourself. On Express, compile one TestingModule per application: a
|
|
14
|
+
* second application of one container fails init() with APP_ADAPTER_CHANGED.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initTestApp<T extends INestApplication>(app: T, options?: {
|
|
17
|
+
closeWith?: (close: () => Promise<void>) => void;
|
|
18
|
+
}): Promise<T>;
|
|
19
|
+
/** Real session headers for `userId` through Better Auth's testUtils() plugin, which the instance must include. */
|
|
20
|
+
export declare function authHeadersFor(auth: AuthLike, userId: string): Promise<Headers>;
|
|
21
|
+
/**
|
|
22
|
+
* Replace principal resolution for one instance (default: 'default') without Better Auth I/O. Acceptance, the
|
|
23
|
+
* origin check and policies still run. A policy's re-classification read of a generic 401 answers absent,
|
|
24
|
+
* because the request carries no session; other instances keep the real resolver.
|
|
25
|
+
*/
|
|
26
|
+
export declare function overridePrincipal(builder: TestingModuleBuilder, principal: FixedPrincipal, options?: {
|
|
27
|
+
instance?: string;
|
|
28
|
+
}): TestingModuleBuilder;
|
|
29
|
+
/**
|
|
30
|
+
* Replace the policy invoker: `decide` answers per requirement, and undefined runs the real policy. Acceptance,
|
|
31
|
+
* the delegation gate, the origin check and the decision memo still run. Later calls are consulted after earlier ones.
|
|
32
|
+
*/
|
|
33
|
+
export declare function overrideDecisions(builder: TestingModuleBuilder, decide: Decide): TestingModuleBuilder;
|
|
34
|
+
/** A principal source that answers a fixed principal (or null for absent), for test module composition. */
|
|
35
|
+
export declare function testPrincipal(principal: AuthPrincipal | ((req: PrincipalRequest) => AuthPrincipal | null), options?: {
|
|
36
|
+
kinds?: readonly string[];
|
|
37
|
+
acceptance?: "default" | "explicit";
|
|
38
|
+
delegates?: boolean;
|
|
39
|
+
}): PrincipalSource;
|
|
40
|
+
/**
|
|
41
|
+
* Publish a principal for this invocation from a hand-written guard. The stamp lives on ctx.getArgs() and its
|
|
42
|
+
* object elements. Readers accept it from the same args array (WS, RPC and GraphQL share it between guards and
|
|
43
|
+
* interceptors) or from another array with the same elements when at least two elements are objects (an HTTP request
|
|
44
|
+
* and its response); a lone shared element such as a socket never identifies an invocation. Pass `instance` for
|
|
45
|
+
* handlers of a named instance.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stampPrincipal(context: ExecutionContext, principal: AuthPrincipal | null, options?: {
|
|
48
|
+
instance?: string;
|
|
49
|
+
}): void;
|
|
50
|
+
/**
|
|
51
|
+
* Replace the guard and scope bodies everywhere with one override each of GUARD_CORE and SCOPE_CORE: the APP_GUARD
|
|
52
|
+
* alias and every @UseBetterAuth()/@UseGuards(BetterAuthGuard) site delegate to the same collaborator, which
|
|
53
|
+
* overrideProvider(BetterAuthGuard) plus overrideGuard(BetterAuthGuard) cannot achieve. With `{ principal }` the
|
|
54
|
+
* stand-in stamps the principal (for the handler's instance) and allows; with a CanActivate, that guard runs as the
|
|
55
|
+
* whole guard body and may call stampPrincipal(); an object with a canActivate method is always treated as a guard,
|
|
56
|
+
* even when it also has a `principal` property. The stand-in interceptor opens the same transport-described scope
|
|
57
|
+
* as the real one, reading the test stamp first, so readers and BetterAuthService perform no Better Auth I/O.
|
|
58
|
+
*/
|
|
59
|
+
export declare function overrideAuthGuard(builder: TestingModuleBuilder, impl: CanActivate | {
|
|
60
|
+
principal: AuthPrincipal | null | ((ctx: ExecutionContext) => AuthPrincipal | null);
|
|
61
|
+
}): TestingModuleBuilder;
|
|
62
|
+
//#endregion
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=testing.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.cts","names":[],"sources":["../src/testing.ts"],"mappings":";;;;KAoDK,iBACD,yBAEE,MAAM,kBAAkB;KACzB,UACH,kBACA,iBACA,WAAW,kBAET,oCAEA,QAAQ;;;;;;;;;;wBAoEU,YAAY,UAAU,kBAC1C,KAAK,GACL;EAAW,aAAa,aAAa;IACpC,QAAQ;;wBA2CW,eACpB,MAAM,UACN,iBACC,QAAQ;;;;;;wBAmBK,kBACd,SAAS,sBACT,WAAW,gBACX;EAAW;IACV;;;;;wBAiCa,kBACd,SAAS,sBACT,QAAQ,SACP;;wBAmBa,cACd,WAAW,kBAAkB,KAAK,qBAAqB,uBACvD;EACE;EACA;EACA;IAED;;;;;;;;wBA0Ba,eACd,SAAS,kBACT,WAAW,sBACX;EAAW;;;;;;;;;;;wBAoCG,kBACd,SAAS,sBACT,MACI;EAEE,WACI,yBAEE,KAAK,qBAAqB;IAErC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Ct as AuthPrincipal, ht as TransportCall, it as PrincipalSource, tt as PrincipalRequest, u as AuthorizationDecision, xt as AuthLike } from "./auth-contracts-Cz7C4_Ch.mjs";
|
|
2
|
+
import { CanActivate, ExecutionContext, INestApplication } from "@nestjs/common";
|
|
3
|
+
import { TestingModuleBuilder } from "@nestjs/testing";
|
|
4
|
+
//#region src/testing.d.ts
|
|
5
|
+
type FixedPrincipal = AuthPrincipal | null | ((call: TransportCall) => AuthPrincipal | null);
|
|
6
|
+
type Decide = (policyId: string, params: unknown, principal: AuthPrincipal) => AuthorizationDecision | undefined | Promise<AuthorizationDecision | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* init() plus Fastify ready() when the adapter exposes it. Before init, closes the previous application that
|
|
9
|
+
* initTestApp initialized for the same Better Auth instance and that is still bound to it: an instance binds
|
|
10
|
+
* exclusively to one application (B06). A failed init() leaves its binding for the next application, which takes it
|
|
11
|
+
* over with W_INSTANCE_TAKEN_OVER. app.close() becomes idempotent, and `closeWith` receives it: a per-test cleanup such as
|
|
12
|
+
* Vitest's onTestFinished or node:test's t.after, or a list that a suite-level afterEach drains (Jest registers no hooks
|
|
13
|
+
* while a test runs). Without `closeWith`, close the last application yourself. On Express, compile one TestingModule per application: a
|
|
14
|
+
* second application of one container fails init() with APP_ADAPTER_CHANGED.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initTestApp<T extends INestApplication>(app: T, options?: {
|
|
17
|
+
closeWith?: (close: () => Promise<void>) => void;
|
|
18
|
+
}): Promise<T>;
|
|
19
|
+
/** Real session headers for `userId` through Better Auth's testUtils() plugin, which the instance must include. */
|
|
20
|
+
export declare function authHeadersFor(auth: AuthLike, userId: string): Promise<Headers>;
|
|
21
|
+
/**
|
|
22
|
+
* Replace principal resolution for one instance (default: 'default') without Better Auth I/O. Acceptance, the
|
|
23
|
+
* origin check and policies still run. A policy's re-classification read of a generic 401 answers absent,
|
|
24
|
+
* because the request carries no session; other instances keep the real resolver.
|
|
25
|
+
*/
|
|
26
|
+
export declare function overridePrincipal(builder: TestingModuleBuilder, principal: FixedPrincipal, options?: {
|
|
27
|
+
instance?: string;
|
|
28
|
+
}): TestingModuleBuilder;
|
|
29
|
+
/**
|
|
30
|
+
* Replace the policy invoker: `decide` answers per requirement, and undefined runs the real policy. Acceptance,
|
|
31
|
+
* the delegation gate, the origin check and the decision memo still run. Later calls are consulted after earlier ones.
|
|
32
|
+
*/
|
|
33
|
+
export declare function overrideDecisions(builder: TestingModuleBuilder, decide: Decide): TestingModuleBuilder;
|
|
34
|
+
/** A principal source that answers a fixed principal (or null for absent), for test module composition. */
|
|
35
|
+
export declare function testPrincipal(principal: AuthPrincipal | ((req: PrincipalRequest) => AuthPrincipal | null), options?: {
|
|
36
|
+
kinds?: readonly string[];
|
|
37
|
+
acceptance?: "default" | "explicit";
|
|
38
|
+
delegates?: boolean;
|
|
39
|
+
}): PrincipalSource;
|
|
40
|
+
/**
|
|
41
|
+
* Publish a principal for this invocation from a hand-written guard. The stamp lives on ctx.getArgs() and its
|
|
42
|
+
* object elements. Readers accept it from the same args array (WS, RPC and GraphQL share it between guards and
|
|
43
|
+
* interceptors) or from another array with the same elements when at least two elements are objects (an HTTP request
|
|
44
|
+
* and its response); a lone shared element such as a socket never identifies an invocation. Pass `instance` for
|
|
45
|
+
* handlers of a named instance.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stampPrincipal(context: ExecutionContext, principal: AuthPrincipal | null, options?: {
|
|
48
|
+
instance?: string;
|
|
49
|
+
}): void;
|
|
50
|
+
/**
|
|
51
|
+
* Replace the guard and scope bodies everywhere with one override each of GUARD_CORE and SCOPE_CORE: the APP_GUARD
|
|
52
|
+
* alias and every @UseBetterAuth()/@UseGuards(BetterAuthGuard) site delegate to the same collaborator, which
|
|
53
|
+
* overrideProvider(BetterAuthGuard) plus overrideGuard(BetterAuthGuard) cannot achieve. With `{ principal }` the
|
|
54
|
+
* stand-in stamps the principal (for the handler's instance) and allows; with a CanActivate, that guard runs as the
|
|
55
|
+
* whole guard body and may call stampPrincipal(); an object with a canActivate method is always treated as a guard,
|
|
56
|
+
* even when it also has a `principal` property. The stand-in interceptor opens the same transport-described scope
|
|
57
|
+
* as the real one, reading the test stamp first, so readers and BetterAuthService perform no Better Auth I/O.
|
|
58
|
+
*/
|
|
59
|
+
export declare function overrideAuthGuard(builder: TestingModuleBuilder, impl: CanActivate | {
|
|
60
|
+
principal: AuthPrincipal | null | ((ctx: ExecutionContext) => AuthPrincipal | null);
|
|
61
|
+
}): TestingModuleBuilder;
|
|
62
|
+
//#endregion
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=testing.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.mts","names":[],"sources":["../src/testing.ts"],"mappings":";;;;KAoDK,iBACD,yBAEE,MAAM,kBAAkB;KACzB,UACH,kBACA,iBACA,WAAW,kBAET,oCAEA,QAAQ;;;;;;;;;;wBAoEU,YAAY,UAAU,kBAC1C,KAAK,GACL;EAAW,aAAa,aAAa;IACpC,QAAQ;;wBA2CW,eACpB,MAAM,UACN,iBACC,QAAQ;;;;;;wBAmBK,kBACd,SAAS,sBACT,WAAW,gBACX;EAAW;IACV;;;;;wBAiCa,kBACd,SAAS,sBACT,QAAQ,SACP;;wBAmBa,cACd,WAAW,kBAAkB,KAAK,qBAAqB,uBACvD;EACE;EACA;EACA;IAED;;;;;;;;wBA0Ba,eACd,SAAS,kBACT,WAAW,sBACX;EAAW;;;;;;;;;;;wBAoCG,kBACd,SAAS,sBACT,MACI;EAEE,WACI,yBAEE,KAAK,qBAAqB;IAErC"}
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { n as BetterAuthConfigurationError } from "./auth-errors-C3rbTV9p.mjs";
|
|
2
|
+
import { C as PRINCIPAL_RESOLVER, D as ROUTE_PLANNER, M as TRANSPORT_REGISTRY, O as SCOPE_CORE, T as REQUEST_SCOPE, f as GUARD_CORE, m as INSTANCE_REGISTRY, y as POLICY_INVOKER } from "./decorate-Dt0e9K6R.mjs";
|
|
3
|
+
import { a as absent, n as ScopeCore, o as authenticated, r as ChainPrincipalResolver } from "./auth-scope-interceptor-DYPn3b8M.mjs";
|
|
4
|
+
import { t as BRIDGE_HANDLE } from "./bridge-protocol-c66aEkr5.mjs";
|
|
5
|
+
import { n as stampResult, r as stampedReading, t as PrincipalReadings } from "./principal-readings-Ied2staw.mjs";
|
|
6
|
+
import { isObservable, lastValueFrom } from "rxjs";
|
|
7
|
+
//#region src/testing.ts
|
|
8
|
+
const TEST_APP = Symbol.for("@nestjs-kit/better-auth:test-app");
|
|
9
|
+
const TEST_APP_CLOSE = Symbol.for("@nestjs-kit/better-auth:test-app-close");
|
|
10
|
+
const TEST_OVERRIDES = Symbol.for("@nestjs-kit/better-auth:test-overrides");
|
|
11
|
+
const PLUGIN_ID = "@nestjs-kit/better-auth";
|
|
12
|
+
const TEST_PRINCIPAL_SOURCE = "@nestjs-kit/better-auth:test-principal";
|
|
13
|
+
/**
|
|
14
|
+
* TestingModuleBuilder keys overrides by token, so repeated helper calls share one provider and its configuration.
|
|
15
|
+
* The configuration lives on the builder under a global symbol, so the ESM and CommonJS copies of this entry compose.
|
|
16
|
+
*/
|
|
17
|
+
function overridesOf(builder) {
|
|
18
|
+
let overrides = Reflect.get(builder, TEST_OVERRIDES);
|
|
19
|
+
if (!overrides) {
|
|
20
|
+
overrides = {
|
|
21
|
+
principals: /* @__PURE__ */ new Map(),
|
|
22
|
+
deciders: []
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(builder, TEST_OVERRIDES, { value: overrides });
|
|
25
|
+
}
|
|
26
|
+
return overrides;
|
|
27
|
+
}
|
|
28
|
+
/** Make app.close() run Nest's shutdown once, whoever calls it: initTestApp, a registered hook or the test. */
|
|
29
|
+
function closeOnce(app) {
|
|
30
|
+
const existing = Reflect.get(app, TEST_APP_CLOSE);
|
|
31
|
+
if (existing) return existing;
|
|
32
|
+
const close = app.close.bind(app);
|
|
33
|
+
let pending;
|
|
34
|
+
const once = () => {
|
|
35
|
+
pending ??= close();
|
|
36
|
+
return pending;
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(app, TEST_APP_CLOSE, { value: once });
|
|
39
|
+
app.close = once;
|
|
40
|
+
return once;
|
|
41
|
+
}
|
|
42
|
+
async function bridgeOf(auth) {
|
|
43
|
+
const plugin = (await auth.$context).getPlugin?.(PLUGIN_ID);
|
|
44
|
+
return plugin && typeof plugin === "object" ? Reflect.get(plugin, BRIDGE_HANDLE) : void 0;
|
|
45
|
+
}
|
|
46
|
+
function registryOf(app) {
|
|
47
|
+
try {
|
|
48
|
+
return app.get(INSTANCE_REGISTRY, { strict: false });
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* init() plus Fastify ready() when the adapter exposes it. Before init, closes the previous application that
|
|
55
|
+
* initTestApp initialized for the same Better Auth instance and that is still bound to it: an instance binds
|
|
56
|
+
* exclusively to one application (B06). A failed init() leaves its binding for the next application, which takes it
|
|
57
|
+
* over with W_INSTANCE_TAKEN_OVER. app.close() becomes idempotent, and `closeWith` receives it: a per-test cleanup such as
|
|
58
|
+
* Vitest's onTestFinished or node:test's t.after, or a list that a suite-level afterEach drains (Jest registers no hooks
|
|
59
|
+
* while a test runs). Without `closeWith`, close the last application yourself. On Express, compile one TestingModule per application: a
|
|
60
|
+
* second application of one container fails init() with APP_ADAPTER_CHANGED.
|
|
61
|
+
*/
|
|
62
|
+
async function initTestApp(app, options = {}) {
|
|
63
|
+
const close = closeOnce(app);
|
|
64
|
+
const bridges = [];
|
|
65
|
+
for (const registration of registryOf(app)?.registrations() ?? []) {
|
|
66
|
+
const bridge = registration.instance ? await bridgeOf(registration.instance) : void 0;
|
|
67
|
+
if (!bridge) continue;
|
|
68
|
+
const previous = Reflect.get(bridge, TEST_APP);
|
|
69
|
+
if (previous && previous !== app && bridge.state === "bound") await previous.close();
|
|
70
|
+
Object.defineProperty(bridge, TEST_APP, {
|
|
71
|
+
value: void 0,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true
|
|
74
|
+
});
|
|
75
|
+
bridges.push(bridge);
|
|
76
|
+
}
|
|
77
|
+
await app.init();
|
|
78
|
+
const server = (app.getHttpAdapter?.())?.getInstance?.();
|
|
79
|
+
if (typeof server?.ready === "function") await server.ready();
|
|
80
|
+
for (const bridge of bridges) Object.defineProperty(bridge, TEST_APP, {
|
|
81
|
+
value: app,
|
|
82
|
+
configurable: true,
|
|
83
|
+
writable: true
|
|
84
|
+
});
|
|
85
|
+
options.closeWith?.(close);
|
|
86
|
+
return app;
|
|
87
|
+
}
|
|
88
|
+
/** Real session headers for `userId` through Better Auth's testUtils() plugin, which the instance must include. */
|
|
89
|
+
async function authHeadersFor(auth, userId) {
|
|
90
|
+
const context = await auth.$context;
|
|
91
|
+
if (typeof context.test?.getAuthHeaders !== "function") throw new BetterAuthConfigurationError("TEST_UTILS_MISSING", "authHeadersFor requires Better Auth's testUtils() plugin on the instance.", "Add testUtils() from better-auth/plugins to the test instance.");
|
|
92
|
+
return context.test.getAuthHeaders({ userId });
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Replace principal resolution for one instance (default: 'default') without Better Auth I/O. Acceptance, the
|
|
96
|
+
* origin check and policies still run. A policy's re-classification read of a generic 401 answers absent,
|
|
97
|
+
* because the request carries no session; other instances keep the real resolver.
|
|
98
|
+
*/
|
|
99
|
+
function overridePrincipal(builder, principal, options = {}) {
|
|
100
|
+
const configured = overridesOf(builder).principals;
|
|
101
|
+
configured.set(options.instance || "default", principal);
|
|
102
|
+
builder.overrideProvider(PRINCIPAL_RESOLVER).useFactory({
|
|
103
|
+
inject: [
|
|
104
|
+
INSTANCE_REGISTRY,
|
|
105
|
+
REQUEST_SCOPE,
|
|
106
|
+
TRANSPORT_REGISTRY
|
|
107
|
+
],
|
|
108
|
+
factory: (instances, scope, transports) => {
|
|
109
|
+
const real = new ChainPrincipalResolver(instances, scope, transports);
|
|
110
|
+
return { resolve(call, request) {
|
|
111
|
+
if (!configured.has(request.auth.name)) return real.resolve(call, request);
|
|
112
|
+
if (request.reclassify) return Promise.resolve(absent());
|
|
113
|
+
const fixed = configured.get(request.auth.name);
|
|
114
|
+
const value = typeof fixed === "function" ? fixed(call) : fixed;
|
|
115
|
+
return Promise.resolve(value ? authenticated(value) : absent());
|
|
116
|
+
} };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return builder;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Replace the policy invoker: `decide` answers per requirement, and undefined runs the real policy. Acceptance,
|
|
123
|
+
* the delegation gate, the origin check and the decision memo still run. Later calls are consulted after earlier ones.
|
|
124
|
+
*/
|
|
125
|
+
function overrideDecisions(builder, decide) {
|
|
126
|
+
const configured = overridesOf(builder).deciders;
|
|
127
|
+
configured.push(decide);
|
|
128
|
+
builder.overrideProvider(POLICY_INVOKER).useValue({ async invoke(policy, params, context) {
|
|
129
|
+
for (const decider of configured) {
|
|
130
|
+
const decision = await decider(policy.id, params, context.principal);
|
|
131
|
+
if (decision !== void 0) return decision;
|
|
132
|
+
}
|
|
133
|
+
return policy.evaluate(params, context);
|
|
134
|
+
} });
|
|
135
|
+
return builder;
|
|
136
|
+
}
|
|
137
|
+
/** A principal source that answers a fixed principal (or null for absent), for test module composition. */
|
|
138
|
+
function testPrincipal(principal, options = {}) {
|
|
139
|
+
const fixed = typeof principal === "function" ? void 0 : principal;
|
|
140
|
+
const id = fixed?.source ?? TEST_PRINCIPAL_SOURCE;
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
kinds: options.kinds ?? (fixed ? [fixed.kind] : ["session"]),
|
|
144
|
+
acceptance: options.acceptance ?? "default",
|
|
145
|
+
delegates: options.delegates ?? Boolean(fixed?.delegation),
|
|
146
|
+
async resolve(request) {
|
|
147
|
+
const value = typeof principal === "function" ? principal(request) : principal;
|
|
148
|
+
return value ? authenticated({
|
|
149
|
+
...value,
|
|
150
|
+
source: id
|
|
151
|
+
}) : absent();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Publish a principal for this invocation from a hand-written guard. The stamp lives on ctx.getArgs() and its
|
|
157
|
+
* object elements. Readers accept it from the same args array (WS, RPC and GraphQL share it between guards and
|
|
158
|
+
* interceptors) or from another array with the same elements when at least two elements are objects (an HTTP request
|
|
159
|
+
* and its response); a lone shared element such as a socket never identifies an invocation. Pass `instance` for
|
|
160
|
+
* handlers of a named instance.
|
|
161
|
+
*/
|
|
162
|
+
function stampPrincipal(context, principal, options = {}) {
|
|
163
|
+
stampResult(context.getArgs(), options.instance || "default", principal ? authenticated(principal) : absent());
|
|
164
|
+
}
|
|
165
|
+
var StampFirstReadings = class extends PrincipalReadings {
|
|
166
|
+
read(input) {
|
|
167
|
+
return (input.plan && input.plan.access !== "public" ? stampedReading(input.args, input.plan.instance) : void 0) ?? super.read(input);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function planInstance(planner, context) {
|
|
171
|
+
try {
|
|
172
|
+
return planner.forContext(context).instance;
|
|
173
|
+
} catch {
|
|
174
|
+
return "default";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Replace the guard and scope bodies everywhere with one override each of GUARD_CORE and SCOPE_CORE: the APP_GUARD
|
|
179
|
+
* alias and every @UseBetterAuth()/@UseGuards(BetterAuthGuard) site delegate to the same collaborator, which
|
|
180
|
+
* overrideProvider(BetterAuthGuard) plus overrideGuard(BetterAuthGuard) cannot achieve. With `{ principal }` the
|
|
181
|
+
* stand-in stamps the principal (for the handler's instance) and allows; with a CanActivate, that guard runs as the
|
|
182
|
+
* whole guard body and may call stampPrincipal(); an object with a canActivate method is always treated as a guard,
|
|
183
|
+
* even when it also has a `principal` property. The stand-in interceptor opens the same transport-described scope
|
|
184
|
+
* as the real one, reading the test stamp first, so readers and BetterAuthService perform no Better Auth I/O.
|
|
185
|
+
*/
|
|
186
|
+
function overrideAuthGuard(builder, impl) {
|
|
187
|
+
builder.overrideProvider(GUARD_CORE).useFactory({
|
|
188
|
+
inject: [ROUTE_PLANNER],
|
|
189
|
+
factory: (planner) => ({ async canActivate(context) {
|
|
190
|
+
if (typeof impl.canActivate === "function") {
|
|
191
|
+
const result = impl.canActivate(context);
|
|
192
|
+
return isObservable(result) ? lastValueFrom(result) : result;
|
|
193
|
+
}
|
|
194
|
+
const { principal } = impl;
|
|
195
|
+
const value = typeof principal === "function" ? principal(context) : principal;
|
|
196
|
+
stampResult(context.getArgs(), planInstance(planner, context), value ? authenticated(value) : absent());
|
|
197
|
+
return true;
|
|
198
|
+
} })
|
|
199
|
+
});
|
|
200
|
+
builder.overrideProvider(SCOPE_CORE).useFactory({
|
|
201
|
+
inject: [
|
|
202
|
+
INSTANCE_REGISTRY,
|
|
203
|
+
ROUTE_PLANNER,
|
|
204
|
+
TRANSPORT_REGISTRY,
|
|
205
|
+
REQUEST_SCOPE
|
|
206
|
+
],
|
|
207
|
+
factory: (instances, planner, transports, scope) => new ScopeCore(instances, planner, transports, scope, new StampFirstReadings(scope))
|
|
208
|
+
});
|
|
209
|
+
return builder;
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
export { authHeadersFor, initTestApp, overrideAuthGuard, overrideDecisions, overridePrincipal, stampPrincipal, testPrincipal };
|
|
213
|
+
|
|
214
|
+
//# sourceMappingURL=testing.mjs.map
|