@happyvertical/smrt-users 0.37.2 → 0.37.4

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.
@@ -0,0 +1,4531 @@
1
+ import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
2
+ import { AccessRequestStatus, MembershipStatus, OverrideEffect, SessionStatus, TenantPermissionEffect, TenantStatus, UserStatus } from "@happyvertical/smrt-types";
3
+ import { getPackageConfig } from "@happyvertical/smrt-config";
4
+ import { createHash, randomBytes } from "node:crypto";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+ //#region \0rolldown/runtime.js
7
+ var __defProp$12 = Object.defineProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp$12(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp$12(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ //#endregion
18
+ //#region src/__smrt-register__.ts
19
+ ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
20
+ //#endregion
21
+ //#region src/types/index.ts
22
+ var DEFAULT_ROLE_SLUGS = {
23
+ OWNER: "owner",
24
+ ADMIN: "admin",
25
+ MEMBER: "member",
26
+ VIEWER: "viewer"
27
+ };
28
+ var DEFAULT_ROLES = [
29
+ {
30
+ slug: DEFAULT_ROLE_SLUGS.OWNER,
31
+ name: "Owner",
32
+ description: "Full access to all resources"
33
+ },
34
+ {
35
+ slug: DEFAULT_ROLE_SLUGS.ADMIN,
36
+ name: "Administrator",
37
+ description: "Manage users and settings"
38
+ },
39
+ {
40
+ slug: DEFAULT_ROLE_SLUGS.MEMBER,
41
+ name: "Member",
42
+ description: "Standard access"
43
+ },
44
+ {
45
+ slug: DEFAULT_ROLE_SLUGS.VIEWER,
46
+ name: "Viewer",
47
+ description: "Read-only access"
48
+ }
49
+ ];
50
+ var DEFAULT_TENANT_POLICY = {
51
+ mode: "flexible",
52
+ maxTenants: 0,
53
+ defaultName: "Default Workspace"
54
+ };
55
+ //#endregion
56
+ //#region src/models/User.ts
57
+ var __defProp$11 = Object.defineProperty;
58
+ var __getOwnPropDesc$11 = Object.getOwnPropertyDescriptor;
59
+ var __decorateClass$11 = (decorators, target, key, kind) => {
60
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$11(target, key) : target;
61
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
62
+ if (kind && result) __defProp$11(target, key, result);
63
+ return result;
64
+ };
65
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
66
+ function isValidEmail(email) {
67
+ if (!email || typeof email !== "string") return false;
68
+ return EMAIL_REGEX.test(email.trim());
69
+ }
70
+ function normalizeEmail(email) {
71
+ if (!email || typeof email !== "string") return "";
72
+ return email.trim().toLowerCase();
73
+ }
74
+ var User = class extends SmrtObject {
75
+ profileId = "";
76
+ /**
77
+ * User's email address (unique, used for lookup)
78
+ */
79
+ email = "";
80
+ status = UserStatus.ACTIVE;
81
+ /**
82
+ * Last login timestamp
83
+ */
84
+ lastLoginAt = null;
85
+ constructor(options = {}) {
86
+ super(options);
87
+ if (options.profileId !== void 0) this.profileId = options.profileId;
88
+ if (options.email !== void 0) this.email = normalizeEmail(options.email);
89
+ if (options.status !== void 0) this.status = options.status;
90
+ if (options.lastLoginAt !== void 0) this.lastLoginAt = options.lastLoginAt;
91
+ }
92
+ /**
93
+ * Validate the user's email format.
94
+ * @returns true if email is valid
95
+ */
96
+ hasValidEmail() {
97
+ return isValidEmail(this.email);
98
+ }
99
+ /**
100
+ * Check if user is active
101
+ */
102
+ isActive() {
103
+ return this.status === UserStatus.ACTIVE;
104
+ }
105
+ /**
106
+ * Check if user is suspended
107
+ */
108
+ isSuspended() {
109
+ return this.status === UserStatus.SUSPENDED;
110
+ }
111
+ /**
112
+ * Check if user is pending verification
113
+ */
114
+ isPending() {
115
+ return this.status === UserStatus.PENDING;
116
+ }
117
+ /**
118
+ * Record a login event
119
+ */
120
+ recordLogin() {
121
+ this.lastLoginAt = /* @__PURE__ */ new Date();
122
+ }
123
+ };
124
+ __decorateClass$11([crossPackageRef("@happyvertical/smrt-profiles:Profile")], User.prototype, "profileId", 2);
125
+ __decorateClass$11([field({ type: "text" })], User.prototype, "status", 2);
126
+ User = __decorateClass$11([smrt({
127
+ api: { include: ["list", "get"] },
128
+ mcp: { include: ["list", "get"] },
129
+ cli: true
130
+ })], User);
131
+ //#endregion
132
+ //#region src/models/CliAuthRequest.ts
133
+ var __defProp$10 = Object.defineProperty;
134
+ var __getOwnPropDesc$10 = Object.getOwnPropertyDescriptor;
135
+ var __decorateClass$10 = (decorators, target, key, kind) => {
136
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$10(target, key) : target;
137
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
138
+ if (kind && result) __defProp$10(target, key, result);
139
+ return result;
140
+ };
141
+ var UsersCliAuthRequest = class extends SmrtObject {
142
+ userCode = "";
143
+ deviceCodeHash = "";
144
+ status = "pending";
145
+ userId = "";
146
+ tenantId = "";
147
+ sessionId = "";
148
+ expiresAt = /* @__PURE__ */ new Date();
149
+ approvedAt = null;
150
+ };
151
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "userCode", 2);
152
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "deviceCodeHash", 2);
153
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "status", 2);
154
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "userId", 2);
155
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "tenantId", 2);
156
+ __decorateClass$10([field({ type: "text" })], UsersCliAuthRequest.prototype, "sessionId", 2);
157
+ __decorateClass$10([field({ type: "datetime" })], UsersCliAuthRequest.prototype, "expiresAt", 2);
158
+ __decorateClass$10([field({
159
+ type: "datetime",
160
+ nullable: true
161
+ })], UsersCliAuthRequest.prototype, "approvedAt", 2);
162
+ UsersCliAuthRequest = __decorateClass$10([smrt({
163
+ tableName: "users_cli_auth_requests",
164
+ api: { include: [] },
165
+ cli: { include: [] },
166
+ mcp: { include: [] }
167
+ })], UsersCliAuthRequest);
168
+ //#endregion
169
+ //#region src/collections/CliAuthRequestCollection.ts
170
+ var UsersCliAuthRequestCollection = class extends SmrtCollection {
171
+ static _itemClass = UsersCliAuthRequest;
172
+ /**
173
+ * Look up a pending or completed request by the short user code shown in the CLI.
174
+ */
175
+ async findByUserCode(userCode) {
176
+ const [request] = await this.list({
177
+ limit: 1,
178
+ where: { userCode: userCode.trim().toUpperCase() }
179
+ });
180
+ return request ?? null;
181
+ }
182
+ /**
183
+ * Look up a request by the hash of its device code (the CLI's polling key).
184
+ */
185
+ async findByDeviceCodeHash(deviceCodeHash) {
186
+ const [request] = await this.list({
187
+ limit: 1,
188
+ where: { deviceCodeHash }
189
+ });
190
+ return request ?? null;
191
+ }
192
+ /**
193
+ * Delete expired pending requests (cleanup job).
194
+ */
195
+ async deleteExpired() {
196
+ const requests = await this.list({ where: {
197
+ status: "pending",
198
+ "expiresAt <": (/* @__PURE__ */ new Date()).toISOString()
199
+ } });
200
+ let count = 0;
201
+ for (const request of requests) {
202
+ await request.delete();
203
+ count++;
204
+ }
205
+ return count;
206
+ }
207
+ };
208
+ //#endregion
209
+ //#region src/models/Group.ts
210
+ var __defProp$9 = Object.defineProperty;
211
+ var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
212
+ var __decorateClass$9 = (decorators, target, key, kind) => {
213
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$9(target, key) : target;
214
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
215
+ if (kind && result) __defProp$9(target, key, result);
216
+ return result;
217
+ };
218
+ var Group = class extends SmrtObject {
219
+ tenantId;
220
+ /**
221
+ * Display name for the group
222
+ */
223
+ name = "";
224
+ /**
225
+ * Description of the group
226
+ */
227
+ description = "";
228
+ constructor(options = {}) {
229
+ super(options);
230
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
231
+ if (options.name !== void 0) this.name = options.name;
232
+ if (options.description !== void 0) this.description = options.description;
233
+ }
234
+ };
235
+ __decorateClass$9([foreignKey("Tenant", { required: true })], Group.prototype, "tenantId", 2);
236
+ Group = __decorateClass$9([smrt({
237
+ api: { include: ["list", "get"] },
238
+ mcp: { include: ["list", "get"] },
239
+ cli: true
240
+ })], Group);
241
+ //#endregion
242
+ //#region src/collections/GroupCollection.ts
243
+ var GroupCollection = class extends SmrtCollection {
244
+ static _itemClass = Group;
245
+ /**
246
+ * Find all groups in a tenant
247
+ */
248
+ async findByTenant(tenantId) {
249
+ return await this.list({
250
+ where: { tenantId },
251
+ orderBy: "name ASC"
252
+ });
253
+ }
254
+ /**
255
+ * Find group by slug within a tenant
256
+ */
257
+ async findBySlug(slug, tenantId) {
258
+ const results = await this.list({
259
+ where: {
260
+ slug,
261
+ tenantId
262
+ },
263
+ limit: 1
264
+ });
265
+ return results.length > 0 ? results[0] : null;
266
+ }
267
+ };
268
+ //#endregion
269
+ //#region src/models/GroupMember.ts
270
+ var __defProp$8 = Object.defineProperty;
271
+ var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
272
+ var __decorateClass$8 = (decorators, target, key, kind) => {
273
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
274
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
275
+ if (kind && result) __defProp$8(target, key, result);
276
+ return result;
277
+ };
278
+ var GroupMember = class extends SmrtObject {
279
+ groupId;
280
+ userId;
281
+ constructor(options = {}) {
282
+ super(options);
283
+ if (options.groupId !== void 0) this.groupId = options.groupId;
284
+ if (options.userId !== void 0) this.userId = options.userId;
285
+ }
286
+ };
287
+ __decorateClass$8([foreignKey("Group", { required: true })], GroupMember.prototype, "groupId", 2);
288
+ __decorateClass$8([foreignKey("User", { required: true })], GroupMember.prototype, "userId", 2);
289
+ GroupMember = __decorateClass$8([smrt({
290
+ api: { include: ["list", "get"] },
291
+ mcp: { include: ["list", "get"] },
292
+ cli: true
293
+ })], GroupMember);
294
+ //#endregion
295
+ //#region src/collections/GroupMemberCollection.ts
296
+ var GroupMemberCollection = class extends SmrtCollection {
297
+ static _itemClass = GroupMember;
298
+ /** Memoized Group collection, used to resolve the Group table name. */
299
+ groupCollection;
300
+ /**
301
+ * Resolve the database table name for the `Group` model from the registry
302
+ * (via a shared-connection GroupCollection) rather than hardcoding `groups`.
303
+ * A `@smrt({ tableName })` override or table prefix on Group would otherwise
304
+ * make raw joins reference a non-existent or foreign table.
305
+ */
306
+ async getGroupTableName() {
307
+ if (!this.groupCollection) this.groupCollection = await GroupCollection.create({ db: this.options.db });
308
+ return this.groupCollection.tableName;
309
+ }
310
+ /**
311
+ * Find all members of a group
312
+ */
313
+ async findByGroup(groupId) {
314
+ return await this.list({ where: { groupId } });
315
+ }
316
+ /**
317
+ * Find all groups a user belongs to
318
+ */
319
+ async findByUser(userId) {
320
+ return await this.list({ where: { userId } });
321
+ }
322
+ /**
323
+ * Check if a user is in a group
324
+ */
325
+ async isMember(groupId, userId) {
326
+ return (await this.list({
327
+ where: {
328
+ groupId,
329
+ userId
330
+ },
331
+ limit: 1
332
+ })).length > 0;
333
+ }
334
+ /**
335
+ * Add user to a group
336
+ */
337
+ async addMember(groupId, userId) {
338
+ const existing = await this.list({
339
+ where: {
340
+ groupId,
341
+ userId
342
+ },
343
+ limit: 1
344
+ });
345
+ if (existing.length > 0) return existing[0];
346
+ const member = await this.create({
347
+ groupId,
348
+ userId
349
+ });
350
+ await member.save();
351
+ return member;
352
+ }
353
+ /**
354
+ * Remove user from a group
355
+ */
356
+ async removeMember(groupId, userId) {
357
+ const existing = await this.list({
358
+ where: {
359
+ groupId,
360
+ userId
361
+ },
362
+ limit: 1
363
+ });
364
+ if (existing.length === 0) return false;
365
+ await existing[0].delete();
366
+ return true;
367
+ }
368
+ /**
369
+ * Get group IDs for a user
370
+ * @deprecated Use getGroupIdsForTenant to prevent cross-tenant leakage
371
+ */
372
+ async getGroupIds(userId) {
373
+ return (await this.findByUser(userId)).map((m) => m.groupId);
374
+ }
375
+ /**
376
+ * Get group IDs for a user within a specific tenant
377
+ * This prevents cross-tenant permission leakage by filtering groups by tenant
378
+ */
379
+ async getGroupIdsForTenant(userId, tenantId) {
380
+ const groupTable = await this.getGroupTableName();
381
+ const sql = `
382
+ SELECT gm.group_id
383
+ FROM ${this.tableName} gm
384
+ INNER JOIN ${groupTable} g ON g.id = gm.group_id
385
+ WHERE gm.user_id = ? AND g.tenant_id = ?
386
+ `;
387
+ return (await this.db.query(sql, userId, tenantId)).rows.map((r) => r.group_id);
388
+ }
389
+ /**
390
+ * Get user IDs in a group
391
+ */
392
+ async getUserIds(groupId) {
393
+ return (await this.findByGroup(groupId)).map((m) => m.userId);
394
+ }
395
+ };
396
+ //#endregion
397
+ //#region src/models/GroupRole.ts
398
+ var __defProp$7 = Object.defineProperty;
399
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
400
+ var __decorateClass$7 = (decorators, target, key, kind) => {
401
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
402
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
403
+ if (kind && result) __defProp$7(target, key, result);
404
+ return result;
405
+ };
406
+ var GroupRole = class extends SmrtObject {
407
+ groupId;
408
+ roleId;
409
+ constructor(options = {}) {
410
+ super(options);
411
+ if (options.groupId !== void 0) this.groupId = options.groupId;
412
+ if (options.roleId !== void 0) this.roleId = options.roleId;
413
+ }
414
+ };
415
+ __decorateClass$7([foreignKey("Group", { required: true })], GroupRole.prototype, "groupId", 2);
416
+ __decorateClass$7([foreignKey("Role", { required: true })], GroupRole.prototype, "roleId", 2);
417
+ GroupRole = __decorateClass$7([smrt({
418
+ api: { include: ["list", "get"] },
419
+ mcp: { include: ["list", "get"] },
420
+ cli: true
421
+ })], GroupRole);
422
+ //#endregion
423
+ //#region src/collections/GroupRoleCollection.ts
424
+ var GroupRoleCollection = class extends SmrtCollection {
425
+ static _itemClass = GroupRole;
426
+ /**
427
+ * Find all roles for a group
428
+ */
429
+ async findByGroup(groupId) {
430
+ return await this.list({ where: { groupId } });
431
+ }
432
+ /**
433
+ * Find all groups that have a role
434
+ */
435
+ async findByRole(roleId) {
436
+ return await this.list({ where: { roleId } });
437
+ }
438
+ /**
439
+ * Check if a group has a role
440
+ */
441
+ async hasRole(groupId, roleId) {
442
+ return (await this.list({
443
+ where: {
444
+ groupId,
445
+ roleId
446
+ },
447
+ limit: 1
448
+ })).length > 0;
449
+ }
450
+ /**
451
+ * Add role to a group
452
+ */
453
+ async addRole(groupId, roleId) {
454
+ const existing = await this.list({
455
+ where: {
456
+ groupId,
457
+ roleId
458
+ },
459
+ limit: 1
460
+ });
461
+ if (existing.length > 0) return existing[0];
462
+ const groupRole = await this.create({
463
+ groupId,
464
+ roleId
465
+ });
466
+ await groupRole.save();
467
+ return groupRole;
468
+ }
469
+ /**
470
+ * Remove role from a group
471
+ */
472
+ async removeRole(groupId, roleId) {
473
+ const existing = await this.list({
474
+ where: {
475
+ groupId,
476
+ roleId
477
+ },
478
+ limit: 1
479
+ });
480
+ if (existing.length === 0) return false;
481
+ await existing[0].delete();
482
+ return true;
483
+ }
484
+ /**
485
+ * Get role IDs for a group
486
+ */
487
+ async getRoleIds(groupId) {
488
+ return (await this.findByGroup(groupId)).map((gr) => gr.roleId);
489
+ }
490
+ };
491
+ //#endregion
492
+ //#region src/models/Membership.ts
493
+ var __defProp$6 = Object.defineProperty;
494
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
495
+ var __decorateClass$6 = (decorators, target, key, kind) => {
496
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
497
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
498
+ if (kind && result) __defProp$6(target, key, result);
499
+ return result;
500
+ };
501
+ var Membership = class extends SmrtObject {
502
+ userId;
503
+ tenantId;
504
+ roleId;
505
+ status = MembershipStatus.ACTIVE;
506
+ constructor(options = {}) {
507
+ super(options);
508
+ if (options.userId !== void 0) this.userId = options.userId;
509
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
510
+ if (options.roleId !== void 0) this.roleId = options.roleId;
511
+ if (options.status !== void 0) this.status = options.status;
512
+ }
513
+ /**
514
+ * Check if membership is active
515
+ */
516
+ isActive() {
517
+ return this.status === MembershipStatus.ACTIVE;
518
+ }
519
+ /**
520
+ * Check if membership is pending (invitation not yet accepted)
521
+ */
522
+ isPending() {
523
+ return this.status === MembershipStatus.PENDING;
524
+ }
525
+ };
526
+ __decorateClass$6([foreignKey("User", { required: true })], Membership.prototype, "userId", 2);
527
+ __decorateClass$6([foreignKey("Tenant", { required: true })], Membership.prototype, "tenantId", 2);
528
+ __decorateClass$6([foreignKey("Role", { required: true })], Membership.prototype, "roleId", 2);
529
+ __decorateClass$6([field({ type: "text" })], Membership.prototype, "status", 2);
530
+ Membership = __decorateClass$6([smrt({
531
+ api: { include: ["list", "get"] },
532
+ mcp: { include: ["list", "get"] },
533
+ cli: true
534
+ })], Membership);
535
+ //#endregion
536
+ //#region src/collections/MembershipCollection.ts
537
+ var MembershipCollection = class extends SmrtCollection {
538
+ static _itemClass = Membership;
539
+ /**
540
+ * Find all memberships for a user
541
+ */
542
+ async findByUser(userId) {
543
+ return await this.list({
544
+ where: { userId },
545
+ orderBy: "created_at DESC"
546
+ });
547
+ }
548
+ /**
549
+ * Find all active memberships for a user
550
+ */
551
+ async findActiveByUser(userId) {
552
+ return await this.list({
553
+ where: {
554
+ userId,
555
+ status: MembershipStatus.ACTIVE
556
+ },
557
+ orderBy: "created_at DESC"
558
+ });
559
+ }
560
+ /**
561
+ * Find all memberships in a tenant
562
+ */
563
+ async findByTenant(tenantId) {
564
+ return await this.list({
565
+ where: { tenantId },
566
+ orderBy: "created_at DESC"
567
+ });
568
+ }
569
+ /**
570
+ * Find active memberships in a tenant
571
+ */
572
+ async findActiveByTenant(tenantId) {
573
+ return await this.list({
574
+ where: {
575
+ tenantId,
576
+ status: MembershipStatus.ACTIVE
577
+ },
578
+ orderBy: "created_at DESC"
579
+ });
580
+ }
581
+ /**
582
+ * Find a specific user's membership in a tenant
583
+ */
584
+ async findByUserAndTenant(userId, tenantId) {
585
+ const results = await this.list({
586
+ where: {
587
+ userId,
588
+ tenantId
589
+ },
590
+ limit: 1
591
+ });
592
+ return results.length > 0 ? results[0] : null;
593
+ }
594
+ /**
595
+ * Find memberships by role
596
+ */
597
+ async findByRole(roleId) {
598
+ return await this.list({
599
+ where: { roleId },
600
+ orderBy: "created_at DESC"
601
+ });
602
+ }
603
+ /**
604
+ * Find memberships by status
605
+ */
606
+ async findByStatus(status) {
607
+ return await this.list({
608
+ where: { status },
609
+ orderBy: "created_at DESC"
610
+ });
611
+ }
612
+ };
613
+ //#endregion
614
+ //#region src/models/MembershipOverride.ts
615
+ var __defProp$5 = Object.defineProperty;
616
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
617
+ var __decorateClass$5 = (decorators, target, key, kind) => {
618
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
619
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
620
+ if (kind && result) __defProp$5(target, key, result);
621
+ return result;
622
+ };
623
+ var MembershipOverride = class extends SmrtObject {
624
+ membershipId;
625
+ permissionId;
626
+ effect = OverrideEffect.GRANT;
627
+ constructor(options = {}) {
628
+ super(options);
629
+ if (options.membershipId !== void 0) this.membershipId = options.membershipId;
630
+ if (options.permissionId !== void 0) this.permissionId = options.permissionId;
631
+ if (options.effect !== void 0) this.effect = options.effect;
632
+ }
633
+ /**
634
+ * Check if this override grants the permission
635
+ */
636
+ isGrant() {
637
+ return this.effect === OverrideEffect.GRANT;
638
+ }
639
+ /**
640
+ * Check if this override denies the permission
641
+ */
642
+ isDeny() {
643
+ return this.effect === OverrideEffect.DENY;
644
+ }
645
+ };
646
+ __decorateClass$5([foreignKey("Membership", { required: true })], MembershipOverride.prototype, "membershipId", 2);
647
+ __decorateClass$5([foreignKey("Permission", { required: true })], MembershipOverride.prototype, "permissionId", 2);
648
+ __decorateClass$5([field({ type: "text" })], MembershipOverride.prototype, "effect", 2);
649
+ MembershipOverride = __decorateClass$5([smrt({
650
+ api: { include: ["list", "get"] },
651
+ mcp: { include: ["list", "get"] },
652
+ cli: true
653
+ })], MembershipOverride);
654
+ //#endregion
655
+ //#region src/collections/MembershipOverrideCollection.ts
656
+ var MembershipOverrideCollection = class extends SmrtCollection {
657
+ static _itemClass = MembershipOverride;
658
+ /**
659
+ * Find all overrides for a membership
660
+ */
661
+ async findByMembership(membershipId) {
662
+ return await this.list({ where: { membershipId } });
663
+ }
664
+ /**
665
+ * Find grant overrides for a membership.
666
+ *
667
+ * Filters in memory because the `effect` column is JSON-typed and
668
+ * Postgres rejects bare `json = text` comparisons. A single
669
+ * `findByMembership` call is reused for both grant and deny lookups
670
+ * within the same request (see `_overridesByMembership` cache).
671
+ */
672
+ async findGrants(membershipId) {
673
+ return (await this.findByMembership(membershipId)).filter((o) => o.effect === OverrideEffect.GRANT);
674
+ }
675
+ /**
676
+ * Find deny overrides for a membership.
677
+ *
678
+ * See `findGrants` for rationale on in-memory filtering.
679
+ */
680
+ async findDenies(membershipId) {
681
+ return (await this.findByMembership(membershipId)).filter((o) => o.effect === OverrideEffect.DENY);
682
+ }
683
+ /**
684
+ * Get grant permission IDs for a membership
685
+ */
686
+ async getGrantedPermissionIds(membershipId) {
687
+ return (await this.findGrants(membershipId)).map((o) => o.permissionId);
688
+ }
689
+ /**
690
+ * Get denied permission IDs for a membership
691
+ */
692
+ async getDeniedPermissionIds(membershipId) {
693
+ return (await this.findDenies(membershipId)).map((o) => o.permissionId);
694
+ }
695
+ /**
696
+ * Set an override for a membership
697
+ */
698
+ async setOverride(membershipId, permissionId, effect) {
699
+ const existing = await this.list({
700
+ where: {
701
+ membershipId,
702
+ permissionId
703
+ },
704
+ limit: 1
705
+ });
706
+ if (existing.length > 0) {
707
+ existing[0].effect = effect;
708
+ await existing[0].save();
709
+ return existing[0];
710
+ }
711
+ const override = await this.create({
712
+ membershipId,
713
+ permissionId,
714
+ effect
715
+ });
716
+ await override.save();
717
+ return override;
718
+ }
719
+ /**
720
+ * Remove an override
721
+ */
722
+ async removeOverride(membershipId, permissionId) {
723
+ const existing = await this.list({
724
+ where: {
725
+ membershipId,
726
+ permissionId
727
+ },
728
+ limit: 1
729
+ });
730
+ if (existing.length === 0) return false;
731
+ await existing[0].delete();
732
+ return true;
733
+ }
734
+ /**
735
+ * Grant a permission to a membership
736
+ * Convenience method for setOverride with GRANT effect
737
+ */
738
+ async grantPermission(membershipId, permissionId) {
739
+ return await this.setOverride(membershipId, permissionId, OverrideEffect.GRANT);
740
+ }
741
+ /**
742
+ * Deny a permission for a membership
743
+ * Convenience method for setOverride with DENY effect
744
+ */
745
+ async denyPermission(membershipId, permissionId) {
746
+ return await this.setOverride(membershipId, permissionId, OverrideEffect.DENY);
747
+ }
748
+ };
749
+ //#endregion
750
+ //#region src/models/Permission.ts
751
+ var __defProp$4 = Object.defineProperty;
752
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
753
+ var __decorateClass$4 = (decorators, target, key, kind) => {
754
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
755
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
756
+ if (kind && result) __defProp$4(target, key, result);
757
+ return result;
758
+ };
759
+ function parsePermissionSlug(slug) {
760
+ const parts = slug.split(".");
761
+ if (parts.length === 2 && parts[0] && parts[1]) return {
762
+ resource: parts[0],
763
+ action: parts[1],
764
+ isValid: true
765
+ };
766
+ return {
767
+ resource: slug,
768
+ action: "",
769
+ isValid: false
770
+ };
771
+ }
772
+ function isValidPermissionSlug(slug) {
773
+ return parsePermissionSlug(slug).isValid;
774
+ }
775
+ var Permission = class extends SmrtObject {
776
+ /**
777
+ * Display name for the permission
778
+ */
779
+ name = "";
780
+ /**
781
+ * Description of what this permission allows
782
+ */
783
+ description = "";
784
+ /**
785
+ * Category for grouping in UI (e.g., 'articles', 'users', 'settings')
786
+ */
787
+ category = "";
788
+ constructor(options = {}) {
789
+ super(options);
790
+ if (options.name !== void 0) this.name = options.name;
791
+ if (options.description !== void 0) this.description = options.description;
792
+ if (options.category !== void 0) this.category = options.category;
793
+ }
794
+ /**
795
+ * Parse the permission slug into resource and action components.
796
+ * @returns Parsed slug with resource, action, and validation status
797
+ */
798
+ parseSlug() {
799
+ return parsePermissionSlug(this.slug ?? "");
800
+ }
801
+ /**
802
+ * Get the resource part of the permission slug
803
+ * e.g., 'articles.create' -> 'articles'
804
+ */
805
+ getResource() {
806
+ return this.parseSlug().resource;
807
+ }
808
+ /**
809
+ * Get the action part of the permission slug
810
+ * e.g., 'articles.create' -> 'create'
811
+ */
812
+ getAction() {
813
+ return this.parseSlug().action;
814
+ }
815
+ /**
816
+ * Check if the permission slug is valid (follows 'resource.action' pattern)
817
+ */
818
+ isValidSlug() {
819
+ return this.parseSlug().isValid;
820
+ }
821
+ };
822
+ Permission = __decorateClass$4([smrt({
823
+ api: { include: ["list", "get"] },
824
+ mcp: { include: ["list", "get"] },
825
+ cli: true
826
+ })], Permission);
827
+ //#endregion
828
+ //#region src/collections/PermissionCollection.ts
829
+ var PermissionCollection = class extends SmrtCollection {
830
+ static _itemClass = Permission;
831
+ /**
832
+ * Find permissions by category
833
+ */
834
+ async findByCategory(category) {
835
+ return await this.list({
836
+ where: { category },
837
+ orderBy: "slug ASC"
838
+ });
839
+ }
840
+ /**
841
+ * Find permission by slug
842
+ */
843
+ async findBySlug(slug) {
844
+ const results = await this.list({
845
+ where: { slug },
846
+ limit: 1
847
+ });
848
+ return results.length > 0 ? results[0] : null;
849
+ }
850
+ /**
851
+ * Batch fetch permissions by IDs
852
+ * Returns a Map of id -> Permission for efficient lookup
853
+ */
854
+ async findByIds(ids) {
855
+ if (ids.length === 0) return /* @__PURE__ */ new Map();
856
+ const uniqueIds = [...new Set(ids)];
857
+ const placeholders = uniqueIds.map(() => "?").join(", ");
858
+ const results = await this.query(`SELECT * FROM ${this.tableName} WHERE id IN (${placeholders})`, uniqueIds);
859
+ const map = /* @__PURE__ */ new Map();
860
+ for (const perm of results) if (perm.id) map.set(perm.id, perm);
861
+ return map;
862
+ }
863
+ /**
864
+ * Get all unique categories
865
+ */
866
+ async getCategories() {
867
+ return (await this.query(`SELECT DISTINCT category FROM ${this.tableName} WHERE category != '' ORDER BY category ASC`)).map((r) => r.category);
868
+ }
869
+ /**
870
+ * Find or create a permission by slug
871
+ */
872
+ async findOrCreate(slug, defaults = {}) {
873
+ const existing = await this.findBySlug(slug);
874
+ if (existing) return existing;
875
+ const permission = await this.create({
876
+ slug,
877
+ name: defaults.name ?? slug,
878
+ description: defaults.description ?? "",
879
+ category: defaults.category ?? slug.split(".")[0]
880
+ });
881
+ await permission.save();
882
+ return permission;
883
+ }
884
+ };
885
+ //#endregion
886
+ //#region src/models/RolePermission.ts
887
+ var __defProp$3 = Object.defineProperty;
888
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
889
+ var __decorateClass$3 = (decorators, target, key, kind) => {
890
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
891
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
892
+ if (kind && result) __defProp$3(target, key, result);
893
+ return result;
894
+ };
895
+ var RolePermission = class extends SmrtObject {
896
+ roleId;
897
+ permissionId;
898
+ constructor(options = {}) {
899
+ super(options);
900
+ if (options.roleId !== void 0) this.roleId = options.roleId;
901
+ if (options.permissionId !== void 0) this.permissionId = options.permissionId;
902
+ }
903
+ };
904
+ __decorateClass$3([foreignKey("Role", { required: true })], RolePermission.prototype, "roleId", 2);
905
+ __decorateClass$3([foreignKey("Permission", { required: true })], RolePermission.prototype, "permissionId", 2);
906
+ RolePermission = __decorateClass$3([smrt({
907
+ api: { include: ["list", "get"] },
908
+ mcp: { include: ["list", "get"] },
909
+ cli: true
910
+ })], RolePermission);
911
+ //#endregion
912
+ //#region src/collections/RolePermissionCollection.ts
913
+ var RolePermissionCollection = class extends SmrtCollection {
914
+ static _itemClass = RolePermission;
915
+ /**
916
+ * Find all permissions for a role
917
+ */
918
+ async findByRole(roleId) {
919
+ return await this.list({ where: { roleId } });
920
+ }
921
+ /**
922
+ * Find all roles that have a permission
923
+ */
924
+ async findByPermission(permissionId) {
925
+ return await this.list({ where: { permissionId } });
926
+ }
927
+ /**
928
+ * Check if a role has a specific permission
929
+ */
930
+ async hasPermission(roleId, permissionId) {
931
+ return (await this.list({
932
+ where: {
933
+ roleId,
934
+ permissionId
935
+ },
936
+ limit: 1
937
+ })).length > 0;
938
+ }
939
+ /**
940
+ * Add a permission to a role
941
+ */
942
+ async addPermission(roleId, permissionId) {
943
+ const existing = await this.list({
944
+ where: {
945
+ roleId,
946
+ permissionId
947
+ },
948
+ limit: 1
949
+ });
950
+ if (existing.length > 0) return existing[0];
951
+ const rolePermission = await this.create({
952
+ roleId,
953
+ permissionId
954
+ });
955
+ await rolePermission.save();
956
+ return rolePermission;
957
+ }
958
+ /**
959
+ * Remove a permission from a role
960
+ */
961
+ async removePermission(roleId, permissionId) {
962
+ const existing = await this.list({
963
+ where: {
964
+ roleId,
965
+ permissionId
966
+ },
967
+ limit: 1
968
+ });
969
+ if (existing.length === 0) return false;
970
+ await existing[0].delete();
971
+ return true;
972
+ }
973
+ /**
974
+ * Get permission IDs for a role
975
+ */
976
+ async getPermissionIds(roleId) {
977
+ return (await this.findByRole(roleId)).map((rp) => rp.permissionId);
978
+ }
979
+ };
980
+ //#endregion
981
+ //#region src/models/Session.ts
982
+ var __defProp$2 = Object.defineProperty;
983
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
984
+ var __decorateClass$2 = (decorators, target, key, kind) => {
985
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
986
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
987
+ if (kind && result) __defProp$2(target, key, result);
988
+ return result;
989
+ };
990
+ var DEFAULT_SESSION_TTL = 10080 * 60;
991
+ function generateSessionId() {
992
+ return crypto.randomUUID();
993
+ }
994
+ var Session = class extends SmrtObject {
995
+ userId = "";
996
+ tenantId = null;
997
+ status = SessionStatus.ACTIVE;
998
+ /**
999
+ * Session expiration time
1000
+ */
1001
+ expiresAt = /* @__PURE__ */ new Date();
1002
+ /**
1003
+ * User agent string from the browser
1004
+ */
1005
+ userAgent = "";
1006
+ /**
1007
+ * IP address of the client
1008
+ */
1009
+ ipAddress = "";
1010
+ /**
1011
+ * Last activity timestamp (updated on each request)
1012
+ */
1013
+ lastAccessedAt = /* @__PURE__ */ new Date();
1014
+ /**
1015
+ * Custom session data (JSON serializable)
1016
+ */
1017
+ data = {};
1018
+ constructor(options = {}) {
1019
+ super(options);
1020
+ if (options.userId !== void 0) this.userId = options.userId;
1021
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
1022
+ if (options.status !== void 0) this.status = options.status;
1023
+ if (options.expiresAt !== void 0) this.expiresAt = options.expiresAt instanceof Date ? options.expiresAt : new Date(options.expiresAt);
1024
+ if (options.userAgent !== void 0) this.userAgent = options.userAgent;
1025
+ if (options.ipAddress !== void 0) this.ipAddress = options.ipAddress;
1026
+ if (options.lastAccessedAt !== void 0) this.lastAccessedAt = options.lastAccessedAt instanceof Date ? options.lastAccessedAt : new Date(options.lastAccessedAt);
1027
+ if (options.data !== void 0) this.data = options.data;
1028
+ }
1029
+ /**
1030
+ * Check if the session is currently valid (active and not expired)
1031
+ */
1032
+ isValid() {
1033
+ return this.status === SessionStatus.ACTIVE && /* @__PURE__ */ new Date() < this.expiresAt;
1034
+ }
1035
+ /**
1036
+ * Check if the session has expired
1037
+ */
1038
+ isExpired() {
1039
+ return /* @__PURE__ */ new Date() >= this.expiresAt;
1040
+ }
1041
+ /**
1042
+ * Check if the session was revoked
1043
+ */
1044
+ isRevoked() {
1045
+ return this.status === SessionStatus.REVOKED;
1046
+ }
1047
+ /**
1048
+ * Update the last accessed timestamp
1049
+ */
1050
+ touch() {
1051
+ this.lastAccessedAt = /* @__PURE__ */ new Date();
1052
+ }
1053
+ /**
1054
+ * Extend the session expiration by the given TTL (in seconds)
1055
+ */
1056
+ extend(ttlSeconds = DEFAULT_SESSION_TTL) {
1057
+ this.expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
1058
+ this.touch();
1059
+ }
1060
+ /**
1061
+ * Revoke the session
1062
+ */
1063
+ revoke() {
1064
+ this.status = SessionStatus.REVOKED;
1065
+ }
1066
+ /**
1067
+ * Set the tenant context for this session
1068
+ */
1069
+ setTenant(tenantId) {
1070
+ this.tenantId = tenantId;
1071
+ }
1072
+ /**
1073
+ * Get or set custom session data
1074
+ */
1075
+ getData(key) {
1076
+ return this.data[key];
1077
+ }
1078
+ /**
1079
+ * Set custom session data
1080
+ */
1081
+ setData(key, value) {
1082
+ this.data[key] = value;
1083
+ }
1084
+ /**
1085
+ * Remove custom session data
1086
+ */
1087
+ removeData(key) {
1088
+ delete this.data[key];
1089
+ }
1090
+ };
1091
+ __decorateClass$2([foreignKey("User")], Session.prototype, "userId", 2);
1092
+ __decorateClass$2([foreignKey("Tenant", { nullable: true })], Session.prototype, "tenantId", 2);
1093
+ __decorateClass$2([field({ type: "text" })], Session.prototype, "status", 2);
1094
+ Session = __decorateClass$2([smrt({
1095
+ api: { include: ["get", "delete"] },
1096
+ mcp: { include: [] },
1097
+ cli: true
1098
+ })], Session);
1099
+ //#endregion
1100
+ //#region src/collections/SessionCollection.ts
1101
+ var SessionCollection = class extends SmrtCollection {
1102
+ static _itemClass = Session;
1103
+ /**
1104
+ * Create a new session with a secure ID
1105
+ */
1106
+ async createSession(options) {
1107
+ const ttl = options.ttl ?? 604800;
1108
+ const expiresAt = new Date(Date.now() + ttl * 1e3);
1109
+ const session = await this.create({
1110
+ id: generateSessionId(),
1111
+ userId: options.userId,
1112
+ tenantId: options.tenantId ?? null,
1113
+ status: SessionStatus.ACTIVE,
1114
+ expiresAt,
1115
+ userAgent: options.userAgent ?? "",
1116
+ ipAddress: options.ipAddress ?? "",
1117
+ lastAccessedAt: /* @__PURE__ */ new Date(),
1118
+ data: options.data ?? {}
1119
+ });
1120
+ await session.save();
1121
+ return session;
1122
+ }
1123
+ /**
1124
+ * Find a valid session by ID
1125
+ * Returns null if session doesn't exist, is expired, or is revoked
1126
+ */
1127
+ async findValidSession(sessionId) {
1128
+ const session = await this.get(sessionId);
1129
+ if (!session) return null;
1130
+ if (!session.isValid()) {
1131
+ if (session.isExpired() && session.status === SessionStatus.ACTIVE) {
1132
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1133
+ await this.db.query(`UPDATE ${this.tableName}
1134
+ SET status = ?, updated_at = ?
1135
+ WHERE id = ? AND status = ? AND expires_at < ?`, SessionStatus.EXPIRED, now, sessionId, SessionStatus.ACTIVE, now);
1136
+ }
1137
+ return null;
1138
+ }
1139
+ return session;
1140
+ }
1141
+ /**
1142
+ * Update last accessed time and optionally extend session
1143
+ */
1144
+ async touch(sessionId, extendTtl = false, ttl = DEFAULT_SESSION_TTL) {
1145
+ const session = await this.findValidSession(sessionId);
1146
+ if (!session) return false;
1147
+ session.touch();
1148
+ if (extendTtl) session.extend(ttl);
1149
+ await session.save();
1150
+ return true;
1151
+ }
1152
+ /**
1153
+ * Find all active sessions for a user
1154
+ */
1155
+ async findByUser(userId) {
1156
+ return (await this.list({
1157
+ where: {
1158
+ userId,
1159
+ status: SessionStatus.ACTIVE
1160
+ },
1161
+ orderBy: "last_accessed_at DESC"
1162
+ })).filter((session) => session.isValid());
1163
+ }
1164
+ /**
1165
+ * Delete all sessions for a user (logout from all devices)
1166
+ */
1167
+ async deleteUserSessions(userId) {
1168
+ const sessions = await this.list({ where: { userId } });
1169
+ let count = 0;
1170
+ for (const session of sessions) {
1171
+ await session.delete();
1172
+ count++;
1173
+ }
1174
+ return count;
1175
+ }
1176
+ /**
1177
+ * Revoke all sessions for a user (soft delete)
1178
+ */
1179
+ async revokeUserSessions(userId) {
1180
+ const sessions = await this.list({ where: {
1181
+ userId,
1182
+ status: SessionStatus.ACTIVE
1183
+ } });
1184
+ let count = 0;
1185
+ for (const session of sessions) {
1186
+ session.revoke();
1187
+ await session.save();
1188
+ count++;
1189
+ }
1190
+ return count;
1191
+ }
1192
+ /**
1193
+ * Revoke a specific session
1194
+ */
1195
+ async revokeSession(sessionId) {
1196
+ const session = await this.get(sessionId);
1197
+ if (!session) return false;
1198
+ session.revoke();
1199
+ await session.save();
1200
+ return true;
1201
+ }
1202
+ /**
1203
+ * Delete expired sessions (cleanup job)
1204
+ * Returns the number of deleted sessions
1205
+ */
1206
+ async deleteExpired() {
1207
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1208
+ const { rowCount } = await this.db.query(`DELETE FROM ${this.tableName}
1209
+ WHERE expires_at < ? OR status = ?`, now, SessionStatus.REVOKED);
1210
+ return rowCount ?? 0;
1211
+ }
1212
+ /**
1213
+ * Count active sessions for a user
1214
+ */
1215
+ async countUserSessions(userId) {
1216
+ return (await this.findByUser(userId)).length;
1217
+ }
1218
+ /**
1219
+ * Update tenant context for a session (low-level primitive).
1220
+ *
1221
+ * SECURITY (#1400): this does NOT verify that the session's user is a member
1222
+ * of `tenantId` — it is the unguarded storage primitive. Application/route
1223
+ * code must go through {@link SessionService.switchTenant}, which fail-closes
1224
+ * on a missing/inactive membership before calling this. Calling it directly
1225
+ * with an untrusted `tenantId` reintroduces the cross-tenant access bug.
1226
+ */
1227
+ async setSessionTenant(sessionId, tenantId) {
1228
+ const session = await this.findValidSession(sessionId);
1229
+ if (!session) return false;
1230
+ session.setTenant(tenantId);
1231
+ await session.save();
1232
+ return true;
1233
+ }
1234
+ /**
1235
+ * Set custom session data
1236
+ */
1237
+ async setSessionData(sessionId, key, value) {
1238
+ const session = await this.findValidSession(sessionId);
1239
+ if (!session) return false;
1240
+ session.setData(key, value);
1241
+ await session.save();
1242
+ return true;
1243
+ }
1244
+ /**
1245
+ * Get custom session data
1246
+ */
1247
+ async getSessionData(sessionId, key) {
1248
+ const session = await this.findValidSession(sessionId);
1249
+ if (!session) return void 0;
1250
+ return session.getData(key);
1251
+ }
1252
+ };
1253
+ //#endregion
1254
+ //#region src/models/Tenant.ts
1255
+ var __defProp$1 = Object.defineProperty;
1256
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
1257
+ var __decorateClass$1 = (decorators, target, key, kind) => {
1258
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
1259
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1260
+ if (kind && result) __defProp$1(target, key, result);
1261
+ return result;
1262
+ };
1263
+ var MAX_TENANT_HIERARCHY_DEPTH = 10;
1264
+ var Tenant = class extends SmrtObject {
1265
+ /**
1266
+ * Display name for the tenant
1267
+ */
1268
+ name = "";
1269
+ status = TenantStatus.ACTIVE;
1270
+ /**
1271
+ * Optional description
1272
+ */
1273
+ description = "";
1274
+ parentTenantId;
1275
+ /**
1276
+ * Depth in the hierarchy tree (0 = root, 1 = first level child, etc.)
1277
+ * Automatically managed by TenantCollection methods.
1278
+ */
1279
+ hierarchyLevel = 0;
1280
+ /**
1281
+ * Materialized path for efficient tree traversal.
1282
+ * Format: "ancestor-id/parent-id" (path to parent; does not include this tenant's id)
1283
+ * Empty string for root tenants.
1284
+ * Automatically managed by TenantCollection methods.
1285
+ */
1286
+ hierarchyPath = "";
1287
+ /**
1288
+ * If true, this tenant's permissions cascade DOWN to child tenants.
1289
+ * Children can still opt-out by setting inheritPermissions: false.
1290
+ * Default: true
1291
+ */
1292
+ cascadePermissions = true;
1293
+ /**
1294
+ * If true, this tenant ACCEPTS permissions from its parent tenant.
1295
+ * Parent must also have cascadePermissions: true for inheritance to work.
1296
+ * Default: true
1297
+ */
1298
+ inheritPermissions = true;
1299
+ constructor(options = {}) {
1300
+ super(options);
1301
+ if (options.name !== void 0) this.name = options.name;
1302
+ if (options.status !== void 0) this.status = options.status;
1303
+ if (options.description !== void 0) this.description = options.description;
1304
+ if (options.parentTenantId !== void 0) this.parentTenantId = options.parentTenantId;
1305
+ if (options.hierarchyLevel !== void 0) this.hierarchyLevel = options.hierarchyLevel;
1306
+ if (options.hierarchyPath !== void 0) this.hierarchyPath = options.hierarchyPath;
1307
+ if (options.cascadePermissions !== void 0) this.cascadePermissions = options.cascadePermissions;
1308
+ if (options.inheritPermissions !== void 0) this.inheritPermissions = options.inheritPermissions;
1309
+ }
1310
+ /**
1311
+ * Check if tenant is active
1312
+ */
1313
+ isActive() {
1314
+ return this.status === TenantStatus.ACTIVE;
1315
+ }
1316
+ /**
1317
+ * Check if tenant is suspended
1318
+ */
1319
+ isSuspended() {
1320
+ return this.status === TenantStatus.SUSPENDED;
1321
+ }
1322
+ /**
1323
+ * Check if this is a root-level tenant (no parent)
1324
+ */
1325
+ isRoot() {
1326
+ return !this.parentTenantId;
1327
+ }
1328
+ /**
1329
+ * Check if this tenant is configured to cascade permissions to children.
1330
+ *
1331
+ * Note: This does NOT indicate whether any child tenants actually exist.
1332
+ * Use TenantCollection.findChildren() for accurate child lookup.
1333
+ */
1334
+ canCascadeToChildren() {
1335
+ return this.cascadePermissions;
1336
+ }
1337
+ /**
1338
+ * Check if permission inheritance is active for this tenant.
1339
+ * Inheritance is active if:
1340
+ * - This tenant has a parent AND
1341
+ * - This tenant has inheritPermissions: true
1342
+ *
1343
+ * Note: The parent must also have cascadePermissions: true
1344
+ * for actual inheritance to occur. Use PermissionResolver
1345
+ * for accurate permission calculation.
1346
+ */
1347
+ acceptsInheritance() {
1348
+ return !!this.parentTenantId && this.inheritPermissions;
1349
+ }
1350
+ /**
1351
+ * Get ancestor IDs from the hierarchy path.
1352
+ * Returns an array of tenant IDs from root to immediate parent.
1353
+ * Empty array for root tenants.
1354
+ */
1355
+ getAncestorIds() {
1356
+ if (!this.hierarchyPath) return [];
1357
+ return this.hierarchyPath.split("/").filter((id) => id.length > 0);
1358
+ }
1359
+ };
1360
+ __decorateClass$1([field({ type: "text" })], Tenant.prototype, "status", 2);
1361
+ __decorateClass$1([foreignKey("Tenant", { nullable: true })], Tenant.prototype, "parentTenantId", 2);
1362
+ Tenant = __decorateClass$1([smrt({
1363
+ tableStrategy: "sti",
1364
+ api: { include: ["list", "get"] },
1365
+ mcp: { include: ["list", "get"] },
1366
+ cli: true
1367
+ })], Tenant);
1368
+ //#endregion
1369
+ //#region src/collections/TenantCollection.ts
1370
+ var TenantHierarchyError = class extends Error {
1371
+ constructor(message, code) {
1372
+ super(message);
1373
+ this.code = code;
1374
+ this.name = "TenantHierarchyError";
1375
+ }
1376
+ code;
1377
+ };
1378
+ var TenantCollection = class extends SmrtCollection {
1379
+ static _itemClass = Tenant;
1380
+ /**
1381
+ * Find tenants by status
1382
+ */
1383
+ async findByStatus(status) {
1384
+ return await this.list({
1385
+ where: { status },
1386
+ orderBy: "name ASC"
1387
+ });
1388
+ }
1389
+ /**
1390
+ * Find all active tenants
1391
+ */
1392
+ async findActive() {
1393
+ return await this.findByStatus(TenantStatus.ACTIVE);
1394
+ }
1395
+ /**
1396
+ * Find tenant by slug
1397
+ */
1398
+ async findBySlug(slug) {
1399
+ const results = await this.list({
1400
+ where: { slug },
1401
+ limit: 1
1402
+ });
1403
+ return results.length > 0 ? results[0] : null;
1404
+ }
1405
+ /**
1406
+ * Find all root tenants (tenants with no parent)
1407
+ */
1408
+ async findRoots() {
1409
+ return await this.list({
1410
+ where: { parentTenantId: null },
1411
+ orderBy: "name ASC"
1412
+ });
1413
+ }
1414
+ /**
1415
+ * Find direct children of a tenant
1416
+ */
1417
+ async findChildren(parentTenantId) {
1418
+ return await this.list({
1419
+ where: { parentTenantId },
1420
+ orderBy: "name ASC"
1421
+ });
1422
+ }
1423
+ /**
1424
+ * Find the parent tenant of a given tenant
1425
+ */
1426
+ async findParent(tenantId) {
1427
+ const tenant = await this.get({ id: tenantId });
1428
+ if (!tenant || !tenant.parentTenantId) return null;
1429
+ return await this.get({ id: tenant.parentTenantId });
1430
+ }
1431
+ /**
1432
+ * Get all ancestors of a tenant, from immediate parent to root.
1433
+ * Uses the hierarchyPath for efficient lookup.
1434
+ */
1435
+ async getAncestors(tenantId) {
1436
+ const tenant = await this.get({ id: tenantId });
1437
+ if (!tenant) return [];
1438
+ const ancestorIds = tenant.getAncestorIds();
1439
+ if (ancestorIds.length === 0) return [];
1440
+ const ancestorsList = await this.listByIds(ancestorIds);
1441
+ const ancestorsMap = new Map(ancestorsList.map((a) => [a.id, a]));
1442
+ const ancestors = [];
1443
+ for (let i = ancestorIds.length - 1; i >= 0; i--) {
1444
+ const ancestor = ancestorsMap.get(ancestorIds[i]);
1445
+ if (ancestor) ancestors.push(ancestor);
1446
+ }
1447
+ return ancestors;
1448
+ }
1449
+ /**
1450
+ * Get all ancestors in order from root to immediate parent.
1451
+ * Reverse of getAncestors.
1452
+ */
1453
+ async getAncestorsFromRoot(tenantId) {
1454
+ return (await this.getAncestors(tenantId)).reverse();
1455
+ }
1456
+ /**
1457
+ * Get all descendants of a tenant (all children, grandchildren, etc.)
1458
+ * Uses hierarchyPath prefix matching for efficient lookup.
1459
+ */
1460
+ async getDescendants(tenantId) {
1461
+ const tenant = await this.get({ id: tenantId });
1462
+ if (!tenant || !tenant.id) return [];
1463
+ const pathPrefix = tenant.hierarchyPath ? `${tenant.hierarchyPath}/${tenant.id}` : tenant.id;
1464
+ return (await this.list({ where: { "hierarchyPath like": `${pathPrefix}%` } })).filter((t) => t.hierarchyPath === pathPrefix || t.hierarchyPath?.startsWith(`${pathPrefix}/`));
1465
+ }
1466
+ /**
1467
+ * Get siblings of a tenant (other tenants with the same parent)
1468
+ */
1469
+ async getSiblings(tenantId) {
1470
+ const tenant = await this.get({ id: tenantId });
1471
+ if (!tenant) return [];
1472
+ return (await this.list({
1473
+ where: { parentTenantId: tenant.parentTenantId ?? null },
1474
+ orderBy: "name ASC"
1475
+ })).filter((s) => s.id !== tenantId);
1476
+ }
1477
+ /**
1478
+ * Check if a tenant is an ancestor of another tenant
1479
+ */
1480
+ async isAncestorOf(potentialAncestorId, tenantId) {
1481
+ const tenant = await this.get({ id: tenantId });
1482
+ if (!tenant) return false;
1483
+ return tenant.getAncestorIds().includes(potentialAncestorId);
1484
+ }
1485
+ /**
1486
+ * Check if a tenant is a descendant of another tenant
1487
+ */
1488
+ async isDescendantOf(potentialDescendantId, tenantId) {
1489
+ return await this.isAncestorOf(tenantId, potentialDescendantId);
1490
+ }
1491
+ /**
1492
+ * Create a child tenant under a parent.
1493
+ * Automatically sets hierarchyLevel and hierarchyPath.
1494
+ */
1495
+ async createChild(parentTenantId, options) {
1496
+ const parent = await this.get({ id: parentTenantId });
1497
+ if (!parent?.id) throw new TenantHierarchyError(`Parent tenant not found: ${parentTenantId}`, "PARENT_NOT_FOUND");
1498
+ const newLevel = parent.hierarchyLevel + 1;
1499
+ if (newLevel >= 10) throw new TenantHierarchyError(`Maximum hierarchy depth (10) exceeded`, "MAX_DEPTH_EXCEEDED");
1500
+ const newPath = parent.hierarchyPath ? `${parent.hierarchyPath}/${parent.id}` : parent.id;
1501
+ const child = await this.create({
1502
+ name: options.name,
1503
+ slug: options.slug,
1504
+ description: options.description ?? "",
1505
+ status: options.status ?? TenantStatus.ACTIVE,
1506
+ parentTenantId,
1507
+ hierarchyLevel: newLevel,
1508
+ hierarchyPath: newPath,
1509
+ cascadePermissions: options.cascadePermissions ?? true,
1510
+ inheritPermissions: options.inheritPermissions ?? true
1511
+ });
1512
+ await child.save();
1513
+ return child;
1514
+ }
1515
+ /**
1516
+ * Move a tenant to a new parent.
1517
+ * Updates hierarchyLevel and hierarchyPath for the tenant and all descendants.
1518
+ */
1519
+ async moveToParent(tenantId, newParentId) {
1520
+ const tenant = await this.get({ id: tenantId });
1521
+ if (!tenant || !tenant.id) throw new TenantHierarchyError(`Tenant not found: ${tenantId}`, "INVALID_OPERATION");
1522
+ if (newParentId === tenantId) throw new TenantHierarchyError("Cannot move tenant to itself", "CIRCULAR_REFERENCE");
1523
+ if (newParentId) {
1524
+ if (await this.isDescendantOf(newParentId, tenantId)) throw new TenantHierarchyError("Cannot move tenant to one of its descendants", "CIRCULAR_REFERENCE");
1525
+ }
1526
+ const descendants = await this.getDescendants(tenantId);
1527
+ let newLevel;
1528
+ let newPath;
1529
+ if (newParentId === null) {
1530
+ newLevel = 0;
1531
+ newPath = "";
1532
+ } else {
1533
+ const newParent = await this.get({ id: newParentId });
1534
+ if (!newParent) throw new TenantHierarchyError(`New parent tenant not found: ${newParentId}`, "PARENT_NOT_FOUND");
1535
+ newLevel = newParent.hierarchyLevel + 1;
1536
+ const maxDescendantDepth = descendants.reduce((max, d) => Math.max(max, d.hierarchyLevel - tenant.hierarchyLevel), 0);
1537
+ if (newLevel + maxDescendantDepth >= 10) throw new TenantHierarchyError(`Moving would exceed maximum hierarchy depth (10)`, "MAX_DEPTH_EXCEEDED");
1538
+ newPath = newParent.hierarchyPath ? `${newParent.hierarchyPath}/${newParent.id}` : newParent.id;
1539
+ }
1540
+ const oldPath = tenant.hierarchyPath ? `${tenant.hierarchyPath}/${tenant.id}` : tenant.id;
1541
+ const newPathForDescendants = newPath ? `${newPath}/${tenant.id}` : tenant.id;
1542
+ const levelDelta = newLevel - tenant.hierarchyLevel;
1543
+ tenant.parentTenantId = newParentId;
1544
+ tenant.hierarchyLevel = newLevel;
1545
+ tenant.hierarchyPath = newPath;
1546
+ await tenant.save();
1547
+ for (const descendant of descendants) {
1548
+ if (!descendant.hierarchyPath) throw new TenantHierarchyError(`Descendant tenant ${descendant.id} has no hierarchyPath while updating hierarchy from ${oldPath} to ${newPathForDescendants}`, "INVALID_OPERATION");
1549
+ if (!descendant.hierarchyPath.startsWith(oldPath)) throw new TenantHierarchyError(`Descendant tenant ${descendant.id} has hierarchyPath "${descendant.hierarchyPath}" which does not start with expected prefix "${oldPath}"`, "INVALID_OPERATION");
1550
+ descendant.hierarchyPath = newPathForDescendants + descendant.hierarchyPath.substring(oldPath.length);
1551
+ descendant.hierarchyLevel += levelDelta;
1552
+ await descendant.save();
1553
+ }
1554
+ return tenant;
1555
+ }
1556
+ /**
1557
+ * Make a tenant a root tenant (remove from hierarchy)
1558
+ */
1559
+ async makeRoot(tenantId) {
1560
+ return await this.moveToParent(tenantId, null);
1561
+ }
1562
+ /**
1563
+ * Validate that a tenant hierarchy is consistent.
1564
+ * Returns validation errors if any.
1565
+ */
1566
+ async validateHierarchy(tenantId) {
1567
+ const errors = [];
1568
+ const tenant = await this.get({ id: tenantId });
1569
+ if (!tenant) return [`Tenant not found: ${tenantId}`];
1570
+ if (tenant.parentTenantId) {
1571
+ const parent = await this.get({ id: tenant.parentTenantId });
1572
+ if (!parent) errors.push(`Parent tenant not found: ${tenant.parentTenantId}`);
1573
+ else {
1574
+ if (tenant.hierarchyLevel !== parent.hierarchyLevel + 1) errors.push(`Hierarchy level mismatch: expected ${parent.hierarchyLevel + 1}, got ${tenant.hierarchyLevel}`);
1575
+ const expectedPath = parent.hierarchyPath ? `${parent.hierarchyPath}/${parent.id}` : parent.id;
1576
+ if (tenant.hierarchyPath !== expectedPath) errors.push(`Hierarchy path mismatch: expected "${expectedPath}", got "${tenant.hierarchyPath}"`);
1577
+ }
1578
+ } else {
1579
+ if (tenant.hierarchyLevel !== 0) errors.push(`Root tenant should have hierarchyLevel 0, got ${tenant.hierarchyLevel}`);
1580
+ if (tenant.hierarchyPath !== "") errors.push(`Root tenant should have empty hierarchyPath, got "${tenant.hierarchyPath}"`);
1581
+ }
1582
+ if (tenant.parentTenantId && tenant.id) {
1583
+ if (tenant.getAncestorIds().includes(tenant.id)) errors.push("Circular reference detected in hierarchy");
1584
+ }
1585
+ return errors;
1586
+ }
1587
+ /**
1588
+ * Get the full hierarchy tree starting from a tenant.
1589
+ * Returns a nested structure useful for UI rendering.
1590
+ */
1591
+ async getTree(rootTenantId) {
1592
+ const roots = rootTenantId ? [await this.get({ id: rootTenantId })] : await this.findRoots();
1593
+ const buildTree = async (tenant) => {
1594
+ if (!tenant) return null;
1595
+ const children = await this.findChildren(tenant.id);
1596
+ const childTrees = await Promise.all(children.map(buildTree));
1597
+ return Object.assign(tenant, { children: childTrees.filter(Boolean) });
1598
+ };
1599
+ return (await Promise.all(roots.map(buildTree))).filter(Boolean);
1600
+ }
1601
+ /**
1602
+ * Override create to automatically set hierarchy fields for new tenants.
1603
+ * Only calculates them when the caller hasn't already supplied
1604
+ * `hierarchyLevel`/`hierarchyPath` in the create input — an explicitly
1605
+ * provided value is preserved as-is.
1606
+ */
1607
+ async create(options) {
1608
+ if (options.parentTenantId) {
1609
+ if (options.hierarchyLevel === void 0 || options.hierarchyPath === void 0) {
1610
+ const parent = await this.get({ id: options.parentTenantId });
1611
+ if (!parent?.id) throw new TenantHierarchyError(`Parent tenant not found: ${options.parentTenantId}`, "PARENT_NOT_FOUND");
1612
+ const newLevel = parent.hierarchyLevel + 1;
1613
+ if (newLevel >= 10) throw new TenantHierarchyError(`Maximum hierarchy depth (10) exceeded`, "MAX_DEPTH_EXCEEDED");
1614
+ if (options.hierarchyLevel === void 0) options.hierarchyLevel = newLevel;
1615
+ if (options.hierarchyPath === void 0) options.hierarchyPath = parent.hierarchyPath ? `${parent.hierarchyPath}/${parent.id}` : parent.id;
1616
+ }
1617
+ } else {
1618
+ options.hierarchyLevel = options.hierarchyLevel ?? 0;
1619
+ options.hierarchyPath = options.hierarchyPath ?? "";
1620
+ }
1621
+ return super.create(options);
1622
+ }
1623
+ };
1624
+ //#endregion
1625
+ //#region src/models/TenantPermissionOverride.ts
1626
+ var __defProp = Object.defineProperty;
1627
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1628
+ var __decorateClass = (decorators, target, key, kind) => {
1629
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
1630
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1631
+ if (kind && result) __defProp(target, key, result);
1632
+ return result;
1633
+ };
1634
+ var TenantPermissionOverride = class extends SmrtObject {
1635
+ tenantId;
1636
+ permissionId;
1637
+ effect = TenantPermissionEffect.INHERIT;
1638
+ constructor(options = {}) {
1639
+ super(options);
1640
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
1641
+ if (options.permissionId !== void 0) this.permissionId = options.permissionId;
1642
+ if (options.effect !== void 0) this.effect = options.effect;
1643
+ }
1644
+ /**
1645
+ * Check if this override inherits from parent
1646
+ */
1647
+ isInherit() {
1648
+ return this.effect === TenantPermissionEffect.INHERIT;
1649
+ }
1650
+ /**
1651
+ * Check if this override grants the permission
1652
+ */
1653
+ isGrant() {
1654
+ return this.effect === TenantPermissionEffect.GRANT;
1655
+ }
1656
+ /**
1657
+ * Check if this override denies the permission
1658
+ */
1659
+ isDeny() {
1660
+ return this.effect === TenantPermissionEffect.DENY;
1661
+ }
1662
+ /**
1663
+ * Check if this override has an explicit effect (not inherit)
1664
+ */
1665
+ hasExplicitEffect() {
1666
+ return this.effect !== TenantPermissionEffect.INHERIT;
1667
+ }
1668
+ };
1669
+ __decorateClass([foreignKey("Tenant", { required: true })], TenantPermissionOverride.prototype, "tenantId", 2);
1670
+ __decorateClass([foreignKey("Permission", { required: true })], TenantPermissionOverride.prototype, "permissionId", 2);
1671
+ __decorateClass([field({ type: "text" })], TenantPermissionOverride.prototype, "effect", 2);
1672
+ TenantPermissionOverride = __decorateClass([smrt({
1673
+ api: { include: ["list", "get"] },
1674
+ mcp: { include: ["list", "get"] },
1675
+ cli: true
1676
+ })], TenantPermissionOverride);
1677
+ //#endregion
1678
+ //#region src/collections/TenantPermissionOverrideCollection.ts
1679
+ var TenantPermissionOverrideCollection = class extends SmrtCollection {
1680
+ static _itemClass = TenantPermissionOverride;
1681
+ /**
1682
+ * Find all overrides for a tenant
1683
+ */
1684
+ async findByTenant(tenantId) {
1685
+ return await this.list({ where: { tenantId } });
1686
+ }
1687
+ /**
1688
+ * Find grant overrides for a tenant.
1689
+ *
1690
+ * Filters in memory because the `effect` column is JSON-typed and
1691
+ * Postgres rejects bare `json = text` comparisons. A single
1692
+ * `findByTenant` call is reused for grant, deny, and inherit lookups.
1693
+ */
1694
+ async findGrants(tenantId) {
1695
+ return (await this.findByTenant(tenantId)).filter((o) => o.effect === TenantPermissionEffect.GRANT);
1696
+ }
1697
+ /**
1698
+ * Find deny overrides for a tenant.
1699
+ *
1700
+ * See `findGrants` for rationale on in-memory filtering.
1701
+ */
1702
+ async findDenies(tenantId) {
1703
+ return (await this.findByTenant(tenantId)).filter((o) => o.effect === TenantPermissionEffect.DENY);
1704
+ }
1705
+ /**
1706
+ * Find inherit overrides for a tenant.
1707
+ *
1708
+ * See `findGrants` for rationale on in-memory filtering.
1709
+ */
1710
+ async findInherits(tenantId) {
1711
+ return (await this.findByTenant(tenantId)).filter((o) => o.effect === TenantPermissionEffect.INHERIT);
1712
+ }
1713
+ /**
1714
+ * Get granted permission IDs for a tenant
1715
+ */
1716
+ async getGrantedPermissionIds(tenantId) {
1717
+ return (await this.findGrants(tenantId)).map((o) => o.permissionId);
1718
+ }
1719
+ /**
1720
+ * Get denied permission IDs for a tenant
1721
+ */
1722
+ async getDeniedPermissionIds(tenantId) {
1723
+ return (await this.findDenies(tenantId)).map((o) => o.permissionId);
1724
+ }
1725
+ /**
1726
+ * Get all overrides for a tenant, organized by effect
1727
+ */
1728
+ async getOverridesByEffect(tenantId) {
1729
+ const overrides = await this.findByTenant(tenantId);
1730
+ const result = {
1731
+ grantedPermissionIds: [],
1732
+ deniedPermissionIds: [],
1733
+ inheritedPermissionIds: []
1734
+ };
1735
+ for (const override of overrides) {
1736
+ const permId = override.permissionId;
1737
+ switch (override.effect) {
1738
+ case TenantPermissionEffect.GRANT:
1739
+ result.grantedPermissionIds.push(permId);
1740
+ break;
1741
+ case TenantPermissionEffect.DENY:
1742
+ result.deniedPermissionIds.push(permId);
1743
+ break;
1744
+ case TenantPermissionEffect.INHERIT:
1745
+ result.inheritedPermissionIds.push(permId);
1746
+ break;
1747
+ }
1748
+ }
1749
+ return result;
1750
+ }
1751
+ /**
1752
+ * Batch get all overrides for multiple tenants, organized by effect.
1753
+ * Fetches all overrides in a single query to avoid N+1 query problem.
1754
+ *
1755
+ * @param tenantIds - Array of tenant IDs to fetch overrides for
1756
+ * @returns Map of tenant ID to their permission override results
1757
+ */
1758
+ async getOverridesByEffectBatch(tenantIds) {
1759
+ const resultMap = /* @__PURE__ */ new Map();
1760
+ for (const tenantId of tenantIds) resultMap.set(tenantId, {
1761
+ grantedPermissionIds: [],
1762
+ deniedPermissionIds: [],
1763
+ inheritedPermissionIds: []
1764
+ });
1765
+ if (tenantIds.length === 0) return resultMap;
1766
+ const allOverrides = await this.list({ where: { tenantId: tenantIds } });
1767
+ for (const override of allOverrides) {
1768
+ const tenantId = override.tenantId;
1769
+ const permId = override.permissionId;
1770
+ const result = resultMap.get(tenantId);
1771
+ if (result) switch (override.effect) {
1772
+ case TenantPermissionEffect.GRANT:
1773
+ result.grantedPermissionIds.push(permId);
1774
+ break;
1775
+ case TenantPermissionEffect.DENY:
1776
+ result.deniedPermissionIds.push(permId);
1777
+ break;
1778
+ case TenantPermissionEffect.INHERIT:
1779
+ result.inheritedPermissionIds.push(permId);
1780
+ break;
1781
+ }
1782
+ }
1783
+ return resultMap;
1784
+ }
1785
+ /**
1786
+ * Set an override for a tenant permission
1787
+ */
1788
+ async setOverride(tenantId, permissionId, effect) {
1789
+ const existing = await this.list({
1790
+ where: {
1791
+ tenantId,
1792
+ permissionId
1793
+ },
1794
+ limit: 1
1795
+ });
1796
+ if (existing.length > 0) {
1797
+ existing[0].effect = effect;
1798
+ await existing[0].save();
1799
+ return existing[0];
1800
+ }
1801
+ const override = await this.create({
1802
+ tenantId,
1803
+ permissionId,
1804
+ effect
1805
+ });
1806
+ await override.save();
1807
+ return override;
1808
+ }
1809
+ /**
1810
+ * Remove an override (permission will use default behavior)
1811
+ */
1812
+ async removeOverride(tenantId, permissionId) {
1813
+ const existing = await this.list({
1814
+ where: {
1815
+ tenantId,
1816
+ permissionId
1817
+ },
1818
+ limit: 1
1819
+ });
1820
+ if (existing.length === 0) return false;
1821
+ await existing[0].delete();
1822
+ return true;
1823
+ }
1824
+ /**
1825
+ * Remove all overrides for a tenant
1826
+ */
1827
+ async removeAllOverrides(tenantId) {
1828
+ const overrides = await this.findByTenant(tenantId);
1829
+ await Promise.all(overrides.map((override) => override.delete()));
1830
+ return overrides.length;
1831
+ }
1832
+ /**
1833
+ * Grant a permission at the tenant level.
1834
+ * Convenience method for setOverride with GRANT effect.
1835
+ */
1836
+ async grantPermission(tenantId, permissionId) {
1837
+ return await this.setOverride(tenantId, permissionId, TenantPermissionEffect.GRANT);
1838
+ }
1839
+ /**
1840
+ * Deny a permission at the tenant level.
1841
+ * Convenience method for setOverride with DENY effect.
1842
+ * This blocks inheritance from parent tenants.
1843
+ */
1844
+ async denyPermission(tenantId, permissionId) {
1845
+ return await this.setOverride(tenantId, permissionId, TenantPermissionEffect.DENY);
1846
+ }
1847
+ /**
1848
+ * Set a permission to inherit from parent.
1849
+ * Convenience method for setOverride with INHERIT effect.
1850
+ * This is the default behavior, so mainly useful to document intent
1851
+ * or to reset a previous grant/deny.
1852
+ */
1853
+ async inheritPermission(tenantId, permissionId) {
1854
+ return await this.setOverride(tenantId, permissionId, TenantPermissionEffect.INHERIT);
1855
+ }
1856
+ /**
1857
+ * Bulk set overrides for a tenant.
1858
+ * Useful for importing or copying permission configurations.
1859
+ */
1860
+ async bulkSetOverrides(tenantId, overrides) {
1861
+ const results = [];
1862
+ for (const { permissionId, effect } of overrides) {
1863
+ const override = await this.setOverride(tenantId, permissionId, effect);
1864
+ results.push(override);
1865
+ }
1866
+ return results;
1867
+ }
1868
+ /**
1869
+ * Copy overrides from one tenant to another.
1870
+ * Useful when creating child tenants or templates.
1871
+ */
1872
+ async copyOverrides(fromTenantId, toTenantId) {
1873
+ const sourceOverrides = await this.findByTenant(fromTenantId);
1874
+ const results = [];
1875
+ for (const source of sourceOverrides) {
1876
+ const override = await this.setOverride(toTenantId, source.permissionId, source.effect);
1877
+ results.push(override);
1878
+ }
1879
+ return results;
1880
+ }
1881
+ };
1882
+ //#endregion
1883
+ //#region src/collections/UserCollection.ts
1884
+ var UserCollection = class extends SmrtCollection {
1885
+ static _itemClass = User;
1886
+ /**
1887
+ * Find user by email address
1888
+ */
1889
+ async findByEmail(email) {
1890
+ const results = await this.list({
1891
+ where: { email },
1892
+ limit: 1
1893
+ });
1894
+ return results.length > 0 ? results[0] : null;
1895
+ }
1896
+ /**
1897
+ * Find user by profile ID
1898
+ */
1899
+ async findByProfile(profileId) {
1900
+ const results = await this.list({
1901
+ where: { profileId },
1902
+ limit: 1
1903
+ });
1904
+ return results.length > 0 ? results[0] : null;
1905
+ }
1906
+ /**
1907
+ * Find users by status
1908
+ */
1909
+ async findByStatus(status) {
1910
+ return await this.list({
1911
+ where: { status },
1912
+ orderBy: "created_at DESC"
1913
+ });
1914
+ }
1915
+ /**
1916
+ * Find all active users
1917
+ */
1918
+ async findActive() {
1919
+ return await this.findByStatus(UserStatus.ACTIVE);
1920
+ }
1921
+ /**
1922
+ * Find all pending users
1923
+ */
1924
+ async findPending() {
1925
+ return await this.findByStatus(UserStatus.PENDING);
1926
+ }
1927
+ /**
1928
+ * Get or create user for a profile
1929
+ */
1930
+ async getOrCreateForProfile(profileId, email, defaults = {}) {
1931
+ const existing = await this.findByProfile(profileId);
1932
+ if (existing) return existing;
1933
+ const user = await this.create({
1934
+ profileId,
1935
+ email,
1936
+ status: defaults.status ?? UserStatus.ACTIVE
1937
+ });
1938
+ await user.save();
1939
+ return user;
1940
+ }
1941
+ /**
1942
+ * Get or create user from OIDC claims
1943
+ *
1944
+ * This is the primary method for resolving identity from an OIDC login.
1945
+ * It handles the full flow:
1946
+ * 1. Find or create Profile from OIDC claims (via smrt-profiles)
1947
+ * 2. Link OidcIdentity to the Profile
1948
+ * 3. Find or create User linked to the Profile
1949
+ *
1950
+ * @param claims - OIDC token claims (sub, iss, email, name)
1951
+ * @param provider - Provider name (e.g., 'kanidm', 'keycloak', 'google')
1952
+ * @param options - Optional settings (recordLogin)
1953
+ * @returns User, Profile, OidcIdentity, and whether profile was created
1954
+ *
1955
+ * @example
1956
+ * ```typescript
1957
+ * const userCollection = await UserCollection.create({ db: dbConfig });
1958
+ *
1959
+ * // In your OIDC callback handler:
1960
+ * const { user, profile } = await userCollection.getOrCreateFromOidc(
1961
+ * {
1962
+ * sub: tokenClaims.sub,
1963
+ * iss: tokenClaims.iss,
1964
+ * email: tokenClaims.email,
1965
+ * name: tokenClaims.name,
1966
+ * },
1967
+ * 'kanidm'
1968
+ * );
1969
+ *
1970
+ * // User and profile are now available
1971
+ * // Login was auto-recorded; pass { recordLogin: false } to skip
1972
+ * ```
1973
+ */
1974
+ async getOrCreateFromOidc(claims, provider, options) {
1975
+ const sub = claims?.sub?.trim();
1976
+ const iss = claims?.iss?.trim();
1977
+ if (!sub || !iss) throw new Error("Invalid OIDC claims: both \"sub\" and \"iss\" must be non-empty strings.");
1978
+ if (!claims.email) throw new Error("OIDC claims missing required \"email\" for user creation.");
1979
+ const allowUnverified = options?.allowUnverifiedEmail === true;
1980
+ if (claims.email_verified === false && !allowUnverified) throw new Error("OIDC claims report an unverified email; refusing to provision a user.");
1981
+ const { createProfileFromOidc } = await import("@happyvertical/smrt-profiles");
1982
+ const { profile, oidcIdentity, created } = await createProfileFromOidc(claims, provider, { db: this.options.db });
1983
+ const shouldRecordLogin = options?.recordLogin !== false;
1984
+ const existingUser = await this.findByProfile(profile.id);
1985
+ const user = existingUser ?? await this.create({
1986
+ email: claims.email,
1987
+ ...shouldRecordLogin ? { lastLoginAt: /* @__PURE__ */ new Date() } : {},
1988
+ profileId: profile.id,
1989
+ status: UserStatus.ACTIVE
1990
+ });
1991
+ if (existingUser && shouldRecordLogin) {
1992
+ existingUser.recordLogin();
1993
+ await existingUser.save();
1994
+ }
1995
+ return {
1996
+ user,
1997
+ profile,
1998
+ oidcIdentity,
1999
+ created
2000
+ };
2001
+ }
2002
+ };
2003
+ //#endregion
2004
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/buffer_utils.js
2005
+ var encoder = new TextEncoder();
2006
+ var decoder = new TextDecoder();
2007
+ function concat(...buffers) {
2008
+ const size = buffers.reduce((acc, { length }) => acc + length, 0);
2009
+ const buf = new Uint8Array(size);
2010
+ let i = 0;
2011
+ for (const buffer of buffers) {
2012
+ buf.set(buffer, i);
2013
+ i += buffer.length;
2014
+ }
2015
+ return buf;
2016
+ }
2017
+ function encode$1(string) {
2018
+ const bytes = new Uint8Array(string.length);
2019
+ for (let i = 0; i < string.length; i++) {
2020
+ const code = string.charCodeAt(i);
2021
+ if (code > 127) throw new TypeError("non-ASCII string encountered in encode()");
2022
+ bytes[i] = code;
2023
+ }
2024
+ return bytes;
2025
+ }
2026
+ //#endregion
2027
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/base64.js
2028
+ function encodeBase64(input) {
2029
+ if (Uint8Array.prototype.toBase64) return input.toBase64();
2030
+ const CHUNK_SIZE = 32768;
2031
+ const arr = [];
2032
+ for (let i = 0; i < input.length; i += CHUNK_SIZE) arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
2033
+ return btoa(arr.join(""));
2034
+ }
2035
+ function decodeBase64(encoded) {
2036
+ if (Uint8Array.fromBase64) return Uint8Array.fromBase64(encoded);
2037
+ const binary = atob(encoded);
2038
+ const bytes = new Uint8Array(binary.length);
2039
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
2040
+ return bytes;
2041
+ }
2042
+ //#endregion
2043
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/util/base64url.js
2044
+ function decode(input) {
2045
+ if (Uint8Array.fromBase64) return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), { alphabet: "base64url" });
2046
+ let encoded = input;
2047
+ if (encoded instanceof Uint8Array) encoded = decoder.decode(encoded);
2048
+ encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
2049
+ try {
2050
+ return decodeBase64(encoded);
2051
+ } catch {
2052
+ throw new TypeError("The input to be decoded is not correctly encoded.");
2053
+ }
2054
+ }
2055
+ function encode(input) {
2056
+ let unencoded = input;
2057
+ if (typeof unencoded === "string") unencoded = encoder.encode(unencoded);
2058
+ if (Uint8Array.prototype.toBase64) return unencoded.toBase64({
2059
+ alphabet: "base64url",
2060
+ omitPadding: true
2061
+ });
2062
+ return encodeBase64(unencoded).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
2063
+ }
2064
+ //#endregion
2065
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/crypto_key.js
2066
+ var unusable = (name, prop = "algorithm.name") => /* @__PURE__ */ new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
2067
+ var isAlgorithm = (algorithm, name) => algorithm.name === name;
2068
+ function getHashLength(hash) {
2069
+ return parseInt(hash.name.slice(4), 10);
2070
+ }
2071
+ function checkHashLength(algorithm, expected) {
2072
+ if (getHashLength(algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
2073
+ }
2074
+ function getNamedCurve(alg) {
2075
+ switch (alg) {
2076
+ case "ES256": return "P-256";
2077
+ case "ES384": return "P-384";
2078
+ case "ES512": return "P-521";
2079
+ default: throw new Error("unreachable");
2080
+ }
2081
+ }
2082
+ function checkUsage(key, usage) {
2083
+ if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
2084
+ }
2085
+ function checkSigCryptoKey(key, alg, usage) {
2086
+ switch (alg) {
2087
+ case "HS256":
2088
+ case "HS384":
2089
+ case "HS512":
2090
+ if (!isAlgorithm(key.algorithm, "HMAC")) throw unusable("HMAC");
2091
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
2092
+ break;
2093
+ case "RS256":
2094
+ case "RS384":
2095
+ case "RS512":
2096
+ if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5")) throw unusable("RSASSA-PKCS1-v1_5");
2097
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
2098
+ break;
2099
+ case "PS256":
2100
+ case "PS384":
2101
+ case "PS512":
2102
+ if (!isAlgorithm(key.algorithm, "RSA-PSS")) throw unusable("RSA-PSS");
2103
+ checkHashLength(key.algorithm, parseInt(alg.slice(2), 10));
2104
+ break;
2105
+ case "Ed25519":
2106
+ case "EdDSA":
2107
+ if (!isAlgorithm(key.algorithm, "Ed25519")) throw unusable("Ed25519");
2108
+ break;
2109
+ case "ML-DSA-44":
2110
+ case "ML-DSA-65":
2111
+ case "ML-DSA-87":
2112
+ if (!isAlgorithm(key.algorithm, alg)) throw unusable(alg);
2113
+ break;
2114
+ case "ES256":
2115
+ case "ES384":
2116
+ case "ES512": {
2117
+ if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusable("ECDSA");
2118
+ const expected = getNamedCurve(alg);
2119
+ if (key.algorithm.namedCurve !== expected) throw unusable(expected, "algorithm.namedCurve");
2120
+ break;
2121
+ }
2122
+ default: throw new TypeError("CryptoKey does not support this operation");
2123
+ }
2124
+ checkUsage(key, usage);
2125
+ }
2126
+ //#endregion
2127
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/invalid_key_input.js
2128
+ function message(msg, actual, ...types) {
2129
+ types = types.filter(Boolean);
2130
+ if (types.length > 2) {
2131
+ const last = types.pop();
2132
+ msg += `one of type ${types.join(", ")}, or ${last}.`;
2133
+ } else if (types.length === 2) msg += `one of type ${types[0]} or ${types[1]}.`;
2134
+ else msg += `of type ${types[0]}.`;
2135
+ if (actual == null) msg += ` Received ${actual}`;
2136
+ else if (typeof actual === "function" && actual.name) msg += ` Received function ${actual.name}`;
2137
+ else if (typeof actual === "object" && actual != null) {
2138
+ if (actual.constructor?.name) msg += ` Received an instance of ${actual.constructor.name}`;
2139
+ }
2140
+ return msg;
2141
+ }
2142
+ var invalidKeyInput = (actual, ...types) => message("Key must be ", actual, ...types);
2143
+ var withAlg = (alg, actual, ...types) => message(`Key for the ${alg} algorithm must be `, actual, ...types);
2144
+ //#endregion
2145
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/util/errors.js
2146
+ var errors_exports = /* @__PURE__ */ __exportAll({
2147
+ JOSEAlgNotAllowed: () => JOSEAlgNotAllowed,
2148
+ JOSEError: () => JOSEError,
2149
+ JOSENotSupported: () => JOSENotSupported,
2150
+ JWEDecryptionFailed: () => JWEDecryptionFailed,
2151
+ JWEInvalid: () => JWEInvalid,
2152
+ JWKInvalid: () => JWKInvalid,
2153
+ JWKSInvalid: () => JWKSInvalid,
2154
+ JWKSMultipleMatchingKeys: () => JWKSMultipleMatchingKeys,
2155
+ JWKSNoMatchingKey: () => JWKSNoMatchingKey,
2156
+ JWKSTimeout: () => JWKSTimeout,
2157
+ JWSInvalid: () => JWSInvalid,
2158
+ JWSSignatureVerificationFailed: () => JWSSignatureVerificationFailed,
2159
+ JWTClaimValidationFailed: () => JWTClaimValidationFailed,
2160
+ JWTExpired: () => JWTExpired,
2161
+ JWTInvalid: () => JWTInvalid
2162
+ });
2163
+ var JOSEError = class extends Error {
2164
+ static code = "ERR_JOSE_GENERIC";
2165
+ code = "ERR_JOSE_GENERIC";
2166
+ constructor(message, options) {
2167
+ super(message, options);
2168
+ this.name = this.constructor.name;
2169
+ Error.captureStackTrace?.(this, this.constructor);
2170
+ }
2171
+ };
2172
+ var JWTClaimValidationFailed = class extends JOSEError {
2173
+ static code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
2174
+ code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
2175
+ claim;
2176
+ reason;
2177
+ payload;
2178
+ constructor(message, payload, claim = "unspecified", reason = "unspecified") {
2179
+ super(message, { cause: {
2180
+ claim,
2181
+ reason,
2182
+ payload
2183
+ } });
2184
+ this.claim = claim;
2185
+ this.reason = reason;
2186
+ this.payload = payload;
2187
+ }
2188
+ };
2189
+ var JWTExpired = class extends JOSEError {
2190
+ static code = "ERR_JWT_EXPIRED";
2191
+ code = "ERR_JWT_EXPIRED";
2192
+ claim;
2193
+ reason;
2194
+ payload;
2195
+ constructor(message, payload, claim = "unspecified", reason = "unspecified") {
2196
+ super(message, { cause: {
2197
+ claim,
2198
+ reason,
2199
+ payload
2200
+ } });
2201
+ this.claim = claim;
2202
+ this.reason = reason;
2203
+ this.payload = payload;
2204
+ }
2205
+ };
2206
+ var JOSEAlgNotAllowed = class extends JOSEError {
2207
+ static code = "ERR_JOSE_ALG_NOT_ALLOWED";
2208
+ code = "ERR_JOSE_ALG_NOT_ALLOWED";
2209
+ };
2210
+ var JOSENotSupported = class extends JOSEError {
2211
+ static code = "ERR_JOSE_NOT_SUPPORTED";
2212
+ code = "ERR_JOSE_NOT_SUPPORTED";
2213
+ };
2214
+ var JWEDecryptionFailed = class extends JOSEError {
2215
+ static code = "ERR_JWE_DECRYPTION_FAILED";
2216
+ code = "ERR_JWE_DECRYPTION_FAILED";
2217
+ constructor(message = "decryption operation failed", options) {
2218
+ super(message, options);
2219
+ }
2220
+ };
2221
+ var JWEInvalid = class extends JOSEError {
2222
+ static code = "ERR_JWE_INVALID";
2223
+ code = "ERR_JWE_INVALID";
2224
+ };
2225
+ var JWSInvalid = class extends JOSEError {
2226
+ static code = "ERR_JWS_INVALID";
2227
+ code = "ERR_JWS_INVALID";
2228
+ };
2229
+ var JWTInvalid = class extends JOSEError {
2230
+ static code = "ERR_JWT_INVALID";
2231
+ code = "ERR_JWT_INVALID";
2232
+ };
2233
+ var JWKInvalid = class extends JOSEError {
2234
+ static code = "ERR_JWK_INVALID";
2235
+ code = "ERR_JWK_INVALID";
2236
+ };
2237
+ var JWKSInvalid = class extends JOSEError {
2238
+ static code = "ERR_JWKS_INVALID";
2239
+ code = "ERR_JWKS_INVALID";
2240
+ };
2241
+ var JWKSNoMatchingKey = class extends JOSEError {
2242
+ static code = "ERR_JWKS_NO_MATCHING_KEY";
2243
+ code = "ERR_JWKS_NO_MATCHING_KEY";
2244
+ constructor(message = "no applicable key found in the JSON Web Key Set", options) {
2245
+ super(message, options);
2246
+ }
2247
+ };
2248
+ var JWKSMultipleMatchingKeys = class extends JOSEError {
2249
+ [Symbol.asyncIterator];
2250
+ static code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
2251
+ code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS";
2252
+ constructor(message = "multiple matching keys found in the JSON Web Key Set", options) {
2253
+ super(message, options);
2254
+ }
2255
+ };
2256
+ var JWKSTimeout = class extends JOSEError {
2257
+ static code = "ERR_JWKS_TIMEOUT";
2258
+ code = "ERR_JWKS_TIMEOUT";
2259
+ constructor(message = "request timed out", options) {
2260
+ super(message, options);
2261
+ }
2262
+ };
2263
+ var JWSSignatureVerificationFailed = class extends JOSEError {
2264
+ static code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
2265
+ code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
2266
+ constructor(message = "signature verification failed", options) {
2267
+ super(message, options);
2268
+ }
2269
+ };
2270
+ //#endregion
2271
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/is_key_like.js
2272
+ var isCryptoKey = (key) => {
2273
+ if (key?.[Symbol.toStringTag] === "CryptoKey") return true;
2274
+ try {
2275
+ return key instanceof CryptoKey;
2276
+ } catch {
2277
+ return false;
2278
+ }
2279
+ };
2280
+ var isKeyObject = (key) => key?.[Symbol.toStringTag] === "KeyObject";
2281
+ var isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);
2282
+ //#endregion
2283
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/helpers.js
2284
+ function assertNotSet(value, name) {
2285
+ if (value) throw new TypeError(`${name} can only be called once`);
2286
+ }
2287
+ function decodeBase64url(value, label, ErrorClass) {
2288
+ try {
2289
+ return decode(value);
2290
+ } catch {
2291
+ throw new ErrorClass(`Failed to base64url decode the ${label}`);
2292
+ }
2293
+ }
2294
+ //#endregion
2295
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/type_checks.js
2296
+ var isObjectLike = (value) => typeof value === "object" && value !== null;
2297
+ function isObject(input) {
2298
+ if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") return false;
2299
+ if (Object.getPrototypeOf(input) === null) return true;
2300
+ let proto = input;
2301
+ while (Object.getPrototypeOf(proto) !== null) proto = Object.getPrototypeOf(proto);
2302
+ return Object.getPrototypeOf(input) === proto;
2303
+ }
2304
+ function isDisjoint(...headers) {
2305
+ const sources = headers.filter(Boolean);
2306
+ if (sources.length === 0 || sources.length === 1) return true;
2307
+ let acc;
2308
+ for (const header of sources) {
2309
+ const parameters = Object.keys(header);
2310
+ if (!acc || acc.size === 0) {
2311
+ acc = new Set(parameters);
2312
+ continue;
2313
+ }
2314
+ for (const parameter of parameters) {
2315
+ if (acc.has(parameter)) return false;
2316
+ acc.add(parameter);
2317
+ }
2318
+ }
2319
+ return true;
2320
+ }
2321
+ var isJWK = (key) => isObject(key) && typeof key.kty === "string";
2322
+ var isPrivateJWK = (key) => key.kty !== "oct" && (key.kty === "AKP" && typeof key.priv === "string" || typeof key.d === "string");
2323
+ var isPublicJWK = (key) => key.kty !== "oct" && key.d === void 0 && key.priv === void 0;
2324
+ var isSecretJWK = (key) => key.kty === "oct" && typeof key.k === "string";
2325
+ //#endregion
2326
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/signing.js
2327
+ function checkKeyLength(alg, key) {
2328
+ if (alg.startsWith("RS") || alg.startsWith("PS")) {
2329
+ const { modulusLength } = key.algorithm;
2330
+ if (typeof modulusLength !== "number" || modulusLength < 2048) throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
2331
+ }
2332
+ }
2333
+ function subtleAlgorithm(alg, algorithm) {
2334
+ const hash = `SHA-${alg.slice(-3)}`;
2335
+ switch (alg) {
2336
+ case "HS256":
2337
+ case "HS384":
2338
+ case "HS512": return {
2339
+ hash,
2340
+ name: "HMAC"
2341
+ };
2342
+ case "PS256":
2343
+ case "PS384":
2344
+ case "PS512": return {
2345
+ hash,
2346
+ name: "RSA-PSS",
2347
+ saltLength: parseInt(alg.slice(-3), 10) >> 3
2348
+ };
2349
+ case "RS256":
2350
+ case "RS384":
2351
+ case "RS512": return {
2352
+ hash,
2353
+ name: "RSASSA-PKCS1-v1_5"
2354
+ };
2355
+ case "ES256":
2356
+ case "ES384":
2357
+ case "ES512": return {
2358
+ hash,
2359
+ name: "ECDSA",
2360
+ namedCurve: algorithm.namedCurve
2361
+ };
2362
+ case "Ed25519":
2363
+ case "EdDSA": return { name: "Ed25519" };
2364
+ case "ML-DSA-44":
2365
+ case "ML-DSA-65":
2366
+ case "ML-DSA-87": return { name: alg };
2367
+ default: throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
2368
+ }
2369
+ }
2370
+ async function getSigKey(alg, key, usage) {
2371
+ if (key instanceof Uint8Array) {
2372
+ if (!alg.startsWith("HS")) throw new TypeError(invalidKeyInput(key, "CryptoKey", "KeyObject", "JSON Web Key"));
2373
+ return crypto.subtle.importKey("raw", key, {
2374
+ hash: `SHA-${alg.slice(-3)}`,
2375
+ name: "HMAC"
2376
+ }, false, [usage]);
2377
+ }
2378
+ checkSigCryptoKey(key, alg, usage);
2379
+ return key;
2380
+ }
2381
+ async function sign(alg, key, data) {
2382
+ const cryptoKey = await getSigKey(alg, key, "sign");
2383
+ checkKeyLength(alg, cryptoKey);
2384
+ const signature = await crypto.subtle.sign(subtleAlgorithm(alg, cryptoKey.algorithm), cryptoKey, data);
2385
+ return new Uint8Array(signature);
2386
+ }
2387
+ async function verify(alg, key, signature, data) {
2388
+ const cryptoKey = await getSigKey(alg, key, "verify");
2389
+ checkKeyLength(alg, cryptoKey);
2390
+ const algorithm = subtleAlgorithm(alg, cryptoKey.algorithm);
2391
+ try {
2392
+ return await crypto.subtle.verify(algorithm, cryptoKey, signature, data);
2393
+ } catch {
2394
+ return false;
2395
+ }
2396
+ }
2397
+ //#endregion
2398
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/jwk_to_key.js
2399
+ var unsupportedAlg = "Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value";
2400
+ function subtleMapping(jwk) {
2401
+ let algorithm;
2402
+ let keyUsages;
2403
+ switch (jwk.kty) {
2404
+ case "AKP":
2405
+ switch (jwk.alg) {
2406
+ case "ML-DSA-44":
2407
+ case "ML-DSA-65":
2408
+ case "ML-DSA-87":
2409
+ algorithm = { name: jwk.alg };
2410
+ keyUsages = jwk.priv ? ["sign"] : ["verify"];
2411
+ break;
2412
+ default: throw new JOSENotSupported(unsupportedAlg);
2413
+ }
2414
+ break;
2415
+ case "RSA":
2416
+ switch (jwk.alg) {
2417
+ case "PS256":
2418
+ case "PS384":
2419
+ case "PS512":
2420
+ algorithm = {
2421
+ name: "RSA-PSS",
2422
+ hash: `SHA-${jwk.alg.slice(-3)}`
2423
+ };
2424
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
2425
+ break;
2426
+ case "RS256":
2427
+ case "RS384":
2428
+ case "RS512":
2429
+ algorithm = {
2430
+ name: "RSASSA-PKCS1-v1_5",
2431
+ hash: `SHA-${jwk.alg.slice(-3)}`
2432
+ };
2433
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
2434
+ break;
2435
+ case "RSA-OAEP":
2436
+ case "RSA-OAEP-256":
2437
+ case "RSA-OAEP-384":
2438
+ case "RSA-OAEP-512":
2439
+ algorithm = {
2440
+ name: "RSA-OAEP",
2441
+ hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`
2442
+ };
2443
+ keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
2444
+ break;
2445
+ default: throw new JOSENotSupported(unsupportedAlg);
2446
+ }
2447
+ break;
2448
+ case "EC":
2449
+ switch (jwk.alg) {
2450
+ case "ES256":
2451
+ case "ES384":
2452
+ case "ES512":
2453
+ algorithm = {
2454
+ name: "ECDSA",
2455
+ namedCurve: {
2456
+ ES256: "P-256",
2457
+ ES384: "P-384",
2458
+ ES512: "P-521"
2459
+ }[jwk.alg]
2460
+ };
2461
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
2462
+ break;
2463
+ case "ECDH-ES":
2464
+ case "ECDH-ES+A128KW":
2465
+ case "ECDH-ES+A192KW":
2466
+ case "ECDH-ES+A256KW":
2467
+ algorithm = {
2468
+ name: "ECDH",
2469
+ namedCurve: jwk.crv
2470
+ };
2471
+ keyUsages = jwk.d ? ["deriveBits"] : [];
2472
+ break;
2473
+ default: throw new JOSENotSupported(unsupportedAlg);
2474
+ }
2475
+ break;
2476
+ case "OKP":
2477
+ switch (jwk.alg) {
2478
+ case "Ed25519":
2479
+ case "EdDSA":
2480
+ algorithm = { name: "Ed25519" };
2481
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
2482
+ break;
2483
+ case "ECDH-ES":
2484
+ case "ECDH-ES+A128KW":
2485
+ case "ECDH-ES+A192KW":
2486
+ case "ECDH-ES+A256KW":
2487
+ algorithm = { name: jwk.crv };
2488
+ keyUsages = jwk.d ? ["deriveBits"] : [];
2489
+ break;
2490
+ default: throw new JOSENotSupported(unsupportedAlg);
2491
+ }
2492
+ break;
2493
+ default: throw new JOSENotSupported("Invalid or unsupported JWK \"kty\" (Key Type) Parameter value");
2494
+ }
2495
+ return {
2496
+ algorithm,
2497
+ keyUsages
2498
+ };
2499
+ }
2500
+ async function jwkToKey(jwk) {
2501
+ if (!jwk.alg) throw new TypeError("\"alg\" argument is required when \"jwk.alg\" is not present");
2502
+ const { algorithm, keyUsages } = subtleMapping(jwk);
2503
+ const keyData = { ...jwk };
2504
+ if (keyData.kty !== "AKP") delete keyData.alg;
2505
+ delete keyData.use;
2506
+ return crypto.subtle.importKey("jwk", keyData, algorithm, jwk.ext ?? (jwk.d || jwk.priv ? false : true), jwk.key_ops ?? keyUsages);
2507
+ }
2508
+ //#endregion
2509
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/normalize_key.js
2510
+ var unusableForAlg = "given KeyObject instance cannot be used for this algorithm";
2511
+ var cache;
2512
+ var handleJWK = async (key, jwk, alg, freeze = false) => {
2513
+ cache ||= /* @__PURE__ */ new WeakMap();
2514
+ let cached = cache.get(key);
2515
+ if (cached?.[alg]) return cached[alg];
2516
+ const cryptoKey = await jwkToKey({
2517
+ ...jwk,
2518
+ alg
2519
+ });
2520
+ if (freeze) Object.freeze(key);
2521
+ if (!cached) cache.set(key, { [alg]: cryptoKey });
2522
+ else cached[alg] = cryptoKey;
2523
+ return cryptoKey;
2524
+ };
2525
+ var handleKeyObject = (keyObject, alg) => {
2526
+ cache ||= /* @__PURE__ */ new WeakMap();
2527
+ let cached = cache.get(keyObject);
2528
+ if (cached?.[alg]) return cached[alg];
2529
+ const isPublic = keyObject.type === "public";
2530
+ const extractable = isPublic ? true : false;
2531
+ let cryptoKey;
2532
+ if (keyObject.asymmetricKeyType === "x25519") {
2533
+ switch (alg) {
2534
+ case "ECDH-ES":
2535
+ case "ECDH-ES+A128KW":
2536
+ case "ECDH-ES+A192KW":
2537
+ case "ECDH-ES+A256KW": break;
2538
+ default: throw new TypeError(unusableForAlg);
2539
+ }
2540
+ cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, isPublic ? [] : ["deriveBits"]);
2541
+ }
2542
+ if (keyObject.asymmetricKeyType === "ed25519") {
2543
+ if (alg !== "EdDSA" && alg !== "Ed25519") throw new TypeError(unusableForAlg);
2544
+ cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [isPublic ? "verify" : "sign"]);
2545
+ }
2546
+ switch (keyObject.asymmetricKeyType) {
2547
+ case "ml-dsa-44":
2548
+ case "ml-dsa-65":
2549
+ case "ml-dsa-87":
2550
+ if (alg !== keyObject.asymmetricKeyType.toUpperCase()) throw new TypeError(unusableForAlg);
2551
+ cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [isPublic ? "verify" : "sign"]);
2552
+ }
2553
+ if (keyObject.asymmetricKeyType === "rsa") {
2554
+ let hash;
2555
+ switch (alg) {
2556
+ case "RSA-OAEP":
2557
+ hash = "SHA-1";
2558
+ break;
2559
+ case "RS256":
2560
+ case "PS256":
2561
+ case "RSA-OAEP-256":
2562
+ hash = "SHA-256";
2563
+ break;
2564
+ case "RS384":
2565
+ case "PS384":
2566
+ case "RSA-OAEP-384":
2567
+ hash = "SHA-384";
2568
+ break;
2569
+ case "RS512":
2570
+ case "PS512":
2571
+ case "RSA-OAEP-512":
2572
+ hash = "SHA-512";
2573
+ break;
2574
+ default: throw new TypeError(unusableForAlg);
2575
+ }
2576
+ if (alg.startsWith("RSA-OAEP")) return keyObject.toCryptoKey({
2577
+ name: "RSA-OAEP",
2578
+ hash
2579
+ }, extractable, isPublic ? ["encrypt"] : ["decrypt"]);
2580
+ cryptoKey = keyObject.toCryptoKey({
2581
+ name: alg.startsWith("PS") ? "RSA-PSS" : "RSASSA-PKCS1-v1_5",
2582
+ hash
2583
+ }, extractable, [isPublic ? "verify" : "sign"]);
2584
+ }
2585
+ if (keyObject.asymmetricKeyType === "ec") {
2586
+ const namedCurve = (/* @__PURE__ */ new Map([
2587
+ ["prime256v1", "P-256"],
2588
+ ["secp384r1", "P-384"],
2589
+ ["secp521r1", "P-521"]
2590
+ ])).get(keyObject.asymmetricKeyDetails?.namedCurve);
2591
+ if (!namedCurve) throw new TypeError(unusableForAlg);
2592
+ const expectedCurve = {
2593
+ ES256: "P-256",
2594
+ ES384: "P-384",
2595
+ ES512: "P-521"
2596
+ };
2597
+ if (expectedCurve[alg] && namedCurve === expectedCurve[alg]) cryptoKey = keyObject.toCryptoKey({
2598
+ name: "ECDSA",
2599
+ namedCurve
2600
+ }, extractable, [isPublic ? "verify" : "sign"]);
2601
+ if (alg.startsWith("ECDH-ES")) cryptoKey = keyObject.toCryptoKey({
2602
+ name: "ECDH",
2603
+ namedCurve
2604
+ }, extractable, isPublic ? [] : ["deriveBits"]);
2605
+ }
2606
+ if (!cryptoKey) throw new TypeError(unusableForAlg);
2607
+ if (!cached) cache.set(keyObject, { [alg]: cryptoKey });
2608
+ else cached[alg] = cryptoKey;
2609
+ return cryptoKey;
2610
+ };
2611
+ async function normalizeKey(key, alg) {
2612
+ if (key instanceof Uint8Array) return key;
2613
+ if (isCryptoKey(key)) return key;
2614
+ if (isKeyObject(key)) {
2615
+ if (key.type === "secret") return key.export();
2616
+ if ("toCryptoKey" in key && typeof key.toCryptoKey === "function") try {
2617
+ return handleKeyObject(key, alg);
2618
+ } catch (err) {
2619
+ if (err instanceof TypeError) throw err;
2620
+ }
2621
+ return handleJWK(key, key.export({ format: "jwk" }), alg);
2622
+ }
2623
+ if (isJWK(key)) {
2624
+ if (key.k) return decode(key.k);
2625
+ return handleJWK(key, key, alg, true);
2626
+ }
2627
+ throw new Error("unreachable");
2628
+ }
2629
+ //#endregion
2630
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/key/import.js
2631
+ async function importJWK(jwk, alg, options) {
2632
+ if (!isObject(jwk)) throw new TypeError("JWK must be an object");
2633
+ let ext;
2634
+ alg ??= jwk.alg;
2635
+ ext ??= options?.extractable ?? jwk.ext;
2636
+ switch (jwk.kty) {
2637
+ case "oct":
2638
+ if (typeof jwk.k !== "string" || !jwk.k) throw new TypeError("missing \"k\" (Key Value) Parameter value");
2639
+ return decode(jwk.k);
2640
+ case "RSA":
2641
+ if ("oth" in jwk && jwk.oth !== void 0) throw new JOSENotSupported("RSA JWK \"oth\" (Other Primes Info) Parameter value is not supported");
2642
+ return jwkToKey({
2643
+ ...jwk,
2644
+ alg,
2645
+ ext
2646
+ });
2647
+ case "AKP":
2648
+ if (typeof jwk.alg !== "string" || !jwk.alg) throw new TypeError("missing \"alg\" (Algorithm) Parameter value");
2649
+ if (alg !== void 0 && alg !== jwk.alg) throw new TypeError("JWK alg and alg option value mismatch");
2650
+ return jwkToKey({
2651
+ ...jwk,
2652
+ ext
2653
+ });
2654
+ case "EC":
2655
+ case "OKP": return jwkToKey({
2656
+ ...jwk,
2657
+ alg,
2658
+ ext
2659
+ });
2660
+ default: throw new JOSENotSupported("Unsupported \"kty\" (Key Type) Parameter value");
2661
+ }
2662
+ }
2663
+ //#endregion
2664
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/validate_crit.js
2665
+ function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
2666
+ if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0) throw new Err("\"crit\" (Critical) Header Parameter MUST be integrity protected");
2667
+ if (!protectedHeader || protectedHeader.crit === void 0) return /* @__PURE__ */ new Set();
2668
+ if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input !== "string" || input.length === 0)) throw new Err("\"crit\" (Critical) Header Parameter MUST be an array of non-empty strings when present");
2669
+ let recognized;
2670
+ if (recognizedOption !== void 0) recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]);
2671
+ else recognized = recognizedDefault;
2672
+ for (const parameter of protectedHeader.crit) {
2673
+ if (!recognized.has(parameter)) throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
2674
+ if (joseHeader[parameter] === void 0) throw new Err(`Extension Header Parameter "${parameter}" is missing`);
2675
+ if (recognized.get(parameter) && protectedHeader[parameter] === void 0) throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
2676
+ }
2677
+ return new Set(protectedHeader.crit);
2678
+ }
2679
+ //#endregion
2680
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/validate_algorithms.js
2681
+ function validateAlgorithms(option, algorithms) {
2682
+ if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) throw new TypeError(`"${option}" option must be an array of strings`);
2683
+ if (!algorithms) return;
2684
+ return new Set(algorithms);
2685
+ }
2686
+ //#endregion
2687
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/check_key_type.js
2688
+ var tag = (key) => key?.[Symbol.toStringTag];
2689
+ var jwkMatchesOp = (alg, key, usage) => {
2690
+ if (key.use !== void 0) {
2691
+ let expected;
2692
+ switch (usage) {
2693
+ case "sign":
2694
+ case "verify":
2695
+ expected = "sig";
2696
+ break;
2697
+ case "encrypt":
2698
+ case "decrypt":
2699
+ expected = "enc";
2700
+ break;
2701
+ }
2702
+ if (key.use !== expected) throw new TypeError(`Invalid key for this operation, its "use" must be "${expected}" when present`);
2703
+ }
2704
+ if (key.alg !== void 0 && key.alg !== alg) throw new TypeError(`Invalid key for this operation, its "alg" must be "${alg}" when present`);
2705
+ if (Array.isArray(key.key_ops)) {
2706
+ let expectedKeyOp;
2707
+ switch (true) {
2708
+ case usage === "sign" || usage === "verify":
2709
+ case alg === "dir":
2710
+ case alg.includes("CBC-HS"):
2711
+ expectedKeyOp = usage;
2712
+ break;
2713
+ case alg.startsWith("PBES2"):
2714
+ expectedKeyOp = "deriveBits";
2715
+ break;
2716
+ case /^A\d{3}(?:GCM)?(?:KW)?$/.test(alg):
2717
+ if (!alg.includes("GCM") && alg.endsWith("KW")) expectedKeyOp = usage === "encrypt" ? "wrapKey" : "unwrapKey";
2718
+ else expectedKeyOp = usage;
2719
+ break;
2720
+ case usage === "encrypt" && alg.startsWith("RSA"):
2721
+ expectedKeyOp = "wrapKey";
2722
+ break;
2723
+ case usage === "decrypt":
2724
+ expectedKeyOp = alg.startsWith("RSA") ? "unwrapKey" : "deriveBits";
2725
+ break;
2726
+ }
2727
+ if (expectedKeyOp && key.key_ops?.includes?.(expectedKeyOp) === false) throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${expectedKeyOp}" when present`);
2728
+ }
2729
+ return true;
2730
+ };
2731
+ var symmetricTypeCheck = (alg, key, usage) => {
2732
+ if (key instanceof Uint8Array) return;
2733
+ if (isJWK(key)) {
2734
+ if (isSecretJWK(key) && jwkMatchesOp(alg, key, usage)) return;
2735
+ throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`);
2736
+ }
2737
+ if (!isKeyLike(key)) throw new TypeError(withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key", "Uint8Array"));
2738
+ if (key.type !== "secret") throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
2739
+ };
2740
+ var asymmetricTypeCheck = (alg, key, usage) => {
2741
+ if (isJWK(key)) switch (usage) {
2742
+ case "decrypt":
2743
+ case "sign":
2744
+ if (isPrivateJWK(key) && jwkMatchesOp(alg, key, usage)) return;
2745
+ throw new TypeError(`JSON Web Key for this operation must be a private JWK`);
2746
+ case "encrypt":
2747
+ case "verify":
2748
+ if (isPublicJWK(key) && jwkMatchesOp(alg, key, usage)) return;
2749
+ throw new TypeError(`JSON Web Key for this operation must be a public JWK`);
2750
+ }
2751
+ if (!isKeyLike(key)) throw new TypeError(withAlg(alg, key, "CryptoKey", "KeyObject", "JSON Web Key"));
2752
+ if (key.type === "secret") throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
2753
+ if (key.type === "public") switch (usage) {
2754
+ case "sign": throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
2755
+ case "decrypt": throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
2756
+ }
2757
+ if (key.type === "private") switch (usage) {
2758
+ case "verify": throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
2759
+ case "encrypt": throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
2760
+ }
2761
+ };
2762
+ function checkKeyType(alg, key, usage) {
2763
+ switch (alg.substring(0, 2)) {
2764
+ case "A1":
2765
+ case "A2":
2766
+ case "di":
2767
+ case "HS":
2768
+ case "PB":
2769
+ symmetricTypeCheck(alg, key, usage);
2770
+ break;
2771
+ default: asymmetricTypeCheck(alg, key, usage);
2772
+ }
2773
+ }
2774
+ //#endregion
2775
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jws/flattened/verify.js
2776
+ async function flattenedVerify(jws, key, options) {
2777
+ if (!isObject(jws)) throw new JWSInvalid("Flattened JWS must be an object");
2778
+ if (jws.protected === void 0 && jws.header === void 0) throw new JWSInvalid("Flattened JWS must have either of the \"protected\" or \"header\" members");
2779
+ if (jws.protected !== void 0 && typeof jws.protected !== "string") throw new JWSInvalid("JWS Protected Header incorrect type");
2780
+ if (jws.payload === void 0) throw new JWSInvalid("JWS Payload missing");
2781
+ if (typeof jws.signature !== "string") throw new JWSInvalid("JWS Signature missing or incorrect type");
2782
+ if (jws.header !== void 0 && !isObject(jws.header)) throw new JWSInvalid("JWS Unprotected Header incorrect type");
2783
+ let parsedProt = {};
2784
+ if (jws.protected) try {
2785
+ const protectedHeader = decode(jws.protected);
2786
+ parsedProt = JSON.parse(decoder.decode(protectedHeader));
2787
+ } catch {
2788
+ throw new JWSInvalid("JWS Protected Header is invalid");
2789
+ }
2790
+ if (!isDisjoint(parsedProt, jws.header)) throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
2791
+ const joseHeader = {
2792
+ ...parsedProt,
2793
+ ...jws.header
2794
+ };
2795
+ const extensions = validateCrit(JWSInvalid, /* @__PURE__ */ new Map([["b64", true]]), options?.crit, parsedProt, joseHeader);
2796
+ let b64 = true;
2797
+ if (extensions.has("b64")) {
2798
+ b64 = parsedProt.b64;
2799
+ if (typeof b64 !== "boolean") throw new JWSInvalid("The \"b64\" (base64url-encode payload) Header Parameter must be a boolean");
2800
+ }
2801
+ const { alg } = joseHeader;
2802
+ if (typeof alg !== "string" || !alg) throw new JWSInvalid("JWS \"alg\" (Algorithm) Header Parameter missing or invalid");
2803
+ const algorithms = options && validateAlgorithms("algorithms", options.algorithms);
2804
+ if (algorithms && !algorithms.has(alg)) throw new JOSEAlgNotAllowed("\"alg\" (Algorithm) Header Parameter value not allowed");
2805
+ if (b64) {
2806
+ if (typeof jws.payload !== "string") throw new JWSInvalid("JWS Payload must be a string");
2807
+ } else if (typeof jws.payload !== "string" && !(jws.payload instanceof Uint8Array)) throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
2808
+ let resolvedKey = false;
2809
+ if (typeof key === "function") {
2810
+ key = await key(parsedProt, jws);
2811
+ resolvedKey = true;
2812
+ }
2813
+ checkKeyType(alg, key, "verify");
2814
+ const data = concat(jws.protected !== void 0 ? encode$1(jws.protected) : /* @__PURE__ */ new Uint8Array(), encode$1("."), typeof jws.payload === "string" ? b64 ? encode$1(jws.payload) : encoder.encode(jws.payload) : jws.payload);
2815
+ const signature = decodeBase64url(jws.signature, "signature", JWSInvalid);
2816
+ const k = await normalizeKey(key, alg);
2817
+ if (!await verify(alg, k, signature, data)) throw new JWSSignatureVerificationFailed();
2818
+ let payload;
2819
+ if (b64) payload = decodeBase64url(jws.payload, "payload", JWSInvalid);
2820
+ else if (typeof jws.payload === "string") payload = encoder.encode(jws.payload);
2821
+ else payload = jws.payload;
2822
+ const result = { payload };
2823
+ if (jws.protected !== void 0) result.protectedHeader = parsedProt;
2824
+ if (jws.header !== void 0) result.unprotectedHeader = jws.header;
2825
+ if (resolvedKey) return {
2826
+ ...result,
2827
+ key: k
2828
+ };
2829
+ return result;
2830
+ }
2831
+ //#endregion
2832
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jws/compact/verify.js
2833
+ async function compactVerify(jws, key, options) {
2834
+ if (jws instanceof Uint8Array) jws = decoder.decode(jws);
2835
+ if (typeof jws !== "string") throw new JWSInvalid("Compact JWS must be a string or Uint8Array");
2836
+ const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split(".");
2837
+ if (length !== 3) throw new JWSInvalid("Invalid Compact JWS");
2838
+ const verified = await flattenedVerify({
2839
+ payload,
2840
+ protected: protectedHeader,
2841
+ signature
2842
+ }, key, options);
2843
+ const result = {
2844
+ payload: verified.payload,
2845
+ protectedHeader: verified.protectedHeader
2846
+ };
2847
+ if (typeof key === "function") return {
2848
+ ...result,
2849
+ key: verified.key
2850
+ };
2851
+ return result;
2852
+ }
2853
+ //#endregion
2854
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/lib/jwt_claims_set.js
2855
+ var epoch = (date) => Math.floor(date.getTime() / 1e3);
2856
+ var minute = 60;
2857
+ var hour = minute * 60;
2858
+ var day = hour * 24;
2859
+ var week = day * 7;
2860
+ var year = day * 365.25;
2861
+ var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
2862
+ function secs(str) {
2863
+ const matched = REGEX.exec(str);
2864
+ if (!matched || matched[4] && matched[1]) throw new TypeError("Invalid time period format");
2865
+ const value = parseFloat(matched[2]);
2866
+ const unit = matched[3].toLowerCase();
2867
+ let numericDate;
2868
+ switch (unit) {
2869
+ case "sec":
2870
+ case "secs":
2871
+ case "second":
2872
+ case "seconds":
2873
+ case "s":
2874
+ numericDate = Math.round(value);
2875
+ break;
2876
+ case "minute":
2877
+ case "minutes":
2878
+ case "min":
2879
+ case "mins":
2880
+ case "m":
2881
+ numericDate = Math.round(value * minute);
2882
+ break;
2883
+ case "hour":
2884
+ case "hours":
2885
+ case "hr":
2886
+ case "hrs":
2887
+ case "h":
2888
+ numericDate = Math.round(value * hour);
2889
+ break;
2890
+ case "day":
2891
+ case "days":
2892
+ case "d":
2893
+ numericDate = Math.round(value * day);
2894
+ break;
2895
+ case "week":
2896
+ case "weeks":
2897
+ case "w":
2898
+ numericDate = Math.round(value * week);
2899
+ break;
2900
+ default:
2901
+ numericDate = Math.round(value * year);
2902
+ break;
2903
+ }
2904
+ if (matched[1] === "-" || matched[4] === "ago") return -numericDate;
2905
+ return numericDate;
2906
+ }
2907
+ function validateInput(label, input) {
2908
+ if (!Number.isFinite(input)) throw new TypeError(`Invalid ${label} input`);
2909
+ return input;
2910
+ }
2911
+ var normalizeTyp = (value) => {
2912
+ if (value.includes("/")) return value.toLowerCase();
2913
+ return `application/${value.toLowerCase()}`;
2914
+ };
2915
+ var checkAudiencePresence = (audPayload, audOption) => {
2916
+ if (typeof audPayload === "string") return audOption.includes(audPayload);
2917
+ if (Array.isArray(audPayload)) return audOption.some(Set.prototype.has.bind(new Set(audPayload)));
2918
+ return false;
2919
+ };
2920
+ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
2921
+ let payload;
2922
+ try {
2923
+ payload = JSON.parse(decoder.decode(encodedPayload));
2924
+ } catch {}
2925
+ if (!isObject(payload)) throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
2926
+ const { typ } = options;
2927
+ if (typ && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) throw new JWTClaimValidationFailed("unexpected \"typ\" JWT header value", payload, "typ", "check_failed");
2928
+ const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
2929
+ const presenceCheck = [...requiredClaims];
2930
+ if (maxTokenAge !== void 0) presenceCheck.push("iat");
2931
+ if (audience !== void 0) presenceCheck.push("aud");
2932
+ if (subject !== void 0) presenceCheck.push("sub");
2933
+ if (issuer !== void 0) presenceCheck.push("iss");
2934
+ for (const claim of new Set(presenceCheck.reverse())) if (!(claim in payload)) throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
2935
+ if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) throw new JWTClaimValidationFailed("unexpected \"iss\" claim value", payload, "iss", "check_failed");
2936
+ if (subject && payload.sub !== subject) throw new JWTClaimValidationFailed("unexpected \"sub\" claim value", payload, "sub", "check_failed");
2937
+ if (audience && !checkAudiencePresence(payload.aud, typeof audience === "string" ? [audience] : audience)) throw new JWTClaimValidationFailed("unexpected \"aud\" claim value", payload, "aud", "check_failed");
2938
+ let tolerance;
2939
+ switch (typeof options.clockTolerance) {
2940
+ case "string":
2941
+ tolerance = secs(options.clockTolerance);
2942
+ break;
2943
+ case "number":
2944
+ tolerance = options.clockTolerance;
2945
+ break;
2946
+ case "undefined":
2947
+ tolerance = 0;
2948
+ break;
2949
+ default: throw new TypeError("Invalid clockTolerance option type");
2950
+ }
2951
+ const { currentDate } = options;
2952
+ const now = epoch(currentDate || /* @__PURE__ */ new Date());
2953
+ if ((payload.iat !== void 0 || maxTokenAge) && typeof payload.iat !== "number") throw new JWTClaimValidationFailed("\"iat\" claim must be a number", payload, "iat", "invalid");
2954
+ if (payload.nbf !== void 0) {
2955
+ if (typeof payload.nbf !== "number") throw new JWTClaimValidationFailed("\"nbf\" claim must be a number", payload, "nbf", "invalid");
2956
+ if (payload.nbf > now + tolerance) throw new JWTClaimValidationFailed("\"nbf\" claim timestamp check failed", payload, "nbf", "check_failed");
2957
+ }
2958
+ if (payload.exp !== void 0) {
2959
+ if (typeof payload.exp !== "number") throw new JWTClaimValidationFailed("\"exp\" claim must be a number", payload, "exp", "invalid");
2960
+ if (payload.exp <= now - tolerance) throw new JWTExpired("\"exp\" claim timestamp check failed", payload, "exp", "check_failed");
2961
+ }
2962
+ if (maxTokenAge) {
2963
+ const age = now - payload.iat;
2964
+ const max = typeof maxTokenAge === "number" ? maxTokenAge : secs(maxTokenAge);
2965
+ if (age - tolerance > max) throw new JWTExpired("\"iat\" claim timestamp check failed (too far in the past)", payload, "iat", "check_failed");
2966
+ if (age < 0 - tolerance) throw new JWTClaimValidationFailed("\"iat\" claim timestamp check failed (it should be in the past)", payload, "iat", "check_failed");
2967
+ }
2968
+ return payload;
2969
+ }
2970
+ var JWTClaimsBuilder = class {
2971
+ #payload;
2972
+ constructor(payload) {
2973
+ if (!isObject(payload)) throw new TypeError("JWT Claims Set MUST be an object");
2974
+ this.#payload = structuredClone(payload);
2975
+ }
2976
+ data() {
2977
+ return encoder.encode(JSON.stringify(this.#payload));
2978
+ }
2979
+ get iss() {
2980
+ return this.#payload.iss;
2981
+ }
2982
+ set iss(value) {
2983
+ this.#payload.iss = value;
2984
+ }
2985
+ get sub() {
2986
+ return this.#payload.sub;
2987
+ }
2988
+ set sub(value) {
2989
+ this.#payload.sub = value;
2990
+ }
2991
+ get aud() {
2992
+ return this.#payload.aud;
2993
+ }
2994
+ set aud(value) {
2995
+ this.#payload.aud = value;
2996
+ }
2997
+ set jti(value) {
2998
+ this.#payload.jti = value;
2999
+ }
3000
+ set nbf(value) {
3001
+ if (typeof value === "number") this.#payload.nbf = validateInput("setNotBefore", value);
3002
+ else if (value instanceof Date) this.#payload.nbf = validateInput("setNotBefore", epoch(value));
3003
+ else this.#payload.nbf = epoch(/* @__PURE__ */ new Date()) + secs(value);
3004
+ }
3005
+ set exp(value) {
3006
+ if (typeof value === "number") this.#payload.exp = validateInput("setExpirationTime", value);
3007
+ else if (value instanceof Date) this.#payload.exp = validateInput("setExpirationTime", epoch(value));
3008
+ else this.#payload.exp = epoch(/* @__PURE__ */ new Date()) + secs(value);
3009
+ }
3010
+ set iat(value) {
3011
+ if (value === void 0) this.#payload.iat = epoch(/* @__PURE__ */ new Date());
3012
+ else if (value instanceof Date) this.#payload.iat = validateInput("setIssuedAt", epoch(value));
3013
+ else if (typeof value === "string") this.#payload.iat = validateInput("setIssuedAt", epoch(/* @__PURE__ */ new Date()) + secs(value));
3014
+ else this.#payload.iat = validateInput("setIssuedAt", value);
3015
+ }
3016
+ };
3017
+ //#endregion
3018
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jwt/verify.js
3019
+ async function jwtVerify(jwt, key, options) {
3020
+ const verified = await compactVerify(jwt, key, options);
3021
+ if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
3022
+ const result = {
3023
+ payload: validateClaimsSet(verified.protectedHeader, verified.payload, options),
3024
+ protectedHeader: verified.protectedHeader
3025
+ };
3026
+ if (typeof key === "function") return {
3027
+ ...result,
3028
+ key: verified.key
3029
+ };
3030
+ return result;
3031
+ }
3032
+ //#endregion
3033
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jws/flattened/sign.js
3034
+ var FlattenedSign = class {
3035
+ #payload;
3036
+ #protectedHeader;
3037
+ #unprotectedHeader;
3038
+ constructor(payload) {
3039
+ if (!(payload instanceof Uint8Array)) throw new TypeError("payload must be an instance of Uint8Array");
3040
+ this.#payload = payload;
3041
+ }
3042
+ setProtectedHeader(protectedHeader) {
3043
+ assertNotSet(this.#protectedHeader, "setProtectedHeader");
3044
+ this.#protectedHeader = protectedHeader;
3045
+ return this;
3046
+ }
3047
+ setUnprotectedHeader(unprotectedHeader) {
3048
+ assertNotSet(this.#unprotectedHeader, "setUnprotectedHeader");
3049
+ this.#unprotectedHeader = unprotectedHeader;
3050
+ return this;
3051
+ }
3052
+ async sign(key, options) {
3053
+ if (!this.#protectedHeader && !this.#unprotectedHeader) throw new JWSInvalid("either setProtectedHeader or setUnprotectedHeader must be called before #sign()");
3054
+ if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader)) throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
3055
+ const joseHeader = {
3056
+ ...this.#protectedHeader,
3057
+ ...this.#unprotectedHeader
3058
+ };
3059
+ const extensions = validateCrit(JWSInvalid, /* @__PURE__ */ new Map([["b64", true]]), options?.crit, this.#protectedHeader, joseHeader);
3060
+ let b64 = true;
3061
+ if (extensions.has("b64")) {
3062
+ b64 = this.#protectedHeader.b64;
3063
+ if (typeof b64 !== "boolean") throw new JWSInvalid("The \"b64\" (base64url-encode payload) Header Parameter must be a boolean");
3064
+ }
3065
+ const { alg } = joseHeader;
3066
+ if (typeof alg !== "string" || !alg) throw new JWSInvalid("JWS \"alg\" (Algorithm) Header Parameter missing or invalid");
3067
+ checkKeyType(alg, key, "sign");
3068
+ let payloadS;
3069
+ let payloadB;
3070
+ if (b64) {
3071
+ payloadS = encode(this.#payload);
3072
+ payloadB = encode$1(payloadS);
3073
+ } else {
3074
+ payloadB = this.#payload;
3075
+ payloadS = "";
3076
+ }
3077
+ let protectedHeaderString;
3078
+ let protectedHeaderBytes;
3079
+ if (this.#protectedHeader) {
3080
+ protectedHeaderString = encode(JSON.stringify(this.#protectedHeader));
3081
+ protectedHeaderBytes = encode$1(protectedHeaderString);
3082
+ } else {
3083
+ protectedHeaderString = "";
3084
+ protectedHeaderBytes = /* @__PURE__ */ new Uint8Array();
3085
+ }
3086
+ const data = concat(protectedHeaderBytes, encode$1("."), payloadB);
3087
+ const jws = {
3088
+ signature: encode(await sign(alg, await normalizeKey(key, alg), data)),
3089
+ payload: payloadS
3090
+ };
3091
+ if (this.#unprotectedHeader) jws.header = this.#unprotectedHeader;
3092
+ if (this.#protectedHeader) jws.protected = protectedHeaderString;
3093
+ return jws;
3094
+ }
3095
+ };
3096
+ //#endregion
3097
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jws/compact/sign.js
3098
+ var CompactSign = class {
3099
+ #flattened;
3100
+ constructor(payload) {
3101
+ this.#flattened = new FlattenedSign(payload);
3102
+ }
3103
+ setProtectedHeader(protectedHeader) {
3104
+ this.#flattened.setProtectedHeader(protectedHeader);
3105
+ return this;
3106
+ }
3107
+ async sign(key, options) {
3108
+ const jws = await this.#flattened.sign(key, options);
3109
+ if (jws.payload === void 0) throw new TypeError("use the flattened module for creating JWS with b64: false");
3110
+ return `${jws.protected}.${jws.payload}.${jws.signature}`;
3111
+ }
3112
+ };
3113
+ //#endregion
3114
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jwt/sign.js
3115
+ var SignJWT = class {
3116
+ #protectedHeader;
3117
+ #jwt;
3118
+ constructor(payload = {}) {
3119
+ this.#jwt = new JWTClaimsBuilder(payload);
3120
+ }
3121
+ setIssuer(issuer) {
3122
+ this.#jwt.iss = issuer;
3123
+ return this;
3124
+ }
3125
+ setSubject(subject) {
3126
+ this.#jwt.sub = subject;
3127
+ return this;
3128
+ }
3129
+ setAudience(audience) {
3130
+ this.#jwt.aud = audience;
3131
+ return this;
3132
+ }
3133
+ setJti(jwtId) {
3134
+ this.#jwt.jti = jwtId;
3135
+ return this;
3136
+ }
3137
+ setNotBefore(input) {
3138
+ this.#jwt.nbf = input;
3139
+ return this;
3140
+ }
3141
+ setExpirationTime(input) {
3142
+ this.#jwt.exp = input;
3143
+ return this;
3144
+ }
3145
+ setIssuedAt(input) {
3146
+ this.#jwt.iat = input;
3147
+ return this;
3148
+ }
3149
+ setProtectedHeader(protectedHeader) {
3150
+ this.#protectedHeader = protectedHeader;
3151
+ return this;
3152
+ }
3153
+ async sign(key, options) {
3154
+ const sig = new CompactSign(this.#jwt.data());
3155
+ sig.setProtectedHeader(this.#protectedHeader);
3156
+ if (Array.isArray(this.#protectedHeader?.crit) && this.#protectedHeader.crit.includes("b64") && this.#protectedHeader.b64 === false) throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
3157
+ return sig.sign(key, options);
3158
+ }
3159
+ };
3160
+ //#endregion
3161
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jwks/local.js
3162
+ function getKtyFromAlg(alg) {
3163
+ switch (typeof alg === "string" && alg.slice(0, 2)) {
3164
+ case "RS":
3165
+ case "PS": return "RSA";
3166
+ case "ES": return "EC";
3167
+ case "Ed": return "OKP";
3168
+ case "ML": return "AKP";
3169
+ default: throw new JOSENotSupported("Unsupported \"alg\" value for a JSON Web Key Set");
3170
+ }
3171
+ }
3172
+ function isJWKSLike(jwks) {
3173
+ return jwks && typeof jwks === "object" && Array.isArray(jwks.keys) && jwks.keys.every(isJWKLike);
3174
+ }
3175
+ function isJWKLike(key) {
3176
+ return isObject(key);
3177
+ }
3178
+ var LocalJWKSet = class {
3179
+ #jwks;
3180
+ #cached = /* @__PURE__ */ new WeakMap();
3181
+ constructor(jwks) {
3182
+ if (!isJWKSLike(jwks)) throw new JWKSInvalid("JSON Web Key Set malformed");
3183
+ this.#jwks = structuredClone(jwks);
3184
+ }
3185
+ jwks() {
3186
+ return this.#jwks;
3187
+ }
3188
+ async getKey(protectedHeader, token) {
3189
+ const { alg, kid } = {
3190
+ ...protectedHeader,
3191
+ ...token?.header
3192
+ };
3193
+ const kty = getKtyFromAlg(alg);
3194
+ const candidates = this.#jwks.keys.filter((jwk) => {
3195
+ let candidate = kty === jwk.kty;
3196
+ if (candidate && typeof kid === "string") candidate = kid === jwk.kid;
3197
+ if (candidate && (typeof jwk.alg === "string" || kty === "AKP")) candidate = alg === jwk.alg;
3198
+ if (candidate && typeof jwk.use === "string") candidate = jwk.use === "sig";
3199
+ if (candidate && Array.isArray(jwk.key_ops)) candidate = jwk.key_ops.includes("verify");
3200
+ if (candidate) switch (alg) {
3201
+ case "ES256":
3202
+ candidate = jwk.crv === "P-256";
3203
+ break;
3204
+ case "ES384":
3205
+ candidate = jwk.crv === "P-384";
3206
+ break;
3207
+ case "ES512":
3208
+ candidate = jwk.crv === "P-521";
3209
+ break;
3210
+ case "Ed25519":
3211
+ case "EdDSA":
3212
+ candidate = jwk.crv === "Ed25519";
3213
+ break;
3214
+ }
3215
+ return candidate;
3216
+ });
3217
+ const { 0: jwk, length } = candidates;
3218
+ if (length === 0) throw new JWKSNoMatchingKey();
3219
+ if (length !== 1) {
3220
+ const error = new JWKSMultipleMatchingKeys();
3221
+ const _cached = this.#cached;
3222
+ error[Symbol.asyncIterator] = async function* () {
3223
+ for (const jwk of candidates) try {
3224
+ yield await importWithAlgCache(_cached, jwk, alg);
3225
+ } catch {}
3226
+ };
3227
+ throw error;
3228
+ }
3229
+ return importWithAlgCache(this.#cached, jwk, alg);
3230
+ }
3231
+ };
3232
+ async function importWithAlgCache(cache, jwk, alg) {
3233
+ const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
3234
+ if (cached[alg] === void 0) {
3235
+ const key = await importJWK({
3236
+ ...jwk,
3237
+ ext: true
3238
+ }, alg);
3239
+ if (key instanceof Uint8Array || key.type !== "public") throw new JWKSInvalid("JSON Web Key Set members must be public keys");
3240
+ cached[alg] = key;
3241
+ }
3242
+ return cached[alg];
3243
+ }
3244
+ function createLocalJWKSet(jwks) {
3245
+ const set = new LocalJWKSet(jwks);
3246
+ const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
3247
+ Object.defineProperties(localJWKSet, { jwks: {
3248
+ value: () => structuredClone(set.jwks()),
3249
+ enumerable: false,
3250
+ configurable: false,
3251
+ writable: false
3252
+ } });
3253
+ return localJWKSet;
3254
+ }
3255
+ //#endregion
3256
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/jwks/remote.js
3257
+ function isCloudflareWorkers() {
3258
+ return typeof WebSocketPair !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime !== "undefined" && EdgeRuntime === "vercel";
3259
+ }
3260
+ var USER_AGENT;
3261
+ if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) USER_AGENT = `jose/v6.2.3`;
3262
+ var customFetch = Symbol();
3263
+ async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
3264
+ const response = await fetchImpl(url, {
3265
+ method: "GET",
3266
+ signal,
3267
+ redirect: "manual",
3268
+ headers
3269
+ }).catch((err) => {
3270
+ if (err.name === "TimeoutError") throw new JWKSTimeout();
3271
+ throw err;
3272
+ });
3273
+ if (response.status !== 200) throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response");
3274
+ try {
3275
+ return await response.json();
3276
+ } catch {
3277
+ throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON");
3278
+ }
3279
+ }
3280
+ var jwksCache = Symbol();
3281
+ function isFreshJwksCache(input, cacheMaxAge) {
3282
+ if (typeof input !== "object" || input === null) return false;
3283
+ if (!("uat" in input) || typeof input.uat !== "number" || Date.now() - input.uat >= cacheMaxAge) return false;
3284
+ if (!("jwks" in input) || !isObject(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isObject)) return false;
3285
+ return true;
3286
+ }
3287
+ var RemoteJWKSet = class {
3288
+ #url;
3289
+ #timeoutDuration;
3290
+ #cooldownDuration;
3291
+ #cacheMaxAge;
3292
+ #jwksTimestamp;
3293
+ #pendingFetch;
3294
+ #headers;
3295
+ #customFetch;
3296
+ #local;
3297
+ #cache;
3298
+ constructor(url, options) {
3299
+ if (!(url instanceof URL)) throw new TypeError("url must be an instance of URL");
3300
+ this.#url = new URL(url.href);
3301
+ this.#timeoutDuration = typeof options?.timeoutDuration === "number" ? options?.timeoutDuration : 5e3;
3302
+ this.#cooldownDuration = typeof options?.cooldownDuration === "number" ? options?.cooldownDuration : 3e4;
3303
+ this.#cacheMaxAge = typeof options?.cacheMaxAge === "number" ? options?.cacheMaxAge : 6e5;
3304
+ this.#headers = new Headers(options?.headers);
3305
+ if (USER_AGENT && !this.#headers.has("User-Agent")) this.#headers.set("User-Agent", USER_AGENT);
3306
+ if (!this.#headers.has("accept")) {
3307
+ this.#headers.set("accept", "application/json");
3308
+ this.#headers.append("accept", "application/jwk-set+json");
3309
+ }
3310
+ this.#customFetch = options?.[customFetch];
3311
+ if (options?.[jwksCache] !== void 0) {
3312
+ this.#cache = options?.[jwksCache];
3313
+ if (isFreshJwksCache(options?.[jwksCache], this.#cacheMaxAge)) {
3314
+ this.#jwksTimestamp = this.#cache.uat;
3315
+ this.#local = createLocalJWKSet(this.#cache.jwks);
3316
+ }
3317
+ }
3318
+ }
3319
+ pendingFetch() {
3320
+ return !!this.#pendingFetch;
3321
+ }
3322
+ coolingDown() {
3323
+ return typeof this.#jwksTimestamp === "number" ? Date.now() < this.#jwksTimestamp + this.#cooldownDuration : false;
3324
+ }
3325
+ fresh() {
3326
+ return typeof this.#jwksTimestamp === "number" ? Date.now() < this.#jwksTimestamp + this.#cacheMaxAge : false;
3327
+ }
3328
+ jwks() {
3329
+ return this.#local?.jwks();
3330
+ }
3331
+ async getKey(protectedHeader, token) {
3332
+ if (!this.#local || !this.fresh()) await this.reload();
3333
+ try {
3334
+ return await this.#local(protectedHeader, token);
3335
+ } catch (err) {
3336
+ if (err instanceof JWKSNoMatchingKey) {
3337
+ if (this.coolingDown() === false) {
3338
+ await this.reload();
3339
+ return this.#local(protectedHeader, token);
3340
+ }
3341
+ }
3342
+ throw err;
3343
+ }
3344
+ }
3345
+ async reload() {
3346
+ if (this.#pendingFetch && isCloudflareWorkers()) this.#pendingFetch = void 0;
3347
+ this.#pendingFetch ||= fetchJwks(this.#url.href, this.#headers, AbortSignal.timeout(this.#timeoutDuration), this.#customFetch).then((json) => {
3348
+ this.#local = createLocalJWKSet(json);
3349
+ if (this.#cache) {
3350
+ this.#cache.uat = Date.now();
3351
+ this.#cache.jwks = json;
3352
+ }
3353
+ this.#jwksTimestamp = Date.now();
3354
+ this.#pendingFetch = void 0;
3355
+ }).catch((err) => {
3356
+ this.#pendingFetch = void 0;
3357
+ throw err;
3358
+ });
3359
+ await this.#pendingFetch;
3360
+ }
3361
+ };
3362
+ function createRemoteJWKSet(url, options) {
3363
+ const set = new RemoteJWKSet(url, options);
3364
+ const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
3365
+ Object.defineProperties(remoteJWKSet, {
3366
+ coolingDown: {
3367
+ get: () => set.coolingDown(),
3368
+ enumerable: true,
3369
+ configurable: false
3370
+ },
3371
+ fresh: {
3372
+ get: () => set.fresh(),
3373
+ enumerable: true,
3374
+ configurable: false
3375
+ },
3376
+ reload: {
3377
+ value: () => set.reload(),
3378
+ enumerable: true,
3379
+ configurable: false,
3380
+ writable: false
3381
+ },
3382
+ reloading: {
3383
+ get: () => set.pendingFetch(),
3384
+ enumerable: true,
3385
+ configurable: false
3386
+ },
3387
+ jwks: {
3388
+ value: () => set.jwks(),
3389
+ enumerable: true,
3390
+ configurable: false,
3391
+ writable: false
3392
+ }
3393
+ });
3394
+ return remoteJWKSet;
3395
+ }
3396
+ //#endregion
3397
+ //#region ../../node_modules/.pnpm/jose@6.2.3/node_modules/jose/dist/webapi/index.js
3398
+ var webapi_exports = /* @__PURE__ */ __exportAll({
3399
+ SignJWT: () => SignJWT,
3400
+ cryptoRuntime: () => cryptoRuntime,
3401
+ errors: () => errors_exports,
3402
+ jwtVerify: () => jwtVerify
3403
+ });
3404
+ //#endregion
3405
+ //#region src/services/OidcLoginService.ts
3406
+ var DEFAULT_SCOPES = [
3407
+ "openid",
3408
+ "profile",
3409
+ "email"
3410
+ ];
3411
+ var DEFAULT_CLOCK_TOLERANCE_SECONDS = 15;
3412
+ var DEFAULT_METADATA_CACHE_TTL_MS = 300 * 1e3;
3413
+ var WELL_KNOWN_PATH = "/.well-known/openid-configuration";
3414
+ var OidcLoginError = class extends Error {
3415
+ constructor(message) {
3416
+ super(message);
3417
+ this.name = "OidcLoginError";
3418
+ }
3419
+ };
3420
+ function getUsersOidcConfig() {
3421
+ return getPackageConfig("users", {}).auth?.oidc ?? {};
3422
+ }
3423
+ function normalizeIssuer(issuer) {
3424
+ return issuer.replace(/\/+$/u, "");
3425
+ }
3426
+ function resolveDiscoveryUrl(provider) {
3427
+ if (provider.discoveryUrl) return provider.discoveryUrl;
3428
+ return `${normalizeIssuer(provider.issuer)}${WELL_KNOWN_PATH}`;
3429
+ }
3430
+ function bytesToBase64Url(bytes) {
3431
+ let binary = "";
3432
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3433
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
3434
+ }
3435
+ function base64UrlToBytes(value) {
3436
+ const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
3437
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
3438
+ const binary = atob(padded);
3439
+ const bytes = new Uint8Array(binary.length);
3440
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
3441
+ return bytes;
3442
+ }
3443
+ function randomBase64Url(byteLength = 32) {
3444
+ const bytes = new Uint8Array(byteLength);
3445
+ crypto.getRandomValues(bytes);
3446
+ return bytesToBase64Url(bytes);
3447
+ }
3448
+ async function pkceChallenge(codeVerifier) {
3449
+ const bytes = new TextEncoder().encode(codeVerifier);
3450
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
3451
+ return bytesToBase64Url(new Uint8Array(digest));
3452
+ }
3453
+ function bytesToBase64(bytes) {
3454
+ let binary = "";
3455
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3456
+ return btoa(binary);
3457
+ }
3458
+ function formUrlEncode(value) {
3459
+ const params = new URLSearchParams();
3460
+ params.set("value", value);
3461
+ return params.toString().slice(6);
3462
+ }
3463
+ function encodeBasicAuth(clientId, clientSecret) {
3464
+ const credentials = `${formUrlEncode(clientId)}:${formUrlEncode(clientSecret)}`;
3465
+ return bytesToBase64(new TextEncoder().encode(credentials));
3466
+ }
3467
+ function asString(value) {
3468
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3469
+ }
3470
+ function asBoolean(value) {
3471
+ if (typeof value === "boolean") return value;
3472
+ if (value === "true") return true;
3473
+ if (value === "false") return false;
3474
+ }
3475
+ function ensureFetch(fetchOverride) {
3476
+ const runtimeFetch = fetchOverride ?? globalThis.fetch;
3477
+ if (!runtimeFetch) throw new OidcLoginError("OIDC login requires a fetch implementation.");
3478
+ return runtimeFetch;
3479
+ }
3480
+ function requireString(value, field) {
3481
+ if (typeof value !== "string" || value.length === 0) throw new OidcLoginError(`OIDC provider metadata missing "${field}".`);
3482
+ return value;
3483
+ }
3484
+ async function readJsonResponse(response, context) {
3485
+ try {
3486
+ return await response.json();
3487
+ } catch {
3488
+ throw new OidcLoginError(`${context} returned invalid JSON.`);
3489
+ }
3490
+ }
3491
+ function normalizeClaims(payload, issuer, sourceName = "OIDC claims") {
3492
+ const sub = asString(payload.sub);
3493
+ if (!sub) throw new OidcLoginError(`${sourceName} missing required "sub" claim.`);
3494
+ return {
3495
+ sub,
3496
+ iss: asString(payload.iss) ?? issuer,
3497
+ email: asString(payload.email),
3498
+ email_verified: asBoolean(payload.email_verified),
3499
+ name: asString(payload.name),
3500
+ preferred_username: asString(payload.preferred_username)
3501
+ };
3502
+ }
3503
+ function mergeClaims(idTokenClaims, userInfoClaims) {
3504
+ if (userInfoClaims.sub !== idTokenClaims.sub) throw new OidcLoginError("OIDC userinfo subject does not match ID token.");
3505
+ return {
3506
+ ...idTokenClaims,
3507
+ email: idTokenClaims.email ?? userInfoClaims.email,
3508
+ email_verified: idTokenClaims.email_verified ?? userInfoClaims.email_verified,
3509
+ name: idTokenClaims.name ?? userInfoClaims.name,
3510
+ preferred_username: idTokenClaims.preferred_username ?? userInfoClaims.preferred_username
3511
+ };
3512
+ }
3513
+ function validateAuthorizedParty(payload, clientId) {
3514
+ const authorizedParty = asString(payload.azp);
3515
+ if (authorizedParty && authorizedParty !== clientId) throw new OidcLoginError("OIDC authorized party validation failed.");
3516
+ if (Array.isArray(payload.aud) && payload.aud.length > 1 && authorizedParty !== clientId) throw new OidcLoginError("OIDC authorized party validation failed.");
3517
+ }
3518
+ function getTokenEndpointAuthMethod(provider) {
3519
+ return provider.tokenEndpointAuthMethod ?? (provider.clientSecret ? "client_secret_basic" : "none");
3520
+ }
3521
+ function getScopes(provider) {
3522
+ return provider.scopes?.length ? provider.scopes : [...DEFAULT_SCOPES];
3523
+ }
3524
+ function parseTokenResponse(json) {
3525
+ if (typeof json.id_token !== "string") throw new OidcLoginError("OIDC token response missing \"id_token\".");
3526
+ return {
3527
+ accessToken: asString(json.access_token),
3528
+ expiresAt: typeof json.expires_in === "number" ? new Date(Date.now() + json.expires_in * 1e3) : void 0,
3529
+ idToken: json.id_token,
3530
+ refreshToken: asString(json.refresh_token),
3531
+ scope: asString(json.scope),
3532
+ tokenType: asString(json.token_type)
3533
+ };
3534
+ }
3535
+ function parseTransaction(value) {
3536
+ if (!value || typeof value !== "object") throw new OidcLoginError("Invalid OIDC login transaction.");
3537
+ const record = value;
3538
+ if (!record.provider || !record.state || !record.nonce || !record.codeVerifier || typeof record.createdAt !== "number") throw new OidcLoginError("Invalid OIDC login transaction.");
3539
+ return {
3540
+ codeVerifier: record.codeVerifier,
3541
+ createdAt: record.createdAt,
3542
+ nonce: record.nonce,
3543
+ provider: record.provider,
3544
+ returnTo: asString(record.returnTo),
3545
+ state: record.state
3546
+ };
3547
+ }
3548
+ function encodeOidcTransaction(transaction) {
3549
+ const json = JSON.stringify(transaction);
3550
+ return bytesToBase64Url(new TextEncoder().encode(json));
3551
+ }
3552
+ function decodeOidcTransaction(value) {
3553
+ try {
3554
+ const json = new TextDecoder().decode(base64UrlToBytes(value));
3555
+ return parseTransaction(JSON.parse(json));
3556
+ } catch (error) {
3557
+ if (error instanceof OidcLoginError) throw error;
3558
+ throw new OidcLoginError("Invalid OIDC login transaction.");
3559
+ }
3560
+ }
3561
+ function resolveOidcProviderConfig(providerName, options = {}) {
3562
+ const oidcConfig = getUsersOidcConfig();
3563
+ const providers = {
3564
+ ...oidcConfig.providers ?? {},
3565
+ ...options.providers ?? {}
3566
+ };
3567
+ const resolvedProviderName = providerName ?? options.defaultProvider ?? oidcConfig.defaultProvider;
3568
+ if (!resolvedProviderName) throw new OidcLoginError("No OIDC provider specified. Pass a provider name or configure packages.users.auth.oidc.defaultProvider.");
3569
+ const provider = providers[resolvedProviderName];
3570
+ if (!provider) throw new OidcLoginError(`Unknown OIDC provider "${resolvedProviderName}". Configure it under packages.users.auth.oidc.providers.`);
3571
+ if (!provider.issuer || !provider.clientId) throw new OidcLoginError(`OIDC provider "${resolvedProviderName}" requires issuer and clientId.`);
3572
+ return {
3573
+ provider,
3574
+ providerName: resolvedProviderName
3575
+ };
3576
+ }
3577
+ var OidcLoginService = class {
3578
+ classOptions;
3579
+ clockTolerance;
3580
+ fetchImpl;
3581
+ metadataCacheTtlMs;
3582
+ metadataFetchedAt = 0;
3583
+ metadataPromise;
3584
+ remoteJwks;
3585
+ provider;
3586
+ providerName;
3587
+ constructor(options) {
3588
+ const { clockTolerance, fetch: fetchOverride, metadataCacheTtlMs, provider, providerName, ...classOptions } = options;
3589
+ if (!provider.redirectUri) throw new OidcLoginError(`OIDC provider "${providerName}" requires a redirectUri.`);
3590
+ this.classOptions = classOptions;
3591
+ this.clockTolerance = clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
3592
+ this.fetchImpl = ensureFetch(fetchOverride);
3593
+ this.metadataCacheTtlMs = metadataCacheTtlMs ?? DEFAULT_METADATA_CACHE_TTL_MS;
3594
+ this.provider = {
3595
+ ...provider,
3596
+ issuer: normalizeIssuer(provider.issuer)
3597
+ };
3598
+ this.providerName = providerName;
3599
+ }
3600
+ createTransaction(returnTo) {
3601
+ return {
3602
+ codeVerifier: randomBase64Url(32),
3603
+ createdAt: Date.now(),
3604
+ nonce: randomBase64Url(32),
3605
+ provider: this.providerName,
3606
+ returnTo,
3607
+ state: randomBase64Url(32)
3608
+ };
3609
+ }
3610
+ async getMetadata() {
3611
+ const cacheExpired = this.metadataFetchedAt > 0 && Date.now() - this.metadataFetchedAt > this.metadataCacheTtlMs;
3612
+ if (!this.metadataPromise || cacheExpired) this.metadataPromise = this.fetchMetadata().then((metadata) => {
3613
+ this.metadataFetchedAt = Date.now();
3614
+ this.remoteJwks = void 0;
3615
+ return metadata;
3616
+ }).catch((error) => {
3617
+ this.metadataPromise = void 0;
3618
+ throw error;
3619
+ });
3620
+ return this.metadataPromise;
3621
+ }
3622
+ async fetchMetadata() {
3623
+ const discoveryUrl = resolveDiscoveryUrl(this.provider);
3624
+ const response = await this.fetchImpl(discoveryUrl, { headers: { accept: "application/json" } });
3625
+ if (!response.ok) throw new OidcLoginError(`OIDC discovery failed for "${this.providerName}" (${response.status}).`);
3626
+ const metadata = await readJsonResponse(response, "OIDC discovery");
3627
+ const issuer = requireString(metadata.issuer ?? this.provider.issuer, "issuer");
3628
+ if (normalizeIssuer(issuer) !== this.provider.issuer) throw new OidcLoginError(`OIDC discovery issuer mismatch for "${this.providerName}".`);
3629
+ return {
3630
+ ...metadata,
3631
+ authorization_endpoint: requireString(metadata.authorization_endpoint, "authorization_endpoint"),
3632
+ issuer,
3633
+ jwks_uri: requireString(metadata.jwks_uri, "jwks_uri"),
3634
+ token_endpoint: requireString(metadata.token_endpoint, "token_endpoint")
3635
+ };
3636
+ }
3637
+ async createAuthorizationUrl(options = {}) {
3638
+ const metadata = await this.getMetadata();
3639
+ const transaction = options.transaction ?? this.createTransaction();
3640
+ const codeChallenge = await pkceChallenge(transaction.codeVerifier);
3641
+ const url = new URL(metadata.authorization_endpoint);
3642
+ url.searchParams.set("response_type", "code");
3643
+ url.searchParams.set("client_id", this.provider.clientId);
3644
+ url.searchParams.set("redirect_uri", this.provider.redirectUri);
3645
+ url.searchParams.set("scope", getScopes(this.provider).join(" "));
3646
+ url.searchParams.set("state", transaction.state);
3647
+ url.searchParams.set("nonce", transaction.nonce);
3648
+ url.searchParams.set("code_challenge", codeChallenge);
3649
+ url.searchParams.set("code_challenge_method", "S256");
3650
+ for (const [key, value] of Object.entries({
3651
+ ...this.provider.authorizationParams,
3652
+ ...options.authorizationParams
3653
+ })) if (value !== void 0) url.searchParams.set(key, String(value));
3654
+ return {
3655
+ transaction,
3656
+ url
3657
+ };
3658
+ }
3659
+ async exchangeCallback(callbackUrl, transaction) {
3660
+ const code = this.validateCallback(callbackUrl, transaction);
3661
+ const tokens = await this.exchangeCode(code, transaction);
3662
+ const idTokenClaims = await this.verifyIdToken(tokens.idToken, transaction.nonce);
3663
+ return {
3664
+ claims: await this.enrichClaimsFromUserInfo(idTokenClaims, tokens),
3665
+ tokens
3666
+ };
3667
+ }
3668
+ async completeLogin(callbackUrl, transaction) {
3669
+ const { claims, tokens } = await this.exchangeCallback(callbackUrl, transaction);
3670
+ return {
3671
+ ...await (await UserCollection.create(this.classOptions)).getOrCreateFromOidc(claims, this.providerName),
3672
+ claims,
3673
+ tokens
3674
+ };
3675
+ }
3676
+ validateCallback(callbackUrl, transaction) {
3677
+ if (transaction.provider !== this.providerName) throw new OidcLoginError("OIDC login transaction provider mismatch.");
3678
+ const url = callbackUrl instanceof URL ? callbackUrl : new URL(callbackUrl);
3679
+ const error = url.searchParams.get("error");
3680
+ if (error) {
3681
+ const description = url.searchParams.get("error_description");
3682
+ throw new OidcLoginError(description ? `OIDC provider returned "${error}": ${description}` : `OIDC provider returned "${error}".`);
3683
+ }
3684
+ const state = url.searchParams.get("state");
3685
+ if (!state || state !== transaction.state) throw new OidcLoginError("OIDC state validation failed.");
3686
+ const responseIssuer = url.searchParams.get("iss");
3687
+ if (responseIssuer && normalizeIssuer(responseIssuer) !== this.provider.issuer) throw new OidcLoginError("OIDC response issuer validation failed.");
3688
+ const code = url.searchParams.get("code");
3689
+ if (!code) throw new OidcLoginError("OIDC callback missing authorization code.");
3690
+ return code;
3691
+ }
3692
+ async exchangeCode(code, transaction) {
3693
+ const metadata = await this.getMetadata();
3694
+ const body = new URLSearchParams({
3695
+ code,
3696
+ code_verifier: transaction.codeVerifier,
3697
+ grant_type: "authorization_code",
3698
+ redirect_uri: this.provider.redirectUri
3699
+ });
3700
+ const headers = new Headers({
3701
+ accept: "application/json",
3702
+ "content-type": "application/x-www-form-urlencoded"
3703
+ });
3704
+ const authMethod = getTokenEndpointAuthMethod(this.provider);
3705
+ if (authMethod === "client_secret_basic") {
3706
+ if (!this.provider.clientSecret) throw new OidcLoginError(`OIDC provider "${this.providerName}" is missing clientSecret.`);
3707
+ headers.set("authorization", `Basic ${encodeBasicAuth(this.provider.clientId, this.provider.clientSecret)}`);
3708
+ } else {
3709
+ body.set("client_id", this.provider.clientId);
3710
+ if (authMethod === "client_secret_post") {
3711
+ if (!this.provider.clientSecret) throw new OidcLoginError(`OIDC provider "${this.providerName}" is missing clientSecret.`);
3712
+ body.set("client_secret", this.provider.clientSecret);
3713
+ }
3714
+ }
3715
+ const response = await this.fetchImpl(metadata.token_endpoint, {
3716
+ body,
3717
+ headers,
3718
+ method: "POST"
3719
+ });
3720
+ const json = await readJsonResponse(response, "OIDC token endpoint");
3721
+ if (!response.ok) {
3722
+ const providerError = asString(json.error);
3723
+ const description = asString(json.error_description);
3724
+ throw new OidcLoginError(description ? `OIDC token exchange failed (${providerError ?? response.status}): ${description}` : `OIDC token exchange failed (${providerError ?? response.status}).`);
3725
+ }
3726
+ return parseTokenResponse(json);
3727
+ }
3728
+ async enrichClaimsFromUserInfo(claims, tokens) {
3729
+ if (claims.email || !tokens.accessToken) return claims;
3730
+ const metadata = await this.getMetadata();
3731
+ if (!metadata.userinfo_endpoint) return claims;
3732
+ const tokenType = tokens.tokenType ?? "Bearer";
3733
+ const response = await this.fetchImpl(metadata.userinfo_endpoint, { headers: {
3734
+ accept: "application/json",
3735
+ authorization: `${tokenType} ${tokens.accessToken}`
3736
+ } });
3737
+ if (!response.ok) throw new OidcLoginError(`OIDC userinfo request failed (${response.status}).`);
3738
+ return mergeClaims(claims, normalizeClaims(await readJsonResponse(response, "OIDC userinfo endpoint"), metadata.issuer, "OIDC userinfo"));
3739
+ }
3740
+ async verifyIdToken(idToken, expectedNonce) {
3741
+ const metadata = await this.getMetadata();
3742
+ this.remoteJwks ??= createRemoteJWKSet(new URL(metadata.jwks_uri), { [customFetch]: this.fetchImpl });
3743
+ const { payload } = await jwtVerify(idToken, this.remoteJwks, {
3744
+ audience: this.provider.clientId,
3745
+ clockTolerance: this.clockTolerance,
3746
+ issuer: metadata.issuer
3747
+ });
3748
+ validateAuthorizedParty(payload, this.provider.clientId);
3749
+ if (payload.nonce !== expectedNonce) throw new OidcLoginError("OIDC nonce validation failed.");
3750
+ return normalizeClaims(payload, metadata.issuer, "OIDC ID token");
3751
+ }
3752
+ };
3753
+ //#endregion
3754
+ //#region src/services/PermissionResolver.ts
3755
+ var PermissionResolver = class PermissionResolver {
3756
+ options;
3757
+ membershipCollection;
3758
+ rolePermissionCollection;
3759
+ membershipOverrideCollection;
3760
+ groupMemberCollection;
3761
+ groupRoleCollection;
3762
+ permissionCollection;
3763
+ tenantCollection;
3764
+ tenantPermissionOverrideCollection;
3765
+ constructor(options) {
3766
+ this.options = options;
3767
+ }
3768
+ /**
3769
+ * Initialize collections
3770
+ *
3771
+ * Each collection is created via the inherited static `SmrtCollection.create()`
3772
+ * factory, which is generically typed to return the concrete subclass instance.
3773
+ */
3774
+ async initialize() {
3775
+ this.membershipCollection = await MembershipCollection.create(this.options);
3776
+ this.rolePermissionCollection = await RolePermissionCollection.create(this.options);
3777
+ this.membershipOverrideCollection = await MembershipOverrideCollection.create(this.options);
3778
+ this.groupMemberCollection = await GroupMemberCollection.create(this.options);
3779
+ this.groupRoleCollection = await GroupRoleCollection.create(this.options);
3780
+ this.permissionCollection = await PermissionCollection.create(this.options);
3781
+ this.tenantCollection = await TenantCollection.create(this.options);
3782
+ this.tenantPermissionOverrideCollection = await TenantPermissionOverrideCollection.create(this.options);
3783
+ }
3784
+ /**
3785
+ * Resolve effective permissions for a tenant, considering hierarchy inheritance.
3786
+ *
3787
+ * Algorithm:
3788
+ * 1. Get the tenant and its ancestors (from root to immediate parent)
3789
+ * 2. Batch fetch all permission overrides for the entire chain (single query)
3790
+ * 3. Walk down the chain, building up permissions:
3791
+ * - Start with root tenant's permissions
3792
+ * - For each child: if parent.cascadePermissions && child.inheritPermissions:
3793
+ * - Merge parent's permissions
3794
+ * - Apply child's overrides (GRANT adds, DENY removes)
3795
+ * 4. Return the final effective permission set
3796
+ */
3797
+ async resolveTenantPermissions(tenantId) {
3798
+ const result = {
3799
+ permissions: /* @__PURE__ */ new Set(),
3800
+ contributingTenantIds: [],
3801
+ inheritanceActive: false,
3802
+ deniedPermissions: /* @__PURE__ */ new Set()
3803
+ };
3804
+ const tenant = await this.tenantCollection.get({ id: tenantId });
3805
+ if (!tenant) return result;
3806
+ const chain = [...await this.tenantCollection.getAncestorsFromRoot(tenantId), tenant];
3807
+ const chainTenantIds = chain.map((t) => t.id);
3808
+ const allOverridesMap = await this.tenantPermissionOverrideCollection.getOverridesByEffectBatch(chainTenantIds);
3809
+ const allPermissionIds = /* @__PURE__ */ new Set();
3810
+ const deniedPermissionIds = /* @__PURE__ */ new Set();
3811
+ for (const overrides of allOverridesMap.values()) {
3812
+ for (const id of overrides.grantedPermissionIds) allPermissionIds.add(id);
3813
+ for (const id of overrides.deniedPermissionIds) {
3814
+ allPermissionIds.add(id);
3815
+ deniedPermissionIds.add(id);
3816
+ }
3817
+ }
3818
+ let inheritedPermissions = /* @__PURE__ */ new Set();
3819
+ for (let i = 0; i < chain.length; i++) {
3820
+ const current = chain[i];
3821
+ const isFirst = i === 0;
3822
+ const previous = isFirst ? null : chain[i - 1];
3823
+ const shouldInherit = !isFirst && previous?.cascadePermissions && current.inheritPermissions;
3824
+ if (shouldInherit) result.inheritanceActive = true;
3825
+ const overrides = allOverridesMap.get(current.id) ?? {
3826
+ grantedPermissionIds: [],
3827
+ deniedPermissionIds: [],
3828
+ inheritedPermissionIds: []
3829
+ };
3830
+ const currentPermissions = /* @__PURE__ */ new Set();
3831
+ let contributed = false;
3832
+ if (shouldInherit && inheritedPermissions.size > 0) {
3833
+ for (const permId of inheritedPermissions) currentPermissions.add(permId);
3834
+ contributed = true;
3835
+ }
3836
+ for (const permId of overrides.grantedPermissionIds) {
3837
+ currentPermissions.add(permId);
3838
+ contributed = true;
3839
+ }
3840
+ for (const permId of overrides.deniedPermissionIds) if (currentPermissions.has(permId)) {
3841
+ currentPermissions.delete(permId);
3842
+ contributed = true;
3843
+ }
3844
+ if (contributed && !result.contributingTenantIds.includes(current.id)) result.contributingTenantIds.push(current.id);
3845
+ inheritedPermissions = currentPermissions;
3846
+ }
3847
+ if (allPermissionIds.size > 0) {
3848
+ const permissionsMap = await this.permissionCollection.findByIds(Array.from(allPermissionIds));
3849
+ for (const permId of inheritedPermissions) {
3850
+ const perm = permissionsMap.get(permId);
3851
+ if (perm?.slug) result.permissions.add(perm.slug);
3852
+ }
3853
+ for (const permId of deniedPermissionIds) {
3854
+ if (inheritedPermissions.has(permId)) continue;
3855
+ const perm = permissionsMap.get(permId);
3856
+ if (perm?.slug) result.deniedPermissions.add(perm.slug);
3857
+ }
3858
+ }
3859
+ return result;
3860
+ }
3861
+ /**
3862
+ * Get the inheritance chain for a tenant (for debugging/display purposes)
3863
+ */
3864
+ async getTenantInheritanceChain(tenantId) {
3865
+ const tenant = await this.tenantCollection.get({ id: tenantId });
3866
+ if (!tenant) return [];
3867
+ const ancestors = await this.tenantCollection.getAncestorsFromRoot(tenantId);
3868
+ const chain = [];
3869
+ for (let i = 0; i < ancestors.length; i++) {
3870
+ const current = ancestors[i];
3871
+ const next = i + 1 < ancestors.length ? ancestors[i + 1] : tenant;
3872
+ chain.push({
3873
+ tenant: current,
3874
+ inherits: false,
3875
+ cascades: current.cascadePermissions && next.inheritPermissions
3876
+ });
3877
+ }
3878
+ chain.push({
3879
+ tenant,
3880
+ inherits: tenant.inheritPermissions && ancestors.length > 0,
3881
+ cascades: tenant.cascadePermissions
3882
+ });
3883
+ return chain;
3884
+ }
3885
+ /**
3886
+ * Resolve all effective permissions for a user in a tenant.
3887
+ *
3888
+ * Precedence (broad -> specific, most-specific wins):
3889
+ * tenant-inherited (cascade)
3890
+ * -> role
3891
+ * -> group roles
3892
+ * -> tenant-DENY (removes; overrides role/group grants, tenant-wide)
3893
+ * -> membership GRANT (re-adds; most specific, can win over a tenant-DENY)
3894
+ * -> membership DENY (absolute; always wins)
3895
+ *
3896
+ * Algorithm:
3897
+ * 1. Get membership and collect all permission IDs from all sources
3898
+ * 2. Batch fetch all permissions in a single query
3899
+ * 3. Apply permissions from role, then groups
3900
+ * 4. Subtract tenant-level DENY'd slugs (hard tenant-wide block)
3901
+ * 5. Apply membership GRANT overrides (can re-add a tenant-DENY'd slug)
3902
+ * 6. Subtract membership DENY overrides (absolute precedence)
3903
+ */
3904
+ async resolvePermissions(userId, tenantId, options = {}) {
3905
+ const result = {
3906
+ permissions: /* @__PURE__ */ new Set(),
3907
+ membershipId: null,
3908
+ roleId: null,
3909
+ groupIds: [],
3910
+ deniedPermissionIds: []
3911
+ };
3912
+ const membership = options.membership === void 0 ? await this.membershipCollection.findByUserAndTenant(userId, tenantId) : options.membership;
3913
+ if (!membership || !membership.isActive()) return result;
3914
+ if (membership.userId !== userId || membership.tenantId !== tenantId) return result;
3915
+ result.membershipId = membership.id ?? null;
3916
+ result.roleId = membership.roleId ?? null;
3917
+ const tenantPermissions = await this.resolveTenantPermissions(tenantId);
3918
+ for (const slug of tenantPermissions.permissions) result.permissions.add(slug);
3919
+ if (!membership.roleId) return result;
3920
+ const allPermissionIds = /* @__PURE__ */ new Set();
3921
+ const rolePermissionIds = [];
3922
+ const groupRolePermissionIds = /* @__PURE__ */ new Map();
3923
+ const baseRolePermIds = await this.rolePermissionCollection.getPermissionIds(membership.roleId);
3924
+ for (const id of baseRolePermIds) {
3925
+ allPermissionIds.add(id);
3926
+ rolePermissionIds.push(id);
3927
+ }
3928
+ const groupIds = await this.groupMemberCollection.getGroupIdsForTenant(userId, tenantId);
3929
+ result.groupIds = groupIds;
3930
+ for (const groupId of groupIds) {
3931
+ const groupRoleIds = await this.groupRoleCollection.getRoleIds(groupId);
3932
+ for (const roleId of groupRoleIds) {
3933
+ const permIds = await this.rolePermissionCollection.getPermissionIds(roleId);
3934
+ for (const id of permIds) allPermissionIds.add(id);
3935
+ groupRolePermissionIds.set(roleId, permIds);
3936
+ }
3937
+ }
3938
+ if (!membership.id) return result;
3939
+ const membershipId = membership.id;
3940
+ const grantedPermissionIds = await this.membershipOverrideCollection.getGrantedPermissionIds(membershipId);
3941
+ const deniedPermissionIds = await this.membershipOverrideCollection.getDeniedPermissionIds(membershipId);
3942
+ result.deniedPermissionIds = deniedPermissionIds;
3943
+ for (const id of grantedPermissionIds) allPermissionIds.add(id);
3944
+ for (const id of deniedPermissionIds) allPermissionIds.add(id);
3945
+ const permissionsMap = await this.permissionCollection.findByIds(Array.from(allPermissionIds));
3946
+ const permissionIdToSlug = /* @__PURE__ */ new Map();
3947
+ for (const [id, perm] of permissionsMap) if (perm.slug) permissionIdToSlug.set(id, perm.slug);
3948
+ for (const permId of rolePermissionIds) {
3949
+ const slug = permissionIdToSlug.get(permId);
3950
+ if (slug) result.permissions.add(slug);
3951
+ }
3952
+ for (const permIds of groupRolePermissionIds.values()) for (const permId of permIds) {
3953
+ const slug = permissionIdToSlug.get(permId);
3954
+ if (slug) result.permissions.add(slug);
3955
+ }
3956
+ for (const slug of tenantPermissions.deniedPermissions) result.permissions.delete(slug);
3957
+ for (const permId of grantedPermissionIds) {
3958
+ const slug = permissionIdToSlug.get(permId);
3959
+ if (slug) result.permissions.add(slug);
3960
+ }
3961
+ for (const permId of deniedPermissionIds) {
3962
+ const slug = permissionIdToSlug.get(permId);
3963
+ if (slug) result.permissions.delete(slug);
3964
+ }
3965
+ return result;
3966
+ }
3967
+ /**
3968
+ * Check if a user has a specific permission in a tenant
3969
+ */
3970
+ async hasPermission(userId, tenantId, permissionSlug, options = {}) {
3971
+ return (await this.resolvePermissions(userId, tenantId, options)).permissions.has(permissionSlug);
3972
+ }
3973
+ /**
3974
+ * Check if a user has all of the specified permissions
3975
+ */
3976
+ async hasAllPermissions(userId, tenantId, permissionSlugs, options = {}) {
3977
+ const result = await this.resolvePermissions(userId, tenantId, options);
3978
+ return permissionSlugs.every((slug) => result.permissions.has(slug));
3979
+ }
3980
+ /**
3981
+ * Check if a user has any of the specified permissions
3982
+ */
3983
+ async hasAnyPermission(userId, tenantId, permissionSlugs, options = {}) {
3984
+ const result = await this.resolvePermissions(userId, tenantId, options);
3985
+ return permissionSlugs.some((slug) => result.permissions.has(slug));
3986
+ }
3987
+ /**
3988
+ * Static factory method
3989
+ */
3990
+ static async create(options) {
3991
+ const resolver = new PermissionResolver(options);
3992
+ await resolver.initialize();
3993
+ return resolver;
3994
+ }
3995
+ };
3996
+ //#endregion
3997
+ //#region src/services/SessionService.ts
3998
+ var SessionService = class SessionService {
3999
+ options;
4000
+ sessionCollection;
4001
+ userCollection;
4002
+ membershipCollection;
4003
+ permissionResolver;
4004
+ defaultTTL;
4005
+ autoExtend;
4006
+ constructor(options) {
4007
+ this.options = options;
4008
+ this.defaultTTL = options.defaultTTL ?? 604800;
4009
+ this.autoExtend = options.autoExtend ?? false;
4010
+ }
4011
+ /**
4012
+ * Initialize collections
4013
+ */
4014
+ async initialize() {
4015
+ this.sessionCollection = await SessionCollection.create(this.options);
4016
+ this.userCollection = await UserCollection.create(this.options);
4017
+ this.membershipCollection = await MembershipCollection.create(this.options);
4018
+ this.permissionResolver = await PermissionResolver.create(this.options);
4019
+ }
4020
+ /**
4021
+ * Create a new session for a user
4022
+ *
4023
+ * @param userId - The user ID
4024
+ * @param tenantId - Optional tenant context
4025
+ * @param options - Additional session options
4026
+ * @returns The session ID
4027
+ */
4028
+ async createSession(userId, tenantId, options) {
4029
+ return (await this.sessionCollection.createSession({
4030
+ userId,
4031
+ tenantId,
4032
+ ttl: options?.ttl ?? this.defaultTTL,
4033
+ userAgent: options?.userAgent,
4034
+ ipAddress: options?.ipAddress,
4035
+ data: options?.data
4036
+ })).id;
4037
+ }
4038
+ /**
4039
+ * Load full session context (user + permissions)
4040
+ *
4041
+ * Returns null if session is invalid or user doesn't exist
4042
+ */
4043
+ async loadSessionContext(sessionId) {
4044
+ const session = await this.sessionCollection.findValidSession(sessionId);
4045
+ if (!session) return null;
4046
+ const user = await this.userCollection.get(session.userId);
4047
+ if (!user || !user.isActive()) return null;
4048
+ let permissions = [];
4049
+ let membership = null;
4050
+ if (session.tenantId) {
4051
+ const resolvedMembership = await this.membershipCollection.findByUserAndTenant(session.userId, session.tenantId);
4052
+ membership = resolvedMembership?.isActive() ? resolvedMembership : null;
4053
+ const result = await this.permissionResolver.resolvePermissions(session.userId, session.tenantId, { membership });
4054
+ permissions = Array.from(result.permissions);
4055
+ }
4056
+ if (this.autoExtend) session.extend(this.defaultTTL);
4057
+ else session.touch();
4058
+ await session.save();
4059
+ return {
4060
+ user,
4061
+ membership,
4062
+ permissions,
4063
+ tenantId: session.tenantId,
4064
+ sessionId: session.id
4065
+ };
4066
+ }
4067
+ /**
4068
+ * Get the initialized database connection backing this session service.
4069
+ */
4070
+ getDatabase() {
4071
+ return this.sessionCollection.db;
4072
+ }
4073
+ /**
4074
+ * Refresh session (extend expiry, update lastAccessed)
4075
+ */
4076
+ async refreshSession(sessionId) {
4077
+ return this.sessionCollection.touch(sessionId, true, this.defaultTTL);
4078
+ }
4079
+ /**
4080
+ * Destroy a session (revoke it)
4081
+ */
4082
+ async destroySession(sessionId) {
4083
+ return this.sessionCollection.revokeSession(sessionId);
4084
+ }
4085
+ /**
4086
+ * Destroy all sessions for a user (logout from all devices)
4087
+ */
4088
+ async destroyAllUserSessions(userId) {
4089
+ return this.sessionCollection.revokeUserSessions(userId);
4090
+ }
4091
+ /**
4092
+ * Switch tenant context for a session.
4093
+ *
4094
+ * A session's `tenantId` is the tenant-isolation key for every `@TenantScoped`
4095
+ * query, so it must never be set to a tenant the session's user is not an
4096
+ * active member of — otherwise a caller could read/write another tenant's data
4097
+ * by feeding an arbitrary id here (e.g. straight from untrusted form data).
4098
+ *
4099
+ * Fail-closed (#1400): the user's ACTIVE membership in the target tenant is
4100
+ * verified BEFORE any write. A non-member switch returns
4101
+ * `{ switched: false, ... }` and mutates nothing.
4102
+ *
4103
+ * Session-id ROTATION (#1354 follow-up): a successful switch into a non-null
4104
+ * tenant mints a BRAND-NEW session (fresh secure id, fresh TTL) for the same
4105
+ * user with the new tenant, then REVOKES the old session — so any captured
4106
+ * pre-switch session id immediately stops validating, shrinking the blast
4107
+ * radius of a leaked id across a privilege/tenant boundary. The device context
4108
+ * (user agent, IP, custom data) carries over to the new session. Callers MUST
4109
+ * persist the returned `sessionId` (e.g. re-set the cookie).
4110
+ *
4111
+ * Passing `null` clears the tenant context, is always allowed, and stays
4112
+ * in-place (no rotation — there is no privilege boundary being crossed).
4113
+ *
4114
+ * @returns A {@link SwitchTenantResult}; check `switched` for success.
4115
+ */
4116
+ async switchTenant(sessionId, tenantId) {
4117
+ const failClosed = {
4118
+ switched: false,
4119
+ sessionId: null,
4120
+ session: null,
4121
+ rotated: false
4122
+ };
4123
+ const session = await this.sessionCollection.findValidSession(sessionId);
4124
+ if (!session) return failClosed;
4125
+ if (tenantId === null) {
4126
+ if (!await this.sessionCollection.setSessionTenant(sessionId, null)) return failClosed;
4127
+ return {
4128
+ switched: true,
4129
+ sessionId,
4130
+ session: await this.sessionCollection.findValidSession(sessionId),
4131
+ rotated: false
4132
+ };
4133
+ }
4134
+ const membership = await this.membershipCollection.findByUserAndTenant(session.userId, tenantId);
4135
+ if (!membership || !membership.isActive()) return failClosed;
4136
+ await this.sessionCollection.revokeSession(sessionId);
4137
+ const rotated = await this.sessionCollection.createSession({
4138
+ userId: session.userId,
4139
+ tenantId,
4140
+ ttl: this.defaultTTL,
4141
+ userAgent: session.userAgent,
4142
+ ipAddress: session.ipAddress,
4143
+ data: session.data
4144
+ });
4145
+ return {
4146
+ switched: true,
4147
+ sessionId: rotated.id,
4148
+ session: rotated,
4149
+ rotated: true
4150
+ };
4151
+ }
4152
+ /**
4153
+ * Get all active sessions for a user (for "manage sessions" UI)
4154
+ */
4155
+ async getUserSessions(userId) {
4156
+ return this.sessionCollection.findByUser(userId);
4157
+ }
4158
+ /**
4159
+ * Clean up expired sessions (run periodically)
4160
+ */
4161
+ async cleanupExpiredSessions() {
4162
+ return this.sessionCollection.deleteExpired();
4163
+ }
4164
+ /**
4165
+ * Check if a permission is granted for the session
4166
+ */
4167
+ async hasPermission(sessionId, permission) {
4168
+ const context = await this.loadSessionContext(sessionId);
4169
+ if (!context) return false;
4170
+ return context.permissions.includes(permission);
4171
+ }
4172
+ /**
4173
+ * Get session data
4174
+ */
4175
+ async getSessionData(sessionId, key) {
4176
+ return this.sessionCollection.getSessionData(sessionId, key);
4177
+ }
4178
+ /**
4179
+ * Set session data
4180
+ */
4181
+ async setSessionData(sessionId, key, value) {
4182
+ return this.sessionCollection.setSessionData(sessionId, key, value);
4183
+ }
4184
+ /**
4185
+ * Static factory method
4186
+ */
4187
+ static async create(options) {
4188
+ const service = new SessionService(options);
4189
+ await service.initialize();
4190
+ return service;
4191
+ }
4192
+ };
4193
+ //#endregion
4194
+ //#region src/services/SessionPermissionContext.ts
4195
+ var requestPermissionContextStorage = new AsyncLocalStorage();
4196
+ function getCurrentSessionPermissionContext() {
4197
+ return requestPermissionContextStorage.getStore();
4198
+ }
4199
+ function getRequestScopedDatabase() {
4200
+ return getCurrentSessionPermissionContext()?.database;
4201
+ }
4202
+ globalThis.__smrtGetRequestPermissionContext ??= getCurrentSessionPermissionContext;
4203
+ globalThis.__smrtGetRequestScopedDatabase ??= getRequestScopedDatabase;
4204
+ function isProbablyPostgres(configDb, database) {
4205
+ if (configDb && typeof configDb === "object" && !("query" in configDb) && "type" in configDb && configDb.type === "postgres") return true;
4206
+ if (typeof database.url === "string" && database.url.startsWith("postgres")) return true;
4207
+ return (database.constructor?.name || "").toLowerCase().includes("postgres");
4208
+ }
4209
+ async function setPostgresSessionVariables(database, context) {
4210
+ await database.query("SELECT set_config('smrt.tenant_id', $1, true)", context.tenantId ?? "");
4211
+ await database.query("SELECT set_config('smrt.user_id', $1, true)", context.userId ?? "");
4212
+ await database.query("SELECT set_config('smrt.session_id', $1, true)", context.sessionId ?? "");
4213
+ await database.query("SELECT set_config('smrt.permissions', $1, true)", JSON.stringify(context.permissions));
4214
+ await database.query("SELECT set_config('smrt.super_admin_bypass', $1, true)", context.superAdminBypass ? "true" : "false");
4215
+ await database.query("SELECT set_config('smrt.system_context', $1, true)", context.systemContext ? "true" : "false");
4216
+ }
4217
+ function isModuleNotFoundError(error) {
4218
+ return error instanceof Error && (error.code === "ERR_MODULE_NOT_FOUND" || /Cannot find package '@happyvertical\/smrt-tenancy'/.test(error.message));
4219
+ }
4220
+ async function runWithOptionalTenantContext(options, context, fn) {
4221
+ if (options.systemContext) try {
4222
+ return await (await import("@happyvertical/smrt-tenancy")).withSystemContext(fn);
4223
+ } catch (error) {
4224
+ if (isModuleNotFoundError(error)) return await fn();
4225
+ throw error;
4226
+ }
4227
+ if (!options.enterTenantContext || !context.tenantId) return await fn();
4228
+ try {
4229
+ return await (await import("@happyvertical/smrt-tenancy")).withTenant({
4230
+ database: context.database,
4231
+ permissions: context.permissionSet,
4232
+ superAdminBypass: context.superAdminBypass,
4233
+ tenantId: context.tenantId,
4234
+ user: context.user ?? void 0,
4235
+ userId: context.userId ?? void 0
4236
+ }, fn);
4237
+ } catch (error) {
4238
+ if (isModuleNotFoundError(error)) return await fn();
4239
+ throw error;
4240
+ }
4241
+ }
4242
+ async function withSessionPermissionContext(options, fn) {
4243
+ const { enterTenantContext: _enterTenantContext, postgresRls: _postgresRls, sessionId: _sessionId, sessionService: _sessionService, superAdminBypass: _superAdminBypass, systemContext: _systemContext, ...sessionServiceOptions } = options;
4244
+ const sessionService = options.sessionService ?? await SessionService.create({ ...sessionServiceOptions });
4245
+ const configuredDb = sessionServiceOptions.db ?? sessionServiceOptions.persistence;
4246
+ const session = options.sessionId === void 0 || options.sessionId === null ? null : await sessionService.loadSessionContext(options.sessionId);
4247
+ const permissionSet = new Set(session?.permissions ?? []);
4248
+ const baseDatabase = sessionService.getDatabase();
4249
+ const config = getPackageConfig("users", {});
4250
+ const usePostgresRls = (options.postgresRls ?? config.permissions?.postgres?.enabled ?? false) && isProbablyPostgres(configuredDb, baseDatabase);
4251
+ let transaction;
4252
+ let rolledBack = false;
4253
+ if (usePostgresRls) {
4254
+ if (!baseDatabase.beginTransaction) throw new Error("Postgres RLS requires a database adapter that supports beginTransaction().");
4255
+ transaction = await baseDatabase.beginTransaction();
4256
+ }
4257
+ const runtimeContext = {
4258
+ database: transaction ?? baseDatabase,
4259
+ permissions: session?.permissions ?? [],
4260
+ permissionSet,
4261
+ membership: session?.membership ?? null,
4262
+ postgresRls: usePostgresRls,
4263
+ session,
4264
+ sessionId: session?.sessionId ?? null,
4265
+ superAdminBypass: options.superAdminBypass ?? false,
4266
+ systemContext: options.systemContext ?? false,
4267
+ tenantId: session?.tenantId ?? null,
4268
+ user: session?.user ?? null,
4269
+ userId: session?.user.id ?? null
4270
+ };
4271
+ try {
4272
+ if (transaction) await setPostgresSessionVariables(transaction, runtimeContext);
4273
+ return await requestPermissionContextStorage.run(runtimeContext, async () => runWithOptionalTenantContext(options, runtimeContext, () => fn(runtimeContext)));
4274
+ } catch (error) {
4275
+ if (transaction) {
4276
+ if (transaction.isActive ? transaction.isActive() : true) {
4277
+ rolledBack = true;
4278
+ await transaction.rollback();
4279
+ }
4280
+ }
4281
+ throw error;
4282
+ } finally {
4283
+ if (transaction && !rolledBack) {
4284
+ if (transaction.isActive ? transaction.isActive() : true) await transaction.commit();
4285
+ }
4286
+ }
4287
+ }
4288
+ //#endregion
4289
+ //#region src/services/TerminalAuthService.ts
4290
+ var DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS = 2;
4291
+ var DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS = 600;
4292
+ var DEFAULT_CLI_SESSION_TTL_SECONDS = 720 * 60 * 60;
4293
+ function base64url(bytes) {
4294
+ return bytes.toString("base64url");
4295
+ }
4296
+ function hashDeviceCode(deviceCode) {
4297
+ return createHash("sha256").update(deviceCode).digest("hex");
4298
+ }
4299
+ function isExpired(request) {
4300
+ return new Date(request.expiresAt).getTime() <= Date.now();
4301
+ }
4302
+ var TerminalAuthService = class TerminalAuthService {
4303
+ options;
4304
+ userCodePrefix;
4305
+ requestTtlSeconds;
4306
+ sessionTtlSeconds;
4307
+ pollIntervalSeconds;
4308
+ verificationPath;
4309
+ maxApproveAttempts;
4310
+ approveAttemptWindowMs;
4311
+ failedApprovesByUser = /* @__PURE__ */ new Map();
4312
+ requestCollection;
4313
+ sessionService;
4314
+ constructor(options) {
4315
+ this.options = options;
4316
+ this.userCodePrefix = (options.userCodePrefix ?? "").trim().toUpperCase();
4317
+ this.requestTtlSeconds = options.requestTtlSeconds ?? 600;
4318
+ this.sessionTtlSeconds = options.sessionTtlSeconds ?? 2592e3;
4319
+ this.pollIntervalSeconds = options.pollIntervalSeconds ?? 2;
4320
+ this.maxApproveAttempts = options.maxApproveAttempts ?? 5;
4321
+ this.approveAttemptWindowMs = (options.approveAttemptWindowSeconds ?? 300) * 1e3;
4322
+ const verificationPath = options.verificationPath ?? "/terminal-login";
4323
+ this.verificationPath = verificationPath.startsWith("/") ? verificationPath : `/${verificationPath}`;
4324
+ }
4325
+ async initialize() {
4326
+ this.requestCollection = await UsersCliAuthRequestCollection.create(this.options);
4327
+ this.sessionService = await SessionService.create({
4328
+ ...this.options,
4329
+ autoExtend: this.options.sessionAutoExtend ?? true,
4330
+ cookieName: this.options.sessionCookieName,
4331
+ defaultTTL: this.sessionTtlSeconds
4332
+ });
4333
+ }
4334
+ static async create(options) {
4335
+ const service = new TerminalAuthService(options);
4336
+ await service.initialize();
4337
+ return service;
4338
+ }
4339
+ makeUserCode() {
4340
+ const raw = randomBytes(6).toString("hex").toUpperCase().slice(0, 8);
4341
+ return this.userCodePrefix ? `${this.userCodePrefix}-${raw}` : raw;
4342
+ }
4343
+ /**
4344
+ * Generate a user code that is not currently in use by another *active*
4345
+ * (pending or approved-but-unclaimed) request. Collisions are vanishingly
4346
+ * rare (1 in 2^32 per attempt) but happen at scale; retrying keeps the
4347
+ * affected CLI from being locked out.
4348
+ */
4349
+ async makeUniqueUserCode(maxAttempts = 5) {
4350
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
4351
+ const candidate = this.makeUserCode();
4352
+ const existing = await this.requestCollection.findByUserCode(candidate);
4353
+ if (!existing || existing.status === "expired") return candidate;
4354
+ }
4355
+ throw new TerminalAuthError("Unable to generate a unique terminal user code; try again.");
4356
+ }
4357
+ /**
4358
+ * Start a new request. Returns the device code the CLI keeps secret, the
4359
+ * user code the human types into the browser, and the verification URL to
4360
+ * open. The device code is stored only as a hash.
4361
+ */
4362
+ async createRequest(origin) {
4363
+ const trimmedOrigin = origin.replace(/\/+$/u, "");
4364
+ const deviceCode = base64url(randomBytes(32));
4365
+ const userCode = await this.makeUniqueUserCode();
4366
+ const expiresAt = new Date(Date.now() + this.requestTtlSeconds * 1e3);
4367
+ await (await this.requestCollection.create({
4368
+ deviceCodeHash: hashDeviceCode(deviceCode),
4369
+ expiresAt,
4370
+ status: "pending",
4371
+ userCode
4372
+ })).save();
4373
+ return {
4374
+ deviceCode,
4375
+ expiresAt: expiresAt.toISOString(),
4376
+ interval: this.pollIntervalSeconds,
4377
+ userCode,
4378
+ verificationUrl: `${trimmedOrigin}${this.verificationPath}?code=${encodeURIComponent(userCode)}`
4379
+ };
4380
+ }
4381
+ /**
4382
+ * Look up a request by user code. Performs lazy expiry: pending requests
4383
+ * past their TTL are flipped to `expired` and persisted.
4384
+ */
4385
+ async getRequestForUserCode(userCode) {
4386
+ const request = await this.requestCollection.findByUserCode(userCode);
4387
+ if (!request) return null;
4388
+ if (request.status === "pending" && isExpired(request)) {
4389
+ request.status = "expired";
4390
+ await request.save();
4391
+ }
4392
+ return request;
4393
+ }
4394
+ /**
4395
+ * Mark a pending request as approved and mint a bearer session bound to the
4396
+ * approving user. Idempotent: re-approving an already-approved request is a
4397
+ * no-op. Throws if the user/tenant are missing, the request is unknown, or
4398
+ * the request has expired.
4399
+ */
4400
+ async approveRequest(input) {
4401
+ if (!input.user.id || !input.tenantId) throw new TerminalAuthError("An authenticated tenant session is required.");
4402
+ this.assertApproveRateLimit(input.user.id);
4403
+ const request = await this.getRequestForUserCode(input.userCode);
4404
+ if (!request) {
4405
+ this.recordFailedApprove(input.user.id);
4406
+ throw new TerminalAuthError("Terminal login request not found.");
4407
+ }
4408
+ if (request.status === "approved") return request;
4409
+ if (request.status !== "pending" || isExpired(request)) {
4410
+ request.status = "expired";
4411
+ await request.save();
4412
+ this.recordFailedApprove(input.user.id);
4413
+ throw new TerminalAuthError("Terminal login request has expired.");
4414
+ }
4415
+ const sessionId = await this.sessionService.createSession(input.user.id, input.tenantId, {
4416
+ data: {
4417
+ approvedBy: input.user.email ?? input.user.id,
4418
+ kind: "terminal"
4419
+ },
4420
+ ipAddress: input.ipAddress,
4421
+ ttl: this.sessionTtlSeconds,
4422
+ userAgent: input.userAgent
4423
+ });
4424
+ request.approvedAt = /* @__PURE__ */ new Date();
4425
+ request.sessionId = sessionId;
4426
+ request.status = "approved";
4427
+ request.tenantId = input.tenantId;
4428
+ request.userId = input.user.id;
4429
+ await request.save();
4430
+ this.failedApprovesByUser.delete(input.user.id);
4431
+ return request;
4432
+ }
4433
+ /**
4434
+ * Throw {@link TerminalAuthRateLimitError} if the user has exceeded the
4435
+ * configured failed-approve budget inside the current sliding window.
4436
+ */
4437
+ assertApproveRateLimit(userId) {
4438
+ const tracking = this.failedApprovesByUser.get(userId);
4439
+ if (!tracking) return;
4440
+ const elapsedMs = Date.now() - tracking.firstAttemptAt;
4441
+ if (elapsedMs >= this.approveAttemptWindowMs) {
4442
+ this.failedApprovesByUser.delete(userId);
4443
+ return;
4444
+ }
4445
+ if (tracking.count >= this.maxApproveAttempts) throw new TerminalAuthRateLimitError("Too many failed terminal-login attempts. Try again later.", Math.max(1, Math.ceil((this.approveAttemptWindowMs - elapsedMs) / 1e3)));
4446
+ }
4447
+ /** Increment the failed-approve counter for `userId`, starting the window if needed. */
4448
+ recordFailedApprove(userId) {
4449
+ const tracking = this.failedApprovesByUser.get(userId);
4450
+ if (!tracking) {
4451
+ this.failedApprovesByUser.set(userId, {
4452
+ count: 1,
4453
+ firstAttemptAt: Date.now()
4454
+ });
4455
+ return;
4456
+ }
4457
+ tracking.count += 1;
4458
+ }
4459
+ /**
4460
+ * Exchange a device code (the secret the CLI keeps) for an access token
4461
+ * once the request has been approved. Returns `{ status: 'pending' }` while
4462
+ * the CLI should keep polling, and `{ status: 'expired' }` if the request
4463
+ * was never approved within its TTL.
4464
+ */
4465
+ async exchangeDeviceCode(deviceCode) {
4466
+ const request = await this.requestCollection.findByDeviceCodeHash(hashDeviceCode(deviceCode));
4467
+ if (!request) return { status: "expired" };
4468
+ if (request.status === "approved" && request.sessionId) return {
4469
+ accessToken: request.sessionId,
4470
+ expiresIn: this.sessionTtlSeconds,
4471
+ status: "approved",
4472
+ tokenType: "Bearer"
4473
+ };
4474
+ if (isExpired(request)) {
4475
+ if (request.status === "pending") {
4476
+ request.status = "expired";
4477
+ await request.save();
4478
+ }
4479
+ return { status: "expired" };
4480
+ }
4481
+ if (request.status === "pending") return {
4482
+ expiresAt: new Date(request.expiresAt).toISOString(),
4483
+ interval: this.pollIntervalSeconds,
4484
+ status: "pending"
4485
+ };
4486
+ return { status: "expired" };
4487
+ }
4488
+ /**
4489
+ * Resolve a bearer token issued by this service into a full session
4490
+ * context — load the user, permissions, and tenant just like the cookie
4491
+ * path does. Returns `null` if the token is unknown, expired, or revoked.
4492
+ */
4493
+ async loadBearerSession(token) {
4494
+ return this.sessionService.loadSessionContext(token);
4495
+ }
4496
+ /**
4497
+ * Revoke a bearer token (CLI logout). Returns true if a session was
4498
+ * actually revoked.
4499
+ */
4500
+ async destroyBearerSession(token) {
4501
+ return this.sessionService.destroySession(token);
4502
+ }
4503
+ /**
4504
+ * Delete expired pending requests (cleanup job).
4505
+ */
4506
+ async cleanupExpiredRequests() {
4507
+ return this.requestCollection.deleteExpired();
4508
+ }
4509
+ /** The default polling interval clients should use. Exposed for tests. */
4510
+ get pollInterval() {
4511
+ return this.pollIntervalSeconds;
4512
+ }
4513
+ };
4514
+ var TerminalAuthError = class extends Error {
4515
+ constructor(message) {
4516
+ super(message);
4517
+ this.name = "TerminalAuthError";
4518
+ }
4519
+ };
4520
+ var TerminalAuthRateLimitError = class extends TerminalAuthError {
4521
+ retryAfterSeconds;
4522
+ constructor(message, retryAfterSeconds) {
4523
+ super(message);
4524
+ this.name = "TerminalAuthRateLimitError";
4525
+ this.retryAfterSeconds = retryAfterSeconds;
4526
+ }
4527
+ };
4528
+ //#endregion
4529
+ export { DEFAULT_ROLE_SLUGS as $, RolePermissionCollection as A, GroupRoleCollection as B, TenantHierarchyError as C, DEFAULT_SESSION_TTL as D, SessionCollection as E, parsePermissionSlug as F, Group as G, GroupMemberCollection as H, MembershipOverrideCollection as I, User as J, UsersCliAuthRequestCollection as K, MembershipOverride as L, PermissionCollection as M, Permission as N, Session as O, isValidPermissionSlug as P, DEFAULT_ROLES as Q, MembershipCollection as R, TenantCollection as S, Tenant as T, GroupMember as U, GroupRole as V, GroupCollection as W, normalizeEmail as X, isValidEmail as Y, AccessRequestStatus as Z, resolveOidcProviderConfig as _, TerminalAuthRateLimitError as a, TenantStatus as at, TenantPermissionOverrideCollection as b, getRequestScopedDatabase as c, PermissionResolver as d, DEFAULT_TENANT_POLICY as et, OidcLoginError as f, getUsersOidcConfig as g, encodeOidcTransaction as h, TerminalAuthError as i, TenantPermissionEffect as it, RolePermission as j, generateSessionId as k, withSessionPermissionContext as l, decodeOidcTransaction as m, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS as n, OverrideEffect as nt, TerminalAuthService as o, UserStatus as ot, OidcLoginService as p, UsersCliAuthRequest as q, DEFAULT_CLI_SESSION_TTL_SECONDS as r, SessionStatus as rt, getCurrentSessionPermissionContext as s, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS as t, MembershipStatus as tt, SessionService as u, webapi_exports as v, MAX_TENANT_HIERARCHY_DEPTH as w, TenantPermissionOverride as x, UserCollection as y, Membership as z };
4530
+
4531
+ //# sourceMappingURL=TerminalAuthService-BXAAuaXf.js.map