@happyvertical/smrt-users 0.30.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/AGENTS.md +85 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +459 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/chunks/TerminalAuthService-DoAMQ_yn.js +5118 -0
- package/dist/chunks/TerminalAuthService-DoAMQ_yn.js.map +1 -0
- package/dist/chunks/index-DkoYIvIu.js +169 -0
- package/dist/chunks/index-DkoYIvIu.js.map +1 -0
- package/dist/collections/CliAuthRequestCollection.d.ts +19 -0
- package/dist/collections/CliAuthRequestCollection.d.ts.map +1 -0
- package/dist/collections/GroupCollection.d.ts +17 -0
- package/dist/collections/GroupCollection.d.ts.map +1 -0
- package/dist/collections/GroupMemberCollection.d.ts +43 -0
- package/dist/collections/GroupMemberCollection.d.ts.map +1 -0
- package/dist/collections/GroupRoleCollection.d.ts +33 -0
- package/dist/collections/GroupRoleCollection.d.ts.map +1 -0
- package/dist/collections/MagicLinkTokenCollection.d.ts +26 -0
- package/dist/collections/MagicLinkTokenCollection.d.ts.map +1 -0
- package/dist/collections/MembershipCollection.d.ts +38 -0
- package/dist/collections/MembershipCollection.d.ts.map +1 -0
- package/dist/collections/MembershipOverrideCollection.d.ts +55 -0
- package/dist/collections/MembershipOverrideCollection.d.ts.map +1 -0
- package/dist/collections/PermissionCollection.d.ts +34 -0
- package/dist/collections/PermissionCollection.d.ts.map +1 -0
- package/dist/collections/RoleCollection.d.ts +29 -0
- package/dist/collections/RoleCollection.d.ts.map +1 -0
- package/dist/collections/RolePermissionCollection.d.ts +33 -0
- package/dist/collections/RolePermissionCollection.d.ts.map +1 -0
- package/dist/collections/SessionCollection.d.ts +82 -0
- package/dist/collections/SessionCollection.d.ts.map +1 -0
- package/dist/collections/TenantCollection.d.ts +119 -0
- package/dist/collections/TenantCollection.d.ts.map +1 -0
- package/dist/collections/TenantPermissionOverrideCollection.d.ts +111 -0
- package/dist/collections/TenantPermissionOverrideCollection.d.ts.map +1 -0
- package/dist/collections/UserCollection.d.ts +116 -0
- package/dist/collections/UserCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +19 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1482 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +5216 -0
- package/dist/models/CliAuthRequest.d.ts +25 -0
- package/dist/models/CliAuthRequest.d.ts.map +1 -0
- package/dist/models/Group.d.ts +34 -0
- package/dist/models/Group.d.ts.map +1 -0
- package/dist/models/GroupMember.d.ts +29 -0
- package/dist/models/GroupMember.d.ts.map +1 -0
- package/dist/models/GroupRole.d.ts +29 -0
- package/dist/models/GroupRole.d.ts.map +1 -0
- package/dist/models/MagicLinkToken.d.ts +22 -0
- package/dist/models/MagicLinkToken.d.ts.map +1 -0
- package/dist/models/Membership.d.ts +48 -0
- package/dist/models/Membership.d.ts.map +1 -0
- package/dist/models/MembershipOverride.d.ts +50 -0
- package/dist/models/MembershipOverride.d.ts.map +1 -0
- package/dist/models/Permission.d.ts +79 -0
- package/dist/models/Permission.d.ts.map +1 -0
- package/dist/models/Role.d.ts +67 -0
- package/dist/models/Role.d.ts.map +1 -0
- package/dist/models/RolePermission.d.ts +29 -0
- package/dist/models/RolePermission.d.ts.map +1 -0
- package/dist/models/Session.d.ts +105 -0
- package/dist/models/Session.d.ts.map +1 -0
- package/dist/models/Tenant.d.ts +138 -0
- package/dist/models/Tenant.d.ts.map +1 -0
- package/dist/models/TenantPermissionOverride.d.ts +74 -0
- package/dist/models/TenantPermissionOverride.d.ts.map +1 -0
- package/dist/models/User.d.ts +72 -0
- package/dist/models/User.d.ts.map +1 -0
- package/dist/models/index.d.ts +19 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +139 -0
- package/dist/playground.js.map +1 -0
- package/dist/services/MagicLinkService.d.ts +84 -0
- package/dist/services/MagicLinkService.d.ts.map +1 -0
- package/dist/services/OidcLoginService.d.ts +134 -0
- package/dist/services/OidcLoginService.d.ts.map +1 -0
- package/dist/services/PermissionCatalogService.d.ts +62 -0
- package/dist/services/PermissionCatalogService.d.ts.map +1 -0
- package/dist/services/PermissionResolver.d.ts +150 -0
- package/dist/services/PermissionResolver.d.ts.map +1 -0
- package/dist/services/PostgresPermissionPolicies.d.ts +29 -0
- package/dist/services/PostgresPermissionPolicies.d.ts.map +1 -0
- package/dist/services/SessionPermissionContext.d.ts +43 -0
- package/dist/services/SessionPermissionContext.d.ts.map +1 -0
- package/dist/services/SessionService.d.ts +139 -0
- package/dist/services/SessionService.d.ts.map +1 -0
- package/dist/services/TenantService.d.ts +135 -0
- package/dist/services/TenantService.d.ts.map +1 -0
- package/dist/services/TerminalAuthService.d.ts +189 -0
- package/dist/services/TerminalAuthService.d.ts.map +1 -0
- package/dist/services/index.d.ts +14 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +2744 -0
- package/dist/svelte/components/InviteUserModal.svelte +351 -0
- package/dist/svelte/components/InviteUserModal.svelte.d.ts +17 -0
- package/dist/svelte/components/InviteUserModal.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserAvatar.svelte +105 -0
- package/dist/svelte/components/UserAvatar.svelte.d.ts +10 -0
- package/dist/svelte/components/UserAvatar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserCard.svelte +179 -0
- package/dist/svelte/components/UserCard.svelte.d.ts +18 -0
- package/dist/svelte/components/UserCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserForm.svelte +194 -0
- package/dist/svelte/components/UserForm.svelte.d.ts +18 -0
- package/dist/svelte/components/UserForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserList.svelte +107 -0
- package/dist/svelte/components/UserList.svelte.d.ts +20 -0
- package/dist/svelte/components/UserList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/UserMenu.svelte +326 -0
- package/dist/svelte/components/UserMenu.svelte.d.ts +33 -0
- package/dist/svelte/components/UserMenu.svelte.d.ts.map +1 -0
- package/dist/svelte/components/__tests__/InviteUserModal.test.js +54 -0
- package/dist/svelte/components/__tests__/UserAvatar.test.js +31 -0
- package/dist/svelte/components/__tests__/UserCard.test.js +39 -0
- package/dist/svelte/components/__tests__/UserForm.test.js +50 -0
- package/dist/svelte/components/__tests__/UserList.test.js +48 -0
- package/dist/svelte/components/__tests__/UserMenu.test.js +38 -0
- package/dist/svelte/i18n.d.ts +15 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +15 -0
- package/dist/svelte/index.d.ts +23 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +27 -0
- package/dist/svelte/playground.d.ts +151 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +134 -0
- package/dist/sveltekit/index.d.ts +379 -0
- package/dist/sveltekit/index.d.ts.map +1 -0
- package/dist/sveltekit/resource-list-handler.d.ts +127 -0
- package/dist/sveltekit/resource-list-handler.d.ts.map +1 -0
- package/dist/sveltekit/types.d.ts +31 -0
- package/dist/sveltekit/types.d.ts.map +1 -0
- package/dist/sveltekit.d.ts +2 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +978 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +75 -0
- package/dist/ui.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
import { O as OidcLoginError, h as DEFAULT_SESSION_TTL, X as withSessionPermissionContext, E as TerminalAuthRateLimitError, C as TerminalAuthError, W as resolveOidcProviderConfig, s as OidcLoginService, K as encodeOidcTransaction, V as getUsersOidcConfig, J as decodeOidcTransaction, F as TerminalAuthService, x as SessionService } from "./chunks/TerminalAuthService-DoAMQ_yn.js";
|
|
2
|
+
import { createLogger } from "@happyvertical/logger";
|
|
3
|
+
import { ObjectRegistry } from "@happyvertical/smrt-core";
|
|
4
|
+
import { classnameToTablename } from "@happyvertical/smrt-core/utils";
|
|
5
|
+
import { resolveApiActionSet, methodNameToKebab } from "@happyvertical/smrt-core/vite-plugin";
|
|
6
|
+
const STANDARD_API_ACTIONS = [
|
|
7
|
+
"list",
|
|
8
|
+
"get",
|
|
9
|
+
"create",
|
|
10
|
+
"update",
|
|
11
|
+
"delete"
|
|
12
|
+
];
|
|
13
|
+
function createResourceListHandler(options) {
|
|
14
|
+
const {
|
|
15
|
+
ensureRegistry,
|
|
16
|
+
resolveSession = (event) => defaultResolveSession(event, options),
|
|
17
|
+
commandPolicy = defaultCommandPolicy,
|
|
18
|
+
resourceSlug = defaultResourceSlug,
|
|
19
|
+
kebabRoutes = false
|
|
20
|
+
} = options;
|
|
21
|
+
return async (event) => {
|
|
22
|
+
await ensureRegistry();
|
|
23
|
+
let session;
|
|
24
|
+
try {
|
|
25
|
+
session = await resolveSession(event);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof InvalidBearerError) {
|
|
28
|
+
return jsonResponse$1({ error: error.message }, 401);
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
const resources = [];
|
|
33
|
+
const skipWarnings = [];
|
|
34
|
+
const slugSeen = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const [registeredName, registered] of ObjectRegistry.getAllClasses()) {
|
|
36
|
+
const def = synthesizeDefinition(
|
|
37
|
+
registeredName,
|
|
38
|
+
registered
|
|
39
|
+
);
|
|
40
|
+
const cliConfig = def.decoratorConfig.cli;
|
|
41
|
+
if (!cliConfig) continue;
|
|
42
|
+
if (!isHttpCliConfig(cliConfig)) continue;
|
|
43
|
+
if (isCollectionClass(def)) continue;
|
|
44
|
+
const apiActionSet = resolveApiActionSet(
|
|
45
|
+
def
|
|
46
|
+
);
|
|
47
|
+
const includedMethods = resolveCliIncludedMethods(
|
|
48
|
+
cliConfig,
|
|
49
|
+
apiActionSet
|
|
50
|
+
);
|
|
51
|
+
if (includedMethods.length === 0) continue;
|
|
52
|
+
const slug = resourceSlug({
|
|
53
|
+
className: def.className,
|
|
54
|
+
collection: def.collection,
|
|
55
|
+
qualifiedName: def.qualifiedName,
|
|
56
|
+
packageName: def.packageName
|
|
57
|
+
});
|
|
58
|
+
const previous = slugSeen.get(slug);
|
|
59
|
+
if (previous && previous !== def.qualifiedName) {
|
|
60
|
+
return jsonResponse$1(
|
|
61
|
+
{
|
|
62
|
+
error: "resource-slug-collision",
|
|
63
|
+
slug,
|
|
64
|
+
classes: [previous, def.qualifiedName ?? def.className].filter(
|
|
65
|
+
Boolean
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
500
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
slugSeen.set(slug, def.qualifiedName ?? def.className);
|
|
72
|
+
const resourceShell = {
|
|
73
|
+
slug,
|
|
74
|
+
className: def.className,
|
|
75
|
+
qualifiedName: def.qualifiedName,
|
|
76
|
+
packageName: def.packageName,
|
|
77
|
+
label: humanLabel(def.className),
|
|
78
|
+
apiPath: resolveApiPath(def)
|
|
79
|
+
};
|
|
80
|
+
const classMeta = {
|
|
81
|
+
name: def.className,
|
|
82
|
+
qualifiedName: def.qualifiedName,
|
|
83
|
+
packageName: def.packageName,
|
|
84
|
+
decoratorConfig: def.decoratorConfig
|
|
85
|
+
};
|
|
86
|
+
const commandNameSeen = /* @__PURE__ */ new Set();
|
|
87
|
+
const candidates = [];
|
|
88
|
+
for (const methodName of includedMethods) {
|
|
89
|
+
const result = buildCommand(def, methodName, kebabRoutes);
|
|
90
|
+
if (!result.ok) {
|
|
91
|
+
skipWarnings.push({
|
|
92
|
+
ref: `${def.className}.${methodName}`,
|
|
93
|
+
reason: result.reason,
|
|
94
|
+
candidate: result.candidate,
|
|
95
|
+
resourceMeta: resourceShell,
|
|
96
|
+
classMeta
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (result.command.kind === "custom" && STANDARD_API_ACTIONS.includes(
|
|
101
|
+
result.command.commandName
|
|
102
|
+
)) {
|
|
103
|
+
return jsonResponse$1(
|
|
104
|
+
{
|
|
105
|
+
error: "command-name-shadows-crud",
|
|
106
|
+
className: def.className,
|
|
107
|
+
methodName,
|
|
108
|
+
commandName: result.command.commandName
|
|
109
|
+
},
|
|
110
|
+
500
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (commandNameSeen.has(result.command.commandName)) {
|
|
114
|
+
return jsonResponse$1(
|
|
115
|
+
{
|
|
116
|
+
error: "command-name-collision",
|
|
117
|
+
className: def.className,
|
|
118
|
+
commandName: result.command.commandName
|
|
119
|
+
},
|
|
120
|
+
500
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
commandNameSeen.add(result.command.commandName);
|
|
124
|
+
candidates.push(result.command);
|
|
125
|
+
}
|
|
126
|
+
const policyResults = await Promise.all(
|
|
127
|
+
candidates.map(
|
|
128
|
+
(candidate) => commandPolicy({
|
|
129
|
+
resource: resourceShell,
|
|
130
|
+
command: candidate,
|
|
131
|
+
session,
|
|
132
|
+
classMeta
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
const allowed = candidates.filter((_, i) => policyResults[i]);
|
|
137
|
+
if (allowed.length === 0) continue;
|
|
138
|
+
resources.push({ ...resourceShell, commands: allowed });
|
|
139
|
+
}
|
|
140
|
+
const warnResults = await Promise.all(
|
|
141
|
+
skipWarnings.map(
|
|
142
|
+
(w) => commandPolicy({
|
|
143
|
+
resource: w.resourceMeta,
|
|
144
|
+
command: w.candidate,
|
|
145
|
+
session,
|
|
146
|
+
classMeta: w.classMeta
|
|
147
|
+
})
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
const visibleWarnings = skipWarnings.filter((_, i) => warnResults[i]).map((w) => `${w.ref}: ${w.reason}`);
|
|
151
|
+
const body = {
|
|
152
|
+
user: session.user ? { authenticated: true, id: String(session.user.id ?? "") } : { authenticated: false },
|
|
153
|
+
warnings: visibleWarnings,
|
|
154
|
+
resources
|
|
155
|
+
};
|
|
156
|
+
return jsonResponse$1(body, 200, {
|
|
157
|
+
"cache-control": "private, no-store"
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function synthesizeDefinition(_registeredName, registered) {
|
|
162
|
+
const methods = {};
|
|
163
|
+
for (const [name, value] of registered.methods.entries()) {
|
|
164
|
+
methods[name] = value;
|
|
165
|
+
}
|
|
166
|
+
const decoratorConfig = registered.config ?? {};
|
|
167
|
+
const collection = registered.collection ?? deriveCollectionFromConfig(decoratorConfig) ?? classnameToTablename(registered.name);
|
|
168
|
+
return {
|
|
169
|
+
className: registered.name,
|
|
170
|
+
qualifiedName: registered.qualifiedName,
|
|
171
|
+
packageName: registered.packageName,
|
|
172
|
+
collection,
|
|
173
|
+
decoratorConfig,
|
|
174
|
+
methods,
|
|
175
|
+
tools: registered.tools,
|
|
176
|
+
extends: registered.extends,
|
|
177
|
+
extendsTypeArg: registered.extendsTypeArg,
|
|
178
|
+
constructor: registered.constructor
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function isCollectionClass(def) {
|
|
182
|
+
if (def.extends === "SmrtCollection") return true;
|
|
183
|
+
if (def.extendsTypeArg) return true;
|
|
184
|
+
return ctorChainContains(def.constructor, "SmrtCollection");
|
|
185
|
+
}
|
|
186
|
+
function ctorChainContains(ctor, name, depthCap = 16) {
|
|
187
|
+
let current = ctor;
|
|
188
|
+
for (let i = 0; i < depthCap && current; i += 1) {
|
|
189
|
+
if (current.name === name) return true;
|
|
190
|
+
const next = Object.getPrototypeOf(current);
|
|
191
|
+
if (!next || next === current) break;
|
|
192
|
+
current = next;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function deriveCollectionFromConfig(config) {
|
|
197
|
+
return config?.tableName ?? void 0;
|
|
198
|
+
}
|
|
199
|
+
function resolveCliIncludedMethods(cliConfig, apiActionSet) {
|
|
200
|
+
if (cliConfig === true) {
|
|
201
|
+
return STANDARD_API_ACTIONS.filter((a) => apiActionSet.has(a));
|
|
202
|
+
}
|
|
203
|
+
if (cliConfig === false) return [];
|
|
204
|
+
if (typeof cliConfig !== "object" || cliConfig === null) return [];
|
|
205
|
+
if (Array.isArray(cliConfig)) return [];
|
|
206
|
+
const obj = cliConfig;
|
|
207
|
+
if (obj.include !== void 0 && !Array.isArray(obj.include)) return [];
|
|
208
|
+
if (obj.exclude !== void 0 && !Array.isArray(obj.exclude)) return [];
|
|
209
|
+
if (obj.mirror !== void 0 && obj.mirror !== "api") return [];
|
|
210
|
+
const include = obj.include;
|
|
211
|
+
const exclude = obj.exclude;
|
|
212
|
+
let candidates;
|
|
213
|
+
if (obj.mirror === "api") {
|
|
214
|
+
candidates = [...apiActionSet];
|
|
215
|
+
} else if (include?.includes("*")) {
|
|
216
|
+
candidates = [...apiActionSet];
|
|
217
|
+
} else if (include && include.length > 0) {
|
|
218
|
+
candidates = include.filter((m) => apiActionSet.has(m));
|
|
219
|
+
} else {
|
|
220
|
+
candidates = STANDARD_API_ACTIONS.filter((a) => apiActionSet.has(a));
|
|
221
|
+
}
|
|
222
|
+
if (exclude && exclude.length > 0) {
|
|
223
|
+
const excludeSet = new Set(exclude);
|
|
224
|
+
candidates = candidates.filter((m) => !excludeSet.has(m));
|
|
225
|
+
}
|
|
226
|
+
return candidates;
|
|
227
|
+
}
|
|
228
|
+
function isHttpCliConfig(cliConfig) {
|
|
229
|
+
if (typeof cliConfig !== "object" || cliConfig === null) return true;
|
|
230
|
+
if (Array.isArray(cliConfig)) return true;
|
|
231
|
+
return cliConfig.http !== false;
|
|
232
|
+
}
|
|
233
|
+
function buildCommand(def, methodName, kebabRoutes) {
|
|
234
|
+
const isCrud = STANDARD_API_ACTIONS.includes(methodName);
|
|
235
|
+
if (isCrud) {
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
command: buildCrudCommand(methodName)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const methodDef = def.methods[methodName];
|
|
242
|
+
if (!methodDef) {
|
|
243
|
+
return {
|
|
244
|
+
ok: true,
|
|
245
|
+
command: buildCustomCommand(def, methodName, void 0, kebabRoutes)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const paramCount = methodDef.parameters?.length ?? 0;
|
|
249
|
+
if (paramCount > 1) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
reason: "multi-arg unsupported (use single-options-object convention)",
|
|
253
|
+
candidate: buildCustomCommand(def, methodName, methodDef, kebabRoutes)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const candidate = buildCustomCommand(def, methodName, methodDef, kebabRoutes);
|
|
257
|
+
if (hasDynamicSegments(candidate.pathSegments)) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
reason: "dynamic-path-params unsupported",
|
|
261
|
+
candidate
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (candidate.httpMethod === "DELETE" && candidate.kind === "custom") {
|
|
265
|
+
const methodHasParams = (methodDef.parameters?.length ?? 0) > 0;
|
|
266
|
+
if (methodHasParams || hasNonIdParameters(candidate.parameters)) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
reason: "DELETE-with-body unsupported",
|
|
270
|
+
candidate
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { ok: true, command: candidate };
|
|
275
|
+
}
|
|
276
|
+
function buildCrudCommand(name) {
|
|
277
|
+
const item = name === "get" || name === "update" || name === "delete";
|
|
278
|
+
const httpMethod = name === "list" || name === "get" ? "GET" : name === "create" ? "POST" : name === "update" ? "PUT" : "DELETE";
|
|
279
|
+
return {
|
|
280
|
+
methodName: name,
|
|
281
|
+
commandName: name,
|
|
282
|
+
kind: "crud",
|
|
283
|
+
scope: item ? "item" : "collection",
|
|
284
|
+
httpMethod,
|
|
285
|
+
pathSegments: [],
|
|
286
|
+
description: defaultCrudDescription(name)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function buildCustomCommand(def, methodName, methodDef, kebabRoutes) {
|
|
290
|
+
const apiConfigObj = getApiConfigObject(def.decoratorConfig.api);
|
|
291
|
+
const routeConfig = apiConfigObj?.routes?.[methodName];
|
|
292
|
+
const defaultScope = methodDef?.isStatic ? "collection" : "item";
|
|
293
|
+
const scope = routeConfig?.scope ?? defaultScope;
|
|
294
|
+
const httpMethod = normalizeApiHttpMethod(routeConfig?.method);
|
|
295
|
+
let pathSegments;
|
|
296
|
+
if (routeConfig?.path) {
|
|
297
|
+
pathSegments = routeConfig.path.split("/").map((s) => s.trim()).filter(Boolean);
|
|
298
|
+
if (pathSegments.length === 0) pathSegments = [methodName];
|
|
299
|
+
} else {
|
|
300
|
+
pathSegments = [kebabRoutes ? methodNameToKebab(methodName) : methodName];
|
|
301
|
+
}
|
|
302
|
+
const parameters = resolveParametersSchema(def, methodName);
|
|
303
|
+
return {
|
|
304
|
+
methodName,
|
|
305
|
+
commandName: methodNameToKebab(methodName),
|
|
306
|
+
kind: "custom",
|
|
307
|
+
scope,
|
|
308
|
+
httpMethod,
|
|
309
|
+
pathSegments,
|
|
310
|
+
description: methodDef?.description,
|
|
311
|
+
parameters
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function resolveParametersSchema(def, methodName, _methodDef) {
|
|
315
|
+
const tool = def.tools?.find((t) => t.function.name === methodName);
|
|
316
|
+
if (tool?.function.parameters) {
|
|
317
|
+
const params = tool.function.parameters;
|
|
318
|
+
if (hasUsableProperties(params)) return params;
|
|
319
|
+
}
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
function hasUsableProperties(schema) {
|
|
323
|
+
if (!schema || typeof schema !== "object") return false;
|
|
324
|
+
const props = schema.properties;
|
|
325
|
+
if (!props || typeof props !== "object") return false;
|
|
326
|
+
return Object.keys(props).length > 0;
|
|
327
|
+
}
|
|
328
|
+
function normalizeApiHttpMethod(method) {
|
|
329
|
+
switch (method?.toUpperCase()) {
|
|
330
|
+
case "GET":
|
|
331
|
+
case "POST":
|
|
332
|
+
case "PUT":
|
|
333
|
+
case "PATCH":
|
|
334
|
+
case "DELETE":
|
|
335
|
+
return method.toUpperCase();
|
|
336
|
+
default:
|
|
337
|
+
return "POST";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function getApiConfigObject(api) {
|
|
341
|
+
if (typeof api !== "object" || api === null) return void 0;
|
|
342
|
+
return api;
|
|
343
|
+
}
|
|
344
|
+
function hasDynamicSegments(segments) {
|
|
345
|
+
return segments.some((s) => /^\[.+\]$/.test(s) || s.startsWith(":"));
|
|
346
|
+
}
|
|
347
|
+
function hasNonIdParameters(parameters) {
|
|
348
|
+
if (!parameters || typeof parameters !== "object") return false;
|
|
349
|
+
const props = parameters.properties;
|
|
350
|
+
if (!props) return false;
|
|
351
|
+
const keys = Object.keys(props).filter((k) => k !== "id");
|
|
352
|
+
return keys.length > 0;
|
|
353
|
+
}
|
|
354
|
+
function defaultCrudDescription(name) {
|
|
355
|
+
switch (name) {
|
|
356
|
+
case "list":
|
|
357
|
+
return "List records";
|
|
358
|
+
case "get":
|
|
359
|
+
return "Get a record by id";
|
|
360
|
+
case "create":
|
|
361
|
+
return "Create a new record";
|
|
362
|
+
case "update":
|
|
363
|
+
return "Update an existing record";
|
|
364
|
+
case "delete":
|
|
365
|
+
return "Delete a record";
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function resolveApiPath(def) {
|
|
369
|
+
return def.collection;
|
|
370
|
+
}
|
|
371
|
+
function defaultResourceSlug(meta) {
|
|
372
|
+
return meta.collection;
|
|
373
|
+
}
|
|
374
|
+
function humanLabel(className) {
|
|
375
|
+
return className.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
376
|
+
}
|
|
377
|
+
class InvalidBearerError extends Error {
|
|
378
|
+
constructor(message = "Invalid or expired bearer token.") {
|
|
379
|
+
super(message);
|
|
380
|
+
this.name = "InvalidBearerError";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function defaultResolveSession(event, options) {
|
|
384
|
+
const locals = event.locals ?? {};
|
|
385
|
+
if (locals.user) {
|
|
386
|
+
return {
|
|
387
|
+
user: locals.user,
|
|
388
|
+
membership: locals.membership ?? null,
|
|
389
|
+
permissions: locals.permissions ?? [],
|
|
390
|
+
tenantId: locals.tenantId ?? null,
|
|
391
|
+
sessionId: locals.sessionId ?? null
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const bearer = parseBearerToken(event.request.headers.get("authorization"));
|
|
395
|
+
if (bearer) {
|
|
396
|
+
const ctx = await loadBearerSessionContext(bearer, options);
|
|
397
|
+
if (!ctx) {
|
|
398
|
+
throw new InvalidBearerError();
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
user: ctx.user,
|
|
402
|
+
membership: ctx.membership ?? null,
|
|
403
|
+
permissions: ctx.permissions,
|
|
404
|
+
tenantId: ctx.tenantId,
|
|
405
|
+
sessionId: ctx.sessionId
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
user: null,
|
|
410
|
+
membership: null,
|
|
411
|
+
permissions: [],
|
|
412
|
+
tenantId: null,
|
|
413
|
+
sessionId: null
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function defaultCommandPolicy(ctx) {
|
|
417
|
+
return ctx.session.user != null;
|
|
418
|
+
}
|
|
419
|
+
function jsonResponse$1(body, status = 200, extraHeaders = {}) {
|
|
420
|
+
return new Response(JSON.stringify(body), {
|
|
421
|
+
status,
|
|
422
|
+
headers: {
|
|
423
|
+
"content-type": "application/json",
|
|
424
|
+
...extraHeaders
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const defaultSessionLocals = {
|
|
429
|
+
user: null,
|
|
430
|
+
membership: null,
|
|
431
|
+
permissions: [],
|
|
432
|
+
tenantId: null,
|
|
433
|
+
sessionId: null
|
|
434
|
+
};
|
|
435
|
+
const logger = createLogger({ level: "info" });
|
|
436
|
+
function createSessionHandler(options) {
|
|
437
|
+
const cookieName = options.cookieName ?? "sid";
|
|
438
|
+
const ttl = options.ttl ?? DEFAULT_SESSION_TTL;
|
|
439
|
+
const skipPaths = options.skipPaths ?? [];
|
|
440
|
+
options.cookiePath ?? "/";
|
|
441
|
+
options.cookieSameSite ?? "lax";
|
|
442
|
+
let sessionService = null;
|
|
443
|
+
const getSessionService = async () => {
|
|
444
|
+
if (!sessionService) {
|
|
445
|
+
sessionService = await SessionService.create({
|
|
446
|
+
...options,
|
|
447
|
+
defaultTTL: ttl,
|
|
448
|
+
autoExtend: options.autoExtend ?? false
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return sessionService;
|
|
452
|
+
};
|
|
453
|
+
return async ({ event, resolve }) => {
|
|
454
|
+
event.locals.user = null;
|
|
455
|
+
event.locals.membership = null;
|
|
456
|
+
event.locals.permissions = [];
|
|
457
|
+
event.locals.tenantId = null;
|
|
458
|
+
event.locals.sessionId = null;
|
|
459
|
+
if (skipPaths.some((path) => event.url.pathname.startsWith(path))) {
|
|
460
|
+
return resolve(event);
|
|
461
|
+
}
|
|
462
|
+
const sessionId = event.cookies.get(cookieName);
|
|
463
|
+
if (!sessionId && !options.postgresRls) {
|
|
464
|
+
return resolve(event);
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
const service = await getSessionService();
|
|
468
|
+
return await withSessionPermissionContext(
|
|
469
|
+
{
|
|
470
|
+
...options,
|
|
471
|
+
enterTenantContext: options.enterTenantContext,
|
|
472
|
+
postgresRls: options.postgresRls,
|
|
473
|
+
sessionId,
|
|
474
|
+
sessionService: service
|
|
475
|
+
},
|
|
476
|
+
async (context) => {
|
|
477
|
+
if (context.session) {
|
|
478
|
+
event.locals.user = context.user;
|
|
479
|
+
event.locals.membership = context.membership ?? null;
|
|
480
|
+
event.locals.permissions = context.permissions;
|
|
481
|
+
event.locals.tenantId = context.tenantId;
|
|
482
|
+
event.locals.sessionId = context.sessionId;
|
|
483
|
+
}
|
|
484
|
+
return resolve(event);
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
logger.error("Session or request context initialization error", {
|
|
489
|
+
error
|
|
490
|
+
});
|
|
491
|
+
if (options.postgresRls) {
|
|
492
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
493
|
+
}
|
|
494
|
+
return resolve(event);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const sessionServiceCache = /* @__PURE__ */ new Map();
|
|
499
|
+
async function getOrCreateSessionService(options, ttl) {
|
|
500
|
+
const cacheKey = JSON.stringify(options.db);
|
|
501
|
+
let service = sessionServiceCache.get(cacheKey);
|
|
502
|
+
if (!service) {
|
|
503
|
+
service = await SessionService.create({
|
|
504
|
+
...options,
|
|
505
|
+
defaultTTL: ttl
|
|
506
|
+
});
|
|
507
|
+
sessionServiceCache.set(cacheKey, service);
|
|
508
|
+
}
|
|
509
|
+
return service;
|
|
510
|
+
}
|
|
511
|
+
async function createSessionCookie(event, userId, tenantId, options) {
|
|
512
|
+
const cookieName = options.cookieName ?? "sid";
|
|
513
|
+
const ttl = options.ttl ?? DEFAULT_SESSION_TTL;
|
|
514
|
+
const cookiePath = options.cookiePath ?? "/";
|
|
515
|
+
const cookieSameSite = options.cookieSameSite ?? "lax";
|
|
516
|
+
const cookieSecure = options.cookieSecure ?? event.url.protocol === "https:";
|
|
517
|
+
const service = await getOrCreateSessionService(options, ttl);
|
|
518
|
+
const sessionId = await service.createSession(userId, tenantId, {
|
|
519
|
+
ttl,
|
|
520
|
+
userAgent: options.userAgent,
|
|
521
|
+
ipAddress: options.ipAddress,
|
|
522
|
+
data: options.data
|
|
523
|
+
});
|
|
524
|
+
event.cookies.set(cookieName, sessionId, {
|
|
525
|
+
path: cookiePath,
|
|
526
|
+
httpOnly: true,
|
|
527
|
+
secure: cookieSecure,
|
|
528
|
+
sameSite: cookieSameSite,
|
|
529
|
+
maxAge: ttl
|
|
530
|
+
});
|
|
531
|
+
return sessionId;
|
|
532
|
+
}
|
|
533
|
+
async function destroySessionCookie(event, options) {
|
|
534
|
+
const cookieName = options.cookieName ?? "sid";
|
|
535
|
+
const cookiePath = options.cookiePath ?? "/";
|
|
536
|
+
const ttl = options.ttl ?? DEFAULT_SESSION_TTL;
|
|
537
|
+
const sessionId = event.cookies.get(cookieName);
|
|
538
|
+
if (sessionId) {
|
|
539
|
+
try {
|
|
540
|
+
const service = await getOrCreateSessionService(options, ttl);
|
|
541
|
+
await service.destroySession(sessionId);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
logger.error("Session destruction error", { error });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
event.cookies.delete(cookieName, { path: cookiePath });
|
|
547
|
+
}
|
|
548
|
+
async function switchSessionTenant(event, tenantId, options) {
|
|
549
|
+
const cookieName = options.cookieName ?? "sid";
|
|
550
|
+
const ttl = options.ttl ?? DEFAULT_SESSION_TTL;
|
|
551
|
+
const sessionId = event.cookies.get(cookieName);
|
|
552
|
+
if (!sessionId) return false;
|
|
553
|
+
const service = await getOrCreateSessionService(options, ttl);
|
|
554
|
+
return service.switchTenant(sessionId, tenantId);
|
|
555
|
+
}
|
|
556
|
+
function getOidcProviderName(event, options) {
|
|
557
|
+
if (typeof options.provider === "function") {
|
|
558
|
+
return options.provider(event);
|
|
559
|
+
}
|
|
560
|
+
return options.provider ?? event.params?.provider ?? options.defaultProvider;
|
|
561
|
+
}
|
|
562
|
+
function getOidcTransactionCookieName(providerName, options) {
|
|
563
|
+
const prefix = options.transactionCookiePrefix ?? "smrt_oidc";
|
|
564
|
+
const safeProvider = providerName.replace(/[^a-z0-9_-]/giu, "_");
|
|
565
|
+
return `${prefix}_${safeProvider}`;
|
|
566
|
+
}
|
|
567
|
+
function useSecureCookie(event, explicit) {
|
|
568
|
+
return explicit ?? event.url.protocol === "https:";
|
|
569
|
+
}
|
|
570
|
+
function resolveCallbackPath(providerName, options) {
|
|
571
|
+
if (typeof options.callbackPath === "function") {
|
|
572
|
+
return options.callbackPath(providerName);
|
|
573
|
+
}
|
|
574
|
+
return options.callbackPath ?? `/auth/${providerName}/callback`;
|
|
575
|
+
}
|
|
576
|
+
function resolveProviderWithRedirectUri(event, providerName, provider, options) {
|
|
577
|
+
return {
|
|
578
|
+
...provider,
|
|
579
|
+
redirectUri: provider.redirectUri ?? new URL(
|
|
580
|
+
resolveCallbackPath(providerName, options),
|
|
581
|
+
event.url.origin
|
|
582
|
+
).toString()
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function createOidcService(event, options) {
|
|
586
|
+
const providerName = getOidcProviderName(event, options);
|
|
587
|
+
const resolved = resolveOidcProviderConfig(providerName, options);
|
|
588
|
+
const provider = resolveProviderWithRedirectUri(
|
|
589
|
+
event,
|
|
590
|
+
resolved.providerName,
|
|
591
|
+
resolved.provider,
|
|
592
|
+
options
|
|
593
|
+
);
|
|
594
|
+
return new OidcLoginService({
|
|
595
|
+
...options,
|
|
596
|
+
provider,
|
|
597
|
+
providerName: resolved.providerName
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
function getReturnTo(event, options) {
|
|
601
|
+
const param = options.returnToParam ?? "returnTo";
|
|
602
|
+
return getLocalReturnTo(event.url.searchParams.get(param));
|
|
603
|
+
}
|
|
604
|
+
function getLocalReturnTo(returnTo) {
|
|
605
|
+
if (!returnTo) return void 0;
|
|
606
|
+
if (returnTo.startsWith("/") && !returnTo.startsWith("//")) {
|
|
607
|
+
return returnTo;
|
|
608
|
+
}
|
|
609
|
+
return void 0;
|
|
610
|
+
}
|
|
611
|
+
function getTransactionCookieSameSite(event, options) {
|
|
612
|
+
if (options.transactionCookieSameSite) {
|
|
613
|
+
return options.transactionCookieSameSite;
|
|
614
|
+
}
|
|
615
|
+
return useSecureCookie(event, options.transactionCookieSecure) ? "none" : "lax";
|
|
616
|
+
}
|
|
617
|
+
function getTransactionCookieSecret(provider, options) {
|
|
618
|
+
const secret = options.transactionCookieSecret ?? provider.clientSecret;
|
|
619
|
+
return secret && secret.length > 0 ? secret : void 0;
|
|
620
|
+
}
|
|
621
|
+
function bytesToBase64Url(bytes) {
|
|
622
|
+
let binary = "";
|
|
623
|
+
for (const byte of bytes) {
|
|
624
|
+
binary += String.fromCharCode(byte);
|
|
625
|
+
}
|
|
626
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
627
|
+
}
|
|
628
|
+
async function signTransactionPayload(payload, secret) {
|
|
629
|
+
const key = await crypto.subtle.importKey(
|
|
630
|
+
"raw",
|
|
631
|
+
new TextEncoder().encode(secret),
|
|
632
|
+
{ hash: "SHA-256", name: "HMAC" },
|
|
633
|
+
false,
|
|
634
|
+
["sign"]
|
|
635
|
+
);
|
|
636
|
+
const signature = await crypto.subtle.sign(
|
|
637
|
+
"HMAC",
|
|
638
|
+
key,
|
|
639
|
+
new TextEncoder().encode(payload)
|
|
640
|
+
);
|
|
641
|
+
return bytesToBase64Url(new Uint8Array(signature));
|
|
642
|
+
}
|
|
643
|
+
function timingSafeEqual(left, right) {
|
|
644
|
+
const leftBytes = new TextEncoder().encode(left);
|
|
645
|
+
const rightBytes = new TextEncoder().encode(right);
|
|
646
|
+
let diff = leftBytes.length ^ rightBytes.length;
|
|
647
|
+
const length = Math.max(leftBytes.length, rightBytes.length);
|
|
648
|
+
for (let index = 0; index < length; index += 1) {
|
|
649
|
+
diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
|
|
650
|
+
}
|
|
651
|
+
return diff === 0;
|
|
652
|
+
}
|
|
653
|
+
async function encodeOidcTransactionCookie(transaction, secret) {
|
|
654
|
+
const payload = encodeOidcTransaction(transaction);
|
|
655
|
+
if (!secret) return payload;
|
|
656
|
+
const signature = await signTransactionPayload(payload, secret);
|
|
657
|
+
return `${payload}.${signature}`;
|
|
658
|
+
}
|
|
659
|
+
async function decodeOidcTransactionCookie(value, secret) {
|
|
660
|
+
if (!secret) return decodeOidcTransaction(value);
|
|
661
|
+
const separatorIndex = value.lastIndexOf(".");
|
|
662
|
+
if (separatorIndex < 0) {
|
|
663
|
+
throw new OidcLoginError("Invalid OIDC login transaction signature.");
|
|
664
|
+
}
|
|
665
|
+
const payload = value.slice(0, separatorIndex);
|
|
666
|
+
const signature = value.slice(separatorIndex + 1);
|
|
667
|
+
const expectedSignature = await signTransactionPayload(payload, secret);
|
|
668
|
+
if (!timingSafeEqual(signature, expectedSignature)) {
|
|
669
|
+
throw new OidcLoginError("Invalid OIDC login transaction signature.");
|
|
670
|
+
}
|
|
671
|
+
return decodeOidcTransaction(payload);
|
|
672
|
+
}
|
|
673
|
+
function getTransactionTtl(options) {
|
|
674
|
+
return options.transactionTtl ?? getUsersOidcConfig().transactionTtl ?? 10 * 60;
|
|
675
|
+
}
|
|
676
|
+
async function resolveTenantId(result, event, options) {
|
|
677
|
+
if (typeof options.tenantId === "function") {
|
|
678
|
+
return options.tenantId(result, event);
|
|
679
|
+
}
|
|
680
|
+
return options.tenantId;
|
|
681
|
+
}
|
|
682
|
+
function redirectResponse(location) {
|
|
683
|
+
return new Response(null, {
|
|
684
|
+
headers: { location: location.toString() },
|
|
685
|
+
status: 303
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function failureResponse(error) {
|
|
689
|
+
const message = error instanceof Error ? error.message : "OIDC login failed.";
|
|
690
|
+
return new Response(message, {
|
|
691
|
+
status: 401,
|
|
692
|
+
headers: {
|
|
693
|
+
"content-type": "text/plain; charset=utf-8"
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
function resolveSuccessRedirect(result, event, options) {
|
|
698
|
+
if (typeof options.successRedirect === "function") {
|
|
699
|
+
return options.successRedirect(result, event);
|
|
700
|
+
}
|
|
701
|
+
return options.successRedirect ?? result.returnTo ?? "/";
|
|
702
|
+
}
|
|
703
|
+
function resolveFailureRedirect(error, event, options) {
|
|
704
|
+
if (!options.failureRedirect) return void 0;
|
|
705
|
+
if (typeof options.failureRedirect === "function") {
|
|
706
|
+
return options.failureRedirect(error, event);
|
|
707
|
+
}
|
|
708
|
+
return options.failureRedirect;
|
|
709
|
+
}
|
|
710
|
+
async function beginOidcLogin(event, options) {
|
|
711
|
+
const service = createOidcService(event, options);
|
|
712
|
+
const transaction = service.createTransaction(getReturnTo(event, options));
|
|
713
|
+
const result = await service.createAuthorizationUrl({ transaction });
|
|
714
|
+
event.cookies.set(
|
|
715
|
+
getOidcTransactionCookieName(service.providerName, options),
|
|
716
|
+
await encodeOidcTransactionCookie(
|
|
717
|
+
transaction,
|
|
718
|
+
getTransactionCookieSecret(service.provider, options)
|
|
719
|
+
),
|
|
720
|
+
{
|
|
721
|
+
httpOnly: true,
|
|
722
|
+
maxAge: getTransactionTtl(options),
|
|
723
|
+
path: options.transactionCookiePath ?? "/",
|
|
724
|
+
sameSite: getTransactionCookieSameSite(event, options),
|
|
725
|
+
secure: useSecureCookie(event, options.transactionCookieSecure)
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
return {
|
|
729
|
+
providerName: service.providerName,
|
|
730
|
+
transaction,
|
|
731
|
+
url: result.url
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
async function completeOidcLogin(event, options) {
|
|
735
|
+
const service = createOidcService(event, options);
|
|
736
|
+
const cookieName = getOidcTransactionCookieName(
|
|
737
|
+
service.providerName,
|
|
738
|
+
options
|
|
739
|
+
);
|
|
740
|
+
const cookiePath = options.transactionCookiePath ?? "/";
|
|
741
|
+
try {
|
|
742
|
+
const rawTransaction = event.cookies.get(cookieName);
|
|
743
|
+
if (!rawTransaction) {
|
|
744
|
+
throw new OidcLoginError("Missing OIDC login transaction cookie.");
|
|
745
|
+
}
|
|
746
|
+
const decodedTransaction = await decodeOidcTransactionCookie(
|
|
747
|
+
rawTransaction,
|
|
748
|
+
getTransactionCookieSecret(service.provider, options)
|
|
749
|
+
);
|
|
750
|
+
const transaction = {
|
|
751
|
+
...decodedTransaction,
|
|
752
|
+
returnTo: getLocalReturnTo(decodedTransaction.returnTo)
|
|
753
|
+
};
|
|
754
|
+
const ttl = getTransactionTtl(options);
|
|
755
|
+
if (Date.now() - transaction.createdAt > ttl * 1e3) {
|
|
756
|
+
throw new OidcLoginError("OIDC login transaction has expired.");
|
|
757
|
+
}
|
|
758
|
+
const result = await service.completeLogin(event.url, transaction);
|
|
759
|
+
const tenantId = await resolveTenantId(result, event, options);
|
|
760
|
+
const sessionId = await createSessionCookie(
|
|
761
|
+
event,
|
|
762
|
+
result.user.id,
|
|
763
|
+
tenantId ?? void 0,
|
|
764
|
+
{
|
|
765
|
+
...options,
|
|
766
|
+
cookieName: options.sessionCookieName,
|
|
767
|
+
cookiePath: options.sessionCookiePath,
|
|
768
|
+
cookieSameSite: options.sessionCookieSameSite,
|
|
769
|
+
cookieSecure: useSecureCookie(event, options.sessionCookieSecure),
|
|
770
|
+
data: {
|
|
771
|
+
oidcIssuer: result.claims.iss,
|
|
772
|
+
oidcProvider: service.providerName
|
|
773
|
+
},
|
|
774
|
+
ipAddress: event.getClientAddress?.(),
|
|
775
|
+
ttl: options.sessionTtl,
|
|
776
|
+
userAgent: event.request.headers.get("user-agent") ?? void 0
|
|
777
|
+
}
|
|
778
|
+
);
|
|
779
|
+
return {
|
|
780
|
+
...result,
|
|
781
|
+
providerName: service.providerName,
|
|
782
|
+
returnTo: transaction.returnTo,
|
|
783
|
+
sessionId
|
|
784
|
+
};
|
|
785
|
+
} finally {
|
|
786
|
+
event.cookies.delete(cookieName, { path: cookiePath });
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function createOidcLoginHandler(options) {
|
|
790
|
+
return async (event) => {
|
|
791
|
+
const { url } = await beginOidcLogin(event, options);
|
|
792
|
+
return redirectResponse(url);
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function createOidcCallbackHandler(options) {
|
|
796
|
+
return async (event) => {
|
|
797
|
+
try {
|
|
798
|
+
const result = await completeOidcLogin(event, options);
|
|
799
|
+
const redirectTo = await resolveSuccessRedirect(result, event, options);
|
|
800
|
+
return redirectResponse(redirectTo);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
const failureRedirect = resolveFailureRedirect(error, event, options);
|
|
803
|
+
if (failureRedirect) return redirectResponse(failureRedirect);
|
|
804
|
+
return failureResponse(error);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const terminalAuthServiceCache = /* @__PURE__ */ new Map();
|
|
809
|
+
function terminalAuthCacheKey(options) {
|
|
810
|
+
return JSON.stringify({
|
|
811
|
+
db: options.db,
|
|
812
|
+
cookie: options.sessionCookieName,
|
|
813
|
+
prefix: options.userCodePrefix,
|
|
814
|
+
reqTtl: options.requestTtlSeconds,
|
|
815
|
+
sessTtl: options.sessionTtlSeconds,
|
|
816
|
+
poll: options.pollIntervalSeconds,
|
|
817
|
+
path: options.verificationPath,
|
|
818
|
+
autoExtend: options.sessionAutoExtend,
|
|
819
|
+
maxAttempts: options.maxApproveAttempts,
|
|
820
|
+
attemptWindow: options.approveAttemptWindowSeconds
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
async function getOrCreateTerminalAuthService(options) {
|
|
824
|
+
const key = terminalAuthCacheKey(options);
|
|
825
|
+
let service = terminalAuthServiceCache.get(key);
|
|
826
|
+
if (!service) {
|
|
827
|
+
service = await TerminalAuthService.create(options);
|
|
828
|
+
terminalAuthServiceCache.set(key, service);
|
|
829
|
+
}
|
|
830
|
+
return service;
|
|
831
|
+
}
|
|
832
|
+
function parseBearerToken(authorization) {
|
|
833
|
+
const match = authorization?.match(/^Bearer\s+(.+)$/iu);
|
|
834
|
+
return match?.[1]?.trim() || null;
|
|
835
|
+
}
|
|
836
|
+
function createTerminalAuthStartHandler(options) {
|
|
837
|
+
return async (event) => {
|
|
838
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
839
|
+
const origin = typeof options.verificationOrigin === "function" ? options.verificationOrigin(event) : options.verificationOrigin ?? event.url.origin;
|
|
840
|
+
const result = await service.createRequest(origin);
|
|
841
|
+
return jsonResponse(result, 201);
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function createTerminalAuthTokenHandler(options) {
|
|
845
|
+
return async (event) => {
|
|
846
|
+
const body = await event.request.json().catch(() => null);
|
|
847
|
+
const deviceCode = typeof body?.deviceCode === "string" ? body.deviceCode.trim() : "";
|
|
848
|
+
if (!deviceCode) {
|
|
849
|
+
return jsonResponse({ error: "deviceCode is required." }, 400);
|
|
850
|
+
}
|
|
851
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
852
|
+
const result = await service.exchangeDeviceCode(deviceCode);
|
|
853
|
+
return jsonResponse(result);
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function createBearerSessionDeleteHandler(options) {
|
|
857
|
+
return async (event) => {
|
|
858
|
+
const token = parseBearerToken(event.request.headers.get("authorization"));
|
|
859
|
+
if (token) {
|
|
860
|
+
try {
|
|
861
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
862
|
+
await service.destroyBearerSession(token);
|
|
863
|
+
} catch (error) {
|
|
864
|
+
logger.error("Terminal bearer session revocation error", { error });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return jsonResponse({ authenticated: false });
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
async function loadBearerSessionContext(token, options) {
|
|
871
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
872
|
+
return service.loadBearerSession(token);
|
|
873
|
+
}
|
|
874
|
+
function mountTerminalLoginPage(options) {
|
|
875
|
+
const codeParam = options.codeQueryParam ?? "code";
|
|
876
|
+
return {
|
|
877
|
+
load: async (event) => {
|
|
878
|
+
const userCode = event.url.searchParams.get(codeParam)?.trim().toUpperCase() ?? "";
|
|
879
|
+
let requestStatus = null;
|
|
880
|
+
if (userCode) {
|
|
881
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
882
|
+
const request = await service.getRequestForUserCode(userCode);
|
|
883
|
+
requestStatus = request?.status ?? null;
|
|
884
|
+
}
|
|
885
|
+
return { requestStatus, userCode };
|
|
886
|
+
},
|
|
887
|
+
approve: async (event) => {
|
|
888
|
+
const formData = await event.request.formData();
|
|
889
|
+
const userCode = formData.get("userCode")?.toString().trim().toUpperCase() ?? "";
|
|
890
|
+
if (!userCode) {
|
|
891
|
+
return {
|
|
892
|
+
type: "failure",
|
|
893
|
+
status: 400,
|
|
894
|
+
data: {
|
|
895
|
+
status: 400,
|
|
896
|
+
error: "Enter a terminal login code.",
|
|
897
|
+
userCode
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const user = options.resolveUser(event);
|
|
902
|
+
const tenantId = options.resolveTenantId(event);
|
|
903
|
+
if (!user?.id) {
|
|
904
|
+
return {
|
|
905
|
+
type: "failure",
|
|
906
|
+
status: 401,
|
|
907
|
+
data: {
|
|
908
|
+
status: 401,
|
|
909
|
+
error: "Sign in before approving terminal access.",
|
|
910
|
+
userCode
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const service = await getOrCreateTerminalAuthService(options);
|
|
916
|
+
const approveInput = {
|
|
917
|
+
ipAddress: event.getClientAddress?.(),
|
|
918
|
+
tenantId: tenantId ?? null,
|
|
919
|
+
user: {
|
|
920
|
+
email: user.email ?? "",
|
|
921
|
+
id: user.id
|
|
922
|
+
},
|
|
923
|
+
userAgent: event.request.headers.get("user-agent") ?? void 0,
|
|
924
|
+
userCode
|
|
925
|
+
};
|
|
926
|
+
const request = await service.approveRequest(approveInput);
|
|
927
|
+
return {
|
|
928
|
+
approved: true,
|
|
929
|
+
requestStatus: request.status,
|
|
930
|
+
userCode
|
|
931
|
+
};
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (error instanceof TerminalAuthRateLimitError) {
|
|
934
|
+
return {
|
|
935
|
+
type: "failure",
|
|
936
|
+
status: 429,
|
|
937
|
+
data: { status: 429, error: error.message, userCode }
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
const message = error instanceof TerminalAuthError ? error.message : error instanceof Error ? error.message : "Unable to approve terminal login.";
|
|
941
|
+
return {
|
|
942
|
+
type: "failure",
|
|
943
|
+
status: 400,
|
|
944
|
+
data: { status: 400, error: message, userCode }
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function jsonResponse(body, status = 200) {
|
|
951
|
+
return new Response(JSON.stringify(body), {
|
|
952
|
+
status,
|
|
953
|
+
headers: { "content-type": "application/json" }
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
export {
|
|
957
|
+
InvalidBearerError,
|
|
958
|
+
TerminalAuthError,
|
|
959
|
+
TerminalAuthRateLimitError,
|
|
960
|
+
TerminalAuthService,
|
|
961
|
+
beginOidcLogin,
|
|
962
|
+
completeOidcLogin,
|
|
963
|
+
createBearerSessionDeleteHandler,
|
|
964
|
+
createOidcCallbackHandler,
|
|
965
|
+
createOidcLoginHandler,
|
|
966
|
+
createResourceListHandler,
|
|
967
|
+
createSessionCookie,
|
|
968
|
+
createSessionHandler,
|
|
969
|
+
createTerminalAuthStartHandler,
|
|
970
|
+
createTerminalAuthTokenHandler,
|
|
971
|
+
defaultSessionLocals,
|
|
972
|
+
destroySessionCookie,
|
|
973
|
+
loadBearerSessionContext,
|
|
974
|
+
mountTerminalLoginPage,
|
|
975
|
+
parseBearerToken,
|
|
976
|
+
switchSessionTenant
|
|
977
|
+
};
|
|
978
|
+
//# sourceMappingURL=sveltekit.js.map
|