@litusarchieve18/agricore-utils 1.0.3 → 1.0.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.
- package/README.md +34 -0
- package/dist/index.d.mts +36 -6
- package/dist/index.d.ts +36 -6
- package/dist/index.js +90 -14
- package/dist/index.mjs +83 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,38 @@ ErrorFactory
|
|
|
62
62
|
4. Domain Enums
|
|
63
63
|
The universal vocabulary of the AgriCore platform, ensuring type-safety across all microservices (e.g., UserRole, UserPrivilege, AccessLevel, FacilityType, NotificationMode, TaskStatus, RESOURCE_PATHS).
|
|
64
64
|
|
|
65
|
+
To document these powerful new security and architectural features in your README.md, you should add sections that explain why these tools exist. They move your library from being a "Type definition" to a "Security Framework."
|
|
66
|
+
|
|
67
|
+
Here are the suggested paragraphs to add to your README.md:
|
|
68
|
+
|
|
69
|
+
🔐 Security & Access Control
|
|
70
|
+
The library now includes a centralized logic layer to enforce granular permissions across all AgriCore services.
|
|
71
|
+
|
|
72
|
+
evaluateBaseAccess: Automatically determines a user's base permission level (NONE, READ, or WRITE) for a specific resource path (e.g., records.financials). It respects the OWNER role "God Mode" bypass and uses ROLE_SECTIONS_BY_KEY to provide sensible defaults for different app sections.
|
|
73
|
+
|
|
74
|
+
Access Ranking (resolveEffectiveAccess): Implements an additive permission model. By ranking levels (NONE < READ < WRITE), it allows the system to compare base permissions against specific user overrides and resolve the highest authorized access level.
|
|
75
|
+
|
|
76
|
+
🛡️ Multi-Tenant Boundary Protection (Backend Only)
|
|
77
|
+
To ensure absolute data isolation in our multi-tenant environment, the library provides a "Boundary Guard."
|
|
78
|
+
|
|
79
|
+
assertTenantBoundary: A critical security function that prevents "Cross-Tenant" attacks. It validates that any target userId or authScopeId (Facility/Legal Entity) involved in a request belongs specifically to the requester's companyId. This serves as a final, mandatory check before any database write operations.
|
|
80
|
+
|
|
81
|
+
🌳 Infrastructure & Hierarchy Resolution
|
|
82
|
+
AgriCore data records require a full "Infrastructure Path" (Company ID, Legal Entity ID, and Facility ID). Instead of manual lookups, the library automates tree-walking.
|
|
83
|
+
|
|
84
|
+
generateInfrastructureFields: This utility takes a user’s activeScope and their availableScopes tree. It recursively walks up the hierarchy—from a specific Facility to the Legal Entity and finally to the Root Company. It resolves all relevant infrastructure IDs and generates a new canonicalId, ensuring that every data record is correctly anchored in the organizational hierarchy.
|
|
85
|
+
|
|
86
|
+
// Example: Using the new utilities in a Base44 Save Hook
|
|
87
|
+
const infrastructure = generateInfrastructureFields(activeScope, session.availableScopes);
|
|
88
|
+
|
|
89
|
+
// Ensure the user isn't trying to save data into a company they don't belong to
|
|
90
|
+
await assertTenantBoundary(db, session.companyId, { authScopeId: infrastructure.authScopeId });
|
|
91
|
+
|
|
92
|
+
const recordToSave = {
|
|
93
|
+
...userInput,
|
|
94
|
+
...infrastructure, // Injects canonicalId, companyId, legalEntityId, etc.
|
|
95
|
+
};
|
|
96
|
+
|
|
65
97
|
🛠️ Development & Publishing Guide
|
|
66
98
|
Zero Dependencies
|
|
67
99
|
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.
|
|
@@ -89,7 +121,9 @@ Run the publish command:
|
|
|
89
121
|
Bash
|
|
90
122
|
npm publish --access public
|
|
91
123
|
📝 Version History
|
|
124
|
+
v1.0.4 — add generateInfrastructureFields, security access control and multi tenant boundary
|
|
92
125
|
v1.0.3 — add the base44Id
|
|
126
|
+
|
|
93
127
|
v1.0.2 — Breaking Change: Upgraded AgriCoreSession interface. Added companyConfig and UI-ready availableScopes arrays to support the frontend hierarchy dropdown without secondary API calls.
|
|
94
128
|
|
|
95
129
|
v1.0.1 — Migrated package from private GitHub Registry to public NPM Registry for Base44/Deno compatibility. Removed baseUrl from tsconfig.json for NodeNext compatibility. Cleaned build artifacts from dependencies.
|
package/dist/index.d.mts
CHANGED
|
@@ -67,6 +67,13 @@ interface TaskTypeConfig {
|
|
|
67
67
|
icon: string;
|
|
68
68
|
approveAction: string;
|
|
69
69
|
}
|
|
70
|
+
interface InfrastructureFields {
|
|
71
|
+
canonicalId: string;
|
|
72
|
+
companyId: string;
|
|
73
|
+
legalEntityId: string | null;
|
|
74
|
+
facilityId: string | null;
|
|
75
|
+
authScopeId: string;
|
|
76
|
+
}
|
|
70
77
|
|
|
71
78
|
declare const AgriLogger: {
|
|
72
79
|
log(level: "INFO" | "WARN" | "ERROR", message: string, meta: any): void;
|
|
@@ -201,11 +208,11 @@ declare function generateCanonicalId(): string;
|
|
|
201
208
|
* This matches the documented rule:
|
|
202
209
|
* "Set to facilityId if available, else legalEntityId, else companyId."
|
|
203
210
|
*/
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
/**
|
|
212
|
+
* generateInfrastructureFields
|
|
213
|
+
* Standardizes the creation of new entities by walking up the scope tree.
|
|
214
|
+
*/
|
|
215
|
+
declare function generateInfrastructureFields(activeScope: AvailableScope, availableScopes: AvailableScope[]): InfrastructureFields;
|
|
209
216
|
declare function buildRlsFilter(session: AgriCoreSession, extra?: Record<string, any>): {
|
|
210
217
|
authScopeId: {
|
|
211
218
|
$in: string[];
|
|
@@ -233,6 +240,7 @@ declare const UserRole: {
|
|
|
233
240
|
readonly LEADING_HAND: "leadingHand";
|
|
234
241
|
readonly WORKER: "worker";
|
|
235
242
|
};
|
|
243
|
+
type UserRoleType = typeof UserRole[keyof typeof UserRole];
|
|
236
244
|
declare const UserPrivilege: {
|
|
237
245
|
readonly FINANCE: "finance";
|
|
238
246
|
readonly HR: "hr";
|
|
@@ -245,6 +253,7 @@ declare const AccessLevel: {
|
|
|
245
253
|
readonly READ: "READ";
|
|
246
254
|
readonly WRITE: "WRITE";
|
|
247
255
|
};
|
|
256
|
+
type AccessLevelType = keyof typeof AccessLevel;
|
|
248
257
|
declare const EnabledModule: {
|
|
249
258
|
readonly FARM_MANAGEMENT: "farm_management";
|
|
250
259
|
readonly PACKHOUSE: "packhouse";
|
|
@@ -478,5 +487,26 @@ declare const RESOURCE_PATHS: {
|
|
|
478
487
|
teamStaff: string;
|
|
479
488
|
teamTraining: string;
|
|
480
489
|
};
|
|
490
|
+
declare const ACCESS_RANK: Record<AccessLevelType, number>;
|
|
491
|
+
declare const ROLE_SECTIONS_BY_KEY: Record<string, UserRoleType[]>;
|
|
492
|
+
declare function evaluateBaseAccess(resourcePath: string, session: {
|
|
493
|
+
role?: UserRoleType;
|
|
494
|
+
privileges?: string[];
|
|
495
|
+
enabledModules?: string[];
|
|
496
|
+
}, requirements?: {
|
|
497
|
+
module?: string;
|
|
498
|
+
allowedRoles?: UserRoleType[];
|
|
499
|
+
requiredPrivileges?: string[];
|
|
500
|
+
}): AccessLevelType;
|
|
501
|
+
/**
|
|
502
|
+
* Tenant Boundary Guard (Backend Only)
|
|
503
|
+
* Note: db is typed as 'any' here because Base44 SDK types vary,
|
|
504
|
+
* but you can refine this with your internal SDK types.
|
|
505
|
+
*/
|
|
506
|
+
declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
|
|
507
|
+
targetUserId?: string;
|
|
508
|
+
authScopeId?: string;
|
|
509
|
+
}): Promise<boolean>;
|
|
510
|
+
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType): AccessLevelType;
|
|
481
511
|
|
|
482
|
-
export { ACT, AccessLevel, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_PATHS, RelationshipType, type ResolvedError, type ResourceOverrides, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, Validator, VigorRating, WaterSource, assertCanWrite, buildRlsFilter, canRead, canWrite, extractAppCode, generateCanonicalId, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError,
|
|
512
|
+
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, 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_PATHS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,13 @@ interface TaskTypeConfig {
|
|
|
67
67
|
icon: string;
|
|
68
68
|
approveAction: string;
|
|
69
69
|
}
|
|
70
|
+
interface InfrastructureFields {
|
|
71
|
+
canonicalId: string;
|
|
72
|
+
companyId: string;
|
|
73
|
+
legalEntityId: string | null;
|
|
74
|
+
facilityId: string | null;
|
|
75
|
+
authScopeId: string;
|
|
76
|
+
}
|
|
70
77
|
|
|
71
78
|
declare const AgriLogger: {
|
|
72
79
|
log(level: "INFO" | "WARN" | "ERROR", message: string, meta: any): void;
|
|
@@ -201,11 +208,11 @@ declare function generateCanonicalId(): string;
|
|
|
201
208
|
* This matches the documented rule:
|
|
202
209
|
* "Set to facilityId if available, else legalEntityId, else companyId."
|
|
203
210
|
*/
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
/**
|
|
212
|
+
* generateInfrastructureFields
|
|
213
|
+
* Standardizes the creation of new entities by walking up the scope tree.
|
|
214
|
+
*/
|
|
215
|
+
declare function generateInfrastructureFields(activeScope: AvailableScope, availableScopes: AvailableScope[]): InfrastructureFields;
|
|
209
216
|
declare function buildRlsFilter(session: AgriCoreSession, extra?: Record<string, any>): {
|
|
210
217
|
authScopeId: {
|
|
211
218
|
$in: string[];
|
|
@@ -233,6 +240,7 @@ declare const UserRole: {
|
|
|
233
240
|
readonly LEADING_HAND: "leadingHand";
|
|
234
241
|
readonly WORKER: "worker";
|
|
235
242
|
};
|
|
243
|
+
type UserRoleType = typeof UserRole[keyof typeof UserRole];
|
|
236
244
|
declare const UserPrivilege: {
|
|
237
245
|
readonly FINANCE: "finance";
|
|
238
246
|
readonly HR: "hr";
|
|
@@ -245,6 +253,7 @@ declare const AccessLevel: {
|
|
|
245
253
|
readonly READ: "READ";
|
|
246
254
|
readonly WRITE: "WRITE";
|
|
247
255
|
};
|
|
256
|
+
type AccessLevelType = keyof typeof AccessLevel;
|
|
248
257
|
declare const EnabledModule: {
|
|
249
258
|
readonly FARM_MANAGEMENT: "farm_management";
|
|
250
259
|
readonly PACKHOUSE: "packhouse";
|
|
@@ -478,5 +487,26 @@ declare const RESOURCE_PATHS: {
|
|
|
478
487
|
teamStaff: string;
|
|
479
488
|
teamTraining: string;
|
|
480
489
|
};
|
|
490
|
+
declare const ACCESS_RANK: Record<AccessLevelType, number>;
|
|
491
|
+
declare const ROLE_SECTIONS_BY_KEY: Record<string, UserRoleType[]>;
|
|
492
|
+
declare function evaluateBaseAccess(resourcePath: string, session: {
|
|
493
|
+
role?: UserRoleType;
|
|
494
|
+
privileges?: string[];
|
|
495
|
+
enabledModules?: string[];
|
|
496
|
+
}, requirements?: {
|
|
497
|
+
module?: string;
|
|
498
|
+
allowedRoles?: UserRoleType[];
|
|
499
|
+
requiredPrivileges?: string[];
|
|
500
|
+
}): AccessLevelType;
|
|
501
|
+
/**
|
|
502
|
+
* Tenant Boundary Guard (Backend Only)
|
|
503
|
+
* Note: db is typed as 'any' here because Base44 SDK types vary,
|
|
504
|
+
* but you can refine this with your internal SDK types.
|
|
505
|
+
*/
|
|
506
|
+
declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
|
|
507
|
+
targetUserId?: string;
|
|
508
|
+
authScopeId?: string;
|
|
509
|
+
}): Promise<boolean>;
|
|
510
|
+
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType): AccessLevelType;
|
|
481
511
|
|
|
482
|
-
export { ACT, AccessLevel, type AgriCoreSession, AgriLogger, AppError, ApprovalStatus, ApprovalType, AuditStatus, AuditType, type AuthScopeFields, type AvailableScope, CAT, type CompanyConfig, Converter, CropCycleStatus, ERROR_DICTIONARY, EmitterType, EnabledModule, ErrorFactory, FORM_REGISTRY, FacilityType, type FormMeta, HarvestMethod, LinkedStatus, MOD, MODULE_LABELS, MicroclimateZone, MimeType, type ModCode, NettingType, NotificationMode, OrderStatus, type PermissionLevel, PhysicalState, PlantingMethod, RESOURCE_PATHS, RelationshipType, type ResolvedError, type ResourceOverrides, RowOrientation, SENTINEL_UUID, type ScopeType, SoilTexture, TASK_TYPE_REGISTRY, type TabConfig, TargetEntityType, TaskStatus, TaskType, type TaskTypeConfig, TransactionType, TrellisType, UserPrivilege, UserRole, Validator, VigorRating, WaterSource, assertCanWrite, buildRlsFilter, canRead, canWrite, extractAppCode, generateCanonicalId, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getTaskTypeOptions, handleAppErrorResponse, isTabVisible, isTabWritable, resolveAccess, resolveAppError,
|
|
512
|
+
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, 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_PATHS, ROLE_SECTIONS_BY_KEY, RelationshipType, type ResolvedError, type ResourceOverrides, 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 };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACCESS_RANK: () => ACCESS_RANK,
|
|
23
24
|
ACT: () => ACT,
|
|
24
25
|
AccessLevel: () => AccessLevel,
|
|
25
26
|
AgriLogger: () => AgriLogger,
|
|
@@ -49,6 +50,7 @@ __export(index_exports, {
|
|
|
49
50
|
PhysicalState: () => PhysicalState,
|
|
50
51
|
PlantingMethod: () => PlantingMethod,
|
|
51
52
|
RESOURCE_PATHS: () => RESOURCE_PATHS,
|
|
53
|
+
ROLE_SECTIONS_BY_KEY: () => ROLE_SECTIONS_BY_KEY,
|
|
52
54
|
RelationshipType: () => RelationshipType,
|
|
53
55
|
RowOrientation: () => RowOrientation,
|
|
54
56
|
SENTINEL_UUID: () => SENTINEL_UUID,
|
|
@@ -65,11 +67,14 @@ __export(index_exports, {
|
|
|
65
67
|
VigorRating: () => VigorRating,
|
|
66
68
|
WaterSource: () => WaterSource,
|
|
67
69
|
assertCanWrite: () => assertCanWrite,
|
|
70
|
+
assertTenantBoundary: () => assertTenantBoundary,
|
|
68
71
|
buildRlsFilter: () => buildRlsFilter,
|
|
69
72
|
canRead: () => canRead,
|
|
70
73
|
canWrite: () => canWrite,
|
|
74
|
+
evaluateBaseAccess: () => evaluateBaseAccess,
|
|
71
75
|
extractAppCode: () => extractAppCode,
|
|
72
76
|
generateCanonicalId: () => generateCanonicalId,
|
|
77
|
+
generateInfrastructureFields: () => generateInfrastructureFields,
|
|
73
78
|
getAcceptedMimeTypes: () => getAcceptedMimeTypes,
|
|
74
79
|
getApproveAction: () => getApproveAction,
|
|
75
80
|
getFormMeta: () => getFormMeta,
|
|
@@ -80,11 +85,10 @@ __export(index_exports, {
|
|
|
80
85
|
isTabWritable: () => isTabWritable,
|
|
81
86
|
resolveAccess: () => resolveAccess,
|
|
82
87
|
resolveAppError: () => resolveAppError,
|
|
83
|
-
|
|
88
|
+
resolveEffectiveAccess: () => resolveEffectiveAccess,
|
|
84
89
|
resolveError: () => resolveError,
|
|
85
90
|
resolveMenuVisibility: () => resolveMenuVisibility,
|
|
86
|
-
withAgriLogging: () => withAgriLogging
|
|
87
|
-
withSaveMiddleware: () => withSaveMiddleware
|
|
91
|
+
withAgriLogging: () => withAgriLogging
|
|
88
92
|
});
|
|
89
93
|
module.exports = __toCommonJS(index_exports);
|
|
90
94
|
|
|
@@ -423,15 +427,30 @@ function generateCanonicalId() {
|
|
|
423
427
|
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
424
428
|
});
|
|
425
429
|
}
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
function generateInfrastructureFields(activeScope, availableScopes) {
|
|
431
|
+
if (!activeScope || !availableScopes?.length) {
|
|
432
|
+
throw new Error("[generateInfrastructureFields] Missing scope context.");
|
|
433
|
+
}
|
|
434
|
+
let companyId = null;
|
|
435
|
+
let legalEntityId = null;
|
|
436
|
+
let facilityId = null;
|
|
437
|
+
let pointer = activeScope;
|
|
438
|
+
while (pointer) {
|
|
439
|
+
if (pointer.type === "company") companyId = pointer.base44Id;
|
|
440
|
+
if (pointer.type === "legalEntity") legalEntityId = pointer.base44Id;
|
|
441
|
+
if (pointer.type === "facility") facilityId = pointer.base44Id;
|
|
442
|
+
const parentId = pointer.parentId ?? null;
|
|
443
|
+
pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
|
|
444
|
+
}
|
|
445
|
+
if (!companyId) {
|
|
446
|
+
throw new Error("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
|
|
447
|
+
}
|
|
431
448
|
return {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
449
|
+
canonicalId: crypto.randomUUID(),
|
|
450
|
+
companyId,
|
|
451
|
+
legalEntityId,
|
|
452
|
+
facilityId,
|
|
453
|
+
authScopeId: activeScope.authScopeId
|
|
435
454
|
};
|
|
436
455
|
}
|
|
437
456
|
function buildRlsFilter(session, extra = {}) {
|
|
@@ -823,8 +842,62 @@ var RESOURCE_PATHS = {
|
|
|
823
842
|
teamStaff: "team.staff",
|
|
824
843
|
teamTraining: "team.training"
|
|
825
844
|
};
|
|
845
|
+
var ACCESS_RANK = {
|
|
846
|
+
NONE: 0,
|
|
847
|
+
READ: 1,
|
|
848
|
+
WRITE: 2
|
|
849
|
+
};
|
|
850
|
+
var ROLE_SECTIONS_BY_KEY = {
|
|
851
|
+
dashboard: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
852
|
+
operations: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
853
|
+
equipment: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
854
|
+
safety: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR],
|
|
855
|
+
team: [UserRole.OWNER, UserRole.MANAGER],
|
|
856
|
+
records: [UserRole.OWNER, UserRole.MANAGER],
|
|
857
|
+
adminSettings: []
|
|
858
|
+
};
|
|
859
|
+
function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
860
|
+
if (!session?.role) return "NONE";
|
|
861
|
+
if (session.role === UserRole.OWNER) return "WRITE";
|
|
862
|
+
const topLevel = resourcePath.split(".")[0];
|
|
863
|
+
const defaultRoles = ROLE_SECTIONS_BY_KEY[topLevel] || [];
|
|
864
|
+
if (requirements.module && !session.enabledModules?.includes(requirements.module)) return "NONE";
|
|
865
|
+
const allowedRoles = requirements.allowedRoles?.length ? requirements.allowedRoles : defaultRoles;
|
|
866
|
+
if (!allowedRoles.includes(session.role)) return "NONE";
|
|
867
|
+
if (requirements.requiredPrivileges?.length) {
|
|
868
|
+
const hasPrivilege = requirements.requiredPrivileges.some((p) => session.privileges?.includes(p));
|
|
869
|
+
if (!hasPrivilege) return "NONE";
|
|
870
|
+
}
|
|
871
|
+
const highRoles = [UserRole.OWNER, UserRole.MANAGER];
|
|
872
|
+
const isHighRole = highRoles.includes(session.role);
|
|
873
|
+
return isHighRole ? "WRITE" : "READ";
|
|
874
|
+
}
|
|
875
|
+
async function assertTenantBoundary(db, requesterCompanyId, targets) {
|
|
876
|
+
if (!requesterCompanyId) throw new Error("Missing requester company context");
|
|
877
|
+
if (targets.targetUserId) {
|
|
878
|
+
const assignments = await db.UserRoleAssignment.filter({
|
|
879
|
+
userId: targets.targetUserId,
|
|
880
|
+
companyId: requesterCompanyId
|
|
881
|
+
});
|
|
882
|
+
if (assignments.length === 0) throw new Error("Tenant boundary violation: User");
|
|
883
|
+
}
|
|
884
|
+
if (targets.authScopeId) {
|
|
885
|
+
const [c, le, fac] = await Promise.all([
|
|
886
|
+
db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId }),
|
|
887
|
+
db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId }),
|
|
888
|
+
db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId })
|
|
889
|
+
]);
|
|
890
|
+
if (!c.length && !le.length && !fac.length) throw new Error("Tenant boundary violation: Scope");
|
|
891
|
+
}
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
function resolveEffectiveAccess(base, override) {
|
|
895
|
+
if (!override) return base;
|
|
896
|
+
return ACCESS_RANK[override] >= ACCESS_RANK[base] ? override : base;
|
|
897
|
+
}
|
|
826
898
|
// Annotate the CommonJS export names for ESM import in node:
|
|
827
899
|
0 && (module.exports = {
|
|
900
|
+
ACCESS_RANK,
|
|
828
901
|
ACT,
|
|
829
902
|
AccessLevel,
|
|
830
903
|
AgriLogger,
|
|
@@ -854,6 +927,7 @@ var RESOURCE_PATHS = {
|
|
|
854
927
|
PhysicalState,
|
|
855
928
|
PlantingMethod,
|
|
856
929
|
RESOURCE_PATHS,
|
|
930
|
+
ROLE_SECTIONS_BY_KEY,
|
|
857
931
|
RelationshipType,
|
|
858
932
|
RowOrientation,
|
|
859
933
|
SENTINEL_UUID,
|
|
@@ -870,11 +944,14 @@ var RESOURCE_PATHS = {
|
|
|
870
944
|
VigorRating,
|
|
871
945
|
WaterSource,
|
|
872
946
|
assertCanWrite,
|
|
947
|
+
assertTenantBoundary,
|
|
873
948
|
buildRlsFilter,
|
|
874
949
|
canRead,
|
|
875
950
|
canWrite,
|
|
951
|
+
evaluateBaseAccess,
|
|
876
952
|
extractAppCode,
|
|
877
953
|
generateCanonicalId,
|
|
954
|
+
generateInfrastructureFields,
|
|
878
955
|
getAcceptedMimeTypes,
|
|
879
956
|
getApproveAction,
|
|
880
957
|
getFormMeta,
|
|
@@ -885,9 +962,8 @@ var RESOURCE_PATHS = {
|
|
|
885
962
|
isTabWritable,
|
|
886
963
|
resolveAccess,
|
|
887
964
|
resolveAppError,
|
|
888
|
-
|
|
965
|
+
resolveEffectiveAccess,
|
|
889
966
|
resolveError,
|
|
890
967
|
resolveMenuVisibility,
|
|
891
|
-
withAgriLogging
|
|
892
|
-
withSaveMiddleware
|
|
968
|
+
withAgriLogging
|
|
893
969
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -333,15 +333,30 @@ function generateCanonicalId() {
|
|
|
333
333
|
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
|
-
function
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
function generateInfrastructureFields(activeScope, availableScopes) {
|
|
337
|
+
if (!activeScope || !availableScopes?.length) {
|
|
338
|
+
throw new Error("[generateInfrastructureFields] Missing scope context.");
|
|
339
|
+
}
|
|
340
|
+
let companyId = null;
|
|
341
|
+
let legalEntityId = null;
|
|
342
|
+
let facilityId = null;
|
|
343
|
+
let pointer = activeScope;
|
|
344
|
+
while (pointer) {
|
|
345
|
+
if (pointer.type === "company") companyId = pointer.base44Id;
|
|
346
|
+
if (pointer.type === "legalEntity") legalEntityId = pointer.base44Id;
|
|
347
|
+
if (pointer.type === "facility") facilityId = pointer.base44Id;
|
|
348
|
+
const parentId = pointer.parentId ?? null;
|
|
349
|
+
pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
|
|
350
|
+
}
|
|
351
|
+
if (!companyId) {
|
|
352
|
+
throw new Error("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
|
|
353
|
+
}
|
|
341
354
|
return {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
355
|
+
canonicalId: crypto.randomUUID(),
|
|
356
|
+
companyId,
|
|
357
|
+
legalEntityId,
|
|
358
|
+
facilityId,
|
|
359
|
+
authScopeId: activeScope.authScopeId
|
|
345
360
|
};
|
|
346
361
|
}
|
|
347
362
|
function buildRlsFilter(session, extra = {}) {
|
|
@@ -733,7 +748,61 @@ var RESOURCE_PATHS = {
|
|
|
733
748
|
teamStaff: "team.staff",
|
|
734
749
|
teamTraining: "team.training"
|
|
735
750
|
};
|
|
751
|
+
var ACCESS_RANK = {
|
|
752
|
+
NONE: 0,
|
|
753
|
+
READ: 1,
|
|
754
|
+
WRITE: 2
|
|
755
|
+
};
|
|
756
|
+
var ROLE_SECTIONS_BY_KEY = {
|
|
757
|
+
dashboard: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
758
|
+
operations: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
759
|
+
equipment: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR, UserRole.LEADING_HAND, UserRole.WORKER],
|
|
760
|
+
safety: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR],
|
|
761
|
+
team: [UserRole.OWNER, UserRole.MANAGER],
|
|
762
|
+
records: [UserRole.OWNER, UserRole.MANAGER],
|
|
763
|
+
adminSettings: []
|
|
764
|
+
};
|
|
765
|
+
function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
766
|
+
if (!session?.role) return "NONE";
|
|
767
|
+
if (session.role === UserRole.OWNER) return "WRITE";
|
|
768
|
+
const topLevel = resourcePath.split(".")[0];
|
|
769
|
+
const defaultRoles = ROLE_SECTIONS_BY_KEY[topLevel] || [];
|
|
770
|
+
if (requirements.module && !session.enabledModules?.includes(requirements.module)) return "NONE";
|
|
771
|
+
const allowedRoles = requirements.allowedRoles?.length ? requirements.allowedRoles : defaultRoles;
|
|
772
|
+
if (!allowedRoles.includes(session.role)) return "NONE";
|
|
773
|
+
if (requirements.requiredPrivileges?.length) {
|
|
774
|
+
const hasPrivilege = requirements.requiredPrivileges.some((p) => session.privileges?.includes(p));
|
|
775
|
+
if (!hasPrivilege) return "NONE";
|
|
776
|
+
}
|
|
777
|
+
const highRoles = [UserRole.OWNER, UserRole.MANAGER];
|
|
778
|
+
const isHighRole = highRoles.includes(session.role);
|
|
779
|
+
return isHighRole ? "WRITE" : "READ";
|
|
780
|
+
}
|
|
781
|
+
async function assertTenantBoundary(db, requesterCompanyId, targets) {
|
|
782
|
+
if (!requesterCompanyId) throw new Error("Missing requester company context");
|
|
783
|
+
if (targets.targetUserId) {
|
|
784
|
+
const assignments = await db.UserRoleAssignment.filter({
|
|
785
|
+
userId: targets.targetUserId,
|
|
786
|
+
companyId: requesterCompanyId
|
|
787
|
+
});
|
|
788
|
+
if (assignments.length === 0) throw new Error("Tenant boundary violation: User");
|
|
789
|
+
}
|
|
790
|
+
if (targets.authScopeId) {
|
|
791
|
+
const [c, le, fac] = await Promise.all([
|
|
792
|
+
db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId }),
|
|
793
|
+
db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId }),
|
|
794
|
+
db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId })
|
|
795
|
+
]);
|
|
796
|
+
if (!c.length && !le.length && !fac.length) throw new Error("Tenant boundary violation: Scope");
|
|
797
|
+
}
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
function resolveEffectiveAccess(base, override) {
|
|
801
|
+
if (!override) return base;
|
|
802
|
+
return ACCESS_RANK[override] >= ACCESS_RANK[base] ? override : base;
|
|
803
|
+
}
|
|
736
804
|
export {
|
|
805
|
+
ACCESS_RANK,
|
|
737
806
|
ACT,
|
|
738
807
|
AccessLevel,
|
|
739
808
|
AgriLogger,
|
|
@@ -763,6 +832,7 @@ export {
|
|
|
763
832
|
PhysicalState,
|
|
764
833
|
PlantingMethod,
|
|
765
834
|
RESOURCE_PATHS,
|
|
835
|
+
ROLE_SECTIONS_BY_KEY,
|
|
766
836
|
RelationshipType,
|
|
767
837
|
RowOrientation,
|
|
768
838
|
SENTINEL_UUID,
|
|
@@ -779,11 +849,14 @@ export {
|
|
|
779
849
|
VigorRating,
|
|
780
850
|
WaterSource,
|
|
781
851
|
assertCanWrite,
|
|
852
|
+
assertTenantBoundary,
|
|
782
853
|
buildRlsFilter,
|
|
783
854
|
canRead,
|
|
784
855
|
canWrite,
|
|
856
|
+
evaluateBaseAccess,
|
|
785
857
|
extractAppCode,
|
|
786
858
|
generateCanonicalId,
|
|
859
|
+
generateInfrastructureFields,
|
|
787
860
|
getAcceptedMimeTypes,
|
|
788
861
|
getApproveAction,
|
|
789
862
|
getFormMeta,
|
|
@@ -794,9 +867,8 @@ export {
|
|
|
794
867
|
isTabWritable,
|
|
795
868
|
resolveAccess,
|
|
796
869
|
resolveAppError,
|
|
797
|
-
|
|
870
|
+
resolveEffectiveAccess,
|
|
798
871
|
resolveError,
|
|
799
872
|
resolveMenuVisibility,
|
|
800
|
-
withAgriLogging
|
|
801
|
-
withSaveMiddleware
|
|
873
|
+
withAgriLogging
|
|
802
874
|
};
|