@happyvertical/smrt-users 0.38.2 → 0.38.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/AGENTS.md +22 -0
  2. package/dist/chunks/{TerminalAuthService-BLxjzcQj.js → TerminalAuthService-B7RYkS27.js} +732 -87
  3. package/dist/chunks/TerminalAuthService-B7RYkS27.js.map +1 -0
  4. package/dist/collections/RoleCollection.d.ts +6 -1
  5. package/dist/collections/RoleCollection.d.ts.map +1 -1
  6. package/dist/collections/RolePermissionCollection.d.ts +35 -0
  7. package/dist/collections/RolePermissionCollection.d.ts.map +1 -1
  8. package/dist/collections/index.d.ts +2 -2
  9. package/dist/collections/index.d.ts.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +101 -436
  13. package/dist/index.js.map +1 -1
  14. package/dist/manifest.json +35 -4
  15. package/dist/models/Permission.d.ts +7 -4
  16. package/dist/models/Permission.d.ts.map +1 -1
  17. package/dist/services/MagicLinkService.d.ts.map +1 -1
  18. package/dist/services/OperationPermissionService.d.ts +45 -0
  19. package/dist/services/OperationPermissionService.d.ts.map +1 -0
  20. package/dist/services/PermissionCatalogService.d.ts +12 -0
  21. package/dist/services/PermissionCatalogService.d.ts.map +1 -1
  22. package/dist/services/PermissionResolver.d.ts.map +1 -1
  23. package/dist/services/SessionService.d.ts.map +1 -1
  24. package/dist/services/TenantService.d.ts.map +1 -1
  25. package/dist/services/TerminalAuthService.d.ts.map +1 -1
  26. package/dist/services/index.d.ts +2 -1
  27. package/dist/services/index.d.ts.map +1 -1
  28. package/dist/smrt-knowledge.json +9 -8
  29. package/dist/sveltekit.js +1 -1
  30. package/package.json +11 -11
  31. package/dist/chunks/TerminalAuthService-BLxjzcQj.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,14 @@
1
- import { $ as DEFAULT_ROLE_SLUGS, A as RolePermissionCollection, B as GroupRoleCollection, C as TenantHierarchyError, D as DEFAULT_SESSION_TTL, E as SessionCollection, F as parsePermissionSlug, G as Group, H as GroupMemberCollection, I as MembershipOverrideCollection, J as User, K as UsersCliAuthRequestCollection, L as MembershipOverride, M as PermissionCollection, N as Permission, O as Session, P as isValidPermissionSlug, Q as DEFAULT_ROLES, R as MembershipCollection, S as TenantCollection, T as Tenant, U as GroupMember, V as GroupRole, W as GroupCollection, X as normalizeEmail, Y as isValidEmail, Z as AccessRequestStatus, _ as resolveOidcProviderConfig, a as TerminalAuthRateLimitError, at as TenantStatus, b as TenantPermissionOverrideCollection, c as getRequestScopedDatabase, d as PermissionResolver, et as DEFAULT_TENANT_POLICY, f as OidcLoginError, g as getUsersOidcConfig, h as encodeOidcTransaction, i as TerminalAuthError, it as TenantPermissionEffect, j as RolePermission, k as generateSessionId, l as withSessionPermissionContext, m as decodeOidcTransaction, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nt as OverrideEffect, o as TerminalAuthService, ot as UserStatus, p as OidcLoginService, q as UsersCliAuthRequest, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as SessionStatus, s as getCurrentSessionPermissionContext, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as MembershipStatus, u as SessionService, w as MAX_TENANT_HIERARCHY_DEPTH, x as TenantPermissionOverride, y as UserCollection, z as Membership } from "./chunks/TerminalAuthService-BLxjzcQj.js";
2
- import { ObjectRegistry, SmrtCollection, SmrtObject, field, findManifestEntryByQualifiedName, foreignKey, smrt } from "@happyvertical/smrt-core";
1
+ import { $ as UsersCliAuthRequestCollection, A as DEFAULT_ROLE_PERMISSION_PATTERNS, B as Role, C as TenantHierarchyError, D as DEFAULT_SESSION_TTL, E as SessionCollection, F as normalizeOperationPermissionAction, G as MembershipCollection, H as Permission, I as registerPermissionDefinitions, J as GroupRole, K as Membership, L as syncPermissionCatalog, M as PermissionCatalogService, N as deriveOperationPermissionCollectionName, O as Session, P as deriveOperationPermissionSlug, Q as Group, R as RolePermission, S as TenantCollection, T as Tenant, U as MembershipOverrideCollection, V as PermissionCollection, W as MembershipOverride, X as GroupMember, Y as GroupMemberCollection, Z as GroupCollection, _ as resolveOidcProviderConfig, a as TerminalAuthRateLimitError, at as DEFAULT_ROLES, b as TenantPermissionOverrideCollection, c as getRequestScopedDatabase, ct as MembershipStatus, d as PermissionResolver, dt as TenantPermissionEffect, et as UsersCliAuthRequest, f as OidcLoginError, ft as TenantStatus, g as getUsersOidcConfig, h as encodeOidcTransaction, i as TerminalAuthError, it as AccessRequestStatus, j as RolePermissionCollection, k as generateSessionId, l as withSessionPermissionContext, lt as OverrideEffect, m as decodeOidcTransaction, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nt as isValidEmail, o as TerminalAuthService, ot as DEFAULT_ROLE_SLUGS, p as OidcLoginService, pt as UserStatus, q as GroupRoleCollection, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as normalizeEmail, s as getCurrentSessionPermissionContext, st as DEFAULT_TENANT_POLICY, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as User, u as SessionService, ut as SessionStatus, w as MAX_TENANT_HIERARCHY_DEPTH, x as TenantPermissionOverride, y as UserCollection, z as RoleCollection } from "./chunks/TerminalAuthService-B7RYkS27.js";
2
+ import { ObjectRegistry, SmrtCollection, SmrtObject, field, findManifestEntryByQualifiedName, smrt } from "@happyvertical/smrt-core";
3
3
  import { createLogger } from "@happyvertical/logger";
4
- import { getPackageConfig } from "@happyvertical/smrt-config";
5
4
  import { createHash } from "node:crypto";
6
5
  //#region src/models/AccessRequest.ts
7
- var __defProp$2 = Object.defineProperty;
8
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
9
- var __decorateClass$2 = (decorators, target, key, kind) => {
10
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
6
+ var __defProp$1 = Object.defineProperty;
7
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass$1 = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
11
10
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
12
- if (kind && result) __defProp$2(target, key, result);
11
+ if (kind && result) __defProp$1(target, key, result);
13
12
  return result;
14
13
  };
15
14
  var AccessRequest = class extends SmrtObject {
@@ -94,24 +93,24 @@ var AccessRequest = class extends SmrtObject {
94
93
  return this.status === AccessRequestStatus.DECLINED || this.status === AccessRequestStatus.GRADUATED || this.status === AccessRequestStatus.CANCELED;
95
94
  }
96
95
  };
97
- __decorateClass$2([field({
96
+ __decorateClass$1([field({
98
97
  required: true,
99
98
  indexed: true
100
99
  })], AccessRequest.prototype, "email", 2);
101
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "name", 2);
102
- __decorateClass$2([field({
100
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "name", 2);
101
+ __decorateClass$1([field({
103
102
  type: "text",
104
103
  indexed: true
105
104
  })], AccessRequest.prototype, "status", 2);
106
- __decorateClass$2([field({ indexed: true })], AccessRequest.prototype, "source", 2);
107
- __decorateClass$2([field()], AccessRequest.prototype, "requestContext", 2);
108
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "note", 2);
109
- __decorateClass$2([field()], AccessRequest.prototype, "requestedAt", 2);
110
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "decidedAt", 2);
111
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "decidedBy", 2);
112
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "resultingUserId", 2);
113
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "tenantHint", 2);
114
- AccessRequest = __decorateClass$2([smrt({
105
+ __decorateClass$1([field({ indexed: true })], AccessRequest.prototype, "source", 2);
106
+ __decorateClass$1([field()], AccessRequest.prototype, "requestContext", 2);
107
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "note", 2);
108
+ __decorateClass$1([field()], AccessRequest.prototype, "requestedAt", 2);
109
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "decidedAt", 2);
110
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "decidedBy", 2);
111
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "resultingUserId", 2);
112
+ __decorateClass$1([field({ nullable: true })], AccessRequest.prototype, "tenantHint", 2);
113
+ AccessRequest = __decorateClass$1([smrt({
115
114
  tableName: "access_requests",
116
115
  conflictColumns: ["id"],
117
116
  api: { include: [] },
@@ -165,12 +164,12 @@ var AccessRequestCollection = class extends SmrtCollection {
165
164
  };
166
165
  //#endregion
167
166
  //#region src/models/MagicLinkToken.ts
168
- var __defProp$1 = Object.defineProperty;
169
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
170
- var __decorateClass$1 = (decorators, target, key, kind) => {
171
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
167
+ var __defProp = Object.defineProperty;
168
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
169
+ var __decorateClass = (decorators, target, key, kind) => {
170
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
172
171
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
173
- if (kind && result) __defProp$1(target, key, result);
172
+ if (kind && result) __defProp(target, key, result);
174
173
  return result;
175
174
  };
176
175
  var DEFAULT_TOKEN_EXPIRY_SECONDS = 600;
@@ -198,11 +197,11 @@ var UsersMagicLinkToken = class extends SmrtObject {
198
197
  return !this.used && !this.isExpired();
199
198
  }
200
199
  };
201
- __decorateClass$1([field({
200
+ __decorateClass([field({
202
201
  required: true,
203
202
  unique: true
204
203
  })], UsersMagicLinkToken.prototype, "nonce", 2);
205
- UsersMagicLinkToken = __decorateClass$1([smrt({
204
+ UsersMagicLinkToken = __decorateClass([smrt({
206
205
  tableName: "users_magic_link_tokens",
207
206
  api: { include: [] },
208
207
  mcp: { include: [] },
@@ -247,140 +246,6 @@ var UsersMagicLinkTokenCollection = class extends SmrtCollection {
247
246
  }
248
247
  };
249
248
  //#endregion
250
- //#region src/models/Role.ts
251
- var __defProp = Object.defineProperty;
252
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
253
- var __decorateClass = (decorators, target, key, kind) => {
254
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
255
- for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
256
- if (kind && result) __defProp(target, key, result);
257
- return result;
258
- };
259
- var Role = class extends SmrtObject {
260
- tenantId;
261
- /**
262
- * Display name for the role
263
- */
264
- name = "";
265
- /**
266
- * Description of the role
267
- */
268
- description = "";
269
- /**
270
- * Whether this is a system role (cannot be deleted)
271
- */
272
- isSystem = false;
273
- constructor(options = {}) {
274
- super(options);
275
- if (options.tenantId !== void 0) this.tenantId = options.tenantId;
276
- if (options.name !== void 0) this.name = options.name;
277
- if (options.description !== void 0) this.description = options.description;
278
- if (options.isSystem !== void 0) this.isSystem = options.isSystem;
279
- }
280
- /**
281
- * Check if this is a system-wide role
282
- */
283
- isSystemRole() {
284
- return this.tenantId === null || this.tenantId === void 0;
285
- }
286
- /**
287
- * Check if this is a tenant-specific role
288
- */
289
- isTenantRole() {
290
- return this.tenantId !== null && this.tenantId !== void 0;
291
- }
292
- /**
293
- * Check if this role can be deleted.
294
- * System roles (isSystem = true) cannot be deleted.
295
- * @returns true if the role can be deleted
296
- */
297
- canDelete() {
298
- return !this.isSystem;
299
- }
300
- /**
301
- * Delete guard - prevents deletion of system roles.
302
- * Override the delete method to check isSystem flag first.
303
- */
304
- async delete() {
305
- if (this.isSystem) throw new Error(`Cannot delete system role '${this.slug}'. System roles are protected.`);
306
- return super.delete();
307
- }
308
- };
309
- __decorateClass([foreignKey("Tenant", { nullable: true })], Role.prototype, "tenantId", 2);
310
- Role = __decorateClass([smrt({
311
- api: { include: ["list", "get"] },
312
- mcp: { include: ["list", "get"] },
313
- cli: true
314
- })], Role);
315
- //#endregion
316
- //#region src/collections/RoleCollection.ts
317
- var RoleCollection = class extends SmrtCollection {
318
- static _itemClass = Role;
319
- /**
320
- * Find all system roles (tenantId is null)
321
- */
322
- async findSystemRoles() {
323
- return await this.query(`SELECT * FROM ${this.tableName} WHERE tenant_id IS NULL ORDER BY name ASC`);
324
- }
325
- /**
326
- * Find roles available for a tenant (system + tenant-specific)
327
- */
328
- async findByTenant(tenantId) {
329
- return await this.query(`SELECT * FROM ${this.tableName}
330
- WHERE tenant_id IS NULL OR tenant_id = ?
331
- ORDER BY is_system DESC, name ASC`, [tenantId]);
332
- }
333
- /**
334
- * Find tenant-specific roles only
335
- */
336
- async findTenantRoles(tenantId) {
337
- return await this.list({
338
- where: { tenantId },
339
- orderBy: "name ASC"
340
- });
341
- }
342
- /**
343
- * Find role by slug within a tenant context
344
- */
345
- async findBySlug(slug, tenantId) {
346
- if (tenantId) {
347
- const tenantRoles = await this.list({
348
- where: {
349
- slug,
350
- tenantId
351
- },
352
- limit: 1
353
- });
354
- if (tenantRoles.length > 0) return tenantRoles[0];
355
- }
356
- const systemRoles = await this.query(`SELECT * FROM ${this.tableName} WHERE slug = ? AND tenant_id IS NULL LIMIT 1`, [slug]);
357
- return systemRoles.length > 0 ? systemRoles[0] : null;
358
- }
359
- /**
360
- * Seed default system roles
361
- */
362
- async seedSystemRoles() {
363
- const roles = [];
364
- for (const roleDef of DEFAULT_ROLES) {
365
- const existing = await this.findBySlug(roleDef.slug);
366
- if (existing) {
367
- roles.push(existing);
368
- continue;
369
- }
370
- const role = await this.create({
371
- slug: roleDef.slug,
372
- name: roleDef.name,
373
- description: roleDef.description,
374
- tenantId: null,
375
- isSystem: true
376
- });
377
- await role.save();
378
- roles.push(role);
379
- }
380
- return roles;
381
- }
382
- };
383
- //#endregion
384
249
  //#region src/services/AccessRequestService.ts
385
250
  var logger = createLogger({ level: "info" });
386
251
  var ACCESS_REQUEST_CAPABILITIES = {
@@ -871,7 +736,7 @@ var MagicLinkService = class MagicLinkService {
871
736
  * The caller is responsible for emailing the token to the user.
872
737
  */
873
738
  async generate(email) {
874
- const { SignJWT } = await import("./chunks/TerminalAuthService-BLxjzcQj.js").then((n) => n.v);
739
+ const { SignJWT } = await import("./chunks/TerminalAuthService-B7RYkS27.js").then((n) => n.v);
875
740
  const key = await this.getSigningKey();
876
741
  const nonce = crypto.randomUUID();
877
742
  const normalizedEmail = normalizeEmail(email);
@@ -900,7 +765,7 @@ var MagicLinkService = class MagicLinkService {
900
765
  * @throws {MagicLinkError} If the token is invalid, expired, or already used
901
766
  */
902
767
  async verify(token) {
903
- const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-BLxjzcQj.js").then((n) => n.v);
768
+ const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-B7RYkS27.js").then((n) => n.v);
904
769
  const key = await this.getSigningKey();
905
770
  let payload;
906
771
  try {
@@ -934,289 +799,89 @@ var MagicLinkService = class MagicLinkService {
934
799
  }
935
800
  };
936
801
  //#endregion
937
- //#region src/services/PermissionCatalogService.ts
938
- function getRuntimePermissionRegistrations() {
939
- globalThis.__smrtUsersPermissionRegistrations ??= /* @__PURE__ */ new Map();
940
- return globalThis.__smrtUsersPermissionRegistrations;
941
- }
942
- function toSnakeCase$1(value) {
943
- return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
944
- }
945
- function pluralize(word) {
946
- if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
947
- if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) return `${word}es`;
948
- return `${word}s`;
949
- }
950
- function deriveCollectionName(className) {
951
- return pluralize(toSnakeCase$1(className));
952
- }
953
- function humanizeResource(resource) {
954
- return resource.replace(/[_-]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
955
- }
956
- function capitalize(value) {
957
- return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
958
- }
959
- function defaultPermissionName(slug) {
960
- const parsed = parsePermissionSlug(slug);
961
- if (!parsed.isValid) return humanizeResource(slug);
962
- return `${capitalize(parsed.action)} ${humanizeResource(parsed.resource)}`;
963
- }
964
- function defaultPermissionDescription(slug) {
965
- const parsed = parsePermissionSlug(slug);
966
- if (!parsed.isValid) return `Allows ${slug}`;
967
- return `Allows ${parsed.action} access for ${humanizeResource(parsed.resource).toLowerCase()}`;
968
- }
969
- function isCollectionManifestEntry(objectDef) {
970
- return objectDef?.extends === "SmrtCollection" || objectDef?.extendsTypeArg !== void 0;
971
- }
972
- function getPublicCustomMethodNames(methodEntries, standardActions) {
973
- return Array.from(new Set(methodEntries.filter((method) => Boolean(method?.name) && method?.isPublic === true && !standardActions.includes(method.name)).map((method) => method.name)));
974
- }
975
- function getCustomMethodExposureNames(config, availableCustomMethods) {
976
- if (!config || config === false) return /* @__PURE__ */ new Set();
977
- if (config === true || typeof config !== "object") return new Set(availableCustomMethods);
978
- const rawInclude = config.include;
979
- const include = Array.isArray(rawInclude) ? [...rawInclude] : void 0;
980
- const rawExclude = config.exclude;
981
- const exclude = Array.isArray(rawExclude) ? [...rawExclude] : [];
982
- if (!include) return new Set(availableCustomMethods.filter((methodName) => !exclude.includes(methodName)));
983
- const baseMethods = include.filter((methodName) => availableCustomMethods.includes(methodName));
984
- return new Set(baseMethods.filter((methodName) => !exclude.includes(methodName)));
985
- }
986
- function isOperationEnabled(config, action) {
987
- if (config === false) return false;
988
- if (config && typeof config === "object") {
989
- const include = Array.isArray(config.include) ? config.include : void 0;
990
- const rawExclude = config.exclude;
991
- const exclude = Array.isArray(rawExclude) ? [...rawExclude] : [];
992
- if (include && !include.includes(action)) return false;
993
- if (exclude.includes(action)) return false;
994
- }
995
- return true;
802
+ //#region src/services/OperationPermissionService.ts
803
+ var OperationPermissionError = class extends Error {
804
+ decision;
805
+ permission;
806
+ status = 403;
807
+ constructor(decision) {
808
+ super(decision.permission ? `Permission denied for '${decision.permission}'.` : "Permission denied.");
809
+ this.name = "OperationPermissionError";
810
+ this.decision = decision;
811
+ this.permission = decision.permission;
812
+ }
813
+ };
814
+ function isModuleNotFoundError(error) {
815
+ return error instanceof Error && (error.code === "ERR_MODULE_NOT_FOUND" || /Cannot find package '@happyvertical\/smrt-tenancy'/.test(error.message));
996
816
  }
997
- function normalizePostgresAction(action) {
998
- const normalized = action.toUpperCase();
999
- if (normalized === "SELECT" || normalized === "INSERT" || normalized === "UPDATE" || normalized === "DELETE") return normalized;
1000
- throw new Error(`Unsupported Postgres permission action '${action}'. Expected SELECT, INSERT, UPDATE, or DELETE.`);
817
+ async function readTenancyBypassContext() {
818
+ try {
819
+ const tenancy = await import("@happyvertical/smrt-tenancy");
820
+ return {
821
+ superAdminBypass: tenancy.isSuperAdminBypass(),
822
+ systemContext: tenancy.isSystemContext()
823
+ };
824
+ } catch (error) {
825
+ if (!isModuleNotFoundError(error)) throw error;
826
+ return {
827
+ superAdminBypass: false,
828
+ systemContext: false
829
+ };
830
+ }
1001
831
  }
1002
- function normalizeBinding(binding, fallbackPermission) {
832
+ function deny(permission, reason, error) {
1003
833
  return {
1004
- action: normalizePostgresAction(binding.action),
1005
- permission: binding.permission || fallbackPermission,
1006
- schemaName: binding.schemaName,
1007
- tableName: binding.tableName,
1008
- tenantField: binding.tenantField
834
+ allowed: false,
835
+ error,
836
+ permission,
837
+ reason
1009
838
  };
1010
839
  }
1011
- function mergeStringField(fieldName, existing, incoming, slug) {
1012
- const existingValue = existing[fieldName];
1013
- const incomingValue = incoming[fieldName];
1014
- if (!incomingValue) return;
1015
- if (!existingValue) {
1016
- existing[fieldName] = incomingValue;
1017
- return;
1018
- }
1019
- if (existingValue !== incomingValue) throw new Error(`Conflicting permission metadata for '${slug}' field '${fieldName}': '${existingValue}' !== '${incomingValue}'`);
1020
- }
1021
- function mergeBindings(existing, incoming) {
1022
- const existingBindings = existing.postgres?.bindings ?? [];
1023
- const incomingBindings = incoming.postgres?.bindings ?? [];
1024
- if (incomingBindings.length === 0) return;
1025
- const seen = new Set(existingBindings.map((binding) => [
1026
- binding.permission,
1027
- binding.action,
1028
- binding.schemaName ?? "",
1029
- binding.tableName,
1030
- binding.tenantField ?? ""
1031
- ].join("|")));
1032
- const mergedBindings = [...existingBindings];
1033
- for (const binding of incomingBindings) {
1034
- const normalized = normalizeBinding(binding, incoming.slug);
1035
- const key = [
1036
- normalized.permission,
1037
- normalized.action,
1038
- normalized.schemaName ?? "",
1039
- normalized.tableName,
1040
- normalized.tenantField ?? ""
1041
- ].join("|");
1042
- if (!seen.has(key)) {
1043
- seen.add(key);
1044
- mergedBindings.push(normalized);
1045
- }
1046
- }
1047
- existing.postgres = { bindings: mergedBindings };
1048
- }
1049
- function normalizeDefinition(definition, source) {
1050
- if (!definition.slug || !isValidPermissionSlug(definition.slug.trim())) throw new Error(`Invalid permission slug '${definition.slug}'. Expected 'resource.action'.`);
1051
- const slug = definition.slug.trim();
840
+ function allow(permission, reason) {
1052
841
  return {
1053
- category: definition.category ?? parsePermissionSlug(slug).resource,
1054
- className: definition.className,
1055
- collection: definition.collection,
1056
- description: definition.description ?? defaultPermissionDescription(slug),
1057
- name: definition.name ?? defaultPermissionName(slug),
1058
- postgres: definition.postgres?.bindings ? { bindings: definition.postgres.bindings.map((binding) => normalizeBinding(binding, slug)) } : void 0,
1059
- qualifiedName: definition.qualifiedName,
1060
- slug,
1061
- source
842
+ allowed: true,
843
+ permission,
844
+ reason
1062
845
  };
1063
846
  }
1064
- function mergeDefinitionSet(current, incomingDefinitions, source) {
1065
- for (const rawDefinition of incomingDefinitions) {
1066
- const definition = normalizeDefinition(rawDefinition, source);
1067
- const existing = current.get(definition.slug);
1068
- if (!existing) {
1069
- current.set(definition.slug, definition);
1070
- continue;
1071
- }
1072
- mergeStringField("category", existing, definition, definition.slug);
1073
- mergeStringField("className", existing, definition, definition.slug);
1074
- mergeStringField("collection", existing, definition, definition.slug);
1075
- mergeStringField("description", existing, definition, definition.slug);
1076
- mergeStringField("name", existing, definition, definition.slug);
1077
- mergeStringField("qualifiedName", existing, definition, definition.slug);
1078
- mergeBindings(existing, definition);
1079
- }
1080
- }
1081
- function registerPermissionDefinitions(definitions) {
1082
- globalThis.__smrtUsersPermissionRegistrationCounter = (globalThis.__smrtUsersPermissionRegistrationCounter ?? 0) + 1;
1083
- const registrationId = globalThis.__smrtUsersPermissionRegistrationCounter;
1084
- getRuntimePermissionRegistrations().set(registrationId, definitions);
1085
- return () => {
1086
- getRuntimePermissionRegistrations().delete(registrationId);
1087
- };
847
+ function hasCatalogSlug(catalog, permission) {
848
+ return catalog.permissions.some((definition) => definition.slug === permission);
1088
849
  }
1089
- var PermissionCatalogService = class PermissionCatalogService {
1090
- constructor(options = {}) {
1091
- this.options = options;
1092
- }
1093
- options;
1094
- getUsersConfig() {
1095
- return getPackageConfig("users", {});
1096
- }
1097
- getRuntimePermissionDefinitions() {
1098
- return Array.from(getRuntimePermissionRegistrations().values()).flat();
1099
- }
1100
- getCustomPermissionDefinitions() {
1101
- return this.getUsersConfig().permissions?.custom ?? [];
1102
- }
1103
- getCatalog() {
1104
- const manifestPermissions = this.getManifestPermissionDefinitions();
1105
- const customPermissions = this.getCustomPermissionDefinitions();
1106
- const runtimePermissions = this.getRuntimePermissionDefinitions();
1107
- const merged = /* @__PURE__ */ new Map();
1108
- mergeDefinitionSet(merged, manifestPermissions, "manifest");
1109
- mergeDefinitionSet(merged, customPermissions, "config");
1110
- mergeDefinitionSet(merged, runtimePermissions, "runtime");
1111
- return {
1112
- customPermissions: customPermissions.map((definition) => normalizeDefinition(definition, "config")),
1113
- manifestPermissions: manifestPermissions.map((definition) => normalizeDefinition(definition, "manifest")),
1114
- permissions: Array.from(merged.values()).sort((left, right) => left.slug.localeCompare(right.slug)),
1115
- runtimePermissions: runtimePermissions.map((definition) => normalizeDefinition(definition, "runtime"))
1116
- };
1117
- }
1118
- async syncPermissionCatalog() {
1119
- const catalog = this.getCatalog();
1120
- const permissions = await PermissionCollection.create(this.options);
1121
- const created = [];
1122
- const unchanged = [];
1123
- const updated = [];
1124
- for (const definition of catalog.permissions) {
1125
- const existing = await permissions.findBySlug(definition.slug);
1126
- if (!existing) {
1127
- await (await permissions.create({
1128
- category: definition.category ?? parsePermissionSlug(definition.slug).resource,
1129
- description: definition.description ?? "",
1130
- name: definition.name ?? definition.slug,
1131
- slug: definition.slug
1132
- })).save();
1133
- created.push(definition.slug);
1134
- continue;
1135
- }
1136
- const nextName = definition.name ?? existing.name;
1137
- const nextDescription = definition.description ?? existing.description;
1138
- const nextCategory = definition.category ?? existing.category;
1139
- if (existing.name === nextName && existing.description === nextDescription && existing.category === nextCategory) {
1140
- unchanged.push(definition.slug);
1141
- continue;
1142
- }
1143
- existing.name = nextName;
1144
- existing.description = nextDescription;
1145
- existing.category = nextCategory;
1146
- await existing.save();
1147
- updated.push(definition.slug);
1148
- }
1149
- return {
1150
- catalog,
1151
- created,
1152
- unchanged,
1153
- updated
1154
- };
1155
- }
1156
- getManifestPermissionDefinitions() {
1157
- const standardActions = [
1158
- "list",
1159
- "get",
1160
- "create",
1161
- "update",
1162
- "delete"
1163
- ];
1164
- const definitions = /* @__PURE__ */ new Map();
1165
- for (const metadata of ObjectRegistry.getAllObjectMetadata()) {
1166
- const registered = ObjectRegistry.getClassByConstructor(metadata.constructor) ?? ObjectRegistry.getClass(metadata.name);
1167
- const manifestEntry = registered?.qualifiedName ? findManifestEntryByQualifiedName(registered.qualifiedName) : void 0;
1168
- if (isCollectionManifestEntry(manifestEntry)) continue;
1169
- const className = metadata.name;
1170
- const qualifiedName = registered?.qualifiedName;
1171
- const objectConfig = manifestEntry?.decoratorConfig ?? metadata.config;
1172
- const rawCollection = objectConfig?.collection;
1173
- const collection = (typeof rawCollection === "string" && rawCollection.length > 0 ? rawCollection : void 0) ?? manifestEntry?.collection ?? deriveCollectionName(metadata.name);
1174
- if (isOperationEnabled(objectConfig.api, "list") || isOperationEnabled(objectConfig.api, "get") || isOperationEnabled(objectConfig.cli, "list") || isOperationEnabled(objectConfig.cli, "get") || isOperationEnabled(objectConfig.mcp, "list") || isOperationEnabled(objectConfig.mcp, "get")) definitions.set(`${collection}.read`, {
1175
- className,
1176
- collection,
1177
- qualifiedName,
1178
- slug: `${collection}.read`
1179
- });
1180
- for (const action of [
1181
- "create",
1182
- "update",
1183
- "delete"
1184
- ]) {
1185
- if (!(isOperationEnabled(objectConfig.api, action) || isOperationEnabled(objectConfig.cli, action) || isOperationEnabled(objectConfig.mcp, action))) continue;
1186
- definitions.set(`${collection}.${action}`, {
1187
- className,
1188
- collection,
1189
- qualifiedName,
1190
- slug: `${collection}.${action}`
1191
- });
1192
- }
1193
- const publicCustomMethodNames = getPublicCustomMethodNames(manifestEntry?.methods ? Object.values(manifestEntry.methods) : Array.from(metadata.methods.values()), standardActions);
1194
- const customApiMethods = /* @__PURE__ */ new Set();
1195
- const customCliMethods = getCustomMethodExposureNames(objectConfig.cli, publicCustomMethodNames);
1196
- const customMcpMethods = getCustomMethodExposureNames(objectConfig.mcp, publicCustomMethodNames);
1197
- for (const methodName of publicCustomMethodNames) if (isOperationEnabled(objectConfig.api, methodName)) customApiMethods.add(methodName);
1198
- const customMethods = /* @__PURE__ */ new Set([
1199
- ...customApiMethods,
1200
- ...customCliMethods,
1201
- ...customMcpMethods
1202
- ]);
1203
- for (const methodName of customMethods) definitions.set(`${collection}.${methodName}`, {
1204
- className,
1205
- collection,
1206
- description: `Allows ${methodName} on ${humanizeResource(collection).toLowerCase()}`,
1207
- name: `${capitalize(methodName)} ${humanizeResource(collection)}`,
1208
- qualifiedName,
1209
- slug: `${collection}.${methodName}`
1210
- });
1211
- }
1212
- return Array.from(definitions.values()).sort((left, right) => left.slug.localeCompare(right.slug));
850
+ async function checkOperationPermission(options) {
851
+ let permission;
852
+ try {
853
+ permission = deriveOperationPermissionSlug(options.collection, options.action);
854
+ } catch (error) {
855
+ return deny(null, "invalid_operation", error);
1213
856
  }
1214
- static create(options = {}) {
1215
- return new PermissionCatalogService(options);
857
+ if (!hasCatalogSlug(options.catalog ?? PermissionCatalogService.create(options).getCatalog(), permission)) return deny(permission, "unknown_permission");
858
+ const sessionContext = getCurrentSessionPermissionContext();
859
+ let tenancyContext;
860
+ try {
861
+ tenancyContext = await readTenancyBypassContext();
862
+ } catch (error) {
863
+ return deny(permission, "resolution_error", error);
864
+ }
865
+ const systemContext = sessionContext?.systemContext === true || tenancyContext.systemContext;
866
+ const superAdminBypass = sessionContext?.superAdminBypass === true || tenancyContext.superAdminBypass;
867
+ if (systemContext && options.allowSystemContextBypass !== false) return allow(permission, "system_context_bypass");
868
+ if (superAdminBypass && options.allowSuperAdminBypass !== false) return allow(permission, "super_admin_bypass");
869
+ const userId = options.userId ?? sessionContext?.userId ?? null;
870
+ const tenantId = options.tenantId ?? sessionContext?.tenantId ?? null;
871
+ if (!userId || !tenantId) return deny(permission, "missing_principal");
872
+ try {
873
+ return await (options.resolver ?? await PermissionResolver.create(options)).hasPermission(userId, tenantId, permission, { membership: options.membership }) ? allow(permission, "permission_granted") : deny(permission, "permission_denied");
874
+ } catch (error) {
875
+ return deny(permission, "resolution_error", error);
1216
876
  }
1217
- };
1218
- async function syncPermissionCatalog(options = {}) {
1219
- return PermissionCatalogService.create(options).syncPermissionCatalog();
877
+ }
878
+ async function hasOperationPermission(options) {
879
+ return (await checkOperationPermission(options)).allowed;
880
+ }
881
+ async function assertOperationPermission(options) {
882
+ const decision = await checkOperationPermission(options);
883
+ if (decision.allowed || options.onDeny === "return") return decision;
884
+ throw new OperationPermissionError(decision);
1220
885
  }
1221
886
  //#endregion
1222
887
  //#region src/services/PostgresPermissionPolicies.ts
@@ -1658,6 +1323,6 @@ var TenantService = class TenantService {
1658
1323
  }
1659
1324
  };
1660
1325
  //#endregion
1661
- export { ACCESS_REQUEST_CAPABILITIES, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_ROLES, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, OidcLoginError, OidcLoginService, OverrideEffect, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, Role, RoleCollection, RolePermission, RolePermissionCollection, Session, SessionCollection, SessionService, SessionStatus, Tenant, TenantCollection, TenantHierarchyError, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, User, UserCollection, UserStatus, applyPostgresPermissionPolicies, decodeOidcTransaction, encodeOidcTransaction, generatePostgresPermissionSql, generateSessionId, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, registerPermissionDefinitions, resolveOidcProviderConfig, syncPermissionCatalog, withSessionPermissionContext };
1326
+ export { ACCESS_REQUEST_CAPABILITIES, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_ROLES, DEFAULT_ROLE_PERMISSION_PATTERNS, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, OidcLoginError, OidcLoginService, OperationPermissionError, OverrideEffect, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, Role, RoleCollection, RolePermission, RolePermissionCollection, Session, SessionCollection, SessionService, SessionStatus, Tenant, TenantCollection, TenantHierarchyError, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, User, UserCollection, UserStatus, applyPostgresPermissionPolicies, assertOperationPermission, checkOperationPermission, decodeOidcTransaction, deriveOperationPermissionCollectionName, deriveOperationPermissionSlug, encodeOidcTransaction, generatePostgresPermissionSql, generateSessionId, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, hasOperationPermission, normalizeOperationPermissionAction, registerPermissionDefinitions, resolveOidcProviderConfig, syncPermissionCatalog, withSessionPermissionContext };
1662
1327
 
1663
1328
  //# sourceMappingURL=index.js.map