@litusarchieve18/agricore-utils 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,6 +90,18 @@ const recordToSave = {
90
90
  ...infrastructure, // Injects canonicalId, companyId, legalEntityId, etc.
91
91
  };
92
92
 
93
+ To completely eliminate database bottlenecks and solve serverless rate-limiting, AgriCore has migrated to a state-of-the-art, "Zero-DB" authorization architecture. By decoupling our security rules from the database SDK, our backend API endpoints now resolve complex multi-tenant boundaries and granular role-based access controls in roughly 1 millisecond.
94
+
95
+ Key Features of the New Security Engine:
96
+
97
+ One-Time Session Generation: On login, the backend dynamically aggregates a user's full hierarchical profile—including role assignments, availableScopes (Company > Legal Entity > Facility), and granular resourceOverrides—into a single, cryptographically signed JSON Web Token (JWT).
98
+
99
+ Zero-DB Library Validations: The @litusarchieve18/agricore-utils library has been upgraded to utilize pure, synchronous functions. Deep hierarchical access checks (findSpecificOverride) and tenant isolation (assertTenantBoundary) are now resolved directly against the JWT payload in-memory, completely bypassing the database for routine requests.
100
+
101
+ Smart Frontend Interceptor: A centralized React hook (useApiClient) acts as an automated API wrapper. It seamlessly injects the user's customJWT and the globally selected activeScopeId into every outbound request. This keeps UI forms completely decoupled from authentication logic.
102
+
103
+ Instant Context Switching: Because the JWT securely holds the user's entire "VIP List" of permitted scopes, users can instantly switch between farms, legal entities, and facilities in the frontend global dropdown without triggering a new authentication handshake or database query.
104
+
93
105
  🛠️ Development & Publishing Guide
94
106
  Zero Dependencies
95
107
  To keep the package ultra-lightweight (~30KB), this library has zero runtime dependencies. All build tools (tsup, typescript) are strictly kept in devDependencies, and the package.json uses the "files": ["dist"] array to ensure only compiled code is uploaded.
@@ -117,7 +129,13 @@ Run the publish command:
117
129
  Bash
118
130
  npm publish --access public
119
131
  📝 Version History
120
- v1.0.5 — add RESOURCE_MODULE_MAP
132
+ V1.0.10 — add an explicit exports map
133
+
134
+ V1.0.9 — added jwt function
135
+
136
+ v1.0.8 — added temporary RESOURCE_REQUIREMENTS
137
+
138
+ v1.0.7 — add RESOURCE_MODULE_MAP
121
139
 
122
140
  v1.0.6 — adding more logic
123
141
 
package/dist/index.d.mts CHANGED
@@ -505,15 +505,18 @@ declare function evaluateBaseAccess(resourcePath: string, session: {
505
505
  * The Judge: Overrides win over base access (Specificity principle).
506
506
  */
507
507
  declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType | null): AccessLevelType;
508
+ declare function findSpecificOverride(session: AgriCoreSession, activeScopeId: string, resourcePath: string): AccessLevelType | null;
508
509
  /**
509
510
  * Tenant Boundary Guard (Backend Only)
510
511
  * Note: db is typed as 'any' here because Base44 SDK types vary,
511
512
  * but you can refine this with your internal SDK types.
512
513
  */
513
- declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
514
+ declare function assertTenantBoundary(db: any, session: AgriCoreSession, // Pass the JWT session instead of just requesterCompanyId
515
+ targets: {
514
516
  targetUserId?: string;
515
517
  authScopeId?: string;
516
518
  expectedScopeType?: 'company' | 'legalEntity' | 'facility';
517
519
  }): Promise<boolean>;
520
+ declare function verifyAndExtractSession(token: string, secret: string): Promise<AgriCoreSession>;
518
521
 
519
- export { ACCESS_RANK, ACT, AccessLevel, type AccessLevelType, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EVERYONE, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, type InfrastructureFields, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_MODULE_MAP, RESOURCE_PATHS, RESOURCE_REQUIREMENTS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, type ResourceRequirement, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, type UserRoleType, Validator, VigorRating, WaterSource, assertCanWrite, assertTenantBoundary, buildRlsFilter, canRead, canWrite, evaluateBaseAccess, extractAppCode, generateCanonicalId, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolveMenuVisibility, withAgriLogging };
522
+ export { ACCESS_RANK, ACT, AccessLevel, type AccessLevelType, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EVERYONE, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, type InfrastructureFields, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_MODULE_MAP, RESOURCE_PATHS, RESOURCE_REQUIREMENTS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, type ResourceRequirement, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, type UserRoleType, Validator, VigorRating, WaterSource, assertCanWrite, assertTenantBoundary, buildRlsFilter, canRead, canWrite, evaluateBaseAccess, extractAppCode, findSpecificOverride, generateCanonicalId, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolveMenuVisibility, verifyAndExtractSession, withAgriLogging };
package/dist/index.d.ts CHANGED
@@ -505,15 +505,18 @@ declare function evaluateBaseAccess(resourcePath: string, session: {
505
505
  * The Judge: Overrides win over base access (Specificity principle).
506
506
  */
507
507
  declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType | null): AccessLevelType;
508
+ declare function findSpecificOverride(session: AgriCoreSession, activeScopeId: string, resourcePath: string): AccessLevelType | null;
508
509
  /**
509
510
  * Tenant Boundary Guard (Backend Only)
510
511
  * Note: db is typed as 'any' here because Base44 SDK types vary,
511
512
  * but you can refine this with your internal SDK types.
512
513
  */
513
- declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
514
+ declare function assertTenantBoundary(db: any, session: AgriCoreSession, // Pass the JWT session instead of just requesterCompanyId
515
+ targets: {
514
516
  targetUserId?: string;
515
517
  authScopeId?: string;
516
518
  expectedScopeType?: 'company' | 'legalEntity' | 'facility';
517
519
  }): Promise<boolean>;
520
+ declare function verifyAndExtractSession(token: string, secret: string): Promise<AgriCoreSession>;
518
521
 
519
- export { ACCESS_RANK, ACT, AccessLevel, type AccessLevelType, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EVERYONE, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, type InfrastructureFields, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_MODULE_MAP, RESOURCE_PATHS, RESOURCE_REQUIREMENTS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, type ResourceRequirement, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, type UserRoleType, Validator, VigorRating, WaterSource, assertCanWrite, assertTenantBoundary, buildRlsFilter, canRead, canWrite, evaluateBaseAccess, extractAppCode, generateCanonicalId, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolveMenuVisibility, withAgriLogging };
522
+ export { ACCESS_RANK, ACT, AccessLevel, type AccessLevelType, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EVERYONE, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, type InfrastructureFields, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_MODULE_MAP, RESOURCE_PATHS, RESOURCE_REQUIREMENTS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, type ResourceRequirement, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, type UserRoleType, Validator, VigorRating, WaterSource, assertCanWrite, assertTenantBoundary, buildRlsFilter, canRead, canWrite, evaluateBaseAccess, extractAppCode, findSpecificOverride, generateCanonicalId, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolveMenuVisibility, verifyAndExtractSession, withAgriLogging };
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ __export(index_exports, {
76
76
  canWrite: () => canWrite,
77
77
  evaluateBaseAccess: () => evaluateBaseAccess,
78
78
  extractAppCode: () => extractAppCode,
79
+ findSpecificOverride: () => findSpecificOverride,
79
80
  generateCanonicalId: () => generateCanonicalId,
80
81
  generateInfrastructureFields: () => generateInfrastructureFields,
81
82
  getAcceptedMimeTypes: () => getAcceptedMimeTypes,
@@ -91,9 +92,11 @@ __export(index_exports, {
91
92
  resolveEffectiveAccess: () => resolveEffectiveAccess,
92
93
  resolveError: () => resolveError,
93
94
  resolveMenuVisibility: () => resolveMenuVisibility,
95
+ verifyAndExtractSession: () => verifyAndExtractSession,
94
96
  withAgriLogging: () => withAgriLogging
95
97
  });
96
98
  module.exports = __toCommonJS(index_exports);
99
+ var import_jose = require("jose");
97
100
 
98
101
  // src/logger.ts
99
102
  var SENSITIVE_KEYS = ["password", "token", "secret", "apiKey", "creditCard", "authorization"];
@@ -432,7 +435,7 @@ function generateCanonicalId() {
432
435
  }
433
436
  function generateInfrastructureFields(activeScope, availableScopes) {
434
437
  if (!activeScope || !availableScopes?.length) {
435
- throw new Error("[generateInfrastructureFields] Missing scope context.");
438
+ throw ErrorFactory.unauthorized("[generateInfrastructureFields] Missing scope context.");
436
439
  }
437
440
  let companyId = null;
438
441
  let legalEntityId = null;
@@ -446,7 +449,7 @@ function generateInfrastructureFields(activeScope, availableScopes) {
446
449
  pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
447
450
  }
448
451
  if (!companyId) {
449
- throw new Error("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
452
+ throw ErrorFactory.unauthorized("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
450
453
  }
451
454
  return {
452
455
  canonicalId: crypto.randomUUID(),
@@ -1000,41 +1003,61 @@ function evaluateBaseAccess(resourcePath, session, requirements = {}) {
1000
1003
  function resolveEffectiveAccess(base, override) {
1001
1004
  return override === void 0 || override === null ? base : override;
1002
1005
  }
1003
- async function assertTenantBoundary(db, requesterCompanyId, targets) {
1004
- if (!requesterCompanyId) {
1005
- throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context for boundary check.");
1006
+ function findSpecificOverride(session, activeScopeId, resourcePath) {
1007
+ const scopeOverrides = session.resourceOverrides?.[activeScopeId];
1008
+ if (!scopeOverrides) return null;
1009
+ const parts = resourcePath.split(".");
1010
+ let currentPath = "";
1011
+ let bestOverride = null;
1012
+ for (const part of parts) {
1013
+ currentPath = currentPath ? `${currentPath}.${part}` : part;
1014
+ if (scopeOverrides[currentPath] !== void 0) {
1015
+ bestOverride = scopeOverrides[currentPath];
1016
+ }
1017
+ }
1018
+ return bestOverride;
1019
+ }
1020
+ async function assertTenantBoundary(db, session, targets) {
1021
+ if (!session.companyId) {
1022
+ throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context.");
1006
1023
  }
1007
1024
  if (targets.targetUserId) {
1008
1025
  const targetUserAssignments = await db.UserRoleAssignment.filter({
1009
1026
  userId: targets.targetUserId,
1010
- companyId: requesterCompanyId
1027
+ companyId: session.companyId
1011
1028
  });
1012
1029
  if (targetUserAssignments.length === 0) {
1013
- console.error(`[SECURITY BREACH ATTEMPT] User tried to modify targetUserId ${targets.targetUserId} outside their company ${requesterCompanyId}`);
1014
- throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user or tenant boundary violation.");
1030
+ console.error(`[SECURITY BREACH] User tried to modify targetUserId ${targets.targetUserId}`);
1031
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user.");
1015
1032
  }
1016
1033
  }
1017
1034
  if (targets.authScopeId) {
1018
- let matchFound = false;
1019
- if (targets.expectedScopeType === "company" || !targets.expectedScopeType) {
1020
- const c = await db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId });
1021
- if (c.length > 0) matchFound = true;
1022
- }
1023
- if (!matchFound && (targets.expectedScopeType === "legalEntity" || !targets.expectedScopeType)) {
1024
- const le = await db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
1025
- if (le.length > 0) matchFound = true;
1026
- }
1027
- if (!matchFound && (targets.expectedScopeType === "facility" || !targets.expectedScopeType)) {
1028
- const fac = await db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
1029
- if (fac.length > 0) matchFound = true;
1035
+ const matchedScope = session.availableScopes?.find(
1036
+ (scope) => scope.authScopeId === targets.authScopeId
1037
+ );
1038
+ if (!matchedScope) {
1039
+ console.error(`[SECURITY BREACH] Scope ${targets.authScopeId} not found in user's permitted list.`);
1040
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Tenant boundary violation.");
1030
1041
  }
1031
- if (!matchFound) {
1032
- console.error(`[SECURITY BREACH/VALIDATION ERROR] Scope ${targets.authScopeId} not found for type ${targets.expectedScopeType} in company ${requesterCompanyId}`);
1033
- throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level or tenant boundary violation.");
1042
+ if (targets.expectedScopeType && matchedScope.type !== targets.expectedScopeType) {
1043
+ console.error(`[VALIDATION ERROR] Expected type ${targets.expectedScopeType} but got ${matchedScope.type}`);
1044
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level.");
1034
1045
  }
1035
1046
  }
1036
1047
  return true;
1037
1048
  }
1049
+ async function verifyAndExtractSession(token, secret) {
1050
+ if (!token) {
1051
+ throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
1052
+ }
1053
+ try {
1054
+ const secretKey = new TextEncoder().encode(secret);
1055
+ const { payload } = await (0, import_jose.jwtVerify)(token, secretKey);
1056
+ return payload;
1057
+ } catch (error) {
1058
+ throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
1059
+ }
1060
+ }
1038
1061
  // Annotate the CommonJS export names for ESM import in node:
1039
1062
  0 && (module.exports = {
1040
1063
  ACCESS_RANK,
@@ -1093,6 +1116,7 @@ async function assertTenantBoundary(db, requesterCompanyId, targets) {
1093
1116
  canWrite,
1094
1117
  evaluateBaseAccess,
1095
1118
  extractAppCode,
1119
+ findSpecificOverride,
1096
1120
  generateCanonicalId,
1097
1121
  generateInfrastructureFields,
1098
1122
  getAcceptedMimeTypes,
@@ -1108,5 +1132,6 @@ async function assertTenantBoundary(db, requesterCompanyId, targets) {
1108
1132
  resolveEffectiveAccess,
1109
1133
  resolveError,
1110
1134
  resolveMenuVisibility,
1135
+ verifyAndExtractSession,
1111
1136
  withAgriLogging
1112
1137
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ // src/index.ts
2
+ import { jwtVerify } from "jose";
3
+
1
4
  // src/logger.ts
2
5
  var SENSITIVE_KEYS = ["password", "token", "secret", "apiKey", "creditCard", "authorization"];
3
6
  function redact(obj) {
@@ -335,7 +338,7 @@ function generateCanonicalId() {
335
338
  }
336
339
  function generateInfrastructureFields(activeScope, availableScopes) {
337
340
  if (!activeScope || !availableScopes?.length) {
338
- throw new Error("[generateInfrastructureFields] Missing scope context.");
341
+ throw ErrorFactory.unauthorized("[generateInfrastructureFields] Missing scope context.");
339
342
  }
340
343
  let companyId = null;
341
344
  let legalEntityId = null;
@@ -349,7 +352,7 @@ function generateInfrastructureFields(activeScope, availableScopes) {
349
352
  pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
350
353
  }
351
354
  if (!companyId) {
352
- throw new Error("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
355
+ throw ErrorFactory.unauthorized("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
353
356
  }
354
357
  return {
355
358
  canonicalId: crypto.randomUUID(),
@@ -903,41 +906,61 @@ function evaluateBaseAccess(resourcePath, session, requirements = {}) {
903
906
  function resolveEffectiveAccess(base, override) {
904
907
  return override === void 0 || override === null ? base : override;
905
908
  }
906
- async function assertTenantBoundary(db, requesterCompanyId, targets) {
907
- if (!requesterCompanyId) {
908
- throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context for boundary check.");
909
+ function findSpecificOverride(session, activeScopeId, resourcePath) {
910
+ const scopeOverrides = session.resourceOverrides?.[activeScopeId];
911
+ if (!scopeOverrides) return null;
912
+ const parts = resourcePath.split(".");
913
+ let currentPath = "";
914
+ let bestOverride = null;
915
+ for (const part of parts) {
916
+ currentPath = currentPath ? `${currentPath}.${part}` : part;
917
+ if (scopeOverrides[currentPath] !== void 0) {
918
+ bestOverride = scopeOverrides[currentPath];
919
+ }
920
+ }
921
+ return bestOverride;
922
+ }
923
+ async function assertTenantBoundary(db, session, targets) {
924
+ if (!session.companyId) {
925
+ throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context.");
909
926
  }
910
927
  if (targets.targetUserId) {
911
928
  const targetUserAssignments = await db.UserRoleAssignment.filter({
912
929
  userId: targets.targetUserId,
913
- companyId: requesterCompanyId
930
+ companyId: session.companyId
914
931
  });
915
932
  if (targetUserAssignments.length === 0) {
916
- console.error(`[SECURITY BREACH ATTEMPT] User tried to modify targetUserId ${targets.targetUserId} outside their company ${requesterCompanyId}`);
917
- throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user or tenant boundary violation.");
933
+ console.error(`[SECURITY BREACH] User tried to modify targetUserId ${targets.targetUserId}`);
934
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user.");
918
935
  }
919
936
  }
920
937
  if (targets.authScopeId) {
921
- let matchFound = false;
922
- if (targets.expectedScopeType === "company" || !targets.expectedScopeType) {
923
- const c = await db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId });
924
- if (c.length > 0) matchFound = true;
925
- }
926
- if (!matchFound && (targets.expectedScopeType === "legalEntity" || !targets.expectedScopeType)) {
927
- const le = await db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
928
- if (le.length > 0) matchFound = true;
929
- }
930
- if (!matchFound && (targets.expectedScopeType === "facility" || !targets.expectedScopeType)) {
931
- const fac = await db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
932
- if (fac.length > 0) matchFound = true;
938
+ const matchedScope = session.availableScopes?.find(
939
+ (scope) => scope.authScopeId === targets.authScopeId
940
+ );
941
+ if (!matchedScope) {
942
+ console.error(`[SECURITY BREACH] Scope ${targets.authScopeId} not found in user's permitted list.`);
943
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Tenant boundary violation.");
933
944
  }
934
- if (!matchFound) {
935
- console.error(`[SECURITY BREACH/VALIDATION ERROR] Scope ${targets.authScopeId} not found for type ${targets.expectedScopeType} in company ${requesterCompanyId}`);
936
- throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level or tenant boundary violation.");
945
+ if (targets.expectedScopeType && matchedScope.type !== targets.expectedScopeType) {
946
+ console.error(`[VALIDATION ERROR] Expected type ${targets.expectedScopeType} but got ${matchedScope.type}`);
947
+ throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level.");
937
948
  }
938
949
  }
939
950
  return true;
940
951
  }
952
+ async function verifyAndExtractSession(token, secret) {
953
+ if (!token) {
954
+ throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
955
+ }
956
+ try {
957
+ const secretKey = new TextEncoder().encode(secret);
958
+ const { payload } = await jwtVerify(token, secretKey);
959
+ return payload;
960
+ } catch (error) {
961
+ throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
962
+ }
963
+ }
941
964
  export {
942
965
  ACCESS_RANK,
943
966
  ACT,
@@ -995,6 +1018,7 @@ export {
995
1018
  canWrite,
996
1019
  evaluateBaseAccess,
997
1020
  extractAppCode,
1021
+ findSpecificOverride,
998
1022
  generateCanonicalId,
999
1023
  generateInfrastructureFields,
1000
1024
  getAcceptedMimeTypes,
@@ -1010,5 +1034,6 @@ export {
1010
1034
  resolveEffectiveAccess,
1011
1035
  resolveError,
1012
1036
  resolveMenuVisibility,
1037
+ verifyAndExtractSession,
1013
1038
  withAgriLogging
1014
1039
  };
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "@litusarchieve18/agricore-utils",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Canonical Backend Utility Library for AgriCore",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "default": "./dist/index.mjs"
14
+ }
15
+ },
8
16
  "files": [
9
17
  "dist"
10
18
  ],
@@ -23,5 +31,7 @@
23
31
  "tsup": "^8.0.0",
24
32
  "typescript": "^5.0.0"
25
33
  },
26
- "dependencies": {}
27
- }
34
+ "dependencies": {
35
+ "jose": "^6.2.2"
36
+ }
37
+ }