@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.
Files changed (150) hide show
  1. package/AGENTS.md +85 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +459 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/TerminalAuthService-DoAMQ_yn.js +5118 -0
  8. package/dist/chunks/TerminalAuthService-DoAMQ_yn.js.map +1 -0
  9. package/dist/chunks/index-DkoYIvIu.js +169 -0
  10. package/dist/chunks/index-DkoYIvIu.js.map +1 -0
  11. package/dist/collections/CliAuthRequestCollection.d.ts +19 -0
  12. package/dist/collections/CliAuthRequestCollection.d.ts.map +1 -0
  13. package/dist/collections/GroupCollection.d.ts +17 -0
  14. package/dist/collections/GroupCollection.d.ts.map +1 -0
  15. package/dist/collections/GroupMemberCollection.d.ts +43 -0
  16. package/dist/collections/GroupMemberCollection.d.ts.map +1 -0
  17. package/dist/collections/GroupRoleCollection.d.ts +33 -0
  18. package/dist/collections/GroupRoleCollection.d.ts.map +1 -0
  19. package/dist/collections/MagicLinkTokenCollection.d.ts +26 -0
  20. package/dist/collections/MagicLinkTokenCollection.d.ts.map +1 -0
  21. package/dist/collections/MembershipCollection.d.ts +38 -0
  22. package/dist/collections/MembershipCollection.d.ts.map +1 -0
  23. package/dist/collections/MembershipOverrideCollection.d.ts +55 -0
  24. package/dist/collections/MembershipOverrideCollection.d.ts.map +1 -0
  25. package/dist/collections/PermissionCollection.d.ts +34 -0
  26. package/dist/collections/PermissionCollection.d.ts.map +1 -0
  27. package/dist/collections/RoleCollection.d.ts +29 -0
  28. package/dist/collections/RoleCollection.d.ts.map +1 -0
  29. package/dist/collections/RolePermissionCollection.d.ts +33 -0
  30. package/dist/collections/RolePermissionCollection.d.ts.map +1 -0
  31. package/dist/collections/SessionCollection.d.ts +82 -0
  32. package/dist/collections/SessionCollection.d.ts.map +1 -0
  33. package/dist/collections/TenantCollection.d.ts +119 -0
  34. package/dist/collections/TenantCollection.d.ts.map +1 -0
  35. package/dist/collections/TenantPermissionOverrideCollection.d.ts +111 -0
  36. package/dist/collections/TenantPermissionOverrideCollection.d.ts.map +1 -0
  37. package/dist/collections/UserCollection.d.ts +116 -0
  38. package/dist/collections/UserCollection.d.ts.map +1 -0
  39. package/dist/collections/index.d.ts +19 -0
  40. package/dist/collections/index.d.ts.map +1 -0
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +1482 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/manifest.json +5216 -0
  46. package/dist/models/CliAuthRequest.d.ts +25 -0
  47. package/dist/models/CliAuthRequest.d.ts.map +1 -0
  48. package/dist/models/Group.d.ts +34 -0
  49. package/dist/models/Group.d.ts.map +1 -0
  50. package/dist/models/GroupMember.d.ts +29 -0
  51. package/dist/models/GroupMember.d.ts.map +1 -0
  52. package/dist/models/GroupRole.d.ts +29 -0
  53. package/dist/models/GroupRole.d.ts.map +1 -0
  54. package/dist/models/MagicLinkToken.d.ts +22 -0
  55. package/dist/models/MagicLinkToken.d.ts.map +1 -0
  56. package/dist/models/Membership.d.ts +48 -0
  57. package/dist/models/Membership.d.ts.map +1 -0
  58. package/dist/models/MembershipOverride.d.ts +50 -0
  59. package/dist/models/MembershipOverride.d.ts.map +1 -0
  60. package/dist/models/Permission.d.ts +79 -0
  61. package/dist/models/Permission.d.ts.map +1 -0
  62. package/dist/models/Role.d.ts +67 -0
  63. package/dist/models/Role.d.ts.map +1 -0
  64. package/dist/models/RolePermission.d.ts +29 -0
  65. package/dist/models/RolePermission.d.ts.map +1 -0
  66. package/dist/models/Session.d.ts +105 -0
  67. package/dist/models/Session.d.ts.map +1 -0
  68. package/dist/models/Tenant.d.ts +138 -0
  69. package/dist/models/Tenant.d.ts.map +1 -0
  70. package/dist/models/TenantPermissionOverride.d.ts +74 -0
  71. package/dist/models/TenantPermissionOverride.d.ts.map +1 -0
  72. package/dist/models/User.d.ts +72 -0
  73. package/dist/models/User.d.ts.map +1 -0
  74. package/dist/models/index.d.ts +19 -0
  75. package/dist/models/index.d.ts.map +1 -0
  76. package/dist/playground.d.ts +2 -0
  77. package/dist/playground.d.ts.map +1 -0
  78. package/dist/playground.js +139 -0
  79. package/dist/playground.js.map +1 -0
  80. package/dist/services/MagicLinkService.d.ts +84 -0
  81. package/dist/services/MagicLinkService.d.ts.map +1 -0
  82. package/dist/services/OidcLoginService.d.ts +134 -0
  83. package/dist/services/OidcLoginService.d.ts.map +1 -0
  84. package/dist/services/PermissionCatalogService.d.ts +62 -0
  85. package/dist/services/PermissionCatalogService.d.ts.map +1 -0
  86. package/dist/services/PermissionResolver.d.ts +150 -0
  87. package/dist/services/PermissionResolver.d.ts.map +1 -0
  88. package/dist/services/PostgresPermissionPolicies.d.ts +29 -0
  89. package/dist/services/PostgresPermissionPolicies.d.ts.map +1 -0
  90. package/dist/services/SessionPermissionContext.d.ts +43 -0
  91. package/dist/services/SessionPermissionContext.d.ts.map +1 -0
  92. package/dist/services/SessionService.d.ts +139 -0
  93. package/dist/services/SessionService.d.ts.map +1 -0
  94. package/dist/services/TenantService.d.ts +135 -0
  95. package/dist/services/TenantService.d.ts.map +1 -0
  96. package/dist/services/TerminalAuthService.d.ts +189 -0
  97. package/dist/services/TerminalAuthService.d.ts.map +1 -0
  98. package/dist/services/index.d.ts +14 -0
  99. package/dist/services/index.d.ts.map +1 -0
  100. package/dist/smrt-knowledge.json +2744 -0
  101. package/dist/svelte/components/InviteUserModal.svelte +351 -0
  102. package/dist/svelte/components/InviteUserModal.svelte.d.ts +17 -0
  103. package/dist/svelte/components/InviteUserModal.svelte.d.ts.map +1 -0
  104. package/dist/svelte/components/UserAvatar.svelte +105 -0
  105. package/dist/svelte/components/UserAvatar.svelte.d.ts +10 -0
  106. package/dist/svelte/components/UserAvatar.svelte.d.ts.map +1 -0
  107. package/dist/svelte/components/UserCard.svelte +179 -0
  108. package/dist/svelte/components/UserCard.svelte.d.ts +18 -0
  109. package/dist/svelte/components/UserCard.svelte.d.ts.map +1 -0
  110. package/dist/svelte/components/UserForm.svelte +194 -0
  111. package/dist/svelte/components/UserForm.svelte.d.ts +18 -0
  112. package/dist/svelte/components/UserForm.svelte.d.ts.map +1 -0
  113. package/dist/svelte/components/UserList.svelte +107 -0
  114. package/dist/svelte/components/UserList.svelte.d.ts +20 -0
  115. package/dist/svelte/components/UserList.svelte.d.ts.map +1 -0
  116. package/dist/svelte/components/UserMenu.svelte +326 -0
  117. package/dist/svelte/components/UserMenu.svelte.d.ts +33 -0
  118. package/dist/svelte/components/UserMenu.svelte.d.ts.map +1 -0
  119. package/dist/svelte/components/__tests__/InviteUserModal.test.js +54 -0
  120. package/dist/svelte/components/__tests__/UserAvatar.test.js +31 -0
  121. package/dist/svelte/components/__tests__/UserCard.test.js +39 -0
  122. package/dist/svelte/components/__tests__/UserForm.test.js +50 -0
  123. package/dist/svelte/components/__tests__/UserList.test.js +48 -0
  124. package/dist/svelte/components/__tests__/UserMenu.test.js +38 -0
  125. package/dist/svelte/i18n.d.ts +15 -0
  126. package/dist/svelte/i18n.d.ts.map +1 -0
  127. package/dist/svelte/i18n.js +15 -0
  128. package/dist/svelte/index.d.ts +23 -0
  129. package/dist/svelte/index.d.ts.map +1 -0
  130. package/dist/svelte/index.js +27 -0
  131. package/dist/svelte/playground.d.ts +151 -0
  132. package/dist/svelte/playground.d.ts.map +1 -0
  133. package/dist/svelte/playground.js +134 -0
  134. package/dist/sveltekit/index.d.ts +379 -0
  135. package/dist/sveltekit/index.d.ts.map +1 -0
  136. package/dist/sveltekit/resource-list-handler.d.ts +127 -0
  137. package/dist/sveltekit/resource-list-handler.d.ts.map +1 -0
  138. package/dist/sveltekit/types.d.ts +31 -0
  139. package/dist/sveltekit/types.d.ts.map +1 -0
  140. package/dist/sveltekit.d.ts +2 -0
  141. package/dist/sveltekit.d.ts.map +1 -0
  142. package/dist/sveltekit.js +978 -0
  143. package/dist/sveltekit.js.map +1 -0
  144. package/dist/types/index.d.ts +61 -0
  145. package/dist/types/index.d.ts.map +1 -0
  146. package/dist/ui.d.ts +10 -0
  147. package/dist/ui.d.ts.map +1 -0
  148. package/dist/ui.js +75 -0
  149. package/dist/ui.js.map +1 -0
  150. 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