@nestjs-kit/better-auth 0.0.0-stage → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +1078 -2
- package/dist/admin.cjs +88 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +14 -0
- package/dist/admin.d.cts.map +1 -0
- package/dist/admin.d.mts +14 -0
- package/dist/admin.d.mts.map +1 -0
- package/dist/admin.mjs +85 -0
- package/dist/admin.mjs.map +1 -0
- package/dist/api-key.cjs +164 -0
- package/dist/api-key.cjs.map +1 -0
- package/dist/api-key.d.cts +42 -0
- package/dist/api-key.d.cts.map +1 -0
- package/dist/api-key.d.mts +42 -0
- package/dist/api-key.d.mts.map +1 -0
- package/dist/api-key.mjs +160 -0
- package/dist/api-key.mjs.map +1 -0
- package/dist/auth-contracts-Brh_Qko9.d.cts +1654 -0
- package/dist/auth-contracts-Brh_Qko9.d.cts.map +1 -0
- package/dist/auth-contracts-Cz7C4_Ch.d.mts +1654 -0
- package/dist/auth-contracts-Cz7C4_Ch.d.mts.map +1 -0
- package/dist/auth-decorators-B1mFnGJ6.mjs +284 -0
- package/dist/auth-decorators-B1mFnGJ6.mjs.map +1 -0
- package/dist/auth-decorators-Cu_nxWnF.cjs +421 -0
- package/dist/auth-decorators-Cu_nxWnF.cjs.map +1 -0
- package/dist/auth-errors-C3rbTV9p.mjs +301 -0
- package/dist/auth-errors-C3rbTV9p.mjs.map +1 -0
- package/dist/auth-errors-DmiKl0L_.cjs +378 -0
- package/dist/auth-errors-DmiKl0L_.cjs.map +1 -0
- package/dist/auth-guard-CLyhAEEj.cjs +136 -0
- package/dist/auth-guard-CLyhAEEj.cjs.map +1 -0
- package/dist/auth-guard-h9QB_l11.mjs +125 -0
- package/dist/auth-guard-h9QB_l11.mjs.map +1 -0
- package/dist/auth-module-BgcY2HpA.cjs +2809 -0
- package/dist/auth-module-BgcY2HpA.cjs.map +1 -0
- package/dist/auth-module-CtHx3gRU.mjs +2708 -0
- package/dist/auth-module-CtHx3gRU.mjs.map +1 -0
- package/dist/auth-scope-interceptor-BjxAL1-S.cjs +290 -0
- package/dist/auth-scope-interceptor-BjxAL1-S.cjs.map +1 -0
- package/dist/auth-scope-interceptor-DYPn3b8M.mjs +231 -0
- package/dist/auth-scope-interceptor-DYPn3b8M.mjs.map +1 -0
- package/dist/bridge-protocol-BDw8opAG.cjs +25 -0
- package/dist/bridge-protocol-BDw8opAG.cjs.map +1 -0
- package/dist/bridge-protocol-c66aEkr5.mjs +8 -0
- package/dist/bridge-protocol-c66aEkr5.mjs.map +1 -0
- package/dist/decorate-Cr4Q7bNE.cjs +319 -0
- package/dist/decorate-Cr4Q7bNE.cjs.map +1 -0
- package/dist/decorate-Dt0e9K6R.mjs +80 -0
- package/dist/decorate-Dt0e9K6R.mjs.map +1 -0
- package/dist/express.cjs +256 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +37 -0
- package/dist/express.d.cts.map +1 -0
- package/dist/express.d.mts +37 -0
- package/dist/express.d.mts.map +1 -0
- package/dist/express.mjs +254 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.cjs +218 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +28 -0
- package/dist/fastify.d.cts.map +1 -0
- package/dist/fastify.d.mts +28 -0
- package/dist/fastify.d.mts.map +1 -0
- package/dist/fastify.mjs +216 -0
- package/dist/fastify.mjs.map +1 -0
- package/dist/graphql.cjs +509 -0
- package/dist/graphql.cjs.map +1 -0
- package/dist/graphql.d.cts +38 -0
- package/dist/graphql.d.cts.map +1 -0
- package/dist/graphql.d.mts +38 -0
- package/dist/graphql.d.mts.map +1 -0
- package/dist/graphql.mjs +503 -0
- package/dist/graphql.mjs.map +1 -0
- package/dist/index.cjs +86 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +8 -0
- package/dist/microservices.cjs +231 -0
- package/dist/microservices.cjs.map +1 -0
- package/dist/microservices.d.cts +39 -0
- package/dist/microservices.d.cts.map +1 -0
- package/dist/microservices.d.mts +39 -0
- package/dist/microservices.d.mts.map +1 -0
- package/dist/microservices.mjs +222 -0
- package/dist/microservices.mjs.map +1 -0
- package/dist/organization.cjs +191 -0
- package/dist/organization.cjs.map +1 -0
- package/dist/organization.d.cts +40 -0
- package/dist/organization.d.cts.map +1 -0
- package/dist/organization.d.mts +40 -0
- package/dist/organization.d.mts.map +1 -0
- package/dist/organization.mjs +177 -0
- package/dist/organization.mjs.map +1 -0
- package/dist/platform-CZl7TY8j.mjs +414 -0
- package/dist/platform-CZl7TY8j.mjs.map +1 -0
- package/dist/platform-YICqqYv6.cjs +473 -0
- package/dist/platform-YICqqYv6.cjs.map +1 -0
- package/dist/platform.cjs +10 -0
- package/dist/platform.d.cts +43 -0
- package/dist/platform.d.cts.map +1 -0
- package/dist/platform.d.mts +43 -0
- package/dist/platform.d.mts.map +1 -0
- package/dist/platform.mjs +1 -0
- package/dist/plugin.cjs +323 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +24 -0
- package/dist/plugin.d.cts.map +1 -0
- package/dist/plugin.d.mts +24 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +321 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/principal-readings-Chmt6rQV.cjs +208 -0
- package/dist/principal-readings-Chmt6rQV.cjs.map +1 -0
- package/dist/principal-readings-Ied2staw.mjs +191 -0
- package/dist/principal-readings-Ied2staw.mjs.map +1 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/testing/conformance.cjs +6214 -0
- package/dist/testing/conformance.cjs.map +1 -0
- package/dist/testing/conformance.d.cts +528 -0
- package/dist/testing/conformance.d.cts.map +1 -0
- package/dist/testing/conformance.d.mts +528 -0
- package/dist/testing/conformance.d.mts.map +1 -0
- package/dist/testing/conformance.mjs +6204 -0
- package/dist/testing/conformance.mjs.map +1 -0
- package/dist/testing.cjs +221 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +64 -0
- package/dist/testing.d.cts.map +1 -0
- package/dist/testing.d.mts +64 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +214 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/websockets.cjs +396 -0
- package/dist/websockets.cjs.map +1 -0
- package/dist/websockets.d.cts +67 -0
- package/dist/websockets.d.cts.map +1 -0
- package/dist/websockets.d.mts +67 -0
- package/dist/websockets.d.mts.map +1 -0
- package/dist/websockets.mjs +383 -0
- package/dist/websockets.mjs.map +1 -0
- package/package.json +327 -3
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { n as BetterAuthConfigurationError } from "./auth-errors-C3rbTV9p.mjs";
|
|
2
|
+
import { u as Require, v as defineInvocationParam } from "./auth-decorators-B1mFnGJ6.mjs";
|
|
3
|
+
import { isAPIError } from "better-auth/api";
|
|
4
|
+
//#region src/organization.ts
|
|
5
|
+
const ACTIVE_ORGANIZATION_ID = Symbol.for("@nestjs-kit/better-auth:org:active-organization-id");
|
|
6
|
+
const ACTIVE_MEMBER_ROLE = Symbol.for("@nestjs-kit/better-auth:org:active-member-role");
|
|
7
|
+
const ActiveOrganizationId = defineInvocationParam(ACTIVE_ORGANIZATION_ID, { missing: "ORGANIZATION_REQUIRED" });
|
|
8
|
+
const ActiveMemberRole = defineInvocationParam(ACTIVE_MEMBER_ROLE, { missing: "MEMBER_ROLE_REQUIRED" });
|
|
9
|
+
const REF = Symbol.for("@nestjs-kit/better-auth:org:ref");
|
|
10
|
+
function organizationRef(resolve, options = {}) {
|
|
11
|
+
return Object.assign((ctx) => resolve(ctx), options);
|
|
12
|
+
}
|
|
13
|
+
function activeOrganization() {
|
|
14
|
+
return Object.assign(organizationRef(async (ctx) => {
|
|
15
|
+
const value = ctx.principal.session.session?.activeOrganizationId;
|
|
16
|
+
if (value !== void 0) return value;
|
|
17
|
+
if (!await ctx.auth.hasPlugin("custom-session")) return;
|
|
18
|
+
try {
|
|
19
|
+
return (await ctx.memo(JSON.stringify(["org:activeMember"]), () => ctx.auth.run({
|
|
20
|
+
cookies: ctx.cookies,
|
|
21
|
+
inbound: () => ctx.headers,
|
|
22
|
+
internal: true
|
|
23
|
+
}, () => ctx.auth.api.getActiveMember({ headers: ctx.headers }))))?.organizationId;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (isAPIError(error) && error.statusCode === 400 && (error.body?.code === "NO_ACTIVE_ORGANIZATION" || error.body?.code === "MEMBER_NOT_FOUND")) return;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}, { missingReason: "NO_ACTIVE_ORGANIZATION" }), { [REF]: { kind: "active" } });
|
|
29
|
+
}
|
|
30
|
+
function fromParam(name) {
|
|
31
|
+
return Object.assign(organizationRef((ctx) => ctx.param(name)), { [REF]: {
|
|
32
|
+
kind: "param",
|
|
33
|
+
name
|
|
34
|
+
} });
|
|
35
|
+
}
|
|
36
|
+
function fromHeader(name) {
|
|
37
|
+
return organizationRef((ctx) => ctx.headers.get(name));
|
|
38
|
+
}
|
|
39
|
+
function requirements(expressions) {
|
|
40
|
+
return expressions.flatMap((expression) => "anyOf" in expression ? requirements(expression.anyOf) : "allOf" in expression ? requirements(expression.allOf) : [expression]);
|
|
41
|
+
}
|
|
42
|
+
function advise(ctx, policy) {
|
|
43
|
+
const advice = [];
|
|
44
|
+
for (const handler of ctx.handlers) for (const requirement of requirements(handler.plan.requirements)) {
|
|
45
|
+
if (requirement.policy !== policy) continue;
|
|
46
|
+
const marker = requirement.params.organization[REF];
|
|
47
|
+
if (marker?.kind === "active") {
|
|
48
|
+
const input = handler.inputs.find((name) => [
|
|
49
|
+
"orgId",
|
|
50
|
+
"organizationId",
|
|
51
|
+
"organization",
|
|
52
|
+
"orgSlug"
|
|
53
|
+
].includes(name));
|
|
54
|
+
if (input) advice.push({
|
|
55
|
+
level: "warn",
|
|
56
|
+
code: "W_ORG_PARAM_IGNORED",
|
|
57
|
+
message: `${handler.plan.site} checks the active organization but accepts ${input}.`,
|
|
58
|
+
hint: input === "orgSlug" ? "Map the slug to an ID with organizationRef and auth.api.listOrganizations({ headers })." : `Use fromParam('${input}').`
|
|
59
|
+
});
|
|
60
|
+
} else if (marker?.kind === "param" && !handler.inputs.includes(marker.name)) advice.push({
|
|
61
|
+
level: "warn",
|
|
62
|
+
code: "W_ORG_PARAM_MISSING",
|
|
63
|
+
message: `${handler.plan.site} reads ${marker.name}, absent from claimed inputs: ${handler.inputs.join(", ") || "none"}.`,
|
|
64
|
+
hint: "Use the correct input name, a method-level organizationRef, or SkipDefaultRequirements with a method-level requirement."
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return advice;
|
|
68
|
+
}
|
|
69
|
+
const requires = {
|
|
70
|
+
plugins: ["organization"],
|
|
71
|
+
principals: ["session"],
|
|
72
|
+
presentsCredentials: true
|
|
73
|
+
};
|
|
74
|
+
const orgPermissionPolicy = {
|
|
75
|
+
id: "better-auth:organization/permission",
|
|
76
|
+
requires,
|
|
77
|
+
advise: (ctx) => advise(ctx, orgPermissionPolicy),
|
|
78
|
+
validate({ permissions }) {
|
|
79
|
+
if (!Object.keys(permissions).length || Object.values(permissions).some((actions) => !Array.isArray(actions) || !actions.length)) throw new BetterAuthConfigurationError("EMPTY_PERMISSIONS", "Organization permissions must contain a resource and actions.");
|
|
80
|
+
},
|
|
81
|
+
async evaluate({ permissions, organization }, context) {
|
|
82
|
+
const organizationId = await organization(context);
|
|
83
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) return {
|
|
84
|
+
effect: "deny",
|
|
85
|
+
reason: organization.missingReason ?? "ORGANIZATION_REQUIRED"
|
|
86
|
+
};
|
|
87
|
+
const stable = Object.entries(permissions).sort(([a], [b]) => a.localeCompare(b)).map(([resource, actions]) => [resource, [...actions].sort()]);
|
|
88
|
+
try {
|
|
89
|
+
if (!(await context.memo(JSON.stringify([
|
|
90
|
+
"org:hasPermission",
|
|
91
|
+
organizationId,
|
|
92
|
+
stable
|
|
93
|
+
]), () => context.auth.run({
|
|
94
|
+
cookies: context.cookies,
|
|
95
|
+
inbound: () => context.headers,
|
|
96
|
+
internal: true
|
|
97
|
+
}, () => context.auth.api.hasPermission({
|
|
98
|
+
headers: context.headers,
|
|
99
|
+
body: {
|
|
100
|
+
organizationId,
|
|
101
|
+
permissions
|
|
102
|
+
}
|
|
103
|
+
})))).success) return {
|
|
104
|
+
effect: "deny",
|
|
105
|
+
reason: "MISSING_PERMISSION"
|
|
106
|
+
};
|
|
107
|
+
context.provide(ACTIVE_ORGANIZATION_ID, organizationId);
|
|
108
|
+
return { effect: "allow" };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (isAPIError(error) && error.body?.code === "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION") return {
|
|
111
|
+
effect: "deny",
|
|
112
|
+
reason: "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION"
|
|
113
|
+
};
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const orgMemberPolicy = {
|
|
119
|
+
id: "better-auth:organization/member",
|
|
120
|
+
requires,
|
|
121
|
+
advise: (ctx) => advise(ctx, orgMemberPolicy),
|
|
122
|
+
async evaluate({ organization }, context) {
|
|
123
|
+
const organizationId = await organization(context);
|
|
124
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) return {
|
|
125
|
+
effect: "deny",
|
|
126
|
+
reason: organization.missingReason ?? "ORGANIZATION_REQUIRED"
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const result = await context.memo(JSON.stringify(["org:activeMemberRole", organizationId]), () => context.auth.run({
|
|
130
|
+
cookies: context.cookies,
|
|
131
|
+
inbound: () => context.headers,
|
|
132
|
+
internal: true
|
|
133
|
+
}, () => context.auth.api.getActiveMemberRole({
|
|
134
|
+
headers: context.headers,
|
|
135
|
+
query: { organizationId }
|
|
136
|
+
})));
|
|
137
|
+
if (typeof result.role !== "string" || !result.role.trim()) return {
|
|
138
|
+
effect: "deny",
|
|
139
|
+
reason: "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION"
|
|
140
|
+
};
|
|
141
|
+
context.provide(ACTIVE_ORGANIZATION_ID, organizationId);
|
|
142
|
+
context.provide(ACTIVE_MEMBER_ROLE, result.role);
|
|
143
|
+
return { effect: "allow" };
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (isAPIError(error) && error.body?.code === "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION") return {
|
|
146
|
+
effect: "deny",
|
|
147
|
+
reason: "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION"
|
|
148
|
+
};
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function orgPermission(permissions, options = {}) {
|
|
154
|
+
return {
|
|
155
|
+
policy: orgPermissionPolicy,
|
|
156
|
+
params: {
|
|
157
|
+
permissions,
|
|
158
|
+
organization: options.organization ?? activeOrganization()
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function RequireOrgPermission(permissions, options) {
|
|
163
|
+
return Require(orgPermission(permissions, options));
|
|
164
|
+
}
|
|
165
|
+
function orgMember(options = {}) {
|
|
166
|
+
return {
|
|
167
|
+
policy: orgMemberPolicy,
|
|
168
|
+
params: { organization: options.organization ?? activeOrganization() }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function RequireOrgMember(options) {
|
|
172
|
+
return Require(orgMember(options));
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { ACTIVE_MEMBER_ROLE, ACTIVE_ORGANIZATION_ID, ActiveMemberRole, ActiveOrganizationId, RequireOrgMember, RequireOrgPermission, activeOrganization, fromHeader, fromParam, orgMember, orgMemberPolicy, orgPermission, orgPermissionPolicy, organizationRef };
|
|
176
|
+
|
|
177
|
+
//# sourceMappingURL=organization.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.mjs","names":[],"sources":["../src/organization.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport type {\n AuthorizationContext,\n AuthorizationPolicy,\n BootAdvice,\n BootAdviceContext,\n Requirement,\n RequirementExpr,\n} from \"./auth-contracts.js\";\nimport { defineInvocationParam, Require } from \"./auth-decorators.js\";\nimport { BetterAuthConfigurationError } from \"./auth-errors.js\";\nimport type { OrgPermissions, SessionPrincipal } from \"./auth-types.js\";\nexport type { OrgPermissions } from \"./auth-types.js\";\nexport interface OrganizationRef {\n (ctx: AuthorizationContext): unknown | Promise<unknown>;\n readonly missingReason?: string;\n}\nexport interface OrgPermissionParams {\n readonly permissions: OrgPermissions;\n readonly organization: OrganizationRef;\n}\nexport interface OrgMemberParams {\n readonly organization: OrganizationRef;\n}\nexport const ACTIVE_ORGANIZATION_ID = Symbol.for(\n \"@nestjs-kit/better-auth:org:active-organization-id\",\n);\nexport const ACTIVE_MEMBER_ROLE = Symbol.for(\n \"@nestjs-kit/better-auth:org:active-member-role\",\n);\nexport const ActiveOrganizationId = defineInvocationParam(\n ACTIVE_ORGANIZATION_ID,\n { missing: \"ORGANIZATION_REQUIRED\" },\n);\nexport const ActiveMemberRole = defineInvocationParam(ACTIVE_MEMBER_ROLE, {\n missing: \"MEMBER_ROLE_REQUIRED\",\n});\nconst REF = Symbol.for(\"@nestjs-kit/better-auth:org:ref\");\ntype MarkedRef = OrganizationRef & {\n readonly [REF]?: { kind: \"active\" } | { kind: \"param\"; name: string };\n};\ninterface OrgApi {\n hasPermission(input: {\n headers: Headers;\n body: { organizationId: string; permissions: OrgPermissions };\n }): Promise<{ success: boolean }>;\n getActiveMemberRole(input: {\n headers: Headers;\n query: { organizationId: string };\n }): Promise<{ role?: string | null }>;\n getActiveMember(input: {\n headers: Headers;\n }): Promise<{ organizationId?: string }>;\n}\nexport function organizationRef(\n resolve: (ctx: AuthorizationContext) => unknown | Promise<unknown>,\n options: { missingReason?: string } = {},\n): OrganizationRef {\n return Object.assign((ctx: AuthorizationContext) => resolve(ctx), options);\n}\nexport function activeOrganization(): OrganizationRef {\n return Object.assign(\n organizationRef(\n async (ctx) => {\n const session = (ctx.principal as SessionPrincipal).session as {\n session?: { activeOrganizationId?: unknown };\n };\n const value = session.session?.activeOrganizationId;\n if (value !== undefined) {\n return value;\n }\n if (!(await ctx.auth.hasPlugin(\"custom-session\"))) {\n return undefined;\n }\n try {\n const member = await ctx.memo(\n JSON.stringify([\"org:activeMember\"]),\n () =>\n ctx.auth.run(\n {\n cookies: ctx.cookies,\n inbound: () => ctx.headers,\n internal: true,\n },\n () =>\n (ctx.auth.api as unknown as OrgApi).getActiveMember({\n headers: ctx.headers,\n }),\n ),\n );\n return member?.organizationId;\n } catch (error) {\n if (\n isAPIError(error) &&\n error.statusCode === 400 &&\n (error.body?.code === \"NO_ACTIVE_ORGANIZATION\" ||\n error.body?.code === \"MEMBER_NOT_FOUND\")\n ) {\n return undefined;\n }\n throw error;\n }\n },\n { missingReason: \"NO_ACTIVE_ORGANIZATION\" },\n ),\n { [REF]: { kind: \"active\" as const } },\n );\n}\nexport function fromParam(name: string): OrganizationRef {\n return Object.assign(\n organizationRef((ctx) => ctx.param(name)),\n { [REF]: { kind: \"param\" as const, name } },\n );\n}\nexport function fromHeader(name: string): OrganizationRef {\n return organizationRef((ctx) => ctx.headers.get(name));\n}\nfunction requirements(expressions: readonly RequirementExpr[]): Requirement[] {\n return expressions.flatMap((expression) =>\n \"anyOf\" in expression\n ? requirements(expression.anyOf)\n : \"allOf\" in expression\n ? requirements(expression.allOf)\n : [expression],\n );\n}\nfunction advise(\n ctx: BootAdviceContext,\n policy: AuthorizationPolicy<any, any>,\n): BootAdvice[] {\n const advice: BootAdvice[] = [];\n for (const handler of ctx.handlers) {\n for (const requirement of requirements(handler.plan.requirements)) {\n if (requirement.policy !== policy) {\n continue;\n }\n const ref = (requirement.params as OrgMemberParams)\n .organization as MarkedRef;\n const marker = ref[REF];\n if (marker?.kind === \"active\") {\n const input = handler.inputs.find((name) =>\n [\"orgId\", \"organizationId\", \"organization\", \"orgSlug\"].includes(name),\n );\n if (input) {\n advice.push({\n level: \"warn\",\n code: \"W_ORG_PARAM_IGNORED\",\n message: `${handler.plan.site} checks the active organization but accepts ${input}.`,\n hint:\n input === \"orgSlug\"\n ? \"Map the slug to an ID with organizationRef and auth.api.listOrganizations({ headers }).\"\n : `Use fromParam('${input}').`,\n });\n }\n } else if (\n marker?.kind === \"param\" &&\n !handler.inputs.includes(marker.name)\n ) {\n advice.push({\n level: \"warn\",\n code: \"W_ORG_PARAM_MISSING\",\n message: `${handler.plan.site} reads ${marker.name}, absent from claimed inputs: ${handler.inputs.join(\", \") || \"none\"}.`,\n hint: \"Use the correct input name, a method-level organizationRef, or SkipDefaultRequirements with a method-level requirement.\",\n });\n }\n }\n }\n return advice;\n}\nconst requires = {\n plugins: [\"organization\"],\n principals: [\"session\" as const],\n presentsCredentials: true,\n};\nexport const orgPermissionPolicy: AuthorizationPolicy<\n OrgPermissionParams,\n SessionPrincipal\n> = {\n id: \"better-auth:organization/permission\", // gitleaks:allow: public policy identifier\n requires,\n advise: (ctx) => advise(ctx, orgPermissionPolicy),\n validate({ permissions }) {\n if (\n !Object.keys(permissions).length ||\n Object.values(permissions).some(\n (actions) => !Array.isArray(actions) || !actions.length,\n )\n ) {\n throw new BetterAuthConfigurationError(\n \"EMPTY_PERMISSIONS\",\n \"Organization permissions must contain a resource and actions.\",\n );\n }\n },\n async evaluate({ permissions, organization }, context) {\n const organizationId = await organization(context);\n if (typeof organizationId !== \"string\" || !organizationId.trim()) {\n return {\n effect: \"deny\",\n reason: organization.missingReason ?? \"ORGANIZATION_REQUIRED\",\n };\n }\n const stable = Object.entries(permissions)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([resource, actions]) => [resource, [...actions].sort()]);\n try {\n const result = await context.memo(\n JSON.stringify([\"org:hasPermission\", organizationId, stable]),\n () =>\n context.auth.run(\n {\n cookies: context.cookies,\n inbound: () => context.headers,\n internal: true,\n },\n () =>\n (context.auth.api as unknown as OrgApi).hasPermission({\n headers: context.headers,\n body: { organizationId, permissions },\n }),\n ),\n );\n if (!result.success) {\n return { effect: \"deny\", reason: \"MISSING_PERMISSION\" };\n }\n context.provide(ACTIVE_ORGANIZATION_ID, organizationId);\n return { effect: \"allow\" };\n } catch (error) {\n if (\n isAPIError(error) &&\n error.body?.code === \"USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\"\n ) {\n return {\n effect: \"deny\",\n reason: \"USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\",\n };\n }\n throw error;\n }\n },\n};\nexport const orgMemberPolicy: AuthorizationPolicy<\n OrgMemberParams,\n SessionPrincipal\n> = {\n id: \"better-auth:organization/member\",\n requires,\n advise: (ctx) => advise(ctx, orgMemberPolicy),\n async evaluate({ organization }, context) {\n const organizationId = await organization(context);\n if (typeof organizationId !== \"string\" || !organizationId.trim()) {\n return {\n effect: \"deny\",\n reason: organization.missingReason ?? \"ORGANIZATION_REQUIRED\",\n };\n }\n try {\n const result = await context.memo(\n JSON.stringify([\"org:activeMemberRole\", organizationId]),\n () =>\n context.auth.run(\n {\n cookies: context.cookies,\n inbound: () => context.headers,\n internal: true,\n },\n () =>\n (context.auth.api as unknown as OrgApi).getActiveMemberRole({\n headers: context.headers,\n query: { organizationId },\n }),\n ),\n );\n if (typeof result.role !== \"string\" || !result.role.trim()) {\n return {\n effect: \"deny\",\n reason: \"YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION\",\n };\n }\n context.provide(ACTIVE_ORGANIZATION_ID, organizationId);\n context.provide(ACTIVE_MEMBER_ROLE, result.role);\n return { effect: \"allow\" };\n } catch (error) {\n if (\n isAPIError(error) &&\n error.body?.code === \"YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION\"\n ) {\n return {\n effect: \"deny\",\n reason: \"YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION\",\n };\n }\n throw error;\n }\n },\n};\nexport function orgPermission<N extends string = \"default\">(\n permissions: OrgPermissions<N>,\n options: { organization?: OrganizationRef } = {},\n): Requirement<OrgPermissionParams> {\n return {\n policy: orgPermissionPolicy,\n params: {\n permissions: permissions as OrgPermissions,\n organization: options.organization ?? activeOrganization(),\n },\n };\n}\nexport function RequireOrgPermission<N extends string = \"default\">(\n permissions: OrgPermissions<N>,\n options?: { organization?: OrganizationRef },\n): ClassDecorator & MethodDecorator {\n return Require(orgPermission<N>(permissions, options));\n}\nexport function orgMember(\n options: { organization?: OrganizationRef } = {},\n): Requirement<OrgMemberParams> {\n return {\n policy: orgMemberPolicy,\n params: { organization: options.organization ?? activeOrganization() },\n };\n}\nexport function RequireOrgMember(options?: {\n organization?: OrganizationRef;\n}): ClassDecorator & MethodDecorator {\n return Require(orgMember(options));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { AsyncResource } from "node:async_hooks";
|
|
2
|
+
//#region src/set-cookies.ts
|
|
3
|
+
/** Set-Cookie line handling shared by the built-in platforms' cookie sinks. */
|
|
4
|
+
function cookieName(line) {
|
|
5
|
+
const separator = line.indexOf("=");
|
|
6
|
+
return (separator === -1 ? line : line.slice(0, separator)).trim();
|
|
7
|
+
}
|
|
8
|
+
/** A header value as the list of lines it holds. */
|
|
9
|
+
function headerLines(value) {
|
|
10
|
+
return Array.isArray(value) ? value.map(String) : value === void 0 ? [] : [String(value)];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The lines of `values` to append after `existing`. A line identical to the latest line already set for its cookie name
|
|
14
|
+
* changes nothing and is skipped (ADR-09: the kernel's cookie sink de-duplicates identical Set-Cookie values, for example
|
|
15
|
+
* a source that appends a call's cookies that the bridge also forwarded). Any other line is kept, so an identical line
|
|
16
|
+
* after a different one for the same cookie still restores it.
|
|
17
|
+
*/
|
|
18
|
+
function setCookieAdditions(existing, values) {
|
|
19
|
+
const latest = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const line of existing) latest.set(cookieName(line), line);
|
|
21
|
+
const additions = [];
|
|
22
|
+
for (const line of values) {
|
|
23
|
+
const name = cookieName(line);
|
|
24
|
+
if (latest.get(name) === line) continue;
|
|
25
|
+
latest.set(name, line);
|
|
26
|
+
additions.push(line);
|
|
27
|
+
}
|
|
28
|
+
return additions;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/platform.ts
|
|
32
|
+
function appendHeaderValue(headers, name, value) {
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
headers.append(name, value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
for (const item of value) if (typeof item === "string") headers.append(name, item);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Convert Node headers to Web headers without exposing HTTP/2 metadata. */
|
|
42
|
+
function toWebHeaders(raw) {
|
|
43
|
+
const headers = new Headers();
|
|
44
|
+
let authority;
|
|
45
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
46
|
+
if (name === ":authority") authority = value;
|
|
47
|
+
if (name.startsWith(":")) continue;
|
|
48
|
+
appendHeaderValue(headers, name, value);
|
|
49
|
+
}
|
|
50
|
+
if (!headers.has("host")) appendHeaderValue(headers, "host", authority);
|
|
51
|
+
return headers;
|
|
52
|
+
}
|
|
53
|
+
/** Determine whether Node request framing permits a request body. */
|
|
54
|
+
function mayHaveBody(method, headers) {
|
|
55
|
+
const normalizedMethod = method.toUpperCase();
|
|
56
|
+
if (normalizedMethod === "GET" || normalizedMethod === "HEAD") return false;
|
|
57
|
+
if (headers["transfer-encoding"] !== void 0) return true;
|
|
58
|
+
const declaredLength = headers["content-length"]?.trim();
|
|
59
|
+
return declaredLength !== void 0 && /^\d+$/.test(declaredLength) && BigInt(declaredLength) > 0n;
|
|
60
|
+
}
|
|
61
|
+
function assertByteLimit(value, name) {
|
|
62
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
63
|
+
}
|
|
64
|
+
function joinBytes(chunks, length) {
|
|
65
|
+
const bytes = new Uint8Array(length);
|
|
66
|
+
let offset = 0;
|
|
67
|
+
for (const chunk of chunks) {
|
|
68
|
+
bytes.set(chunk, offset);
|
|
69
|
+
offset += chunk.byteLength;
|
|
70
|
+
}
|
|
71
|
+
return bytes;
|
|
72
|
+
}
|
|
73
|
+
async function readBoundedWebBody(stream, options) {
|
|
74
|
+
const reader = stream.getReader();
|
|
75
|
+
const chunks = [];
|
|
76
|
+
let length = 0;
|
|
77
|
+
try {
|
|
78
|
+
if (options.declaredLength !== void 0 && options.declaredLength > options.limit) {
|
|
79
|
+
await reader.cancel().catch(() => void 0);
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: "too-large"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
while (true) {
|
|
86
|
+
const next = await reader.read();
|
|
87
|
+
if (next.done) return {
|
|
88
|
+
ok: true,
|
|
89
|
+
bytes: joinBytes(chunks, length)
|
|
90
|
+
};
|
|
91
|
+
length += next.value.byteLength;
|
|
92
|
+
if (length > options.limit) {
|
|
93
|
+
await reader.cancel().catch(() => void 0);
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
reason: "too-large"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
chunks.push(next.value);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
await reader.cancel().catch(() => void 0);
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
reason: "aborted"
|
|
106
|
+
};
|
|
107
|
+
} finally {
|
|
108
|
+
reader.releaseLock();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function nodeChunkBytes(chunk) {
|
|
112
|
+
if (typeof chunk === "string") return Buffer.from(chunk);
|
|
113
|
+
if (chunk instanceof Uint8Array) return chunk;
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function readBoundedNodeBody(stream, options) {
|
|
117
|
+
const drainCap = options.drainCap ?? options.limit * 8;
|
|
118
|
+
assertByteLimit(drainCap, "drainCap");
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const chunks = [];
|
|
121
|
+
let length = 0;
|
|
122
|
+
let overflow = options.declaredLength !== void 0 && options.declaredLength > options.limit;
|
|
123
|
+
let settled = false;
|
|
124
|
+
const cleanup = () => {
|
|
125
|
+
stream.removeListener("data", onData);
|
|
126
|
+
stream.removeListener("end", onEnd);
|
|
127
|
+
stream.removeListener("aborted", onAborted);
|
|
128
|
+
stream.removeListener("error", onAborted);
|
|
129
|
+
stream.removeListener("close", onClose);
|
|
130
|
+
};
|
|
131
|
+
const destroy = () => {
|
|
132
|
+
if (!stream.destroyed) {
|
|
133
|
+
const absorbDestroyError = () => void 0;
|
|
134
|
+
const removeDestroyErrorHandler = () => {
|
|
135
|
+
stream.removeListener("error", absorbDestroyError);
|
|
136
|
+
};
|
|
137
|
+
stream.once("error", absorbDestroyError);
|
|
138
|
+
stream.once("close", removeDestroyErrorHandler);
|
|
139
|
+
try {
|
|
140
|
+
stream.destroy?.();
|
|
141
|
+
} catch {
|
|
142
|
+
stream.removeListener("error", absorbDestroyError);
|
|
143
|
+
stream.removeListener("close", removeDestroyErrorHandler);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const finish = (result, destroyStream = false) => {
|
|
148
|
+
if (settled) return;
|
|
149
|
+
settled = true;
|
|
150
|
+
cleanup();
|
|
151
|
+
resolve(result);
|
|
152
|
+
if (destroyStream) destroy();
|
|
153
|
+
};
|
|
154
|
+
const tooLarge = () => {
|
|
155
|
+
finish({
|
|
156
|
+
ok: false,
|
|
157
|
+
reason: "too-large"
|
|
158
|
+
}, true);
|
|
159
|
+
};
|
|
160
|
+
const onData = (chunk) => {
|
|
161
|
+
const bytes = nodeChunkBytes(chunk);
|
|
162
|
+
if (bytes === null) {
|
|
163
|
+
finish({
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: "aborted"
|
|
166
|
+
}, true);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
length += bytes.byteLength;
|
|
170
|
+
if (!overflow && length <= options.limit) {
|
|
171
|
+
chunks.push(bytes);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
overflow = true;
|
|
175
|
+
chunks.length = 0;
|
|
176
|
+
if (!options.drainOnOverflow || length > drainCap) tooLarge();
|
|
177
|
+
};
|
|
178
|
+
const onEnd = () => {
|
|
179
|
+
if (overflow) {
|
|
180
|
+
finish({
|
|
181
|
+
ok: false,
|
|
182
|
+
reason: "too-large"
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
finish({
|
|
187
|
+
ok: true,
|
|
188
|
+
bytes: joinBytes(chunks, length)
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const onAborted = () => {
|
|
192
|
+
finish({
|
|
193
|
+
ok: false,
|
|
194
|
+
reason: "aborted"
|
|
195
|
+
}, true);
|
|
196
|
+
};
|
|
197
|
+
const onClose = () => {
|
|
198
|
+
finish({
|
|
199
|
+
ok: false,
|
|
200
|
+
reason: "aborted"
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
stream.once("end", onEnd);
|
|
204
|
+
stream.once("aborted", onAborted);
|
|
205
|
+
stream.once("error", onAborted);
|
|
206
|
+
stream.once("close", onClose);
|
|
207
|
+
if (overflow && !options.drainOnOverflow) {
|
|
208
|
+
tooLarge();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (overflow && options.declaredLength !== void 0 && options.declaredLength > drainCap) {
|
|
212
|
+
tooLarge();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
stream.on("data", onData);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** Read a Node or Web body stream without retaining bytes past the limit. */
|
|
219
|
+
function readBoundedBody(stream, options) {
|
|
220
|
+
assertByteLimit(options.limit, "limit");
|
|
221
|
+
if (options.declaredLength !== void 0 && (!Number.isSafeInteger(options.declaredLength) || options.declaredLength < 0)) throw new RangeError("declaredLength must be a non-negative safe integer");
|
|
222
|
+
if ("getReader" in stream) return readBoundedWebBody(stream, options);
|
|
223
|
+
return readBoundedNodeBody(stream, options);
|
|
224
|
+
}
|
|
225
|
+
/** Append response cookies without replacing values already set by the host. */
|
|
226
|
+
function appendSetCookie(res, values) {
|
|
227
|
+
if (res.headersSent) return false;
|
|
228
|
+
const existing = headerLines(res.getHeader("set-cookie"));
|
|
229
|
+
const additions = setCookieAdditions(existing, values);
|
|
230
|
+
if (additions.length > 0) res.setHeader("set-cookie", [...existing, ...additions]);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
function varyValues(value) {
|
|
234
|
+
return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).flatMap((item) => String(item).split(","));
|
|
235
|
+
}
|
|
236
|
+
function mergeVary(current, added) {
|
|
237
|
+
const values = [...varyValues(current), ...varyValues(added)].map((value) => value.trim()).filter(Boolean);
|
|
238
|
+
if (values.includes("*")) return "*";
|
|
239
|
+
const seen = /* @__PURE__ */ new Set();
|
|
240
|
+
return values.filter((value) => {
|
|
241
|
+
const normalized = value.toLowerCase();
|
|
242
|
+
if (seen.has(normalized)) return false;
|
|
243
|
+
seen.add(normalized);
|
|
244
|
+
return true;
|
|
245
|
+
}).join(", ");
|
|
246
|
+
}
|
|
247
|
+
function endServerResponse(res) {
|
|
248
|
+
if (res.writableEnded) return Promise.resolve();
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
let settled = false;
|
|
251
|
+
const cleanup = () => {
|
|
252
|
+
res.removeListener("error", onError);
|
|
253
|
+
res.removeListener("close", onClose);
|
|
254
|
+
};
|
|
255
|
+
const finish = (error) => {
|
|
256
|
+
if (settled) return;
|
|
257
|
+
settled = true;
|
|
258
|
+
cleanup();
|
|
259
|
+
if (error) reject(error);
|
|
260
|
+
else resolve();
|
|
261
|
+
};
|
|
262
|
+
const onError = (error) => {
|
|
263
|
+
finish(error);
|
|
264
|
+
};
|
|
265
|
+
const onClose = () => {
|
|
266
|
+
finish();
|
|
267
|
+
};
|
|
268
|
+
res.once("error", onError);
|
|
269
|
+
res.once("close", onClose);
|
|
270
|
+
res.end(() => finish());
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function waitForDrainOrStop(res, stopped) {
|
|
274
|
+
return new Promise((resolve) => {
|
|
275
|
+
let settled = false;
|
|
276
|
+
const finish = () => {
|
|
277
|
+
if (settled) return;
|
|
278
|
+
settled = true;
|
|
279
|
+
res.removeListener("drain", finish);
|
|
280
|
+
resolve();
|
|
281
|
+
};
|
|
282
|
+
res.once("drain", finish);
|
|
283
|
+
stopped.then(finish);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/** Write a Web response while retaining host headers and Node backpressure. */
|
|
287
|
+
async function writeWebResponse(res, response, options) {
|
|
288
|
+
res.statusCode = response.status;
|
|
289
|
+
for (const [name, value] of response.headers) {
|
|
290
|
+
if (name === "set-cookie") continue;
|
|
291
|
+
if (name === "vary") res.setHeader("vary", mergeVary(res.getHeader("vary"), value));
|
|
292
|
+
else res.setHeader(name, value);
|
|
293
|
+
}
|
|
294
|
+
appendSetCookie(res, response.headers.getSetCookie());
|
|
295
|
+
if (options?.head || response.body === null) {
|
|
296
|
+
if (options?.head && response.body !== null) await response.body.cancel().catch(() => void 0);
|
|
297
|
+
await endServerResponse(res);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const reader = response.body.getReader();
|
|
301
|
+
let stopped = false;
|
|
302
|
+
let writeError;
|
|
303
|
+
let stop;
|
|
304
|
+
const stoppedPromise = new Promise((resolve) => {
|
|
305
|
+
stop = resolve;
|
|
306
|
+
});
|
|
307
|
+
const onClose = () => {
|
|
308
|
+
stopped = true;
|
|
309
|
+
stop();
|
|
310
|
+
reader.cancel().catch(() => void 0);
|
|
311
|
+
};
|
|
312
|
+
const onError = (error) => {
|
|
313
|
+
writeError = error;
|
|
314
|
+
stopped = true;
|
|
315
|
+
stop();
|
|
316
|
+
reader.cancel(error).catch(() => void 0);
|
|
317
|
+
};
|
|
318
|
+
res.once("close", onClose);
|
|
319
|
+
res.once("error", onError);
|
|
320
|
+
try {
|
|
321
|
+
while (!stopped) {
|
|
322
|
+
const read = reader.read().then((value) => ({
|
|
323
|
+
type: "read",
|
|
324
|
+
value
|
|
325
|
+
}), (error) => ({
|
|
326
|
+
type: "read-error",
|
|
327
|
+
error
|
|
328
|
+
}));
|
|
329
|
+
const outcome = await Promise.race([read, stoppedPromise.then(() => ({ type: "stopped" }))]);
|
|
330
|
+
if (outcome.type === "stopped") break;
|
|
331
|
+
if (outcome.type === "read-error") throw outcome.error;
|
|
332
|
+
if (outcome.value.done) break;
|
|
333
|
+
if (!res.write(outcome.value.value)) await waitForDrainOrStop(res, stoppedPromise);
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
await reader.cancel(error).catch(() => void 0);
|
|
337
|
+
throw error;
|
|
338
|
+
} finally {
|
|
339
|
+
res.removeListener("close", onClose);
|
|
340
|
+
res.removeListener("error", onError);
|
|
341
|
+
reader.releaseLock();
|
|
342
|
+
}
|
|
343
|
+
if (writeError) throw writeError;
|
|
344
|
+
if (!stopped) await endServerResponse(res);
|
|
345
|
+
}
|
|
346
|
+
/** Capture the active async resource for a later stream continuation. */
|
|
347
|
+
function bindAsyncContext(fn) {
|
|
348
|
+
return AsyncResource.bind(fn);
|
|
349
|
+
}
|
|
350
|
+
function contentTypeOf(req) {
|
|
351
|
+
const raw = req.headers["content-type"];
|
|
352
|
+
return (Array.isArray(raw) ? raw[0] : raw)?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
353
|
+
}
|
|
354
|
+
function serializeFormBody(body) {
|
|
355
|
+
if (typeof body === "string" || body instanceof URLSearchParams) return new URLSearchParams(body).toString();
|
|
356
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
357
|
+
const parameters = new URLSearchParams();
|
|
358
|
+
for (const [name, value] of Object.entries(body)) if (Array.isArray(value)) for (const item of value) parameters.append(name, String(item));
|
|
359
|
+
else parameters.append(name, String(value));
|
|
360
|
+
return parameters.toString();
|
|
361
|
+
}
|
|
362
|
+
/** Recover bytes after an earlier host parser consumed the request stream. */
|
|
363
|
+
function recoverConsumedBody(req) {
|
|
364
|
+
if (Buffer.isBuffer(req.rawBody)) return Uint8Array.from(req.rawBody);
|
|
365
|
+
const contentType = contentTypeOf(req);
|
|
366
|
+
try {
|
|
367
|
+
if (contentType === "application/json" || contentType.endsWith("+json")) {
|
|
368
|
+
const serialized = JSON.stringify(req.body);
|
|
369
|
+
return serialized === void 0 ? null : new TextEncoder().encode(serialized);
|
|
370
|
+
}
|
|
371
|
+
if (contentType === "application/x-www-form-urlencoded") {
|
|
372
|
+
const serialized = serializeFormBody(req.body);
|
|
373
|
+
return serialized === null ? null : new TextEncoder().encode(serialized);
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
function hostHeader(headers) {
|
|
381
|
+
if (headers instanceof Headers) return headers.get("host");
|
|
382
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
383
|
+
if (name.toLowerCase() !== "host") continue;
|
|
384
|
+
if (typeof value === "string") return value;
|
|
385
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
/** Build an upgrade URL from socket TLS state, Host, and the raw target. */
|
|
390
|
+
function upgradeRequestUrl(request) {
|
|
391
|
+
const scheme = request.secure || request.socket?.encrypted ? "https" : "http";
|
|
392
|
+
const host = hostHeader(request.headers);
|
|
393
|
+
if (host === null || host.length === 0 || [...host].some((character) => {
|
|
394
|
+
const code = character.charCodeAt(0);
|
|
395
|
+
return code <= 32 || code === 127 || "/\\?#@".includes(character);
|
|
396
|
+
})) throw new TypeError("A valid Host header is required for an upgrade URL");
|
|
397
|
+
let origin;
|
|
398
|
+
try {
|
|
399
|
+
origin = new URL(`${scheme}://${host}`);
|
|
400
|
+
} catch {
|
|
401
|
+
throw new TypeError("A valid Host header is required for an upgrade URL");
|
|
402
|
+
}
|
|
403
|
+
if (origin.username !== "" || origin.password !== "" || origin.pathname !== "/" || origin.search !== "" || origin.hash !== "" || origin.host === "") throw new TypeError("A valid Host header is required for an upgrade URL");
|
|
404
|
+
const target = request.url ?? "/";
|
|
405
|
+
if ([...target].some((character) => {
|
|
406
|
+
const code = character.charCodeAt(0);
|
|
407
|
+
return code <= 32 || code === 127 || character === "#";
|
|
408
|
+
})) throw new TypeError("A valid request target is required for an upgrade URL");
|
|
409
|
+
return `${scheme}://${host}${target.startsWith("/") ? target : `/${target}`}`;
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
export { recoverConsumedBody as a, writeWebResponse as c, readBoundedBody as i, headerLines as l, bindAsyncContext as n, toWebHeaders as o, mayHaveBody as r, upgradeRequestUrl as s, appendSetCookie as t, setCookieAdditions as u };
|
|
413
|
+
|
|
414
|
+
//# sourceMappingURL=platform-CZl7TY8j.mjs.map
|