nestjs-slightly-better-auth 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +57 -0
- 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 +163 -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 +159 -0
- package/dist/api-key.mjs.map +1 -0
- package/dist/auth-contracts-BBA1C1gj.d.cts +1635 -0
- package/dist/auth-contracts-BBA1C1gj.d.cts.map +1 -0
- package/dist/auth-contracts-tqsfBZ8B.d.mts +1635 -0
- package/dist/auth-contracts-tqsfBZ8B.d.mts.map +1 -0
- package/dist/auth-decorators-CwW5JY4y.cjs +1274 -0
- package/dist/auth-decorators-CwW5JY4y.cjs.map +1 -0
- package/dist/auth-decorators-DfAJbHar.mjs +849 -0
- package/dist/auth-decorators-DfAJbHar.mjs.map +1 -0
- package/dist/auth-errors-CQjfgjaO.mjs +283 -0
- package/dist/auth-errors-CQjfgjaO.mjs.map +1 -0
- package/dist/auth-errors-CsERBNWI.cjs +348 -0
- package/dist/auth-errors-CsERBNWI.cjs.map +1 -0
- package/dist/express.cjs +247 -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 +245 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.cjs +215 -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 +213 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/index.cjs +2634 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2556 -0
- package/dist/index.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.cjs +393 -0
- package/dist/platform.cjs.map +1 -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 +385 -0
- package/dist/platform.mjs.map +1 -0
- package/dist/plugin.cjs +312 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +26 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.mts +26 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +310 -0
- package/dist/plugin.mjs.map +1 -0
- package/package.json +186 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2556 @@
|
|
|
1
|
+
import { a as getAPIErrorHeaders, c as isConfigurationError, d as ErrorRedactor, i as createInfrastructureError, l as isInfrastructureError, n as BetterAuthConfigurationError, o as getRawCause, r as BetterAuthInfrastructureError, s as isAuthFailure, t as AuthFailures } from "./auth-errors-CQjfgjaO.mjs";
|
|
2
|
+
import { $ as POLICY_RESOLVER, A as __decorateParam, B as ACCEPT_PRINCIPALS_METADATA, C as RequestScope, D as BetterAuthGuard, E as ScopeCore, F as authenticated, G as FRESHNESS_METADATA, H as AUTH_ENHANCER, I as definePrincipalSource, J as INVOCATION_PARAMS_METADATA, K as GUARD_CORE, L as normalizeForRequest, M as ChainPrincipalResolver, N as MemoKeys, O as GuardCore, P as absent, Q as POLICY_INVOKER, R as rejected, S as PrincipalReadings, T as BetterAuthScopeInterceptor, U as AUTH_INSTANCE_METADATA, V as ACCESS_METADATA, W as FORWARD_COOKIES_METADATA, X as MOUNT_COORDINATOR, Y as INVOCATION_VALUES, Z as PLATFORMS, _ as anyOf, _t as getPrincipalSourcesToken, a as BeforeDatabase, at as ROUTE_PLANNER, b as readHookMetadata, c as OptionalAuth, ct as SKIP_ORIGIN_CHECK_METADATA, d as RequireAuth, dt as USE_BETTER_AUTH_METADATA, et as PRINCIPAL_PARAMS_METADATA, f as SkipDefaultRequirements, ft as getBetterAuthHandleToken, g as allOf, gt as getExtensionToken, h as UseBetterAuth, ht as getBetterAuthServiceToken, i as BeforeAuth, it as REQUIREMENTS_METADATA, j as __decorateMetadata, k as __decorate, l as Public, lt as TRANSPORTS, m as UseAuthInstance, mt as getBetterAuthOptionsToken, n as AfterAuth, nt as PRINCIPAL_RESOLVER, o as CurrentPrincipal, ot as SCOPE_CORE, p as SkipOriginCheck, pt as getBetterAuthInstanceToken, q as INSTANCE_REGISTRY, r as AfterDatabase, rt as REQUEST_SCOPE, s as ForwardAuthCookies, st as SKIP_DEFAULT_REQUIREMENTS_METADATA, t as AcceptPrincipals, tt as PRINCIPAL_READINGS, u as Require, ut as TRANSPORT_REGISTRY, v as defineInvocationParam, w as memoKey, x as requirement, y as definePrincipalParam, z as requestHeaders } from "./auth-decorators-DfAJbHar.mjs";
|
|
3
|
+
import { readBoundedBody } from "./platform.mjs";
|
|
4
|
+
import { isAPIError } from "better-auth/api";
|
|
5
|
+
import { Controller, ForbiddenException, Get, Global, HttpException, Inject, Logger, Module, RequestMethod, UnauthorizedException, UseGuards, UseInterceptors, VERSION_NEUTRAL, VersioningType } from "@nestjs/common";
|
|
6
|
+
import { APP_GUARD, APP_INTERCEPTOR, ApplicationConfig, DiscoveryModule, DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, ModulesContainer, Reflector } from "@nestjs/core";
|
|
7
|
+
import { RoutePathFactory } from "@nestjs/core/router/route-path-factory.js";
|
|
8
|
+
//#region src/route-planner.ts
|
|
9
|
+
function classes(target) {
|
|
10
|
+
const result = [];
|
|
11
|
+
for (let current = target; current && current !== Function.prototype; current = Object.getPrototypeOf(current)) result.push(current);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
function own(key, target) {
|
|
15
|
+
return Reflect.getOwnMetadata(key, target);
|
|
16
|
+
}
|
|
17
|
+
function closest(key, targets) {
|
|
18
|
+
for (const target of targets) {
|
|
19
|
+
const value = own(key, target);
|
|
20
|
+
if (value !== void 0) return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function methodName(target, handler) {
|
|
24
|
+
for (let prototype = target.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)) for (const name of Object.getOwnPropertyNames(prototype)) if (name !== "constructor" && Object.getOwnPropertyDescriptor(prototype, name)?.value === handler) return name;
|
|
25
|
+
throw new BetterAuthConfigurationError("UNKNOWN_HANDLER", `Cannot locate handler on ${target.name}`);
|
|
26
|
+
}
|
|
27
|
+
var RoutePlanner = class {
|
|
28
|
+
instances;
|
|
29
|
+
policies;
|
|
30
|
+
transports;
|
|
31
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
constructor(instances, policies, transports) {
|
|
33
|
+
this.instances = instances;
|
|
34
|
+
this.policies = policies;
|
|
35
|
+
this.transports = transports;
|
|
36
|
+
}
|
|
37
|
+
prepare(target, method) {
|
|
38
|
+
const handler = Reflect.get(target.prototype, method);
|
|
39
|
+
if (typeof handler !== "function") throw new BetterAuthConfigurationError("UNKNOWN_HANDLER", `${target.name}.${method} is not a handler`);
|
|
40
|
+
const chain = classes(target);
|
|
41
|
+
const nested = this.transports.defaultAccessFor(target, method) === "inherit";
|
|
42
|
+
const levels = nested ? [handler] : [handler, ...chain];
|
|
43
|
+
const site = `${target.name}.${method}`;
|
|
44
|
+
for (const level of levels) {
|
|
45
|
+
const access = own(ACCESS_METADATA, level);
|
|
46
|
+
if ((access === "public" || access === "optional") && (own(REQUIREMENTS_METADATA, level)?.length ?? 0) > 0) throw new BetterAuthConfigurationError("CONTRADICTORY_ACCESS", `${site} combines anonymous access and requirements at the same level`);
|
|
47
|
+
}
|
|
48
|
+
const instance = closest(AUTH_INSTANCE_METADATA, [handler, ...chain]) ?? "default";
|
|
49
|
+
const entry = this.instances.get(instance);
|
|
50
|
+
let access = nested ? "inherit" : entry.options.defaultAccess === "public" ? "public" : "required";
|
|
51
|
+
for (const level of levels) {
|
|
52
|
+
const declared = own(ACCESS_METADATA, level);
|
|
53
|
+
if (declared) {
|
|
54
|
+
access = declared;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if ((own(REQUIREMENTS_METADATA, level)?.length ?? 0) > 0 || nested && own(ACCEPT_PRINCIPALS_METADATA, level) !== void 0) {
|
|
58
|
+
access = "required";
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const skips = closest(SKIP_DEFAULT_REQUIREMENTS_METADATA, levels) === true;
|
|
63
|
+
const declaredRequirements = access === "required" ? [
|
|
64
|
+
...(skips ? [] : entry.options.defaultRequirements ?? []).map((expression) => ({
|
|
65
|
+
expression,
|
|
66
|
+
origin: "defaultRequirements"
|
|
67
|
+
})),
|
|
68
|
+
...nested ? [] : [...chain].reverse().flatMap((level) => (own(REQUIREMENTS_METADATA, level) ?? []).map((expression) => ({
|
|
69
|
+
expression,
|
|
70
|
+
origin: `${level.name} (class)`
|
|
71
|
+
}))),
|
|
72
|
+
...(own(REQUIREMENTS_METADATA, handler) ?? []).map((expression) => ({
|
|
73
|
+
expression,
|
|
74
|
+
origin: `${site} (method)`
|
|
75
|
+
}))
|
|
76
|
+
] : [];
|
|
77
|
+
const requirements = declaredRequirements.map((item) => item.expression);
|
|
78
|
+
return {
|
|
79
|
+
handler,
|
|
80
|
+
chain,
|
|
81
|
+
levels,
|
|
82
|
+
nested,
|
|
83
|
+
site,
|
|
84
|
+
instance,
|
|
85
|
+
entry,
|
|
86
|
+
access,
|
|
87
|
+
skips,
|
|
88
|
+
requirements,
|
|
89
|
+
declaredRequirements
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
requirementsOf(target, method) {
|
|
93
|
+
return Object.freeze(this.prepare(target, method).requirements);
|
|
94
|
+
}
|
|
95
|
+
forContext(context) {
|
|
96
|
+
try {
|
|
97
|
+
return this.plan(context.getClass(), methodName(context.getClass(), context.getHandler()));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (isConfigurationError(error) && error.phase !== "request") throw BetterAuthConfigurationError.atRequest(error.code, error.detail, {
|
|
100
|
+
site: `${context.getClass().name}.${context.getHandler().name}`,
|
|
101
|
+
hint: error.hint
|
|
102
|
+
});
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
plan(target, method) {
|
|
107
|
+
const handler = Reflect.get(target.prototype, method);
|
|
108
|
+
let cache = this.cache.get(target);
|
|
109
|
+
const previous = cache?.get(handler);
|
|
110
|
+
if (previous) return previous;
|
|
111
|
+
const p = this.prepare(target, method);
|
|
112
|
+
const { entry, levels, chain } = p;
|
|
113
|
+
const allKinds = new Set(entry.sources.flatMap((source) => [...source.kinds]));
|
|
114
|
+
const accepts = new Set(closest(ACCEPT_PRINCIPALS_METADATA, levels) ?? entry.sources.filter((source) => source.acceptance === "default").flatMap((source) => [...source.kinds]));
|
|
115
|
+
let fresh = closest(FRESHNESS_METADATA, levels) === true || entry.options.session !== false && entry.options.session?.freshness === "authoritative";
|
|
116
|
+
const admitted = (expression) => {
|
|
117
|
+
if ("anyOf" in expression) return new Set(expression.anyOf.flatMap((child) => [...admitted(child)]));
|
|
118
|
+
if ("allOf" in expression) return intersection(expression.allOf.map(admitted), allKinds);
|
|
119
|
+
const policy = this.policies.resolve(expression.policy);
|
|
120
|
+
const kinds = expression.principals ?? policy.requires?.principals;
|
|
121
|
+
if (expression.freshIdentity ?? policy.requires?.freshIdentity) fresh = true;
|
|
122
|
+
if (kinds) for (const kind of kinds) accepts.add(kind);
|
|
123
|
+
return new Set(kinds ?? entry.sources.filter((source) => !source.delegates).flatMap((source) => [...source.kinds]));
|
|
124
|
+
};
|
|
125
|
+
const permitted = p.requirements.map(admitted);
|
|
126
|
+
for (const kind of accepts) if (!allKinds.has(kind)) throw new BetterAuthConfigurationError("UNKNOWN_PRINCIPAL_KIND", `${p.site} accepts unavailable principal kind ${kind}`);
|
|
127
|
+
const parameters = own(PRINCIPAL_PARAMS_METADATA, handler) ?? [];
|
|
128
|
+
if (p.access === "public" && parameters.length) throw new BetterAuthConfigurationError("PUBLIC_READS_PRINCIPAL", `${p.site} reads a principal on a public handler`, "Use OptionalAuth");
|
|
129
|
+
const principalParams = parameters.flatMap((param) => param.kind ? [{
|
|
130
|
+
kind: param.kind,
|
|
131
|
+
reason: param.reason
|
|
132
|
+
}] : []);
|
|
133
|
+
if (p.access !== "inherit") {
|
|
134
|
+
for (const param of principalParams) if ([...accepts].some((kind) => kind !== param.kind)) throw new BetterAuthConfigurationError("PRINCIPAL_PARAM_CONFLICT", `${p.site} admits a kind rejected by its principal parameter`, "Use CurrentPrincipal and narrow kind");
|
|
135
|
+
}
|
|
136
|
+
if (p.access === "required" && !intersection([accepts, ...permitted], allKinds).size) {
|
|
137
|
+
const describe = (expression) => {
|
|
138
|
+
if ("anyOf" in expression) return `anyOf(${expression.anyOf.map(describe).join(", ")})`;
|
|
139
|
+
if ("allOf" in expression) return `allOf(${expression.allOf.map(describe).join(", ")})`;
|
|
140
|
+
return expression.label ?? this.policies.resolve(expression.policy).id;
|
|
141
|
+
};
|
|
142
|
+
const declarations = p.declaredRequirements.map(({ expression, origin }) => `${origin}: ${describe(expression)}`);
|
|
143
|
+
throw new BetterAuthConfigurationError("UNSATISFIABLE_PRINCIPAL_KINDS", `${p.site} has no eligible principal kind satisfying its requirements. Accepted kinds: ${[...accepts].join(", ") || "none"}.${declarations.length ? ` Requirements: ${declarations.join("; ")}.` : ""}`, p.declaredRequirements.some((item) => item.origin === "defaultRequirements") ? "Review the conflicting requirements; use @SkipDefaultRequirements() to explicitly opt this handler out of instance defaults." : "Review accepted kinds and requirement restrictions; configure a default source or explicitly accept a registered principal kind.");
|
|
144
|
+
}
|
|
145
|
+
const forwardDirectCalls = closest(FORWARD_COOKIES_METADATA, [handler, ...chain]) ?? entry.options.cookies?.forwardDirectCalls ?? false;
|
|
146
|
+
const originCheck = closest(SKIP_ORIGIN_CHECK_METADATA, [handler, ...chain]) || entry.options.originCheck?.mode === "off" ? "off" : forwardDirectCalls ? "form" : "cookie";
|
|
147
|
+
const declares = forwardDirectCalls || levels.some((level) => {
|
|
148
|
+
const access = own(ACCESS_METADATA, level);
|
|
149
|
+
return access === "required" || access === "optional" || [
|
|
150
|
+
ACCEPT_PRINCIPALS_METADATA,
|
|
151
|
+
REQUIREMENTS_METADATA,
|
|
152
|
+
PRINCIPAL_PARAMS_METADATA,
|
|
153
|
+
INVOCATION_PARAMS_METADATA
|
|
154
|
+
].some((key) => own(key, level) !== void 0);
|
|
155
|
+
});
|
|
156
|
+
const plan = Object.freeze({
|
|
157
|
+
instance: p.instance,
|
|
158
|
+
access: p.access,
|
|
159
|
+
nested: p.nested,
|
|
160
|
+
site: p.site,
|
|
161
|
+
requirements: Object.freeze(p.requirements),
|
|
162
|
+
skipsDefaultRequirements: p.access === "required" && p.skips,
|
|
163
|
+
freshness: fresh ? "authoritative" : "default",
|
|
164
|
+
accepts,
|
|
165
|
+
principalParams: Object.freeze(principalParams),
|
|
166
|
+
originCheck,
|
|
167
|
+
sourceSet: JSON.stringify(entry.sources.flatMap((source, index) => source.kinds.some((kind) => accepts.has(kind)) ? [index] : [])),
|
|
168
|
+
forwardDirectCalls,
|
|
169
|
+
declares
|
|
170
|
+
});
|
|
171
|
+
if (!cache) {
|
|
172
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
173
|
+
this.cache.set(target, cache);
|
|
174
|
+
}
|
|
175
|
+
cache.set(handler, plan);
|
|
176
|
+
return plan;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
function intersection(sets, universe) {
|
|
180
|
+
return new Set([...universe].filter((kind) => sets.every((set) => set.has(kind))));
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/authorization-evaluator.ts
|
|
184
|
+
const allow = () => ({ effect: "allow" });
|
|
185
|
+
const deny = (input) => ({
|
|
186
|
+
effect: "deny",
|
|
187
|
+
...input
|
|
188
|
+
});
|
|
189
|
+
function definePolicy(policy) {
|
|
190
|
+
return (params, options) => ({
|
|
191
|
+
policy,
|
|
192
|
+
params,
|
|
193
|
+
...options
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
var DefaultPolicyInvoker = class {
|
|
197
|
+
async invoke(policy, params, context) {
|
|
198
|
+
return policy.evaluate(params, context);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var AuthorizationEvaluator = class {
|
|
202
|
+
policies;
|
|
203
|
+
invoker;
|
|
204
|
+
resolver;
|
|
205
|
+
scope;
|
|
206
|
+
keys = new MemoKeys();
|
|
207
|
+
constructor(policies, invoker, resolver, scope) {
|
|
208
|
+
this.policies = policies;
|
|
209
|
+
this.invoker = invoker;
|
|
210
|
+
this.resolver = resolver;
|
|
211
|
+
this.scope = scope;
|
|
212
|
+
}
|
|
213
|
+
async evaluate(plan, principal, call, entry, execution, transport) {
|
|
214
|
+
if (!plan.requirements.length) return allow();
|
|
215
|
+
const headers = requestHeaders(call, entry);
|
|
216
|
+
const context = {
|
|
217
|
+
principal,
|
|
218
|
+
instance: entry.name,
|
|
219
|
+
transport,
|
|
220
|
+
headers,
|
|
221
|
+
cookies: call.cookies,
|
|
222
|
+
request: call.request,
|
|
223
|
+
param: (name) => call.param(name),
|
|
224
|
+
handler: {
|
|
225
|
+
class: execution.getClass(),
|
|
226
|
+
method: methodName(execution.getClass(), execution.getHandler())
|
|
227
|
+
},
|
|
228
|
+
auth: entry.handle,
|
|
229
|
+
execution,
|
|
230
|
+
memo: (key, fn) => this.scope.memoPolicyIo(call.key, entry.name, this.keys.key(call.key, "policy-io", key), fn, entry.options.limits?.maxAuthorizationCallsPerRequest),
|
|
231
|
+
provide: (slot, value) => {
|
|
232
|
+
this.scope.stateFor(call.invocation).values.set(this.scope.valueKey(entry.name, slot), value);
|
|
233
|
+
let instances = Reflect.get(call.invocation, INVOCATION_VALUES);
|
|
234
|
+
if (!instances) {
|
|
235
|
+
instances = /* @__PURE__ */ new Map();
|
|
236
|
+
Object.defineProperty(call.invocation, INVOCATION_VALUES, { value: instances });
|
|
237
|
+
}
|
|
238
|
+
let values = instances.get(entry.name);
|
|
239
|
+
if (!values) {
|
|
240
|
+
values = /* @__PURE__ */ new Map();
|
|
241
|
+
instances.set(entry.name, values);
|
|
242
|
+
}
|
|
243
|
+
values.set(slot, value);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const evaluate = (expression) => this.scope.memoDecision(call.invocation, entry.name, this.keys.key(call.invocation, "decision", expression), async () => {
|
|
247
|
+
if ("anyOf" in expression || "allOf" in expression) {
|
|
248
|
+
const disjunction = "anyOf" in expression;
|
|
249
|
+
const children = "anyOf" in expression ? expression.anyOf : expression.allOf;
|
|
250
|
+
const denials = [];
|
|
251
|
+
for (const child of children) {
|
|
252
|
+
const decision = await evaluate(child);
|
|
253
|
+
if (decision.effect === "allow" && disjunction) return decision;
|
|
254
|
+
if (decision.effect === "deny") {
|
|
255
|
+
if (!disjunction) return decision;
|
|
256
|
+
denials.push(decision);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!disjunction) return allow();
|
|
260
|
+
return deny({
|
|
261
|
+
reason: denials[0]?.reason ?? "NO_REQUIREMENT_SATISFIED",
|
|
262
|
+
status: denials.some((d) => d.status === 401) ? 401 : 403,
|
|
263
|
+
message: children.map((child) => "policy" in child ? child.label ?? this.policies.resolve(child.policy).id : "requirement group").join(" or ")
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const policy = this.policies.resolve(expression.policy);
|
|
267
|
+
const kinds = expression.principals ?? policy.requires?.principals;
|
|
268
|
+
if (kinds && !kinds.includes(principal.kind) || !kinds && principal.delegation) return deny({ reason: "PRINCIPAL_NOT_SUPPORTED" });
|
|
269
|
+
try {
|
|
270
|
+
return await entry.handle.run({
|
|
271
|
+
cookies: call.cookies,
|
|
272
|
+
forward: "same-credential",
|
|
273
|
+
inbound: () => headers,
|
|
274
|
+
internal: true
|
|
275
|
+
}, () => this.invoker.invoke(policy, expression.params, context));
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const normalized = normalizeForRequest(error, "policy", plan.site, entry, headers);
|
|
278
|
+
if (isAuthFailure(normalized)) {
|
|
279
|
+
if (normalized.status === 429) throw normalized;
|
|
280
|
+
return deny({
|
|
281
|
+
status: normalized.status,
|
|
282
|
+
reason: normalized.reason ?? normalized.code,
|
|
283
|
+
message: normalized.message,
|
|
284
|
+
challenge: normalized.challenge
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const source = entry.sources.find((item) => item.id === principal.source && item.sessionBacked);
|
|
288
|
+
if (source) await this.scope.memoPolicyIo(call.key, entry.name, JSON.stringify(["reclassify", source.id]), async () => {
|
|
289
|
+
const result = await this.resolver.resolve(call, {
|
|
290
|
+
auth: entry.handle,
|
|
291
|
+
freshness: "authoritative",
|
|
292
|
+
accepts: plan.accepts,
|
|
293
|
+
sourceSet: plan.sourceSet,
|
|
294
|
+
reclassify: { sourceId: source.id }
|
|
295
|
+
});
|
|
296
|
+
if (result.outcome === "authenticated") throw createInfrastructureError(normalized.sessionLoss, {
|
|
297
|
+
headers,
|
|
298
|
+
credentialHeaders: entry.credentialHeaders,
|
|
299
|
+
exposeRawCause: entry.options.errors?.exposeRawCause
|
|
300
|
+
});
|
|
301
|
+
return result;
|
|
302
|
+
}, entry.options.limits?.maxAuthorizationCallsPerRequest);
|
|
303
|
+
return deny({
|
|
304
|
+
status: 401,
|
|
305
|
+
reason: normalized.reason ?? "UNAUTHENTICATED"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
for (const expression of plan.requirements) {
|
|
310
|
+
const decision = await evaluate(expression);
|
|
311
|
+
if (decision.effect === "deny") return decision;
|
|
312
|
+
}
|
|
313
|
+
return allow();
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/trusted-origins.ts
|
|
318
|
+
/** Auth route path exceptions never exempt application operations. */
|
|
319
|
+
function originChecksDisabled(context) {
|
|
320
|
+
return context.skipCSRFCheck || context.skipOriginCheck === true && context.options.advanced?.disableCSRFCheck === void 0;
|
|
321
|
+
}
|
|
322
|
+
var TrustedOrigins = class {
|
|
323
|
+
context;
|
|
324
|
+
constructor(context) {
|
|
325
|
+
this.context = context;
|
|
326
|
+
}
|
|
327
|
+
async list(request) {
|
|
328
|
+
const context = await this.context();
|
|
329
|
+
const configured = context.options.trustedOrigins;
|
|
330
|
+
const contributed = typeof configured === "function" ? await configured(request) : configured;
|
|
331
|
+
const origins = new Set(context.trustedOrigins);
|
|
332
|
+
if (Array.isArray(contributed)) {
|
|
333
|
+
for (const origin of contributed) if (typeof origin === "string" && origin) origins.add(origin);
|
|
334
|
+
}
|
|
335
|
+
if (request && !context.options.baseURL && !context.baseURL) origins.add(new URL(request.url).origin);
|
|
336
|
+
return [...origins];
|
|
337
|
+
}
|
|
338
|
+
async isTrusted(origin, request) {
|
|
339
|
+
const [context, trustedOrigins] = await Promise.all([this.context(), this.list(request)]);
|
|
340
|
+
return context.isTrustedOrigin.call({ trustedOrigins }, origin, { allowRelativePaths: false });
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/bridge-client.ts
|
|
345
|
+
/** Preserve capabilities lazily; spreading a ScopeView can extract credentials. */
|
|
346
|
+
function derivedScope(scope, init) {
|
|
347
|
+
const outer = scope.current();
|
|
348
|
+
return {
|
|
349
|
+
view: {
|
|
350
|
+
get cookies() {
|
|
351
|
+
return init.cookies;
|
|
352
|
+
},
|
|
353
|
+
get forward() {
|
|
354
|
+
return init.forward ?? "same-credential";
|
|
355
|
+
},
|
|
356
|
+
get inbound() {
|
|
357
|
+
return init.inbound;
|
|
358
|
+
},
|
|
359
|
+
get internal() {
|
|
360
|
+
return init.internal ?? false;
|
|
361
|
+
},
|
|
362
|
+
get browserHeaders() {
|
|
363
|
+
return outer?.view.browserHeaders;
|
|
364
|
+
},
|
|
365
|
+
get checkCallerSession() {
|
|
366
|
+
return outer?.view.checkCallerSession;
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
get call() {
|
|
370
|
+
return outer?.call;
|
|
371
|
+
},
|
|
372
|
+
get plan() {
|
|
373
|
+
return outer?.plan;
|
|
374
|
+
},
|
|
375
|
+
get reading() {
|
|
376
|
+
return outer?.reading;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function createAuthHandle(name, instance, scope, ready) {
|
|
381
|
+
const context = () => instance.$context;
|
|
382
|
+
const trusted = new TrustedOrigins(context);
|
|
383
|
+
return {
|
|
384
|
+
name,
|
|
385
|
+
instance,
|
|
386
|
+
get api() {
|
|
387
|
+
return instance.api;
|
|
388
|
+
},
|
|
389
|
+
context,
|
|
390
|
+
async hasPlugin(id) {
|
|
391
|
+
return (await context()).hasPlugin(id);
|
|
392
|
+
},
|
|
393
|
+
run(init, fn) {
|
|
394
|
+
return scope.run(derivedScope(scope, init), fn);
|
|
395
|
+
},
|
|
396
|
+
checkOrigin(browser, mode) {
|
|
397
|
+
return ready().origin.check(browser, mode);
|
|
398
|
+
},
|
|
399
|
+
producedByEndpoint(value) {
|
|
400
|
+
return ready().bridge.producedByEndpoint(value);
|
|
401
|
+
},
|
|
402
|
+
isTrustedOrigin(origin, request) {
|
|
403
|
+
return trusted.isTrusted(origin, request);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
var BridgeClient = class {
|
|
408
|
+
#registrations = [];
|
|
409
|
+
bind(entry, binding) {
|
|
410
|
+
const result = entry.bridge.bind(binding);
|
|
411
|
+
this.#registrations.push({
|
|
412
|
+
binding,
|
|
413
|
+
close: result.close
|
|
414
|
+
});
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
bootstrap() {
|
|
418
|
+
for (const { binding } of this.#registrations) binding.state = "bootstrapped";
|
|
419
|
+
}
|
|
420
|
+
close() {
|
|
421
|
+
for (const registration of this.#registrations) registration.close();
|
|
422
|
+
this.#registrations.length = 0;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/bridge-protocol.ts
|
|
427
|
+
const BRIDGE_HANDLE = Symbol.for("nestjs-slightly-better-auth:bridge");
|
|
428
|
+
const EXTENSION_DEFINITION = Symbol.for("nestjs-slightly-better-auth:extension");
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/origin-check.ts
|
|
431
|
+
const ORIGIN_PROOF = Symbol.for("nestjs-slightly-better-auth:origin-proof");
|
|
432
|
+
const ORIGIN_CALCULATION_FAILURES = Symbol.for("nestjs-slightly-better-auth:origin-calculation-failures");
|
|
433
|
+
function diagnosticOrigin(headers) {
|
|
434
|
+
const origin = headers.get("origin");
|
|
435
|
+
const referer = headers.get("referer");
|
|
436
|
+
let value = origin || referer || "(missing)";
|
|
437
|
+
if (!origin && referer) try {
|
|
438
|
+
value = new URL(referer).origin;
|
|
439
|
+
} catch {
|
|
440
|
+
value = "(invalid referer)";
|
|
441
|
+
}
|
|
442
|
+
return value.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 100);
|
|
443
|
+
}
|
|
444
|
+
/** Bounded, per-application diagnostics; the next denial rolls the window without timers. */
|
|
445
|
+
var OriginDiagnostics = class {
|
|
446
|
+
logger;
|
|
447
|
+
#openedAt;
|
|
448
|
+
#pairs = /* @__PURE__ */ new Set();
|
|
449
|
+
#further = /* @__PURE__ */ new Map();
|
|
450
|
+
#origins = /* @__PURE__ */ new Map();
|
|
451
|
+
constructor(logger) {
|
|
452
|
+
this.logger = logger;
|
|
453
|
+
}
|
|
454
|
+
record(instance, failure, headers, trustedCount) {
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
if (this.#openedAt !== void 0 && now - this.#openedAt >= 36e5) {
|
|
457
|
+
this.logger.warn(`Origin check window closed: further denials ${JSON.stringify([...this.#further])}; frequent origins ${JSON.stringify([...this.#origins].sort((a, b) => b[1] - a[1]).slice(0, 5))}`);
|
|
458
|
+
this.#pairs.clear();
|
|
459
|
+
this.#further.clear();
|
|
460
|
+
this.#origins.clear();
|
|
461
|
+
this.#openedAt = void 0;
|
|
462
|
+
}
|
|
463
|
+
this.#openedAt ??= now;
|
|
464
|
+
const origin = diagnosticOrigin(headers);
|
|
465
|
+
const reason = failure.reason ?? failure.code;
|
|
466
|
+
const pair = JSON.stringify([reason, origin]);
|
|
467
|
+
const message = `Origin check ${instance}: ${reason}, origin ${origin}, trusted origins ${trustedCount}. Add the origin to trustedOrigins or send Origin from non-browser clients.`;
|
|
468
|
+
if (!this.#pairs.has(pair) && this.#pairs.size < 100) {
|
|
469
|
+
this.#pairs.add(pair);
|
|
470
|
+
this.logger.warn(message);
|
|
471
|
+
} else this.#further.set(reason, (this.#further.get(reason) ?? 0) + 1);
|
|
472
|
+
if (this.#origins.has(origin) || this.#origins.size < 100) this.#origins.set(origin, (this.#origins.get(origin) ?? 0) + 1);
|
|
473
|
+
this.logger.debug(message);
|
|
474
|
+
}
|
|
475
|
+
advisoryFailure(instance, headers, trustedCount) {
|
|
476
|
+
this.record(instance, AuthFailures.forbidden("ORIGIN_CHECK_UNAVAILABLE"), headers, trustedCount);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
var OriginCheck = class {
|
|
480
|
+
scope;
|
|
481
|
+
init;
|
|
482
|
+
#trusted;
|
|
483
|
+
#trustedCounts = /* @__PURE__ */ new WeakMap();
|
|
484
|
+
constructor(scope, init) {
|
|
485
|
+
this.scope = scope;
|
|
486
|
+
this.init = init;
|
|
487
|
+
this.#trusted = new TrustedOrigins(async () => init.context);
|
|
488
|
+
}
|
|
489
|
+
check(browser, mode) {
|
|
490
|
+
const origins = this.scope.stateFor(browser.key).origins;
|
|
491
|
+
const key = memoKey(this.init.instance, mode);
|
|
492
|
+
const existing = origins.get(key);
|
|
493
|
+
if (existing) return existing;
|
|
494
|
+
let headers;
|
|
495
|
+
let calculationFailed = false;
|
|
496
|
+
const promise = Promise.resolve().then(async () => {
|
|
497
|
+
if (this.init.options?.mode === "off" || originChecksDisabled(this.init.context)) return null;
|
|
498
|
+
headers = browser.headers();
|
|
499
|
+
const failure = await this.verdict(browser, headers, mode, () => {
|
|
500
|
+
calculationFailed = true;
|
|
501
|
+
});
|
|
502
|
+
if (failure) this.init.diagnostics?.record(this.init.instance, failure, headers, this.#trustedCounts.get(browser.key) ?? this.init.context.trustedOrigins.length);
|
|
503
|
+
else if (mode === "form" || headers.has("cookie")) this.scope.stateFor(browser.key).values.set(this.scope.valueKey(this.init.instance, ORIGIN_PROOF), true);
|
|
504
|
+
return failure;
|
|
505
|
+
}).catch((error) => {
|
|
506
|
+
if (origins.get(key) === promise) origins.delete(key);
|
|
507
|
+
if (isConfigurationError(error)) throw error;
|
|
508
|
+
const failure = isInfrastructureError(error) ? error : createInfrastructureError(error, {
|
|
509
|
+
headers,
|
|
510
|
+
credentialHeaders: this.init.credentialHeaders,
|
|
511
|
+
exposeRawCause: this.init.exposeRawCause
|
|
512
|
+
});
|
|
513
|
+
if (calculationFailed && headers) this.calculationFailures(browser).set(promise, headers);
|
|
514
|
+
throw failure;
|
|
515
|
+
});
|
|
516
|
+
origins.set(key, promise);
|
|
517
|
+
return promise;
|
|
518
|
+
}
|
|
519
|
+
async advisory(browser) {
|
|
520
|
+
const verdict = this.check(browser, "cookie");
|
|
521
|
+
try {
|
|
522
|
+
await verdict;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
const headers = this.calculationFailures(browser).get(verdict);
|
|
525
|
+
if (!isInfrastructureError(error) || !headers) throw error;
|
|
526
|
+
this.init.diagnostics?.advisoryFailure(this.init.instance, headers, this.init.context.trustedOrigins.length);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
assertCallerSession(browser, path, instance) {
|
|
530
|
+
if (instance === this.init.instance && (this.init.options?.mode === "off" || originChecksDisabled(this.init.context) || this.scope.stateFor(browser.key).values.get(this.scope.valueKey(this.init.instance, ORIGIN_PROOF)) === true)) return;
|
|
531
|
+
throw BetterAuthConfigurationError.atRequest("PUBLIC_HANDLER_USED_CALLER_SESSION", "A direct caller-session call requires a passing origin verdict for this browser leg and instance", {
|
|
532
|
+
site: path,
|
|
533
|
+
hint: "Guard the handler and pass its origin check before making this call."
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
calculationFailures(browser) {
|
|
537
|
+
const values = this.scope.stateFor(browser.key).values;
|
|
538
|
+
const key = this.scope.valueKey(this.init.instance, ORIGIN_CALCULATION_FAILURES);
|
|
539
|
+
let failures = values.get(key);
|
|
540
|
+
if (!failures) {
|
|
541
|
+
failures = /* @__PURE__ */ new WeakMap();
|
|
542
|
+
values.set(key, failures);
|
|
543
|
+
}
|
|
544
|
+
return failures;
|
|
545
|
+
}
|
|
546
|
+
async verdict(browser, headers, mode, onCalculationFailure) {
|
|
547
|
+
if (!headers.has("cookie")) {
|
|
548
|
+
if (mode === "cookie") return null;
|
|
549
|
+
const site = headers.get("sec-fetch-site");
|
|
550
|
+
const navigation = headers.get("sec-fetch-mode");
|
|
551
|
+
const metadata = [
|
|
552
|
+
site,
|
|
553
|
+
navigation,
|
|
554
|
+
headers.get("sec-fetch-dest")
|
|
555
|
+
].some((value) => Boolean(value?.trim()));
|
|
556
|
+
if (metadata && site === "cross-site" && navigation === "navigate") return AuthFailures.forbidden("CROSS_SITE_NAVIGATION_LOGIN_BLOCKED");
|
|
557
|
+
if (!metadata && !headers.get("origin") && !headers.get("referer")) return null;
|
|
558
|
+
}
|
|
559
|
+
const origin = headers.get("origin");
|
|
560
|
+
const referer = headers.get("referer");
|
|
561
|
+
const site = headers.get("sec-fetch-site");
|
|
562
|
+
const value = origin === "null" && site === "same-origin" ? new URL(browser.url).origin : origin || referer;
|
|
563
|
+
if (!value || value === "null") {
|
|
564
|
+
if (this.init.options?.missingOrigin === "allow-non-browser" && !headers.has("origin") && !headers.has("referer") && !headers.has("sec-fetch-site")) return null;
|
|
565
|
+
return AuthFailures.forbidden("MISSING_OR_NULL_ORIGIN");
|
|
566
|
+
}
|
|
567
|
+
const request = new Request(browser.url, { headers });
|
|
568
|
+
const trustedOrigins = await this.#trusted.list(request).catch((error) => {
|
|
569
|
+
onCalculationFailure();
|
|
570
|
+
throw error;
|
|
571
|
+
});
|
|
572
|
+
this.#trustedCounts.set(browser.key, trustedOrigins.length);
|
|
573
|
+
return this.init.context.isTrustedOrigin.call({ trustedOrigins }, value, { allowRelativePaths: false }) ? null : AuthFailures.forbidden("INVALID_ORIGIN");
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/instance-registry.ts
|
|
578
|
+
/** Nest considers transient providers static; auth collaborators require singleton trees. */
|
|
579
|
+
function isSingletonDependencyTree(root) {
|
|
580
|
+
const pending = [root];
|
|
581
|
+
const seen = /* @__PURE__ */ new Set();
|
|
582
|
+
while (pending.length) {
|
|
583
|
+
const wrapper = pending.pop();
|
|
584
|
+
if (seen.has(wrapper)) continue;
|
|
585
|
+
seen.add(wrapper);
|
|
586
|
+
if (wrapper.isTransient || !wrapper.isDependencyTreeStatic()) return false;
|
|
587
|
+
for (const dependency of wrapper.getCtorMetadata() ?? []) if (dependency) pending.push(dependency);
|
|
588
|
+
for (const property of wrapper.getPropertiesMetadata() ?? []) pending.push(property.wrapper);
|
|
589
|
+
for (const enhancer of wrapper.getEnhancersMetadata() ?? []) pending.push(enhancer);
|
|
590
|
+
}
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
function configurationIssue(error, code, detail) {
|
|
594
|
+
if (isConfigurationError(error)) return error;
|
|
595
|
+
const issue = new BetterAuthConfigurationError(code, detail);
|
|
596
|
+
Object.defineProperty(issue, "cause", { value: error });
|
|
597
|
+
return issue;
|
|
598
|
+
}
|
|
599
|
+
let InstanceRegistry = class InstanceRegistry {
|
|
600
|
+
scope;
|
|
601
|
+
#registrations = [];
|
|
602
|
+
#entries = /* @__PURE__ */ new Map();
|
|
603
|
+
logger = new Logger("BetterAuth");
|
|
604
|
+
diagnostics = new OriginDiagnostics(this.logger);
|
|
605
|
+
state = "new";
|
|
606
|
+
adapter = null;
|
|
607
|
+
owner = { description: "Nest application" };
|
|
608
|
+
constructor(scope) {
|
|
609
|
+
this.scope = scope;
|
|
610
|
+
}
|
|
611
|
+
register(options, staticOptions, sources, appOptions) {
|
|
612
|
+
const name = staticOptions.name || "default";
|
|
613
|
+
const registration = {
|
|
614
|
+
name,
|
|
615
|
+
options,
|
|
616
|
+
staticOptions,
|
|
617
|
+
sources,
|
|
618
|
+
appOptions,
|
|
619
|
+
instance: options.auth,
|
|
620
|
+
handle: createAuthHandle(name, options.auth, this.scope, () => this.get(name))
|
|
621
|
+
};
|
|
622
|
+
this.#registrations.push(registration);
|
|
623
|
+
return registration;
|
|
624
|
+
}
|
|
625
|
+
registrations() {
|
|
626
|
+
return [...this.#registrations];
|
|
627
|
+
}
|
|
628
|
+
get(name) {
|
|
629
|
+
const entry = this.#entries.get(name);
|
|
630
|
+
if (!entry) throw new BetterAuthConfigurationError("UNKNOWN_INSTANCE", `Auth instance '${name}' is unknown or not initialized.`, "Register the instance once with BetterAuthModule.forRoot().");
|
|
631
|
+
return entry;
|
|
632
|
+
}
|
|
633
|
+
list() {
|
|
634
|
+
return [...this.#entries.values()];
|
|
635
|
+
}
|
|
636
|
+
begin(adapter) {
|
|
637
|
+
if (this.state !== "new") {
|
|
638
|
+
if (this.adapter !== adapter) throw new BetterAuthConfigurationError("LATE_HTTP_ADAPTER", "Another application changed the active HTTP adapter.", "Close the first application before creating another.");
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
this.adapter = adapter;
|
|
642
|
+
this.state = "initialized";
|
|
643
|
+
this.owner = { description: `Nest application ${globalThis.crypto.randomUUID()}` };
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
async initialize(issues) {
|
|
647
|
+
this.#entries.clear();
|
|
648
|
+
const seen = /* @__PURE__ */ new Set();
|
|
649
|
+
const bridgeOwners = /* @__PURE__ */ new Map();
|
|
650
|
+
const shared = /* @__PURE__ */ new Set();
|
|
651
|
+
let globals = 0;
|
|
652
|
+
for (const registration of this.#registrations) {
|
|
653
|
+
const { name, instance, options, staticOptions, appOptions, sources, handle } = registration;
|
|
654
|
+
if (seen.has(name)) issues.push(new BetterAuthConfigurationError("DUPLICATE_INSTANCE", `Instance '${name}' was registered more than once.`, "Import BetterAuthModule.forRoot() once per instance."));
|
|
655
|
+
seen.add(name);
|
|
656
|
+
if (staticOptions.globalGuard ?? name === "default") globals++;
|
|
657
|
+
if (name !== "default" && (appOptions.platforms !== void 0 || appOptions.transports !== void 0)) issues.push(new BetterAuthConfigurationError("APP_EXTENSIONS_ON_NAMED_INSTANCE", `Instance '${name}' declares app-wide extensions.`, "Declare platforms and transports on the default instance."));
|
|
658
|
+
if (!instance || typeof instance.handler !== "function" || typeof instance.api?.getSession !== "function" || typeof instance.$context?.then !== "function") {
|
|
659
|
+
issues.push(new BetterAuthConfigurationError("NOT_AN_AUTH_INSTANCE", `Instance '${name}' must be the object returned by betterAuth().`));
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
let context;
|
|
663
|
+
try {
|
|
664
|
+
context = await instance.$context;
|
|
665
|
+
} catch (error) {
|
|
666
|
+
issues.push(configurationIssue(error, "AUTH_CONTEXT_FAILED", `Instance '${name}' failed to initialize its auth context.`));
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const plugin = context.getPlugin("nestjs-slightly-better-auth");
|
|
670
|
+
const bridge = plugin && typeof plugin === "object" ? Reflect.get(plugin, BRIDGE_HANDLE) : void 0;
|
|
671
|
+
if (!bridge) {
|
|
672
|
+
issues.push(new BetterAuthConfigurationError("PLUGIN_MISSING", `Instance '${name}' has no nestjs() bridge.`, "Add nestjs() as the LAST betterAuth plugins entry."));
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (bridge.protocol !== 4 || typeof bridge.bind !== "function") {
|
|
676
|
+
issues.push(new BetterAuthConfigurationError("PLUGIN_PROTOCOL_MISMATCH", `Instance '${name}' has bridge protocol ${bridge.protocol}; expected 4.`, "Align the module and plugin package versions."));
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const previous = bridgeOwners.get(bridge);
|
|
680
|
+
if (previous !== void 0) {
|
|
681
|
+
shared.add(name);
|
|
682
|
+
shared.add(previous);
|
|
683
|
+
issues.push(new BetterAuthConfigurationError("PLUGIN_SHARED_BETWEEN_INSTANCES", `'${previous}' and '${name}' share one plugin object.`, "Call nestjs() separately inside every betterAuth() construction."));
|
|
684
|
+
}
|
|
685
|
+
bridgeOwners.set(bridge, name);
|
|
686
|
+
const plugins = context.options.plugins ?? [];
|
|
687
|
+
const index = plugins.findIndex((value) => value.id === "nestjs-slightly-better-auth");
|
|
688
|
+
if (index >= 0 && plugins.slice(index + 1).some((value) => value.hooks?.after?.length)) this.logger.warn(`W_PLUGIN_NOT_LAST: '${name}': put nestjs() after plugins with after hooks.`);
|
|
689
|
+
const credentialHeaders = [...new Set(sources.flatMap((source) => source.credentialHeaders ?? []).map((value) => value.toLowerCase()))];
|
|
690
|
+
this.#entries.set(name, {
|
|
691
|
+
name,
|
|
692
|
+
instance,
|
|
693
|
+
options,
|
|
694
|
+
staticOptions,
|
|
695
|
+
sources,
|
|
696
|
+
handle,
|
|
697
|
+
context,
|
|
698
|
+
bridge,
|
|
699
|
+
credentialHeaders,
|
|
700
|
+
origin: new OriginCheck(this.scope, {
|
|
701
|
+
instance: name,
|
|
702
|
+
context,
|
|
703
|
+
options: options.originCheck,
|
|
704
|
+
diagnostics: this.diagnostics,
|
|
705
|
+
credentialHeaders,
|
|
706
|
+
exposeRawCause: options.errors?.exposeRawCause
|
|
707
|
+
})
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
for (const name of shared) this.#entries.delete(name);
|
|
711
|
+
if (globals > 1) issues.push(new BetterAuthConfigurationError("DUPLICATE_GLOBAL_GUARD", "More than one registration enables the global guard.", "Enable globalGuard on only one instance."));
|
|
712
|
+
if (!seen.has("default")) issues.push(new BetterAuthConfigurationError("NO_DEFAULT_INSTANCE", "Named registrations require one default instance.", "Add BetterAuthModule.forRoot({ auth })."));
|
|
713
|
+
}
|
|
714
|
+
reset() {
|
|
715
|
+
this.state = "new";
|
|
716
|
+
this.#entries.clear();
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
InstanceRegistry = __decorate([__decorateParam(0, Inject(REQUEST_SCOPE)), __decorateMetadata("design:paramtypes", [Object])], InstanceRegistry);
|
|
720
|
+
//#endregion
|
|
721
|
+
//#region src/boot-validator.ts
|
|
722
|
+
function requirementLeaves(expressions) {
|
|
723
|
+
return expressions.flatMap((expression) => "anyOf" in expression ? requirementLeaves(expression.anyOf) : "allOf" in expression ? requirementLeaves(expression.allOf) : [expression]);
|
|
724
|
+
}
|
|
725
|
+
function productionMode() {
|
|
726
|
+
return ![
|
|
727
|
+
"development",
|
|
728
|
+
"dev",
|
|
729
|
+
"test"
|
|
730
|
+
].includes(process.env.NODE_ENV ?? "");
|
|
731
|
+
}
|
|
732
|
+
function ancestors(target) {
|
|
733
|
+
const result = [];
|
|
734
|
+
for (let current = target; typeof current === "function" && current !== Function.prototype; current = Object.getPrototypeOf(current)) result.push(current);
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
function domainsOverlap(a, b) {
|
|
738
|
+
if (!a || !b) return true;
|
|
739
|
+
const left = a.replace(/^\./, "").toLowerCase();
|
|
740
|
+
const right = b.replace(/^\./, "").toLowerCase();
|
|
741
|
+
return left === right || left.endsWith(`.${right}`) || right.endsWith(`.${left}`);
|
|
742
|
+
}
|
|
743
|
+
function pathsOverlap(a = "/", b = "/") {
|
|
744
|
+
return a === b || a.startsWith(b.endsWith("/") ? b : `${b}/`) || b.startsWith(a.endsWith("/") ? a : `${a}/`);
|
|
745
|
+
}
|
|
746
|
+
function routeMayWarnAboutMount(path, basePath) {
|
|
747
|
+
const route = path.toLowerCase().replace(/(?<!\/)\/+$/, "") || "/";
|
|
748
|
+
const base = basePath.toLowerCase().replace(/(?<!\/)\/+$/, "") || "/";
|
|
749
|
+
if (base === "/") return route.startsWith("/");
|
|
750
|
+
if (route === base || route.startsWith(`${base}/`)) return true;
|
|
751
|
+
const marker = route.search(/[:*{]/);
|
|
752
|
+
if (marker === -1) return false;
|
|
753
|
+
const literal = route.slice(0, marker);
|
|
754
|
+
return base.startsWith(literal) || literal.startsWith(`${base}/`);
|
|
755
|
+
}
|
|
756
|
+
let BootValidator = class BootValidator {
|
|
757
|
+
discovery;
|
|
758
|
+
scanner;
|
|
759
|
+
reflector;
|
|
760
|
+
moduleRef;
|
|
761
|
+
appConfig;
|
|
762
|
+
instances;
|
|
763
|
+
planner;
|
|
764
|
+
policies;
|
|
765
|
+
transports;
|
|
766
|
+
mounts;
|
|
767
|
+
logger = new Logger("BetterAuth");
|
|
768
|
+
#plans = [];
|
|
769
|
+
#claims = [];
|
|
770
|
+
#globalGuard = false;
|
|
771
|
+
#globalScope = false;
|
|
772
|
+
constructor(discovery, scanner, reflector, moduleRef, appConfig, instances, planner, policies, transports, mounts) {
|
|
773
|
+
this.discovery = discovery;
|
|
774
|
+
this.scanner = scanner;
|
|
775
|
+
this.reflector = reflector;
|
|
776
|
+
this.moduleRef = moduleRef;
|
|
777
|
+
this.appConfig = appConfig;
|
|
778
|
+
this.instances = instances;
|
|
779
|
+
this.planner = planner;
|
|
780
|
+
this.policies = policies;
|
|
781
|
+
this.transports = transports;
|
|
782
|
+
this.mounts = mounts;
|
|
783
|
+
}
|
|
784
|
+
warn(code, message) {
|
|
785
|
+
this.logger.warn(`${code}: ${message}`);
|
|
786
|
+
}
|
|
787
|
+
sites() {
|
|
788
|
+
const sites = [];
|
|
789
|
+
const seen = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const wrapper of [...this.discovery.getControllers(), ...this.discovery.getProviders()]) {
|
|
791
|
+
if (wrapper.isAlias || !wrapper.instance || typeof wrapper.instance !== "object" && typeof wrapper.instance !== "function") continue;
|
|
792
|
+
const prototype = Object.getPrototypeOf(wrapper.instance);
|
|
793
|
+
const target = prototype?.constructor;
|
|
794
|
+
if (!prototype || !target || target === Object || target === Function) continue;
|
|
795
|
+
const methods = seen.get(target) ?? /* @__PURE__ */ new Set();
|
|
796
|
+
seen.set(target, methods);
|
|
797
|
+
for (const method of this.scanner.getAllMethodNames(prototype)) {
|
|
798
|
+
if (methods.has(method)) continue;
|
|
799
|
+
methods.add(method);
|
|
800
|
+
const handler = Reflect.get(prototype, method);
|
|
801
|
+
if (typeof handler === "function") sites.push({
|
|
802
|
+
target,
|
|
803
|
+
method,
|
|
804
|
+
handler
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return sites;
|
|
809
|
+
}
|
|
810
|
+
singletonChecks(issues, references) {
|
|
811
|
+
const refs = new Set(references.map((value) => value.policy));
|
|
812
|
+
for (const wrapper of this.discovery.getProviders()) if (((typeof wrapper.token === "symbol" ? Symbol.keyFor(wrapper.token) : void 0)?.startsWith("nestjs-slightly-better-auth:ext:") || refs.has(wrapper.token) || refs.has(wrapper.metatype)) && !isSingletonDependencyTree(wrapper)) issues.push(new BetterAuthConfigurationError("NON_SINGLETON_EXTENSION", `Extension/policy '${String(wrapper.name)}' has a non-static dependency tree.`, "Use singleton extensions and singleton dependencies."));
|
|
813
|
+
}
|
|
814
|
+
canaries() {
|
|
815
|
+
let Probe = class Probe {
|
|
816
|
+
run() {}
|
|
817
|
+
};
|
|
818
|
+
__decorate([
|
|
819
|
+
Get("handler"),
|
|
820
|
+
__decorateMetadata("design:type", Function),
|
|
821
|
+
__decorateMetadata("design:paramtypes", []),
|
|
822
|
+
__decorateMetadata("design:returntype", void 0)
|
|
823
|
+
], Probe.prototype, "run", null);
|
|
824
|
+
Probe = __decorate([Controller("coverage-probe")], Probe);
|
|
825
|
+
if (Reflect.getOwnMetadata("path", Probe) !== "coverage-probe" || Reflect.getOwnMetadata("path", Probe.prototype.run) !== "handler" || Reflect.getOwnMetadata("method", Probe.prototype.run) !== 0) throw new BetterAuthConfigurationError("NEST_METADATA_KEY_CHANGED", "Boot route checks cannot read Nest path/method metadata.");
|
|
826
|
+
UseGuards(BetterAuthGuard)(Probe);
|
|
827
|
+
UseInterceptors(BetterAuthScopeInterceptor)(Probe);
|
|
828
|
+
if (!Reflect.getOwnMetadata("__guards__", Probe)?.includes(BetterAuthGuard) || !Reflect.getOwnMetadata("__interceptors__", Probe)?.includes(BetterAuthScopeInterceptor)) throw new BetterAuthConfigurationError("NEST_METADATA_KEY_CHANGED", "Boot coverage cannot read Nest '__guards__' / '__interceptors__' metadata.", "Use a compatible Nest version and report the metadata change.");
|
|
829
|
+
}
|
|
830
|
+
enhancer(value, kind) {
|
|
831
|
+
const token = kind === "guard" ? BetterAuthGuard : BetterAuthScopeInterceptor;
|
|
832
|
+
let actual;
|
|
833
|
+
try {
|
|
834
|
+
actual = this.moduleRef.get(token, { strict: false });
|
|
835
|
+
} catch {
|
|
836
|
+
actual = void 0;
|
|
837
|
+
}
|
|
838
|
+
if (value === token || actual !== void 0 && value === actual) return true;
|
|
839
|
+
if (value && (typeof value === "object" || typeof value === "function")) {
|
|
840
|
+
const brand = Reflect.get(value, AUTH_ENHANCER) ?? (typeof value === "function" ? Reflect.get(value.prototype ?? {}, AUTH_ENHANCER) : Reflect.get(Object.getPrototypeOf(value) ?? {}, AUTH_ENHANCER));
|
|
841
|
+
return brand === kind || kind === "scope" && brand === "interceptor" || typeof brand === "object" && brand !== null && Reflect.get(brand, kind) === true;
|
|
842
|
+
}
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
local(site, kind) {
|
|
846
|
+
const handler = site.method ? Reflect.get(site.target.prototype ?? {}, site.method) : void 0;
|
|
847
|
+
const targets = [...ancestors(site.target), ...handler ? [handler] : []];
|
|
848
|
+
if (targets.some((target) => Reflect.getOwnMetadata(USE_BETTER_AUTH_METADATA, target) === true)) return true;
|
|
849
|
+
const key = kind === "guard" ? "__guards__" : "__interceptors__";
|
|
850
|
+
return targets.some((target) => (Reflect.getOwnMetadata(key, target) ?? []).some((value) => this.enhancer(value, kind)));
|
|
851
|
+
}
|
|
852
|
+
trigger(target) {
|
|
853
|
+
const access = Reflect.getOwnMetadata(ACCESS_METADATA, target);
|
|
854
|
+
if (access === "required" || access === "optional" || access === "authenticated") return true;
|
|
855
|
+
return [
|
|
856
|
+
ACCEPT_PRINCIPALS_METADATA,
|
|
857
|
+
REQUIREMENTS_METADATA,
|
|
858
|
+
PRINCIPAL_PARAMS_METADATA,
|
|
859
|
+
INVOCATION_PARAMS_METADATA,
|
|
860
|
+
FORWARD_COOKIES_METADATA,
|
|
861
|
+
FRESHNESS_METADATA,
|
|
862
|
+
AUTH_INSTANCE_METADATA,
|
|
863
|
+
USE_BETTER_AUTH_METADATA
|
|
864
|
+
].some((key) => Reflect.hasOwnMetadata(key, target));
|
|
865
|
+
}
|
|
866
|
+
coverage(sites, issues) {
|
|
867
|
+
this.#globalGuard = [...this.appConfig.getGlobalGuards(), ...this.appConfig.getGlobalRequestGuards().map((wrapper) => wrapper.instance)].some((value) => this.enhancer(value, "guard"));
|
|
868
|
+
this.#globalScope = [...this.appConfig.getGlobalInterceptors(), ...this.appConfig.getGlobalRequestInterceptors().map((wrapper) => wrapper.instance)].some((value) => this.enhancer(value, "scope"));
|
|
869
|
+
for (const claim of this.#claims) {
|
|
870
|
+
let plan;
|
|
871
|
+
try {
|
|
872
|
+
plan = claim.method ? this.planner.plan(claim.target, claim.method) : void 0;
|
|
873
|
+
} catch {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
const classEntry = this.instances.list().find((entry) => entry.name === (Reflect.getMetadata(AUTH_INSTANCE_METADATA, claim.target) ?? "default"));
|
|
877
|
+
const classForm = (Reflect.getMetadata(FORWARD_COOKIES_METADATA, claim.target) ?? classEntry?.options.cookies?.forwardDirectCalls ?? false) && Reflect.getMetadata(SKIP_ORIGIN_CHECK_METADATA, claim.target) !== true && classEntry?.options.originCheck?.mode !== "off";
|
|
878
|
+
const accessCovered = plan ? (plan.access === "public" || plan.access === "inherit") && plan.originCheck !== "form" : Reflect.getMetadata(ACCESS_METADATA, claim.target) === "public" && !classForm;
|
|
879
|
+
const localGuard = this.local(claim, "guard");
|
|
880
|
+
const localScope = this.local(claim, "scope");
|
|
881
|
+
const covered = accessCovered || claim.reach === "global" && (this.#globalGuard || localGuard) || claim.reach === "explicit" && localGuard && localScope;
|
|
882
|
+
const declares = plan?.declares ?? ancestors(claim.target).some((target) => this.trigger(target));
|
|
883
|
+
if (!covered && (claim.options.everyHandler || declares) && claim.options.coverage !== "off") {
|
|
884
|
+
const message = `${claim.target.name}${claim.method ? `.${claim.method}` : ""} is not covered by the required auth enhancers.`;
|
|
885
|
+
if (claim.options.coverage === "warn") this.warn(claim.options.code, `${message} ${claim.options.hint}`);
|
|
886
|
+
else issues.push(new BetterAuthConfigurationError(claim.options.code, message, claim.options.hint));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
for (const site of sites) {
|
|
890
|
+
const classClaims = this.#claims.filter((claim) => claim.target === site.target);
|
|
891
|
+
if (classClaims.some((claim) => claim.method === void 0 || claim.method === site.method)) continue;
|
|
892
|
+
if (this.trigger(site.handler) || !classClaims.length && ancestors(site.target).some((target) => this.trigger(target))) issues.push(new BetterAuthConfigurationError("UNGUARDED_AUTH_METADATA", `${site.target.name}.${site.method} declares auth metadata but no transport claims it. Registered transports: ${this.transports.list().map((value) => value.id).join(", ")}.`, "Register a transport that claims this handler."));
|
|
893
|
+
}
|
|
894
|
+
if (!this.#globalScope) {
|
|
895
|
+
const affected = this.#claims.flatMap((claim) => {
|
|
896
|
+
if (this.local(claim, "scope")) return [];
|
|
897
|
+
const plan = this.#plans.find((site) => site.target === claim.target && site.method === claim.method)?.plan;
|
|
898
|
+
if (plan) return plan.declares ? [{
|
|
899
|
+
claim,
|
|
900
|
+
forwarding: plan.forwardDirectCalls
|
|
901
|
+
}] : [];
|
|
902
|
+
if (claim.method !== void 0) return [];
|
|
903
|
+
const entry = this.instances.list().find((value) => value.name === (Reflect.getMetadata(AUTH_INSTANCE_METADATA, claim.target) ?? "default"));
|
|
904
|
+
const forwarding = Reflect.getMetadata(FORWARD_COOKIES_METADATA, claim.target) ?? entry?.options.cookies?.forwardDirectCalls ?? false;
|
|
905
|
+
return forwarding || ancestors(claim.target).some((target) => this.trigger(target)) ? [{
|
|
906
|
+
claim,
|
|
907
|
+
forwarding: Boolean(forwarding)
|
|
908
|
+
}] : [];
|
|
909
|
+
});
|
|
910
|
+
const seen = /* @__PURE__ */ new Map();
|
|
911
|
+
const uncovered = affected.sort((left, right) => Number(right.forwarding) - Number(left.forwarding)).filter(({ claim }) => {
|
|
912
|
+
const methods = seen.get(claim.target) ?? /* @__PURE__ */ new Set();
|
|
913
|
+
seen.set(claim.target, methods);
|
|
914
|
+
if (methods.has(claim.method)) return false;
|
|
915
|
+
methods.add(claim.method);
|
|
916
|
+
return true;
|
|
917
|
+
}).slice(0, 20).map(({ claim }) => `${claim.target.name}.${claim.method ?? "*"}`);
|
|
918
|
+
this.warn("W_NO_GLOBAL_SCOPE", `BetterAuthScopeInterceptor is not global; unscoped direct auth.api calls have no origin proof, refresh suppression or cookie bridge. Enable globalScope or apply @UseBetterAuth(). ${uncovered.join(", ")}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
entryChecks(entry, policies, issues) {
|
|
922
|
+
const { name, context, options, sources, bridge } = entry;
|
|
923
|
+
for (const unit of [...sources, ...policies]) for (const plugin of unit.requires?.plugins ?? []) if (!context.hasPlugin(plugin)) issues.push(new BetterAuthConfigurationError("PLUGIN_PREREQUISITE", `'${name}': '${unit.id}' requires the '${plugin}' plugin.`, `Add the '${plugin}' Better Auth plugin.`));
|
|
924
|
+
const base = context.options.baseURL;
|
|
925
|
+
const dynamic = !!base && typeof base === "object" && "allowedHosts" in base;
|
|
926
|
+
if (dynamic && !("fallback" in base && base.fallback)) {
|
|
927
|
+
const hostless = [
|
|
928
|
+
...sources,
|
|
929
|
+
...policies,
|
|
930
|
+
...this.transports.list()
|
|
931
|
+
].filter((unit) => unit.requires?.hostlessCalls);
|
|
932
|
+
if (hostless.length) issues.push(new BetterAuthConfigurationError("DYNAMIC_BASE_URL_WITHOUT_FALLBACK", `'${name}': ${hostless.map((unit) => unit.id).join(", ")} make hostless calls.`, "Add baseURL.fallback to the dynamic allowedHosts configuration."));
|
|
933
|
+
else this.warn("W_DYNAMIC_HOST", `'${name}': dynamic baseURL has no fallback; rejected hosts may make auth.handler throw.`);
|
|
934
|
+
}
|
|
935
|
+
const production = productionMode();
|
|
936
|
+
if (production && !context.baseURL && !dynamic) {
|
|
937
|
+
if (options.http?.allowRequestDerivedBaseURL) this.warn("W_REQUEST_DERIVED_BASE_URL", `'${name}': requests choose the origin of token links and trusted origins.`);
|
|
938
|
+
else issues.push(new BetterAuthConfigurationError("UNSAFE_BASE_URL", `'${name}' has no safe baseURL in production.`, "Set baseURL / BETTER_AUTH_URL, or dynamic baseURL.allowedHosts."));
|
|
939
|
+
}
|
|
940
|
+
if (production && context.secret === "better-auth-secret-12345678901234567890") issues.push(new BetterAuthConfigurationError("DEFAULT_SECRET", `'${name}' uses Better Auth's public default signing secret.`, "Set BETTER_AUTH_SECRET or betterAuth({ secret })."));
|
|
941
|
+
if (context.skipCSRFCheck || context.skipOriginCheck === true) {
|
|
942
|
+
const explicit = context.options.advanced;
|
|
943
|
+
const cause = `disableCSRFCheck=${String(explicit?.disableCSRFCheck)}, disableOriginCheck=${String(explicit?.disableOriginCheck)}; unset flags use Better Auth's import-time NODE_ENV/TEST defaults`;
|
|
944
|
+
const exception = context.skipOriginCheck === true && explicit?.disableCSRFCheck === false ? "; disableCSRFCheck:false does not re-enable SDK origin checks; app-route checks remain on" : "";
|
|
945
|
+
this.warn("W_ORIGIN_CHECK", `'${name}': resolved skipCSRFCheck=${context.skipCSRFCheck}, skipOriginCheck=${String(context.skipOriginCheck)} (${cause})${exception}.`);
|
|
946
|
+
}
|
|
947
|
+
if (Array.isArray(context.skipOriginCheck) && context.skipOriginCheck.length) this.logger.log(`I_AUTH_ORIGIN_PATH_SKIPS: '${name}': ${context.skipOriginCheck.join(", ")}; app-route checks are unchanged.`);
|
|
948
|
+
if (production && options.originCheck?.mode === "off") this.warn("W_ORIGIN_CHECK", `'${name}': application origin checks are disabled.`);
|
|
949
|
+
if (name === "default" && options.cookies?.forwardDirectCalls) this.warn("W_FORWARD_DIRECT_CALLS", "The default instance forwards same-credential direct-call cookies app-wide; every handler requires form-CSRF enforcement.");
|
|
950
|
+
const proxy = this.mounts.adapter && this.mounts.platform?.proxyTrust?.(this.mounts.adapter);
|
|
951
|
+
const ip = context.options.advanced?.ipAddress;
|
|
952
|
+
const role = context.rateLimit.enabled ? "rate-limit buckets and session IP" : "session IP only (rate limiter disabled)";
|
|
953
|
+
if (production && bridge.clientIpHeader && ip?.ipAddressHeaders?.[0] !== bridge.clientIpHeader) this.warn("W_CLIENT_IP", `'${name}': the bridge IP header is not first in effective ipAddressHeaders (${role}).`);
|
|
954
|
+
if (production && !bridge.clientIpHeader && !ip?.ipAddressHeaders?.length && !ip?.trustedProxies?.length) this.warn("W_CLIENT_IP", `'${name}': no bridge IP header or SDK IP configuration (${role}).`);
|
|
955
|
+
if (production && bridge.clientIpHeader && ip?.trustedProxies?.length && proxy && proxy.mode === "none") this.warn("W_CLIENT_IP", `'${name}': SDK trustedProxies rechecks the socket IP without platform proxy trust (${role}); configure platform trust or set clientIpHeader:false.`);
|
|
956
|
+
if (production && proxy && proxy.mode === "all") this.warn("W_PROXY_TRUST_ALL", `'${name}': the platform trusts every proxy; clients can choose ${role}.`);
|
|
957
|
+
if (production && !context.rateLimit.enabled && context.options.rateLimit?.enabled === void 0) {
|
|
958
|
+
const contradiction = process.env.NODE_ENV === "production" ? " while process.env.NODE_ENV is production; Better Auth imported before that value existed" : "";
|
|
959
|
+
this.warn("W_RATE_LIMIT_DISABLED", `'${name}': better-auth resolved rateLimit.enabled=false${contradiction}. Set NODE_ENV=production before auth.ts is imported, rateLimit.enabled:true, or explicitly disable it when an edge proxy limits traffic.`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
cookies(issues) {
|
|
963
|
+
const entries = this.instances.list();
|
|
964
|
+
for (let i = 0; i < entries.length; i++) for (const right of entries.slice(i + 1)) {
|
|
965
|
+
const left = entries[i];
|
|
966
|
+
for (const a of Object.values(left.context.authCookies)) for (const b of Object.values(right.context.authCookies)) if (a.name === b.name && domainsOverlap(a.attributes.domain, b.attributes.domain) && pathsOverlap(a.attributes.path, b.attributes.path)) issues.push(new BetterAuthConfigurationError("COOKIE_NAME_COLLISION", `'${left.name}' and '${right.name}' both use cookie '${a.name}' on overlapping scopes.`, "Set advanced.cookiePrefix on one instance."));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
async validate(issues) {
|
|
970
|
+
this.#plans.length = 0;
|
|
971
|
+
this.#claims.length = 0;
|
|
972
|
+
const sites = this.sites();
|
|
973
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
974
|
+
const prepared = /* @__PURE__ */ new Set();
|
|
975
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
976
|
+
const failures = /* @__PURE__ */ new Map();
|
|
977
|
+
const reported = /* @__PURE__ */ new Set();
|
|
978
|
+
const references = [];
|
|
979
|
+
const report = (error, detail) => {
|
|
980
|
+
if (!reported.has(error)) {
|
|
981
|
+
reported.add(error);
|
|
982
|
+
issues.push(configurationIssue(error, "UNRESOLVED_POLICY", detail));
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
const candidate = (target, method) => {
|
|
986
|
+
let methods = candidates.get(target);
|
|
987
|
+
if (!methods) {
|
|
988
|
+
methods = /* @__PURE__ */ new Map();
|
|
989
|
+
candidates.set(target, methods);
|
|
990
|
+
}
|
|
991
|
+
const existing = methods.get(method);
|
|
992
|
+
if (existing) return existing;
|
|
993
|
+
const handler = Reflect.get(target.prototype ?? {}, method);
|
|
994
|
+
if (typeof handler !== "function") throw new BetterAuthConfigurationError("UNKNOWN_HANDLER", `${target.name}.${method} is not a handler.`);
|
|
995
|
+
const site = {
|
|
996
|
+
target,
|
|
997
|
+
method,
|
|
998
|
+
handler
|
|
999
|
+
};
|
|
1000
|
+
methods.set(method, site);
|
|
1001
|
+
return site;
|
|
1002
|
+
};
|
|
1003
|
+
const prepare = (site) => {
|
|
1004
|
+
if (failures.has(site)) throw failures.get(site);
|
|
1005
|
+
if (prepared.has(site)) return;
|
|
1006
|
+
try {
|
|
1007
|
+
const leaves = requirementLeaves(this.planner.requirementsOf(site.target, site.method));
|
|
1008
|
+
references.push(...leaves);
|
|
1009
|
+
for (const requirement of leaves) this.policies.resolve(requirement.policy);
|
|
1010
|
+
prepared.add(site);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
failures.set(site, error);
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
const planOf = (target, method) => {
|
|
1017
|
+
const site = candidate(target, method);
|
|
1018
|
+
prepare(site);
|
|
1019
|
+
const cached = compiled.get(site);
|
|
1020
|
+
if (cached) return cached;
|
|
1021
|
+
try {
|
|
1022
|
+
const plan = this.planner.plan(site.target, site.method);
|
|
1023
|
+
compiled.set(site, plan);
|
|
1024
|
+
this.#plans.push({
|
|
1025
|
+
...site,
|
|
1026
|
+
plan
|
|
1027
|
+
});
|
|
1028
|
+
return plan;
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
failures.set(site, error);
|
|
1031
|
+
throw error;
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
for (const site of sites) if (this.trigger(site.handler) || [
|
|
1035
|
+
ACCESS_METADATA,
|
|
1036
|
+
FRESHNESS_METADATA,
|
|
1037
|
+
SKIP_ORIGIN_CHECK_METADATA,
|
|
1038
|
+
SKIP_DEFAULT_REQUIREMENTS_METADATA
|
|
1039
|
+
].some((key) => Reflect.hasOwnMetadata(key, site.handler))) {
|
|
1040
|
+
const selected = candidate(site.target, site.method);
|
|
1041
|
+
try {
|
|
1042
|
+
prepare(selected);
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
report(error, `Cannot prepare ${site.target.name}.${site.method}.`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
let canariesPassed = false;
|
|
1048
|
+
try {
|
|
1049
|
+
this.canaries();
|
|
1050
|
+
canariesPassed = true;
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
issues.push(configurationIssue(error, "NEST_METADATA_KEY_CHANGED", "Coverage metadata canaries failed."));
|
|
1053
|
+
}
|
|
1054
|
+
if (canariesPassed) for (const transport of this.transports.list()) try {
|
|
1055
|
+
await transport.validate?.({
|
|
1056
|
+
discovery: this.discovery,
|
|
1057
|
+
reflector: this.reflector,
|
|
1058
|
+
moduleRef: this.moduleRef,
|
|
1059
|
+
hasHttpAdapter: this.mounts.adapter !== null,
|
|
1060
|
+
planOf,
|
|
1061
|
+
claim: (target, method, reach, options) => {
|
|
1062
|
+
this.#claims.push({
|
|
1063
|
+
transport,
|
|
1064
|
+
target,
|
|
1065
|
+
method,
|
|
1066
|
+
reach,
|
|
1067
|
+
options
|
|
1068
|
+
});
|
|
1069
|
+
if (method !== void 0) candidate(target, method);
|
|
1070
|
+
},
|
|
1071
|
+
logger: this.logger
|
|
1072
|
+
});
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
if (!reported.has(error)) {
|
|
1075
|
+
reported.add(error);
|
|
1076
|
+
issues.push(configurationIssue(error, "TRANSPORT_VALIDATION_FAILED", `Transport '${transport.id}' failed validation.`));
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
for (const methods of candidates.values()) for (const site of methods.values()) try {
|
|
1080
|
+
prepare(site);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
report(error, `Cannot prepare ${site.target.name}.${site.method}.`);
|
|
1083
|
+
}
|
|
1084
|
+
this.singletonChecks(issues, references);
|
|
1085
|
+
for (const methods of candidates.values()) for (const site of methods.values()) try {
|
|
1086
|
+
planOf(site.target, site.method);
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
report(error, `Cannot compile ${site.target.name}.${site.method}.`);
|
|
1089
|
+
}
|
|
1090
|
+
for (const site of this.#plans) try {
|
|
1091
|
+
const entry = this.instances.get(site.plan.instance);
|
|
1092
|
+
for (const requirement of requirementLeaves(site.plan.requirements)) await this.policies.resolve(requirement.policy).validate?.(requirement.params, {
|
|
1093
|
+
auth: entry.handle,
|
|
1094
|
+
context: entry.context,
|
|
1095
|
+
site: site.plan.site
|
|
1096
|
+
});
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
report(error, `Cannot validate ${site.target.name}.${site.method}.`);
|
|
1099
|
+
}
|
|
1100
|
+
if (canariesPassed) {
|
|
1101
|
+
this.coverage(sites, issues);
|
|
1102
|
+
const applicationRoutes = [];
|
|
1103
|
+
const routePathFactory = new RoutePathFactory(this.appConfig);
|
|
1104
|
+
const globalPrefix = this.appConfig.getGlobalPrefix();
|
|
1105
|
+
const versioningOptions = this.appConfig.getVersioning();
|
|
1106
|
+
const modules = this.moduleRef.get(ModulesContainer, { strict: false });
|
|
1107
|
+
for (const wrapper of this.discovery.getControllers()) {
|
|
1108
|
+
const target = wrapper.metatype;
|
|
1109
|
+
if (!target?.prototype) continue;
|
|
1110
|
+
const moduleType = wrapper.host?.metatype;
|
|
1111
|
+
const modulePath = moduleType ? Reflect.getMetadata(`__module_path__${modules.applicationId}`, moduleType) ?? Reflect.getMetadata("__module_path__", moduleType) : void 0;
|
|
1112
|
+
const host = Reflect.getMetadata("host", target);
|
|
1113
|
+
const controllerVersion = versioningOptions ? Reflect.getMetadata("__version__", target) ?? versioningOptions.defaultVersion : void 0;
|
|
1114
|
+
const rawPrefixes = Reflect.getMetadata("path", target);
|
|
1115
|
+
const prefixes = Array.isArray(rawPrefixes) ? rawPrefixes : [rawPrefixes ?? ""];
|
|
1116
|
+
for (const method of this.scanner.getAllMethodNames(target.prototype)) {
|
|
1117
|
+
const handler = Reflect.get(target.prototype, method);
|
|
1118
|
+
const rawPaths = Reflect.getMetadata("path", handler);
|
|
1119
|
+
const requestMethod = Reflect.getMetadata("method", handler);
|
|
1120
|
+
if (rawPaths === void 0 || requestMethod === void 0) continue;
|
|
1121
|
+
const methodVersion = Reflect.getMetadata("__version__", handler);
|
|
1122
|
+
const effectiveVersion = methodVersion ?? controllerVersion;
|
|
1123
|
+
const conditions = [];
|
|
1124
|
+
if (host !== void 0) conditions.push("host");
|
|
1125
|
+
if (versioningOptions && versioningOptions.type !== VersioningType.URI && effectiveVersion !== void 0 && effectiveVersion !== VERSION_NEUTRAL) conditions.push("version");
|
|
1126
|
+
for (const prefix of prefixes) for (const path of Array.isArray(rawPaths) ? rawPaths : [rawPaths]) {
|
|
1127
|
+
const resolvedPaths = routePathFactory.create({
|
|
1128
|
+
ctrlPath: prefix,
|
|
1129
|
+
methodPath: path,
|
|
1130
|
+
modulePath,
|
|
1131
|
+
globalPrefix,
|
|
1132
|
+
controllerVersion,
|
|
1133
|
+
methodVersion,
|
|
1134
|
+
versioningOptions
|
|
1135
|
+
}, requestMethod).map((route) => this.mounts.normalizeApplicationRoute(route));
|
|
1136
|
+
const source = `${target.name}.${method}`;
|
|
1137
|
+
applicationRoutes.push(Object.freeze({
|
|
1138
|
+
method: RequestMethod[requestMethod] ?? String(requestMethod),
|
|
1139
|
+
paths: Object.freeze([...resolvedPaths]),
|
|
1140
|
+
conditions: Object.freeze([...conditions]),
|
|
1141
|
+
source
|
|
1142
|
+
}));
|
|
1143
|
+
for (const route of resolvedPaths) {
|
|
1144
|
+
const binding = this.mounts.bindings().find((candidate) => routeMayWarnAboutMount(route, candidate.basePath));
|
|
1145
|
+
if (!binding) continue;
|
|
1146
|
+
this.warn("W_ROUTE_SHADOWS_AUTH", `${source} at '${route}' may shadow auth mount '${binding.basePath}'.`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
this.mounts.registerApplicationRoutes(Object.freeze(applicationRoutes), issues);
|
|
1152
|
+
}
|
|
1153
|
+
this.cookies(issues);
|
|
1154
|
+
for (const entry of this.instances.list()) {
|
|
1155
|
+
const plans = this.#plans.filter((site) => site.plan.instance === entry.name);
|
|
1156
|
+
const policies = [...new Set(plans.flatMap((site) => requirementLeaves(site.plan.requirements).map((requirement) => this.policies.resolve(requirement.policy))))];
|
|
1157
|
+
this.entryChecks(entry, policies, issues);
|
|
1158
|
+
const handlers = plans.map((site) => {
|
|
1159
|
+
const claims = this.#claims.filter((claim) => claim.target === site.target && (claim.method === void 0 || claim.method === site.method));
|
|
1160
|
+
return {
|
|
1161
|
+
plan: site.plan,
|
|
1162
|
+
transports: [...new Set(claims.map((claim) => claim.transport.id))],
|
|
1163
|
+
inputs: [...new Set(claims.flatMap((claim) => claim.options.inputs ?? []))]
|
|
1164
|
+
};
|
|
1165
|
+
});
|
|
1166
|
+
const advice = {
|
|
1167
|
+
instance: entry.name,
|
|
1168
|
+
auth: entry.handle,
|
|
1169
|
+
context: entry.context,
|
|
1170
|
+
production: productionMode(),
|
|
1171
|
+
hasHttpAdapter: this.mounts.adapter !== null,
|
|
1172
|
+
originCheck: {
|
|
1173
|
+
mode: entry.options.originCheck?.mode ?? "cookie",
|
|
1174
|
+
missingOrigin: entry.options.originCheck?.missingOrigin ?? "reject"
|
|
1175
|
+
},
|
|
1176
|
+
handlers,
|
|
1177
|
+
transports: this.transports.list().map((transport) => ({ id: transport.id })),
|
|
1178
|
+
sources: entry.sources,
|
|
1179
|
+
policies
|
|
1180
|
+
};
|
|
1181
|
+
for (const unit of [
|
|
1182
|
+
...entry.sources,
|
|
1183
|
+
...policies,
|
|
1184
|
+
...this.transports.list(),
|
|
1185
|
+
...this.mounts.platform ? [this.mounts.platform] : []
|
|
1186
|
+
]) try {
|
|
1187
|
+
for (const item of await unit.advise?.(advice) ?? []) {
|
|
1188
|
+
const line = `${item.code}: '${entry.name}' ${unit.id}: ${item.message}${item.hint ? ` ${item.hint}` : ""}`;
|
|
1189
|
+
if (item.level === "warn") this.logger.warn(line);
|
|
1190
|
+
else this.logger.log(line);
|
|
1191
|
+
}
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
issues.push(configurationIssue(error, "EXTENSION_ADVICE_FAILED", `Advice from '${unit.id}' failed.`));
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
summary(hooks) {
|
|
1198
|
+
for (const entry of this.instances.list()) {
|
|
1199
|
+
if (entry.options.logSummary === false) continue;
|
|
1200
|
+
const mount = this.mounts.binding(entry.name);
|
|
1201
|
+
const plans = this.#plans.filter((site) => site.plan.instance === entry.name).map((site) => site.plan);
|
|
1202
|
+
const rate = entry.context.rateLimit.enabled ? "on" : entry.context.options.rateLimit?.enabled === false ? "off (rateLimit.enabled:false)" : process.env.NODE_ENV === "production" ? "off (Better Auth saw no NODE_ENV=production at import time; see W_RATE_LIMIT_DISABLED)" : `off (NODE_ENV ${process.env.NODE_ENV ?? "unset"}; Better Auth enables it for NODE_ENV=production)`;
|
|
1203
|
+
const table = hooks.get(entry.name);
|
|
1204
|
+
const hookSummary = table ? `${table.before.length} before, ${table.after.length} after, ${Object.values(table.database).reduce((count, value) => count + value.before.length + value.after.length, 0)} database` : "none";
|
|
1205
|
+
const proxy = this.mounts.adapter ? this.mounts.platform?.proxyTrust?.(this.mounts.adapter) : void 0;
|
|
1206
|
+
const defaults = `access: ${entry.options.defaultAccess ?? "authenticated"}; origin: ${entry.options.originCheck?.mode ?? "cookie"}/${entry.options.originCheck?.missingOrigin ?? "reject"}; direct-call cookies: ${entry.options.cookies?.forwardDirectCalls ? "same-credential" : "opt-in"}`;
|
|
1207
|
+
const guardOrder = this.appConfig.getGlobalGuards().map((guard) => guard.constructor.name).join(" → ");
|
|
1208
|
+
this.logger.log(`'${entry.name}': ${mount ? `mounted at ${mount.basePath} (${mount.bodyLimit} bytes)` : "not mounted"}; platform: ${this.mounts.platform?.id ?? "none"} (trust proxy: ${proxy?.mode ?? "unknown"}${proxy?.detail ? ` ${proxy.detail}` : ""}); outside global prefix '${this.appConfig.getGlobalPrefix()}'; hooks: ${hookSummary}; defaults: ${defaults}; transports: ${this.transports.list().map((unit) => unit.id).join(", ")}; principals: ${entry.sources.map((unit) => `${unit.id} (${unit.acceptance ?? "explicit"})`).join(" → ")}; handlers: ${plans.length} (${plans.filter((plan) => plan.access === "public").length} public, ${plans.filter((plan) => plan.access === "optional").length} optional, ${plans.filter((plan) => plan.access === "inherit").length} inherit, ${plans.filter((plan) => plan.skipsDefaultRequirements).length} skip defaults); environment: ${productionMode() ? "production" : "development"} (NODE_ENV ${process.env.NODE_ENV ?? "unset"}); rate limit: ${rate}; scope: ${this.#globalScope ? "global" : "off — see W_NO_GLOBAL_SCOPE"}; global guards: ${guardOrder}`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
BootValidator = __decorate([
|
|
1213
|
+
__decorateParam(0, Inject(DiscoveryService)),
|
|
1214
|
+
__decorateParam(1, Inject(MetadataScanner)),
|
|
1215
|
+
__decorateParam(2, Inject(Reflector)),
|
|
1216
|
+
__decorateParam(3, Inject(ModuleRef)),
|
|
1217
|
+
__decorateParam(4, Inject(ApplicationConfig)),
|
|
1218
|
+
__decorateParam(5, Inject(INSTANCE_REGISTRY)),
|
|
1219
|
+
__decorateParam(6, Inject(ROUTE_PLANNER)),
|
|
1220
|
+
__decorateParam(7, Inject(POLICY_RESOLVER)),
|
|
1221
|
+
__decorateParam(8, Inject(TRANSPORT_REGISTRY)),
|
|
1222
|
+
__decorateParam(9, Inject(MOUNT_COORDINATOR))
|
|
1223
|
+
], BootValidator);
|
|
1224
|
+
//#endregion
|
|
1225
|
+
//#region src/hook-binder.ts
|
|
1226
|
+
const DATABASE_TARGETS = [
|
|
1227
|
+
"user",
|
|
1228
|
+
"session",
|
|
1229
|
+
"account",
|
|
1230
|
+
"verification"
|
|
1231
|
+
].flatMap((model) => [
|
|
1232
|
+
"create",
|
|
1233
|
+
"update",
|
|
1234
|
+
"delete"
|
|
1235
|
+
].map((operation) => `${model}.${operation}`));
|
|
1236
|
+
function emptyHookTables() {
|
|
1237
|
+
return {
|
|
1238
|
+
before: [],
|
|
1239
|
+
after: [],
|
|
1240
|
+
database: Object.fromEntries(DATABASE_TARGETS.map((target) => [target, {
|
|
1241
|
+
before: [],
|
|
1242
|
+
after: []
|
|
1243
|
+
}]))
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
function distance$1(a, b) {
|
|
1247
|
+
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1248
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1249
|
+
const next = [i];
|
|
1250
|
+
for (let j = 1; j <= b.length; j++) next[j] = Math.min(next[j - 1] + 1, row[j] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
1251
|
+
row = next;
|
|
1252
|
+
}
|
|
1253
|
+
return row[b.length];
|
|
1254
|
+
}
|
|
1255
|
+
let HookBinder = class HookBinder {
|
|
1256
|
+
discovery;
|
|
1257
|
+
scanner;
|
|
1258
|
+
logger = new Logger("BetterAuth");
|
|
1259
|
+
constructor(discovery, scanner) {
|
|
1260
|
+
this.discovery = discovery;
|
|
1261
|
+
this.scanner = scanner;
|
|
1262
|
+
}
|
|
1263
|
+
discover(registry, issues) {
|
|
1264
|
+
const endpoint = /* @__PURE__ */ new Map();
|
|
1265
|
+
const database = /* @__PURE__ */ new Map();
|
|
1266
|
+
const registrations = new Set(registry.registrations().map((item) => item.name));
|
|
1267
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
1268
|
+
for (const wrapper of this.discovery.getProviders()) {
|
|
1269
|
+
if (wrapper.isAlias || !wrapper.instance || typeof wrapper.instance !== "object" && typeof wrapper.instance !== "function") continue;
|
|
1270
|
+
const instance = wrapper.instance;
|
|
1271
|
+
if (!Object.getPrototypeOf(instance)) continue;
|
|
1272
|
+
const methods = new Set(this.scanner.getAllMethodNames(instance));
|
|
1273
|
+
for (let current = instance; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) for (const key of Object.getOwnPropertySymbols(current)) if (typeof Object.getOwnPropertyDescriptor(current, key)?.value === "function") methods.add(key);
|
|
1274
|
+
let found = false;
|
|
1275
|
+
for (const method of methods) {
|
|
1276
|
+
const handler = Reflect.get(instance, method);
|
|
1277
|
+
if (typeof handler !== "function") continue;
|
|
1278
|
+
const metadata = readHookMetadata(handler);
|
|
1279
|
+
if (!metadata.hooks.length && !metadata.databaseHooks.length) continue;
|
|
1280
|
+
found = true;
|
|
1281
|
+
if (!isSingletonDependencyTree(wrapper)) {
|
|
1282
|
+
issues.push(new BetterAuthConfigurationError("NON_SINGLETON_EXTENSION", `Hook provider '${wrapper.name}' is not a static singleton.`, "Remove request/transient dependencies from hook providers."));
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
for (const declaration of metadata.hooks) {
|
|
1286
|
+
const name = declaration.options.instance ?? "default";
|
|
1287
|
+
if (!registrations.has(name)) {
|
|
1288
|
+
issues.push(new BetterAuthConfigurationError("UNKNOWN_INSTANCE", `Hook '${wrapper.name}.${String(method)}' names unknown instance '${name}'.`));
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
const entry = registry.list().find((value) => value.name === name);
|
|
1292
|
+
if (!entry) continue;
|
|
1293
|
+
const paths = Object.values(entry.instance.api).flatMap((api) => typeof api === "function" && typeof Reflect.get(api, "path") === "string" ? [Reflect.get(api, "path")] : []);
|
|
1294
|
+
const { match, options } = declaration;
|
|
1295
|
+
const patterns = typeof match === "string" ? [match] : Array.isArray(match) ? match : [];
|
|
1296
|
+
for (const pattern of patterns) if (!paths.includes(pattern)) {
|
|
1297
|
+
const closest = [...paths].sort((a, b) => distance$1(a, pattern) - distance$1(b, pattern))[0];
|
|
1298
|
+
issues.push(new BetterAuthConfigurationError("UNKNOWN_HOOK_PATH", `Hook '${wrapper.name}.${String(method)}' has unknown path '${pattern}'.`, closest ? `Did you mean '${closest}'?` : "Use an endpoint path exposed by this auth instance."));
|
|
1299
|
+
}
|
|
1300
|
+
const hook = {
|
|
1301
|
+
matches(ctx, scope) {
|
|
1302
|
+
const router = Reflect.get(ctx, "_flag") === "router";
|
|
1303
|
+
if (options.calls === "http" && !router || options.calls === "server" && router || options.skipInternal && scope?.internal) return false;
|
|
1304
|
+
if (match === void 0) return true;
|
|
1305
|
+
if (typeof match === "function") return match(ctx);
|
|
1306
|
+
return typeof match === "string" ? ctx.path === match : match.includes(ctx.path ?? "");
|
|
1307
|
+
},
|
|
1308
|
+
async run(ctx) {
|
|
1309
|
+
return handler.call(instance, ctx);
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
const list = endpoint.get(name) ?? [];
|
|
1313
|
+
list.push({
|
|
1314
|
+
phase: declaration.phase,
|
|
1315
|
+
order: options.order ?? 0,
|
|
1316
|
+
hook
|
|
1317
|
+
});
|
|
1318
|
+
endpoint.set(name, list);
|
|
1319
|
+
}
|
|
1320
|
+
for (const declaration of metadata.databaseHooks) {
|
|
1321
|
+
const name = declaration.options.instance ?? "default";
|
|
1322
|
+
if (!registrations.has(name)) {
|
|
1323
|
+
issues.push(new BetterAuthConfigurationError("UNKNOWN_INSTANCE", `Database hook '${wrapper.name}.${String(method)}' names unknown instance '${name}'.`));
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const list = database.get(name) ?? [];
|
|
1327
|
+
list.push({
|
|
1328
|
+
phase: declaration.phase,
|
|
1329
|
+
target: declaration.target,
|
|
1330
|
+
order: declaration.options.order ?? 0,
|
|
1331
|
+
hook: handler.bind(instance)
|
|
1332
|
+
});
|
|
1333
|
+
database.set(name, list);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (found) {
|
|
1337
|
+
const owner = wrapper.host?.name ?? "unknown module";
|
|
1338
|
+
const previous = discovered.get(wrapper.metatype);
|
|
1339
|
+
if (previous) this.logger.warn(`W_DUPLICATE_HOOK_PROVIDER: '${wrapper.name}' is provided in '${previous}' and '${owner}'; both registrations run.`);
|
|
1340
|
+
discovered.set(wrapper.metatype, owner);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const tables = /* @__PURE__ */ new Map();
|
|
1344
|
+
for (const name of registrations) {
|
|
1345
|
+
const hooks = (endpoint.get(name) ?? []).sort((a, b) => a.order - b.order);
|
|
1346
|
+
const db = (database.get(name) ?? []).sort((a, b) => a.order - b.order);
|
|
1347
|
+
tables.set(name, {
|
|
1348
|
+
before: hooks.filter((value) => value.phase === "before").map((value) => value.hook),
|
|
1349
|
+
after: hooks.filter((value) => value.phase === "after").map((value) => value.hook),
|
|
1350
|
+
database: Object.fromEntries(DATABASE_TARGETS.map((target) => [target, {
|
|
1351
|
+
before: db.filter((value) => value.target === target && value.phase === "before").map((value) => value.hook),
|
|
1352
|
+
after: db.filter((value) => value.target === target && value.phase === "after").map((value) => value.hook)
|
|
1353
|
+
}]))
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
return tables;
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
HookBinder = __decorate([
|
|
1360
|
+
__decorateParam(0, Inject(DiscoveryService)),
|
|
1361
|
+
__decorateParam(1, Inject(MetadataScanner)),
|
|
1362
|
+
__decorateMetadata("design:paramtypes", [typeof DiscoveryService === "undefined" ? Object : DiscoveryService, typeof MetadataScanner === "undefined" ? Object : MetadataScanner])
|
|
1363
|
+
], HookBinder);
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region src/auth-exchange.ts
|
|
1366
|
+
const HOP_HEADERS = [
|
|
1367
|
+
"connection",
|
|
1368
|
+
"keep-alive",
|
|
1369
|
+
"proxy-connection",
|
|
1370
|
+
"transfer-encoding",
|
|
1371
|
+
"te",
|
|
1372
|
+
"trailer",
|
|
1373
|
+
"upgrade",
|
|
1374
|
+
"proxy-authenticate",
|
|
1375
|
+
"proxy-authorization"
|
|
1376
|
+
];
|
|
1377
|
+
function sdkAPIError(error) {
|
|
1378
|
+
try {
|
|
1379
|
+
return isAPIError(error);
|
|
1380
|
+
} catch {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function production() {
|
|
1385
|
+
return ![
|
|
1386
|
+
"development",
|
|
1387
|
+
"dev",
|
|
1388
|
+
"test"
|
|
1389
|
+
].includes(process.env.NODE_ENV ?? "");
|
|
1390
|
+
}
|
|
1391
|
+
function distance(left, right) {
|
|
1392
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
1393
|
+
for (let row = 0; row < left.length; row++) {
|
|
1394
|
+
const current = [row + 1];
|
|
1395
|
+
for (let column = 0; column < right.length; column++) current.push(Math.min(current[column] + 1, previous[column + 1] + 1, previous[column] + Number(left[row] !== right[column])));
|
|
1396
|
+
previous = current;
|
|
1397
|
+
}
|
|
1398
|
+
return previous[right.length];
|
|
1399
|
+
}
|
|
1400
|
+
/** App-owned exchange; all platform request and response operations stay outside core. */
|
|
1401
|
+
var AuthExchange = class {
|
|
1402
|
+
scope;
|
|
1403
|
+
#warnedProxy = false;
|
|
1404
|
+
constructor(scope) {
|
|
1405
|
+
this.scope = scope;
|
|
1406
|
+
}
|
|
1407
|
+
create(entry, init) {
|
|
1408
|
+
const paths = [...new Set(Object.values(entry.instance.api).flatMap((endpoint) => {
|
|
1409
|
+
const path = Reflect.get(endpoint, "path");
|
|
1410
|
+
return typeof path === "string" ? [path] : [];
|
|
1411
|
+
}))];
|
|
1412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1413
|
+
const binding = Object.freeze({
|
|
1414
|
+
instance: entry.name,
|
|
1415
|
+
basePath: init.basePath,
|
|
1416
|
+
bodyLimit: init.bodyLimit,
|
|
1417
|
+
staticOrigin: init.staticOrigin,
|
|
1418
|
+
matches: (pathname) => init.basePath === "/" ? pathname.startsWith("/") : pathname === init.basePath || pathname.startsWith(`${init.basePath}/`),
|
|
1419
|
+
payloadTooLarge: () => Response.json({
|
|
1420
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
1421
|
+
message: "Request body exceeds the configured limit"
|
|
1422
|
+
}, { status: 413 }),
|
|
1423
|
+
handle: async (inbound) => {
|
|
1424
|
+
const response = await this.handle(entry, init, binding, inbound);
|
|
1425
|
+
if (response.status === 404 && (entry.options.http?.diagnostics ?? !production())) {
|
|
1426
|
+
const pathname = new URL(inbound.url).pathname;
|
|
1427
|
+
const key = `${inbound.method.toUpperCase()} ${pathname}`;
|
|
1428
|
+
if (binding.matches(pathname) && !seen.has(key) && seen.size < 100) {
|
|
1429
|
+
seen.add(key);
|
|
1430
|
+
const requested = pathname.slice(init.basePath === "/" ? 0 : init.basePath.length);
|
|
1431
|
+
const closest = paths.reduce((best, path) => best === void 0 || distance(requested, path) < distance(requested, best) ? path : best, void 0);
|
|
1432
|
+
const suggestion = closest ? ` Did you mean ${init.basePath === "/" ? "" : init.basePath}${closest}?` : "";
|
|
1433
|
+
init.logger.warn(`better-auth has no route for ${key} (mounted at ${init.basePath}).${suggestion}`);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return response;
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
return binding;
|
|
1440
|
+
}
|
|
1441
|
+
async handle(entry, init, binding, inbound) {
|
|
1442
|
+
const redactors = [new ErrorRedactor({
|
|
1443
|
+
secrets: [entry.context.secret],
|
|
1444
|
+
headers: inbound.headers,
|
|
1445
|
+
credentialHeaders: entry.credentialHeaders
|
|
1446
|
+
})];
|
|
1447
|
+
try {
|
|
1448
|
+
const method = inbound.method.toUpperCase();
|
|
1449
|
+
let body = null;
|
|
1450
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1451
|
+
if (inbound.body instanceof Uint8Array) {
|
|
1452
|
+
if (inbound.body.byteLength > init.bodyLimit) return binding.payloadTooLarge();
|
|
1453
|
+
body = inbound.body;
|
|
1454
|
+
} else if (inbound.body !== null) {
|
|
1455
|
+
const bounded = await readBoundedBody(inbound.body, { limit: init.bodyLimit });
|
|
1456
|
+
if (!bounded.ok) {
|
|
1457
|
+
if (bounded.reason === "too-large") return binding.payloadTooLarge();
|
|
1458
|
+
throw new Error("Authentication request body was aborted");
|
|
1459
|
+
}
|
|
1460
|
+
body = bounded.bytes;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
let url;
|
|
1464
|
+
try {
|
|
1465
|
+
url = new URL(inbound.url);
|
|
1466
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new TypeError("Invalid request URL");
|
|
1467
|
+
if (init.staticOrigin) url = new URL(`${init.staticOrigin}${url.pathname}${url.search}`);
|
|
1468
|
+
} catch {
|
|
1469
|
+
return Response.json({
|
|
1470
|
+
code: "INVALID_REQUEST_URL",
|
|
1471
|
+
message: "Invalid request URL"
|
|
1472
|
+
}, { status: 400 });
|
|
1473
|
+
}
|
|
1474
|
+
const headers = new Headers(inbound.headers);
|
|
1475
|
+
const nominated = (headers.get("connection") ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
1476
|
+
for (const name of [...HOP_HEADERS, ...nominated]) headers.delete(name);
|
|
1477
|
+
const ipHeader = entry.bridge.clientIpHeader;
|
|
1478
|
+
for (const name of [...headers.keys()]) if (name.startsWith("x-nsba-ip-") || name === ipHeader?.toLowerCase()) headers.delete(name);
|
|
1479
|
+
if (ipHeader && inbound.clientIp !== null) headers.set(ipHeader, inbound.clientIp);
|
|
1480
|
+
this.warnProxy(entry, init, inbound);
|
|
1481
|
+
const request = new Request(url, {
|
|
1482
|
+
method,
|
|
1483
|
+
headers,
|
|
1484
|
+
body: body === null ? null : Uint8Array.from(body),
|
|
1485
|
+
signal: inbound.signal
|
|
1486
|
+
});
|
|
1487
|
+
const interceptors = entry.options.http?.around ?? [];
|
|
1488
|
+
const dispatch = (index, current) => {
|
|
1489
|
+
redactors.push(new ErrorRedactor({
|
|
1490
|
+
headers: current.headers,
|
|
1491
|
+
credentialHeaders: entry.credentialHeaders
|
|
1492
|
+
}));
|
|
1493
|
+
const interceptor = interceptors[index];
|
|
1494
|
+
return interceptor ? interceptor({
|
|
1495
|
+
request: current,
|
|
1496
|
+
platformRequest: inbound.platformRequest,
|
|
1497
|
+
instance: entry.name
|
|
1498
|
+
}, (replacement = current) => dispatch(index + 1, replacement)) : this.scope.exit(() => entry.instance.handler(current));
|
|
1499
|
+
};
|
|
1500
|
+
return await dispatch(0, request);
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
if (sdkAPIError(error)) {
|
|
1503
|
+
if (entry.context.options.onAPIError?.throw) throw error;
|
|
1504
|
+
return Response.json(error.body ?? { message: error.message }, {
|
|
1505
|
+
status: error.statusCode,
|
|
1506
|
+
headers: getAPIErrorHeaders(error)
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
let cause = error;
|
|
1510
|
+
for (const redactor of redactors) cause = redactor.redact(cause);
|
|
1511
|
+
const wrapped = createInfrastructureError(cause);
|
|
1512
|
+
if (entry.options.errors?.exposeRawCause) {
|
|
1513
|
+
const raw = createInfrastructureError(error, { exposeRawCause: true });
|
|
1514
|
+
Object.defineProperty(raw, "cause", { value: wrapped.cause });
|
|
1515
|
+
throw raw;
|
|
1516
|
+
}
|
|
1517
|
+
throw wrapped;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
warnProxy(entry, init, inbound) {
|
|
1521
|
+
if (this.#warnedProxy || !production() || init.proxyTrust?.mode !== "none" || ![
|
|
1522
|
+
"x-forwarded-for",
|
|
1523
|
+
"forwarded",
|
|
1524
|
+
"x-real-ip"
|
|
1525
|
+
].some((name) => inbound.headers.has(name))) return;
|
|
1526
|
+
this.#warnedProxy = true;
|
|
1527
|
+
const ip = entry.context.options.advanced?.ipAddress;
|
|
1528
|
+
const bridgeHeader = entry.bridge.clientIpHeader;
|
|
1529
|
+
const bridgeHeaderFirst = bridgeHeader !== null && ip?.ipAddressHeaders?.[0]?.toLowerCase() === bridgeHeader.toLowerCase();
|
|
1530
|
+
let effect;
|
|
1531
|
+
if (!entry.context.rateLimit.enabled) {
|
|
1532
|
+
if (bridgeHeader === null) effect = "Rate limiting is disabled; the bridge does not insert this socket IP, so better-auth's configured IP resolution determines the session IP.";
|
|
1533
|
+
else if (!bridgeHeaderFirst) effect = "Rate limiting is disabled; a configured better-auth IP header precedes the bridge header, so this socket IP is not necessarily recorded on sessions.";
|
|
1534
|
+
else effect = "Rate limiting is disabled; this IP is recorded on sessions only.";
|
|
1535
|
+
} else if (bridgeHeader === null) effect = "The bridge does not insert this socket IP; better-auth's configured IP resolution determines the rate-limit key.";
|
|
1536
|
+
else if (!bridgeHeaderFirst) effect = "A configured better-auth IP header precedes the bridge header, so this socket IP is not necessarily the rate-limit key.";
|
|
1537
|
+
else if (ip?.trustedProxies?.length) effect = "If better-auth trustedProxies covers this address, no client IP remains and all clients share one rate-limit bucket. Otherwise this socket IP is the rate-limit key.";
|
|
1538
|
+
else effect = "This socket IP is the rate-limit key, so clients behind the proxy share its bucket.";
|
|
1539
|
+
init.logger.warn(`W_PROXY_UNTRUSTED: platform resolved socket IP ${inbound.clientIp ?? "unknown"}. ${effect} Configure platform proxy trust (${init.proxyTrust.detail}).`);
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
/** Credentialed CORS for the auth mount, using the initialized SDK trusted origins. */
|
|
1543
|
+
function betterAuthCorsOrigin(auth, options) {
|
|
1544
|
+
const context = async () => await auth.$context;
|
|
1545
|
+
const trusted = new TrustedOrigins(context);
|
|
1546
|
+
return (origin, callback) => {
|
|
1547
|
+
(async () => {
|
|
1548
|
+
if (typeof (await context()).options.trustedOrigins === "function") throw new BetterAuthConfigurationError("DYNAMIC_CORS_ORIGIN", "Function-valued trustedOrigins needs a request that a CORS origin callback cannot provide", "Supply a request-aware CORS origin function instead of betterAuthCorsOrigin.");
|
|
1549
|
+
if (!origin) return false;
|
|
1550
|
+
if (options?.allowPatterns) return trusted.isTrusted(origin);
|
|
1551
|
+
return (await trusted.list()).some((candidate) => {
|
|
1552
|
+
try {
|
|
1553
|
+
const parsed = new URL(candidate);
|
|
1554
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && !candidate.includes("*") && candidate === parsed.origin && origin === candidate;
|
|
1555
|
+
} catch {
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
})().then((allow) => callback(null, allow), (error) => callback(error instanceof Error ? error : /* @__PURE__ */ new Error("Failed to resolve trusted CORS origins")));
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
//#endregion
|
|
1563
|
+
//#region src/mount-coordinator.ts
|
|
1564
|
+
const MOUNTED = Symbol.for("nestjs-slightly-better-auth:mounted");
|
|
1565
|
+
function parseBodyLimit(input) {
|
|
1566
|
+
if (input === void 0) return 1048576;
|
|
1567
|
+
const match = typeof input === "string" ? /^(\d+(?:\.\d+)?)(b|kb|mb)$/i.exec(input) : null;
|
|
1568
|
+
const value = typeof input === "number" ? input : match ? Number(match[1]) * ({
|
|
1569
|
+
b: 1,
|
|
1570
|
+
kb: 1024,
|
|
1571
|
+
mb: 1048576
|
|
1572
|
+
}[match[2].toLowerCase()] ?? 0) : NaN;
|
|
1573
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new BetterAuthConfigurationError("INVALID_BODY_LIMIT", `Invalid auth body limit '${input}'.`, "Use a nonnegative byte count or a value such as '1mb'.");
|
|
1574
|
+
return value;
|
|
1575
|
+
}
|
|
1576
|
+
let MountCoordinator = class MountCoordinator {
|
|
1577
|
+
host;
|
|
1578
|
+
registry;
|
|
1579
|
+
logger = new Logger("BetterAuth");
|
|
1580
|
+
#exchange;
|
|
1581
|
+
#bindings = /* @__PURE__ */ new Map();
|
|
1582
|
+
#platforms;
|
|
1583
|
+
#prepared = null;
|
|
1584
|
+
#selected;
|
|
1585
|
+
#preparationErrors = [];
|
|
1586
|
+
constructor(host, registry, scope) {
|
|
1587
|
+
this.host = host;
|
|
1588
|
+
this.registry = registry;
|
|
1589
|
+
this.#exchange = new AuthExchange(scope);
|
|
1590
|
+
host.init$.subscribe(() => this.tryPrepare(false));
|
|
1591
|
+
}
|
|
1592
|
+
registerPlatforms(platforms) {
|
|
1593
|
+
this.#platforms = platforms;
|
|
1594
|
+
this.tryPrepare(false);
|
|
1595
|
+
}
|
|
1596
|
+
get platform() {
|
|
1597
|
+
return this.#selected;
|
|
1598
|
+
}
|
|
1599
|
+
get adapter() {
|
|
1600
|
+
return this.host.httpAdapter ?? null;
|
|
1601
|
+
}
|
|
1602
|
+
tryPrepare(atInit) {
|
|
1603
|
+
const adapter = this.adapter;
|
|
1604
|
+
if (!adapter || !this.#platforms || this.#prepared === adapter) return;
|
|
1605
|
+
try {
|
|
1606
|
+
if (this.registry.state !== "new" && this.registry.adapter !== adapter) throw new BetterAuthConfigurationError("LATE_HTTP_ADAPTER", "The HTTP adapter changed while the application is active.", "Close the first application before starting another.");
|
|
1607
|
+
const supported = this.#platforms.filter((platform) => platform.supports(adapter));
|
|
1608
|
+
if (supported.length !== 1) throw new BetterAuthConfigurationError(supported.length ? "AMBIGUOUS_PLATFORM" : "NO_PLATFORM", `Expected exactly one supporting HTTP platform; found ${supported.length}.`, "Register the platform for your HTTP adapter on the default instance.");
|
|
1609
|
+
const platform = supported[0];
|
|
1610
|
+
if (atInit && this.#prepared !== null && !platform.capabilities?.prepareAtInit) throw new BetterAuthConfigurationError("APP_ADAPTER_CHANGED", "The application uses a different HTTP adapter after preparation.", "Compile a new TestingModule for each application (or use NestFactory).");
|
|
1611
|
+
platform.prepare?.({
|
|
1612
|
+
adapter,
|
|
1613
|
+
logger: this.logger,
|
|
1614
|
+
route: (pathname) => this.route(pathname)
|
|
1615
|
+
});
|
|
1616
|
+
this.#selected = platform;
|
|
1617
|
+
this.#prepared = adapter;
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
this.#preparationErrors.push(configurationIssue(error, "PLATFORM_PREPARATION_FAILED", "HTTP platform preparation failed."));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
prepareAtInit(issues) {
|
|
1623
|
+
this.tryPrepare(true);
|
|
1624
|
+
issues.push(...this.#preparationErrors);
|
|
1625
|
+
this.#preparationErrors = [];
|
|
1626
|
+
if (this.adapter && this.#selected) try {
|
|
1627
|
+
this.#selected.validate?.(this.adapter);
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
issues.push(configurationIssue(error, "PLATFORM_VALIDATION_FAILED", "HTTP platform validation failed."));
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
resolve(issues) {
|
|
1633
|
+
this.#bindings.clear();
|
|
1634
|
+
const adapter = this.adapter;
|
|
1635
|
+
if (!adapter || !this.#selected) return;
|
|
1636
|
+
const server = adapter.getInstance();
|
|
1637
|
+
const mounted = Reflect.get(server, MOUNTED);
|
|
1638
|
+
for (const entry of this.registry.list()) {
|
|
1639
|
+
if (entry.options.http?.mount === false) continue;
|
|
1640
|
+
try {
|
|
1641
|
+
const raw = entry.context.baseURL ? new URL(entry.context.baseURL).pathname : entry.context.options.basePath || "/api/auth";
|
|
1642
|
+
const basePath = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
|
|
1643
|
+
if (!basePath.startsWith("/") || basePath === "/" && !entry.options.http?.allowRootMount) throw new BetterAuthConfigurationError("INVALID_MOUNT_PATH", `Invalid auth mount path '${basePath}'.`, "Use an absolute path; opt into root mounting with http.allowRootMount.");
|
|
1644
|
+
if (mounted?.has(basePath)) throw new BetterAuthConfigurationError("DUPLICATE_MOUNT", `Auth path '${basePath}' is already mounted on this server.`, "Create a fresh application and adapter.");
|
|
1645
|
+
for (const existing of this.#bindings.values()) if (basePath === "/" || existing.basePath === "/" || existing.matches(basePath) || basePath.startsWith(`${existing.basePath}/`) || existing.basePath.startsWith(`${basePath}/`)) throw new BetterAuthConfigurationError("OVERLAPPING_MOUNTS", `Mounts '${existing.basePath}' and '${basePath}' overlap.`, "Use separate basePath values for named instances.");
|
|
1646
|
+
this.#bindings.set(entry.name, this.#exchange.create(entry, {
|
|
1647
|
+
basePath,
|
|
1648
|
+
bodyLimit: parseBodyLimit(entry.options.http?.bodyLimit),
|
|
1649
|
+
staticOrigin: entry.context.baseURL ? new URL(entry.context.baseURL).origin : void 0,
|
|
1650
|
+
logger: this.logger,
|
|
1651
|
+
proxyTrust: this.#selected.proxyTrust?.(adapter)
|
|
1652
|
+
}));
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
issues.push(configurationIssue(error, "INVALID_MOUNT_PATH", `Cannot resolve mount for '${entry.name}'.`));
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
route(pathname) {
|
|
1659
|
+
return [...this.#bindings.values()].find((binding) => binding.matches(pathname));
|
|
1660
|
+
}
|
|
1661
|
+
binding(name) {
|
|
1662
|
+
return this.#bindings.get(name);
|
|
1663
|
+
}
|
|
1664
|
+
bindings() {
|
|
1665
|
+
return [...this.#bindings.values()];
|
|
1666
|
+
}
|
|
1667
|
+
normalizeApplicationRoute(path) {
|
|
1668
|
+
const adapter = this.adapter;
|
|
1669
|
+
return adapter && typeof adapter.normalizePath === "function" ? adapter.normalizePath(path) : path;
|
|
1670
|
+
}
|
|
1671
|
+
registerApplicationRoutes(routes, issues) {
|
|
1672
|
+
try {
|
|
1673
|
+
this.#selected?.applicationRoutes?.(routes, Object.freeze([...this.#bindings.values()]));
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
issues.push(configurationIssue(error, "APPLICATION_ROUTE_REGISTRATION_FAILED", "Cannot register application routes with the HTTP platform."));
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async mount() {
|
|
1679
|
+
const adapter = this.adapter;
|
|
1680
|
+
const platform = this.#selected;
|
|
1681
|
+
if (!adapter || !platform) return;
|
|
1682
|
+
const server = adapter.getInstance();
|
|
1683
|
+
let mounted = Reflect.get(server, MOUNTED);
|
|
1684
|
+
if (!mounted) {
|
|
1685
|
+
mounted = /* @__PURE__ */ new Set();
|
|
1686
|
+
Object.defineProperty(server, MOUNTED, { value: mounted });
|
|
1687
|
+
}
|
|
1688
|
+
for (const binding of this.#bindings.values()) {
|
|
1689
|
+
if (mounted.has(binding.basePath)) throw new BetterAuthConfigurationError("DUPLICATE_MOUNT", `Auth path '${binding.basePath}' is already mounted.`);
|
|
1690
|
+
await platform.mount({
|
|
1691
|
+
adapter,
|
|
1692
|
+
logger: this.logger,
|
|
1693
|
+
binding
|
|
1694
|
+
});
|
|
1695
|
+
mounted.add(binding.basePath);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
reset() {
|
|
1699
|
+
this.#bindings.clear();
|
|
1700
|
+
this.#preparationErrors = [];
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
MountCoordinator = __decorate([
|
|
1704
|
+
__decorateParam(0, Inject(HttpAdapterHost)),
|
|
1705
|
+
__decorateParam(1, Inject(INSTANCE_REGISTRY)),
|
|
1706
|
+
__decorateParam(2, Inject(REQUEST_SCOPE)),
|
|
1707
|
+
__decorateMetadata("design:paramtypes", [
|
|
1708
|
+
typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost,
|
|
1709
|
+
Object,
|
|
1710
|
+
Object
|
|
1711
|
+
])
|
|
1712
|
+
], MountCoordinator);
|
|
1713
|
+
//#endregion
|
|
1714
|
+
//#region src/policy-resolver.ts
|
|
1715
|
+
var PolicyResolver = class {
|
|
1716
|
+
moduleRef;
|
|
1717
|
+
cache = /* @__PURE__ */ new Map();
|
|
1718
|
+
constructor(moduleRef) {
|
|
1719
|
+
this.moduleRef = moduleRef;
|
|
1720
|
+
}
|
|
1721
|
+
resolve(reference) {
|
|
1722
|
+
const cached = this.cache.get(reference);
|
|
1723
|
+
if (cached) return cached;
|
|
1724
|
+
let policy;
|
|
1725
|
+
try {
|
|
1726
|
+
policy = typeof reference === "object" && reference !== null && "evaluate" in reference ? reference : this.moduleRef.get(reference, { strict: false });
|
|
1727
|
+
} catch {
|
|
1728
|
+
throw new BetterAuthConfigurationError("UNRESOLVED_POLICY", "A requirement references a policy that is not registered as a provider");
|
|
1729
|
+
}
|
|
1730
|
+
if (!policy || typeof policy.evaluate !== "function" || typeof policy.id !== "string") throw new BetterAuthConfigurationError("UNRESOLVED_POLICY", "A requirement must reference an authorization policy");
|
|
1731
|
+
this.cache.set(reference, policy);
|
|
1732
|
+
return policy;
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
//#endregion
|
|
1736
|
+
//#region src/transport-registry.ts
|
|
1737
|
+
var TransportRegistry = class {
|
|
1738
|
+
transports = [];
|
|
1739
|
+
owners = /* @__PURE__ */ new WeakMap();
|
|
1740
|
+
kit = { http: null };
|
|
1741
|
+
register(transports) {
|
|
1742
|
+
this.transports = [...transports];
|
|
1743
|
+
}
|
|
1744
|
+
setKit(kit) {
|
|
1745
|
+
this.kit = kit;
|
|
1746
|
+
}
|
|
1747
|
+
list() {
|
|
1748
|
+
return [...this.transports];
|
|
1749
|
+
}
|
|
1750
|
+
defaultAccessFor(target, method) {
|
|
1751
|
+
for (const transport of this.transports) {
|
|
1752
|
+
const answer = transport.defaultAccessFor?.(target, method);
|
|
1753
|
+
if (answer !== void 0) return answer;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
find(context) {
|
|
1757
|
+
return this.transports.find((unit) => unit.handles(context));
|
|
1758
|
+
}
|
|
1759
|
+
select(context) {
|
|
1760
|
+
const transport = this.find(context);
|
|
1761
|
+
if (!transport) throw BetterAuthConfigurationError.atRequest("NO_TRANSPORT", "No registered transport handles this execution context");
|
|
1762
|
+
return transport;
|
|
1763
|
+
}
|
|
1764
|
+
idOf(call) {
|
|
1765
|
+
return this.owners.get(call) ?? "unknown";
|
|
1766
|
+
}
|
|
1767
|
+
describe(context, transport = this.select(context)) {
|
|
1768
|
+
const call = transport.describe(context, this.kit);
|
|
1769
|
+
this.owners.set(call, transport.id);
|
|
1770
|
+
return call;
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
//#endregion
|
|
1774
|
+
//#region src/auth-core-module.ts
|
|
1775
|
+
const EVALUATOR = Symbol("authorization-evaluator");
|
|
1776
|
+
const exports = [
|
|
1777
|
+
INSTANCE_REGISTRY,
|
|
1778
|
+
REQUEST_SCOPE,
|
|
1779
|
+
PRINCIPAL_READINGS,
|
|
1780
|
+
MOUNT_COORDINATOR,
|
|
1781
|
+
POLICY_RESOLVER,
|
|
1782
|
+
ROUTE_PLANNER,
|
|
1783
|
+
TRANSPORT_REGISTRY,
|
|
1784
|
+
PRINCIPAL_RESOLVER,
|
|
1785
|
+
POLICY_INVOKER,
|
|
1786
|
+
GUARD_CORE,
|
|
1787
|
+
SCOPE_CORE,
|
|
1788
|
+
BetterAuthGuard,
|
|
1789
|
+
BetterAuthScopeInterceptor
|
|
1790
|
+
];
|
|
1791
|
+
let BetterAuthCoreModule = class BetterAuthCoreModule {
|
|
1792
|
+
instances;
|
|
1793
|
+
scope;
|
|
1794
|
+
transports;
|
|
1795
|
+
mounts;
|
|
1796
|
+
hooks;
|
|
1797
|
+
bridges;
|
|
1798
|
+
validator;
|
|
1799
|
+
#initialization;
|
|
1800
|
+
#ready = false;
|
|
1801
|
+
constructor(instances, scope, transports, mounts, hooks, bridges, validator) {
|
|
1802
|
+
this.instances = instances;
|
|
1803
|
+
this.scope = scope;
|
|
1804
|
+
this.transports = transports;
|
|
1805
|
+
this.mounts = mounts;
|
|
1806
|
+
this.hooks = hooks;
|
|
1807
|
+
this.bridges = bridges;
|
|
1808
|
+
this.validator = validator;
|
|
1809
|
+
}
|
|
1810
|
+
onModuleInit() {
|
|
1811
|
+
if (!this.instances.begin(this.mounts.adapter)) return this.#initialization ?? Promise.resolve();
|
|
1812
|
+
this.#initialization = this.initialize();
|
|
1813
|
+
return this.#initialization;
|
|
1814
|
+
}
|
|
1815
|
+
async initialize() {
|
|
1816
|
+
const issues = [];
|
|
1817
|
+
await this.instances.initialize(issues);
|
|
1818
|
+
const hooks = this.hooks.discover(this.instances, issues);
|
|
1819
|
+
for (const entry of this.instances.list()) {
|
|
1820
|
+
const tables = hooks.get(entry.name) ?? emptyHookTables();
|
|
1821
|
+
const unbound = entry.bridge.unboundDispatches;
|
|
1822
|
+
const count = tables.before.length + tables.after.length + Object.values(tables.database).reduce((sum, value) => sum + value.before.length + value.after.length, 0);
|
|
1823
|
+
try {
|
|
1824
|
+
const result = this.bridges.bind(entry, {
|
|
1825
|
+
owner: this.instances.owner,
|
|
1826
|
+
instance: entry.name,
|
|
1827
|
+
state: "initialized",
|
|
1828
|
+
current: () => this.scope.current()?.view,
|
|
1829
|
+
credentialHeaders: entry.credentialHeaders,
|
|
1830
|
+
...tables,
|
|
1831
|
+
onDropped: (path) => this.instances.logger.debug(`Direct auth call '${path}' used foreign credentials; cookies dropped for '${entry.name}'.`)
|
|
1832
|
+
});
|
|
1833
|
+
if (result.tookOverFrom) this.instances.logger.warn(`W_INSTANCE_TAKEN_OVER: '${entry.name}' took over from never-bootstrapped '${result.tookOverFrom}'.`);
|
|
1834
|
+
if (unbound > 0 && count > 0) this.instances.logger.warn(`W_CALLS_BEFORE_BIND: '${entry.name}': ${unbound} auth dispatches or database writes ran before Nest hooks; move seeding to onApplicationBootstrap.`);
|
|
1835
|
+
} catch (error) {
|
|
1836
|
+
issues.push(configurationIssue(error, "INSTANCE_ALREADY_BOUND", `Cannot bind '${entry.name}' to this application.`));
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
this.mounts.prepareAtInit(issues);
|
|
1840
|
+
this.mounts.resolve(issues);
|
|
1841
|
+
this.transports.setKit({ http: this.mounts.platform?.requests ?? null });
|
|
1842
|
+
await this.validator.validate(issues);
|
|
1843
|
+
if (issues.length) {
|
|
1844
|
+
const failure = new BetterAuthConfigurationError("AUTH_BOOT_FAILED", `Authentication bootstrap failed:\n${issues.map((issue) => `[${issue.code}] ${issue.detail}${issue.hint ? ` Hint: ${issue.hint}` : ""}`).join("\n")}`, "Resolve every reported configuration problem before starting the application.");
|
|
1845
|
+
Object.defineProperty(failure, "issues", {
|
|
1846
|
+
value: Object.freeze([...issues]),
|
|
1847
|
+
enumerable: true
|
|
1848
|
+
});
|
|
1849
|
+
throw failure;
|
|
1850
|
+
}
|
|
1851
|
+
this.validator.summary(hooks);
|
|
1852
|
+
await this.mounts.mount();
|
|
1853
|
+
this.#ready = true;
|
|
1854
|
+
}
|
|
1855
|
+
onApplicationBootstrap() {
|
|
1856
|
+
if (this.#ready && this.instances.state !== "bootstrapped") {
|
|
1857
|
+
this.bridges.bootstrap();
|
|
1858
|
+
this.instances.state = "bootstrapped";
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
onApplicationShutdown() {
|
|
1862
|
+
this.bridges.close();
|
|
1863
|
+
this.instances.reset();
|
|
1864
|
+
this.mounts.reset();
|
|
1865
|
+
this.#initialization = void 0;
|
|
1866
|
+
this.#ready = false;
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
BetterAuthCoreModule = __decorate([
|
|
1870
|
+
Global(),
|
|
1871
|
+
Module({
|
|
1872
|
+
imports: [DiscoveryModule],
|
|
1873
|
+
providers: [
|
|
1874
|
+
MetadataScanner,
|
|
1875
|
+
{
|
|
1876
|
+
provide: REQUEST_SCOPE,
|
|
1877
|
+
useFactory: () => new RequestScope()
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
provide: INSTANCE_REGISTRY,
|
|
1881
|
+
inject: [REQUEST_SCOPE],
|
|
1882
|
+
useFactory: (scope) => new InstanceRegistry(scope)
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
provide: PRINCIPAL_READINGS,
|
|
1886
|
+
inject: [REQUEST_SCOPE],
|
|
1887
|
+
useFactory: (scope) => new PrincipalReadings(scope)
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
provide: TRANSPORT_REGISTRY,
|
|
1891
|
+
useFactory: () => new TransportRegistry()
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
provide: POLICY_RESOLVER,
|
|
1895
|
+
inject: [ModuleRef],
|
|
1896
|
+
useFactory: (ref) => new PolicyResolver(ref)
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
provide: ROUTE_PLANNER,
|
|
1900
|
+
inject: [
|
|
1901
|
+
INSTANCE_REGISTRY,
|
|
1902
|
+
POLICY_RESOLVER,
|
|
1903
|
+
TRANSPORT_REGISTRY
|
|
1904
|
+
],
|
|
1905
|
+
useFactory: (instances, policies, transports) => new RoutePlanner(instances, policies, transports)
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
provide: MOUNT_COORDINATOR,
|
|
1909
|
+
inject: [
|
|
1910
|
+
HttpAdapterHost,
|
|
1911
|
+
INSTANCE_REGISTRY,
|
|
1912
|
+
REQUEST_SCOPE
|
|
1913
|
+
],
|
|
1914
|
+
useFactory: (host, instances, scope) => new MountCoordinator(host, instances, scope)
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
provide: PRINCIPAL_RESOLVER,
|
|
1918
|
+
inject: [
|
|
1919
|
+
INSTANCE_REGISTRY,
|
|
1920
|
+
REQUEST_SCOPE,
|
|
1921
|
+
TRANSPORT_REGISTRY
|
|
1922
|
+
],
|
|
1923
|
+
useFactory: (instances, scope, transports) => new ChainPrincipalResolver(instances, scope, transports)
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
provide: POLICY_INVOKER,
|
|
1927
|
+
useFactory: () => new DefaultPolicyInvoker()
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
provide: EVALUATOR,
|
|
1931
|
+
inject: [
|
|
1932
|
+
POLICY_RESOLVER,
|
|
1933
|
+
POLICY_INVOKER,
|
|
1934
|
+
PRINCIPAL_RESOLVER,
|
|
1935
|
+
REQUEST_SCOPE
|
|
1936
|
+
],
|
|
1937
|
+
useFactory: (policies, invoker, resolver, scope) => new AuthorizationEvaluator(policies, invoker, resolver, scope)
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
provide: GUARD_CORE,
|
|
1941
|
+
inject: [
|
|
1942
|
+
INSTANCE_REGISTRY,
|
|
1943
|
+
ROUTE_PLANNER,
|
|
1944
|
+
TRANSPORT_REGISTRY,
|
|
1945
|
+
PRINCIPAL_RESOLVER,
|
|
1946
|
+
EVALUATOR,
|
|
1947
|
+
REQUEST_SCOPE,
|
|
1948
|
+
PRINCIPAL_READINGS
|
|
1949
|
+
],
|
|
1950
|
+
useFactory: (instances, planner, transports, resolver, evaluator, scope, readings) => new GuardCore(instances, planner, transports, resolver, evaluator, scope, readings)
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
provide: SCOPE_CORE,
|
|
1954
|
+
inject: [
|
|
1955
|
+
INSTANCE_REGISTRY,
|
|
1956
|
+
ROUTE_PLANNER,
|
|
1957
|
+
TRANSPORT_REGISTRY,
|
|
1958
|
+
REQUEST_SCOPE,
|
|
1959
|
+
PRINCIPAL_READINGS
|
|
1960
|
+
],
|
|
1961
|
+
useFactory: (instances, planner, transports, scope, readings) => new ScopeCore(instances, planner, transports, scope, readings)
|
|
1962
|
+
},
|
|
1963
|
+
BetterAuthGuard,
|
|
1964
|
+
BetterAuthScopeInterceptor,
|
|
1965
|
+
HookBinder,
|
|
1966
|
+
BridgeClient,
|
|
1967
|
+
{
|
|
1968
|
+
provide: BootValidator,
|
|
1969
|
+
inject: [
|
|
1970
|
+
DiscoveryService,
|
|
1971
|
+
MetadataScanner,
|
|
1972
|
+
Reflector,
|
|
1973
|
+
ModuleRef,
|
|
1974
|
+
ApplicationConfig,
|
|
1975
|
+
INSTANCE_REGISTRY,
|
|
1976
|
+
ROUTE_PLANNER,
|
|
1977
|
+
POLICY_RESOLVER,
|
|
1978
|
+
TRANSPORT_REGISTRY,
|
|
1979
|
+
MOUNT_COORDINATOR
|
|
1980
|
+
],
|
|
1981
|
+
useFactory: (discovery, scanner, reflector, moduleRef, appConfig, instances, planner, policies, transports, mounts) => new BootValidator(discovery, scanner, reflector, moduleRef, appConfig, instances, planner, policies, transports, mounts)
|
|
1982
|
+
}
|
|
1983
|
+
],
|
|
1984
|
+
exports
|
|
1985
|
+
}),
|
|
1986
|
+
__decorateParam(0, Inject(INSTANCE_REGISTRY)),
|
|
1987
|
+
__decorateParam(1, Inject(REQUEST_SCOPE)),
|
|
1988
|
+
__decorateParam(2, Inject(TRANSPORT_REGISTRY)),
|
|
1989
|
+
__decorateParam(3, Inject(MOUNT_COORDINATOR)),
|
|
1990
|
+
__decorateParam(4, Inject(HookBinder)),
|
|
1991
|
+
__decorateParam(5, Inject(BridgeClient)),
|
|
1992
|
+
__decorateParam(6, Inject(BootValidator)),
|
|
1993
|
+
__decorateMetadata("design:paramtypes", [
|
|
1994
|
+
typeof InstanceRegistry === "undefined" ? Object : InstanceRegistry,
|
|
1995
|
+
typeof RequestScope === "undefined" ? Object : RequestScope,
|
|
1996
|
+
typeof TransportRegistry === "undefined" ? Object : TransportRegistry,
|
|
1997
|
+
typeof MountCoordinator === "undefined" ? Object : MountCoordinator,
|
|
1998
|
+
typeof HookBinder === "undefined" ? Object : HookBinder,
|
|
1999
|
+
typeof BridgeClient === "undefined" ? Object : BridgeClient,
|
|
2000
|
+
typeof BootValidator === "undefined" ? Object : BootValidator
|
|
2001
|
+
])
|
|
2002
|
+
], BetterAuthCoreModule);
|
|
2003
|
+
//#endregion
|
|
2004
|
+
//#region src/session-principal.ts
|
|
2005
|
+
const SESSION_PRINCIPAL_KIND = "session";
|
|
2006
|
+
const CurrentSession = definePrincipalParam({
|
|
2007
|
+
kind: SESSION_PRINCIPAL_KIND,
|
|
2008
|
+
reason: "SESSION_REQUIRED",
|
|
2009
|
+
project: (principal) => principal.session
|
|
2010
|
+
});
|
|
2011
|
+
const CurrentUser = definePrincipalParam({
|
|
2012
|
+
kind: SESSION_PRINCIPAL_KIND,
|
|
2013
|
+
reason: "SESSION_REQUIRED",
|
|
2014
|
+
project: (principal) => "user" in principal.session ? principal.session.user : void 0
|
|
2015
|
+
});
|
|
2016
|
+
function readCurrentSession(readings) {
|
|
2017
|
+
return readings.project(readings.current(), {
|
|
2018
|
+
kind: SESSION_PRINCIPAL_KIND,
|
|
2019
|
+
reason: "SESSION_REQUIRED",
|
|
2020
|
+
site: "BetterAuthService.getSession",
|
|
2021
|
+
project: (principal) => principal.session
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
function sessionPrincipal(options = {}) {
|
|
2025
|
+
return {
|
|
2026
|
+
id: "better-auth:session",
|
|
2027
|
+
kinds: [SESSION_PRINCIPAL_KIND],
|
|
2028
|
+
acceptance: "default",
|
|
2029
|
+
credentialHeaders: ["cookie", "authorization"],
|
|
2030
|
+
sessionBacked: true,
|
|
2031
|
+
async resolve(request) {
|
|
2032
|
+
const context = await request.auth.context();
|
|
2033
|
+
const authoritative = request.freshness === "authoritative";
|
|
2034
|
+
const query = {
|
|
2035
|
+
...authoritative && (context.options.database || context.options.secondaryStorage) ? { disableCookieCache: true } : {},
|
|
2036
|
+
...request.cookies === null ? { disableRefresh: true } : {}
|
|
2037
|
+
};
|
|
2038
|
+
let result;
|
|
2039
|
+
try {
|
|
2040
|
+
result = await request.auth.run({
|
|
2041
|
+
cookies: request.cookies,
|
|
2042
|
+
forward: "none",
|
|
2043
|
+
internal: true
|
|
2044
|
+
}, () => request.auth.api.getSession({
|
|
2045
|
+
headers: request.headers,
|
|
2046
|
+
returnHeaders: true,
|
|
2047
|
+
query
|
|
2048
|
+
}));
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
const failure = AuthFailures.fromAPIError(error);
|
|
2051
|
+
if (!failure) throw error;
|
|
2052
|
+
const cookies = getAPIErrorHeaders(error).getSetCookie();
|
|
2053
|
+
if (cookies.length) request.cookies?.append(cookies);
|
|
2054
|
+
return rejected(failure);
|
|
2055
|
+
}
|
|
2056
|
+
const cookies = result.headers?.getSetCookie() ?? [];
|
|
2057
|
+
if (cookies.length) request.cookies?.append(cookies);
|
|
2058
|
+
if (result.response == null || authoritative && !request.auth.producedByEndpoint(result.response)) return absent();
|
|
2059
|
+
const userId = options.userId ? options.userId(result.response) : result.response.user?.id ?? null;
|
|
2060
|
+
if (typeof userId !== "string" || !userId) throw BetterAuthConfigurationError.atRequest("SESSION_USER_ID", "session.userId must return a nonempty user id for a resolved session");
|
|
2061
|
+
return authenticated({
|
|
2062
|
+
kind: SESSION_PRINCIPAL_KIND,
|
|
2063
|
+
source: "better-auth:session",
|
|
2064
|
+
userId,
|
|
2065
|
+
session: result.response
|
|
2066
|
+
});
|
|
2067
|
+
},
|
|
2068
|
+
advise(context) {
|
|
2069
|
+
const warnings = [];
|
|
2070
|
+
if (context.context.hasPlugin("jwt")) warnings.push({
|
|
2071
|
+
level: "info",
|
|
2072
|
+
code: "I_SESSION_JWT_COST",
|
|
2073
|
+
message: "Unless disableSettingJwtHeader is enabled, session reads may also sign a JWT."
|
|
2074
|
+
});
|
|
2075
|
+
if (context.context.hasPlugin("bearer")) warnings.push({
|
|
2076
|
+
level: "info",
|
|
2077
|
+
code: "I_SESSION_BEARER_COST",
|
|
2078
|
+
message: "Without requireSignature, foreign bearer tokens can trigger a session lookup."
|
|
2079
|
+
});
|
|
2080
|
+
const sites = context.handlers.filter(({ plan }) => (plan.access === "required" || plan.access === "optional") && [...plan.accepts].some((kind) => kind !== "session")).map(({ plan }) => plan.site);
|
|
2081
|
+
if (sites.length) warnings.push({
|
|
2082
|
+
level: "warn",
|
|
2083
|
+
code: "W_MIXED_KIND_SESSION_READER",
|
|
2084
|
+
message: `If these handlers read getSession(), another authenticated kind throws SESSION_REQUIRED: ${sites.slice(0, 10).join(", ")}`,
|
|
2085
|
+
hint: "Use getPrincipal() and narrow p.kind."
|
|
2086
|
+
});
|
|
2087
|
+
if (context.context.hasPlugin("api-key") && options.apiKeySessions !== false) {
|
|
2088
|
+
warnings.push({
|
|
2089
|
+
level: "warn",
|
|
2090
|
+
code: "W_API_KEY_FULL_SESSION",
|
|
2091
|
+
message: `${options.apiKeySessions ? "API-key sessions grant" : "If enableSessionForAPIKeys is enabled, API-key sessions grant"} full session access on ordinary authenticated routes.`
|
|
2092
|
+
});
|
|
2093
|
+
if (context.policies.some((policy) => policy.requires?.presentsCredentials)) warnings.push({
|
|
2094
|
+
level: "warn",
|
|
2095
|
+
code: "W_API_KEY_SESSION_MULTIPLIER",
|
|
2096
|
+
message: "Policies presenting credentials can validate API-key sessions again and consume additional quota."
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
if (context.context.hasPlugin("expo") && context.originCheck.missingOrigin === "reject") warnings.push({
|
|
2100
|
+
level: "warn",
|
|
2101
|
+
code: "W_ORIGIN_CHECK_NATIVE_CLIENTS",
|
|
2102
|
+
message: "Native clients must send a trusted Origin for cookie-carrying operations."
|
|
2103
|
+
});
|
|
2104
|
+
return warnings;
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
const freshPolicy = {
|
|
2109
|
+
id: "better-auth:session/fresh",
|
|
2110
|
+
requires: { principals: [SESSION_PRINCIPAL_KIND] },
|
|
2111
|
+
validate(params) {
|
|
2112
|
+
if (params.maxAgeSeconds !== void 0 && (!Number.isFinite(params.maxAgeSeconds) || params.maxAgeSeconds < 0)) throw new BetterAuthConfigurationError("INVALID_FRESH_AGE", "maxAgeSeconds must be finite and nonnegative");
|
|
2113
|
+
},
|
|
2114
|
+
advise(context) {
|
|
2115
|
+
return context.context.hasPlugin("custom-session") ? [{
|
|
2116
|
+
level: "warn",
|
|
2117
|
+
code: "W_FRESH_SESSION_SHAPE",
|
|
2118
|
+
message: "freshSession requires custom session results to retain session.createdAt; unsupported shapes deny PRINCIPAL_NOT_SUPPORTED."
|
|
2119
|
+
}] : [];
|
|
2120
|
+
},
|
|
2121
|
+
async evaluate(params, context) {
|
|
2122
|
+
const createdAt = context.principal.session.session?.createdAt;
|
|
2123
|
+
const age = params.maxAgeSeconds ?? (await context.auth.context()).sessionConfig.freshAge;
|
|
2124
|
+
if (age === 0) return { effect: "allow" };
|
|
2125
|
+
if (!(createdAt instanceof Date) && typeof createdAt !== "string" && typeof createdAt !== "number") return {
|
|
2126
|
+
effect: "deny",
|
|
2127
|
+
reason: "PRINCIPAL_NOT_SUPPORTED"
|
|
2128
|
+
};
|
|
2129
|
+
const time = new Date(createdAt).getTime();
|
|
2130
|
+
return Number.isFinite(time) && Date.now() - time < age * 1e3 ? { effect: "allow" } : {
|
|
2131
|
+
effect: "deny",
|
|
2132
|
+
status: 403,
|
|
2133
|
+
reason: "SESSION_NOT_FRESH"
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
const freshSession = (options = {}) => ({
|
|
2138
|
+
policy: freshPolicy,
|
|
2139
|
+
params: options
|
|
2140
|
+
});
|
|
2141
|
+
const RequireFreshSession = (options) => Require(freshSession(options));
|
|
2142
|
+
//#endregion
|
|
2143
|
+
//#region src/auth-service.ts
|
|
2144
|
+
let BetterAuthService = class BetterAuthService {
|
|
2145
|
+
registry;
|
|
2146
|
+
scope;
|
|
2147
|
+
readings;
|
|
2148
|
+
planner;
|
|
2149
|
+
transports;
|
|
2150
|
+
coordinator;
|
|
2151
|
+
name;
|
|
2152
|
+
instance;
|
|
2153
|
+
api;
|
|
2154
|
+
#context;
|
|
2155
|
+
constructor(options, registry, scope, readings, planner, transports, coordinator) {
|
|
2156
|
+
this.registry = registry;
|
|
2157
|
+
this.scope = scope;
|
|
2158
|
+
this.readings = readings;
|
|
2159
|
+
this.planner = planner;
|
|
2160
|
+
this.transports = transports;
|
|
2161
|
+
this.coordinator = coordinator;
|
|
2162
|
+
const registration = registry.registrations().find((value) => value.options === options);
|
|
2163
|
+
if (!registration) throw new BetterAuthConfigurationError("UNKNOWN_INSTANCE", "The service options have no registered instance.");
|
|
2164
|
+
this.name = registration.name;
|
|
2165
|
+
this.instance = options.auth;
|
|
2166
|
+
this.api = options.auth?.api;
|
|
2167
|
+
this.#context = options.auth?.$context;
|
|
2168
|
+
}
|
|
2169
|
+
context() {
|
|
2170
|
+
return this.#context;
|
|
2171
|
+
}
|
|
2172
|
+
mount() {
|
|
2173
|
+
const binding = this.coordinator.binding(this.name);
|
|
2174
|
+
const platform = this.coordinator.platform;
|
|
2175
|
+
return binding && platform ? {
|
|
2176
|
+
basePath: binding.basePath,
|
|
2177
|
+
bodyLimit: binding.bodyLimit,
|
|
2178
|
+
platform: platform.id
|
|
2179
|
+
} : void 0;
|
|
2180
|
+
}
|
|
2181
|
+
getPrincipal() {
|
|
2182
|
+
const reading = this.readings.current();
|
|
2183
|
+
return Promise.resolve(this.readings.project(reading, {
|
|
2184
|
+
site: "BetterAuthService.getPrincipal",
|
|
2185
|
+
project: (principal) => principal
|
|
2186
|
+
}));
|
|
2187
|
+
}
|
|
2188
|
+
getSession() {
|
|
2189
|
+
return Promise.resolve(readCurrentSession(this.readings));
|
|
2190
|
+
}
|
|
2191
|
+
principalFor(context) {
|
|
2192
|
+
const plan = this.planner.forContext(context);
|
|
2193
|
+
const transport = this.transports.find(context);
|
|
2194
|
+
const lineage = transport?.lineage?.(context);
|
|
2195
|
+
const call = plan.access !== "public" && plan.access !== "inherit" && transport ? this.transports.describe(context, transport) : void 0;
|
|
2196
|
+
return this.readings.read({
|
|
2197
|
+
args: context.getArgs(),
|
|
2198
|
+
plan,
|
|
2199
|
+
call,
|
|
2200
|
+
lineage,
|
|
2201
|
+
beforeGuard: true
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
headersFrom(platformRequest) {
|
|
2205
|
+
const accessor = this.coordinator.platform?.requests;
|
|
2206
|
+
if (!accessor?.isRequest(platformRequest)) throw BetterAuthConfigurationError.atRequest("NO_HTTP_REQUEST", "headersFrom needs a request from the selected HTTP platform.");
|
|
2207
|
+
const entry = this.registry.get(this.name);
|
|
2208
|
+
const headers = new Headers(accessor.headers(platformRequest));
|
|
2209
|
+
for (const name of [...headers.keys()]) if (name.startsWith(":") || name.startsWith("x-nsba-ip-") || name === entry.bridge.clientIpHeader?.toLowerCase()) headers.delete(name);
|
|
2210
|
+
const clientIp = accessor.clientIp(platformRequest);
|
|
2211
|
+
if (entry.bridge.clientIpHeader && clientIp !== null) headers.set(entry.bridge.clientIpHeader, clientIp);
|
|
2212
|
+
return headers;
|
|
2213
|
+
}
|
|
2214
|
+
forwardForeignCookies(fn) {
|
|
2215
|
+
const outer = this.scope.current();
|
|
2216
|
+
if (!outer?.plan?.forwardDirectCalls) throw BetterAuthConfigurationError.atRequest("FORWARDING_NOT_DECLARED", "Declare cookie forwarding on the active handler before forwarding foreign credentials.", { hint: "Use @ForwardAuthCookies() on a guarded handler." });
|
|
2217
|
+
return this.scope.run(derivedScope(this.scope, {
|
|
2218
|
+
get cookies() {
|
|
2219
|
+
return outer.view.cookies;
|
|
2220
|
+
},
|
|
2221
|
+
get inbound() {
|
|
2222
|
+
return outer.view.inbound;
|
|
2223
|
+
},
|
|
2224
|
+
get internal() {
|
|
2225
|
+
return outer.view.internal;
|
|
2226
|
+
},
|
|
2227
|
+
forward: "any"
|
|
2228
|
+
}), fn);
|
|
2229
|
+
}
|
|
2230
|
+
withoutCookies(fn) {
|
|
2231
|
+
const outer = this.scope.current();
|
|
2232
|
+
return this.scope.run(derivedScope(this.scope, {
|
|
2233
|
+
cookies: null,
|
|
2234
|
+
forward: "none",
|
|
2235
|
+
get inbound() {
|
|
2236
|
+
return outer?.view.inbound;
|
|
2237
|
+
},
|
|
2238
|
+
get internal() {
|
|
2239
|
+
return outer?.view.internal ?? false;
|
|
2240
|
+
}
|
|
2241
|
+
}), fn);
|
|
2242
|
+
}
|
|
2243
|
+
runOutsideScope(fn) {
|
|
2244
|
+
return this.scope.exit(fn);
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
BetterAuthService = __decorate([
|
|
2248
|
+
__decorateParam(0, Inject(getBetterAuthOptionsToken())),
|
|
2249
|
+
__decorateParam(1, Inject(INSTANCE_REGISTRY)),
|
|
2250
|
+
__decorateParam(2, Inject(REQUEST_SCOPE)),
|
|
2251
|
+
__decorateParam(3, Inject(PRINCIPAL_READINGS)),
|
|
2252
|
+
__decorateParam(4, Inject(ROUTE_PLANNER)),
|
|
2253
|
+
__decorateParam(5, Inject(TRANSPORT_REGISTRY)),
|
|
2254
|
+
__decorateParam(6, Inject(MOUNT_COORDINATOR)),
|
|
2255
|
+
__decorateMetadata("design:paramtypes", [
|
|
2256
|
+
Object,
|
|
2257
|
+
Object,
|
|
2258
|
+
Object,
|
|
2259
|
+
Object,
|
|
2260
|
+
Object,
|
|
2261
|
+
Object,
|
|
2262
|
+
Object
|
|
2263
|
+
])
|
|
2264
|
+
], BetterAuthService);
|
|
2265
|
+
//#endregion
|
|
2266
|
+
//#region src/http-transport.ts
|
|
2267
|
+
let HttpTransport = class HttpTransport {
|
|
2268
|
+
adapterHost;
|
|
2269
|
+
id = "http";
|
|
2270
|
+
constructor(adapterHost) {
|
|
2271
|
+
this.adapterHost = adapterHost;
|
|
2272
|
+
}
|
|
2273
|
+
handles(context) {
|
|
2274
|
+
return context.getType() === "http";
|
|
2275
|
+
}
|
|
2276
|
+
describe(context, kit) {
|
|
2277
|
+
const http = context.switchToHttp();
|
|
2278
|
+
const request = http.getRequest();
|
|
2279
|
+
const response = http.getResponse();
|
|
2280
|
+
const key = kit.http ? kit.http.key(request) : typeof request === "object" && request !== null ? request : context.getArgs();
|
|
2281
|
+
const accessor = () => {
|
|
2282
|
+
if (!kit.http) throw BetterAuthConfigurationError.atRequest("HTTP_PLATFORM_MISSING", "No HTTP platform is available for this request", { hint: "Register the HTTP platform that supports the application's adapter." });
|
|
2283
|
+
return kit.http;
|
|
2284
|
+
};
|
|
2285
|
+
return {
|
|
2286
|
+
key,
|
|
2287
|
+
invocation: key,
|
|
2288
|
+
headers: () => accessor().headers(request),
|
|
2289
|
+
get clientIp() {
|
|
2290
|
+
return accessor().clientIp(request);
|
|
2291
|
+
},
|
|
2292
|
+
get cookies() {
|
|
2293
|
+
return accessor().cookieSink(request, response);
|
|
2294
|
+
},
|
|
2295
|
+
get request() {
|
|
2296
|
+
return accessor().request(request);
|
|
2297
|
+
},
|
|
2298
|
+
param: (name) => accessor().param(request, name),
|
|
2299
|
+
get browser() {
|
|
2300
|
+
const info = accessor().request(request);
|
|
2301
|
+
return {
|
|
2302
|
+
enforce: ![
|
|
2303
|
+
"GET",
|
|
2304
|
+
"HEAD",
|
|
2305
|
+
"OPTIONS"
|
|
2306
|
+
].includes(info.method.toUpperCase()),
|
|
2307
|
+
headers: () => accessor().headers(request),
|
|
2308
|
+
url: info.url,
|
|
2309
|
+
key
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
toException(failure, context) {
|
|
2315
|
+
const adapter = this.adapterHost.httpAdapter;
|
|
2316
|
+
const response = context.switchToHttp().getResponse();
|
|
2317
|
+
if (adapter && response) {
|
|
2318
|
+
if (failure.challenge) adapter.setHeader(response, "WWW-Authenticate", failure.challenge);
|
|
2319
|
+
const retryAfter = failure.headers?.get("retry-after");
|
|
2320
|
+
if (failure.status === 429 && retryAfter !== null && retryAfter !== void 0) adapter.setHeader(response, "Retry-After", retryAfter);
|
|
2321
|
+
}
|
|
2322
|
+
const body = {
|
|
2323
|
+
statusCode: failure.status,
|
|
2324
|
+
error: failure.status === 401 ? "Unauthorized" : failure.status === 403 ? "Forbidden" : "Too Many Requests",
|
|
2325
|
+
code: failure.code,
|
|
2326
|
+
...failure.reason ? { reason: failure.reason } : {},
|
|
2327
|
+
message: failure.message
|
|
2328
|
+
};
|
|
2329
|
+
if (failure.status === 401) return new UnauthorizedException(body);
|
|
2330
|
+
if (failure.status === 403) return new ForbiddenException(body);
|
|
2331
|
+
return new HttpException(body, 429);
|
|
2332
|
+
}
|
|
2333
|
+
validate(context) {
|
|
2334
|
+
class Probe {
|
|
2335
|
+
route() {}
|
|
2336
|
+
}
|
|
2337
|
+
Get("/nsba-canary/:id")(Probe.prototype, "route", Object.getOwnPropertyDescriptor(Probe.prototype, "route"));
|
|
2338
|
+
if (Reflect.getMetadata("path", Probe.prototype.route) !== "/nsba-canary/:id" || Reflect.getMetadata("method", Probe.prototype.route) !== RequestMethod.GET) throw new BetterAuthConfigurationError("NEST_METADATA_KEY_CHANGED", "Nest HTTP route metadata failed its path/method canary");
|
|
2339
|
+
const scanner = new MetadataScanner();
|
|
2340
|
+
for (const wrapper of context.discovery.getControllers()) {
|
|
2341
|
+
const target = wrapper.metatype;
|
|
2342
|
+
if (!target?.prototype || wrapper.isAlias) continue;
|
|
2343
|
+
const controllerPaths = Reflect.getMetadata("path", target);
|
|
2344
|
+
for (const method of scanner.getAllMethodNames(target.prototype)) {
|
|
2345
|
+
const handler = target.prototype[method];
|
|
2346
|
+
if (typeof handler !== "function" || Reflect.getMetadata("method", handler) === void 0) continue;
|
|
2347
|
+
const paths = [controllerPaths, Reflect.getMetadata("path", handler)].flatMap((value) => typeof value === "string" ? [value] : Array.isArray(value) ? value.filter((item) => typeof item === "string") : []);
|
|
2348
|
+
const inputs = [...new Set(paths.flatMap((path) => [...path.matchAll(/:([A-Za-z0-9_]+)/g)].map((match) => match[1])))];
|
|
2349
|
+
context.claim(target, method, "global", {
|
|
2350
|
+
code: "ROUTE_UNGUARDED",
|
|
2351
|
+
inputs,
|
|
2352
|
+
hint: "Register the global guard (globalGuard: true) or add @UseBetterAuth() to the controller."
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
2358
|
+
HttpTransport = __decorate([__decorateParam(0, Inject(HttpAdapterHost)), __decorateMetadata("design:paramtypes", [typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost])], HttpTransport);
|
|
2359
|
+
function httpTransport() {
|
|
2360
|
+
return HttpTransport;
|
|
2361
|
+
}
|
|
2362
|
+
//#endregion
|
|
2363
|
+
//#region src/auth-module-definition.ts
|
|
2364
|
+
function defineExtension(definition) {
|
|
2365
|
+
return {
|
|
2366
|
+
...definition,
|
|
2367
|
+
[EXTENSION_DEFINITION]: true
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function defineHttpPlatform(platform) {
|
|
2371
|
+
return platform;
|
|
2372
|
+
}
|
|
2373
|
+
function defineTransport(transport) {
|
|
2374
|
+
return transport;
|
|
2375
|
+
}
|
|
2376
|
+
const STATIC_KEYS = [
|
|
2377
|
+
"name",
|
|
2378
|
+
"isGlobal",
|
|
2379
|
+
"globalGuard",
|
|
2380
|
+
"globalScope",
|
|
2381
|
+
"principals",
|
|
2382
|
+
"platforms",
|
|
2383
|
+
"transports"
|
|
2384
|
+
];
|
|
2385
|
+
function moduleDefinition(module, definition, async) {
|
|
2386
|
+
const name = definition.name || "default";
|
|
2387
|
+
const providers = [];
|
|
2388
|
+
const imports = [BetterAuthCoreModule];
|
|
2389
|
+
const exports = [];
|
|
2390
|
+
const registrationToken = Symbol(`registration:${name}`);
|
|
2391
|
+
const staticOptions = {
|
|
2392
|
+
name,
|
|
2393
|
+
isGlobal: definition.isGlobal,
|
|
2394
|
+
globalGuard: definition.globalGuard,
|
|
2395
|
+
globalScope: definition.globalScope,
|
|
2396
|
+
principals: definition.principals
|
|
2397
|
+
};
|
|
2398
|
+
const appOptions = {
|
|
2399
|
+
platforms: definition.platforms,
|
|
2400
|
+
transports: definition.transports
|
|
2401
|
+
};
|
|
2402
|
+
const optionsToken = getBetterAuthOptionsToken(name);
|
|
2403
|
+
if (async) {
|
|
2404
|
+
const factory = definition;
|
|
2405
|
+
imports.push(...factory.imports ?? []);
|
|
2406
|
+
providers.push({
|
|
2407
|
+
provide: optionsToken,
|
|
2408
|
+
inject: [...factory.inject ?? []],
|
|
2409
|
+
useFactory: async (...deps) => {
|
|
2410
|
+
const options = await factory.useFactory(...deps);
|
|
2411
|
+
for (const key of STATIC_KEYS) if (Object.hasOwn(options, key)) throw new BetterAuthConfigurationError("STATIC_OPTION_IN_FACTORY", `'${key}' was returned from useFactory.`, `Pass '${key}' next to useFactory in forRootAsync().`);
|
|
2412
|
+
return options;
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
} else {
|
|
2416
|
+
const runtime = { ...definition };
|
|
2417
|
+
for (const key of STATIC_KEYS) delete runtime[key];
|
|
2418
|
+
providers.push({
|
|
2419
|
+
provide: optionsToken,
|
|
2420
|
+
useValue: runtime
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
function extensions(point, refs) {
|
|
2424
|
+
return refs.map((ref, index) => {
|
|
2425
|
+
const token = getExtensionToken(point, name, index);
|
|
2426
|
+
if (typeof ref === "function") providers.push({
|
|
2427
|
+
provide: token,
|
|
2428
|
+
useClass: ref
|
|
2429
|
+
});
|
|
2430
|
+
else if (ref && typeof ref === "object" && Reflect.get(ref, EXTENSION_DEFINITION) === true) {
|
|
2431
|
+
const definition = ref;
|
|
2432
|
+
imports.push(...definition.imports ?? []);
|
|
2433
|
+
providers.push(...definition.providers ?? []);
|
|
2434
|
+
if ("useClass" in definition.use) providers.push({
|
|
2435
|
+
provide: token,
|
|
2436
|
+
useClass: definition.use.useClass
|
|
2437
|
+
});
|
|
2438
|
+
else if ("useExisting" in definition.use) providers.push({
|
|
2439
|
+
provide: token,
|
|
2440
|
+
useExisting: definition.use.useExisting
|
|
2441
|
+
});
|
|
2442
|
+
else providers.push({
|
|
2443
|
+
provide: token,
|
|
2444
|
+
useFactory: definition.use.useFactory,
|
|
2445
|
+
inject: [...definition.use.inject ?? []]
|
|
2446
|
+
});
|
|
2447
|
+
exports.push(...definition.exports ?? []);
|
|
2448
|
+
} else providers.push({
|
|
2449
|
+
provide: token,
|
|
2450
|
+
useValue: ref
|
|
2451
|
+
});
|
|
2452
|
+
return token;
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
const sourceTokens = extensions("principals", definition.principals ?? []);
|
|
2456
|
+
providers.push({
|
|
2457
|
+
provide: getPrincipalSourcesToken(name),
|
|
2458
|
+
inject: [optionsToken, ...sourceTokens],
|
|
2459
|
+
useFactory: (options, ...sources) => {
|
|
2460
|
+
const builtIn = sessionPrincipal(options.session === false ? void 0 : options.session);
|
|
2461
|
+
return options.session === false ? sources : [...sources, builtIn];
|
|
2462
|
+
}
|
|
2463
|
+
});
|
|
2464
|
+
providers.push({
|
|
2465
|
+
provide: registrationToken,
|
|
2466
|
+
inject: [
|
|
2467
|
+
INSTANCE_REGISTRY,
|
|
2468
|
+
optionsToken,
|
|
2469
|
+
getPrincipalSourcesToken(name)
|
|
2470
|
+
],
|
|
2471
|
+
useFactory: (registry, options, sources) => registry.register(options, staticOptions, sources, appOptions)
|
|
2472
|
+
});
|
|
2473
|
+
providers.push({
|
|
2474
|
+
provide: getBetterAuthInstanceToken(name),
|
|
2475
|
+
inject: [registrationToken],
|
|
2476
|
+
useFactory: (registration) => registration.instance
|
|
2477
|
+
});
|
|
2478
|
+
providers.push({
|
|
2479
|
+
provide: getBetterAuthHandleToken(name),
|
|
2480
|
+
inject: [registrationToken],
|
|
2481
|
+
useFactory: (registration) => registration.handle
|
|
2482
|
+
});
|
|
2483
|
+
const serviceProvider = {
|
|
2484
|
+
provide: getBetterAuthServiceToken(name),
|
|
2485
|
+
inject: [
|
|
2486
|
+
registrationToken,
|
|
2487
|
+
INSTANCE_REGISTRY,
|
|
2488
|
+
REQUEST_SCOPE,
|
|
2489
|
+
PRINCIPAL_READINGS,
|
|
2490
|
+
ROUTE_PLANNER,
|
|
2491
|
+
TRANSPORT_REGISTRY,
|
|
2492
|
+
MOUNT_COORDINATOR
|
|
2493
|
+
],
|
|
2494
|
+
useFactory: (registration, registry, scope, readings, planner, transports, mounts) => new BetterAuthService(registration.options, registry, scope, readings, planner, transports, mounts)
|
|
2495
|
+
};
|
|
2496
|
+
if (name === "default") {
|
|
2497
|
+
providers.push({
|
|
2498
|
+
...serviceProvider,
|
|
2499
|
+
provide: BetterAuthService
|
|
2500
|
+
}, {
|
|
2501
|
+
provide: getBetterAuthServiceToken(name),
|
|
2502
|
+
useExisting: BetterAuthService
|
|
2503
|
+
});
|
|
2504
|
+
exports.push(BetterAuthService);
|
|
2505
|
+
const platformTokens = extensions("platforms", definition.platforms ?? []);
|
|
2506
|
+
const transportTokens = extensions("transports", [...definition.transports ?? [], httpTransport()]);
|
|
2507
|
+
providers.push({
|
|
2508
|
+
provide: PLATFORMS,
|
|
2509
|
+
inject: [MOUNT_COORDINATOR, ...platformTokens],
|
|
2510
|
+
useFactory: (mounts, ...platforms) => {
|
|
2511
|
+
mounts.registerPlatforms(platforms);
|
|
2512
|
+
return platforms;
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
providers.push({
|
|
2516
|
+
provide: TRANSPORTS,
|
|
2517
|
+
inject: [TRANSPORT_REGISTRY, ...transportTokens],
|
|
2518
|
+
useFactory: (registry, ...transports) => {
|
|
2519
|
+
registry.register(transports);
|
|
2520
|
+
return transports;
|
|
2521
|
+
}
|
|
2522
|
+
});
|
|
2523
|
+
} else providers.push(serviceProvider);
|
|
2524
|
+
if (definition.globalGuard ?? name === "default") providers.push({
|
|
2525
|
+
provide: APP_GUARD,
|
|
2526
|
+
useExisting: BetterAuthGuard
|
|
2527
|
+
});
|
|
2528
|
+
if (name === "default" && (definition.globalScope ?? true)) providers.push({
|
|
2529
|
+
provide: APP_INTERCEPTOR,
|
|
2530
|
+
useExisting: BetterAuthScopeInterceptor
|
|
2531
|
+
});
|
|
2532
|
+
exports.push(optionsToken, getBetterAuthInstanceToken(name), getBetterAuthHandleToken(name), getBetterAuthServiceToken(name));
|
|
2533
|
+
return {
|
|
2534
|
+
module,
|
|
2535
|
+
global: definition.isGlobal ?? true,
|
|
2536
|
+
imports,
|
|
2537
|
+
providers,
|
|
2538
|
+
exports
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
//#endregion
|
|
2542
|
+
//#region src/auth-module.ts
|
|
2543
|
+
var _BetterAuthModule;
|
|
2544
|
+
let BetterAuthModule = _BetterAuthModule = class BetterAuthModule {
|
|
2545
|
+
static forRoot(options) {
|
|
2546
|
+
return moduleDefinition(_BetterAuthModule, options, false);
|
|
2547
|
+
}
|
|
2548
|
+
static forRootAsync(options) {
|
|
2549
|
+
return moduleDefinition(_BetterAuthModule, options, true);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
BetterAuthModule = _BetterAuthModule = __decorate([Module({})], BetterAuthModule);
|
|
2553
|
+
//#endregion
|
|
2554
|
+
export { AcceptPrincipals, AfterAuth, AfterDatabase, AuthFailures, BeforeAuth, BeforeDatabase, BetterAuthConfigurationError, BetterAuthGuard, BetterAuthInfrastructureError, BetterAuthModule, BetterAuthScopeInterceptor, BetterAuthService, CurrentPrincipal, CurrentSession, CurrentUser, EXTENSION_DEFINITION, ForwardAuthCookies, GUARD_CORE, OptionalAuth, POLICY_INVOKER, PRINCIPAL_RESOLVER, Public, Require, RequireAuth, RequireFreshSession, SCOPE_CORE, SESSION_PRINCIPAL_KIND, SkipDefaultRequirements, SkipOriginCheck, UseAuthInstance, UseBetterAuth, absent, allOf, allow, anyOf, authenticated, betterAuthCorsOrigin, defineExtension, defineHttpPlatform, defineInvocationParam, definePolicy, definePrincipalParam, definePrincipalSource, defineTransport, deny, freshSession, getBetterAuthHandleToken, getBetterAuthInstanceToken, getBetterAuthOptionsToken, getBetterAuthServiceToken, getRawCause, httpTransport, isAuthFailure, isConfigurationError, isInfrastructureError, rejected, requirement, sessionPrincipal };
|
|
2555
|
+
|
|
2556
|
+
//# sourceMappingURL=index.mjs.map
|