@litusarchieve18/agricore-utils 1.0.4 → 1.0.6
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 +5 -6
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +27 -11
- package/dist/index.mjs +27 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @litusarchieve18/agricore-utils
|
|
|
14
14
|
|
|
15
15
|
For Base44 / Deno Environments
|
|
16
16
|
You do not need a .npmrc or GITHUB_TOKEN to use this library in Base44. Import it directly using the npm: protocol:
|
|
17
|
-
import { AgriLogger, AppError, MOD } from 'npm:@litusarchieve18/agricore-utils@^1.0.
|
|
17
|
+
import { AgriLogger, AppError, MOD } from 'npm:@litusarchieve18/agricore-utils@^1.0.4';
|
|
18
18
|
|
|
19
19
|
🏗️ Core Architecture & Features
|
|
20
20
|
This library is designed with the "Adapter Pattern" in mind. It defines strict contracts (AgriCoreSession) so that your business logic remains completely isolated from the underlying Base44 database infrastructure.
|
|
@@ -23,7 +23,7 @@ This library is designed with the "Adapter Pattern" in mind. It defines strict c
|
|
|
23
23
|
The universal contract for authenticated users. The v2 upgrade includes injected UI-ready navigation data (availableScopes) and tenant configurations to prevent unnecessary frontend API calls.
|
|
24
24
|
|
|
25
25
|
TypeScript
|
|
26
|
-
import { AgriCoreSession } from 'npm:@litusarchieve18/agricore-utils@^1.0.
|
|
26
|
+
import { AgriCoreSession } from 'npm:@litusarchieve18/agricore-utils@^1.0.4';
|
|
27
27
|
|
|
28
28
|
// Example of the v2 structure:
|
|
29
29
|
const session: AgriCoreSession = {
|
|
@@ -62,10 +62,6 @@ 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
65
|
🔐 Security & Access Control
|
|
70
66
|
The library now includes a centralized logic layer to enforce granular permissions across all AgriCore services.
|
|
71
67
|
|
|
@@ -121,7 +117,10 @@ Run the publish command:
|
|
|
121
117
|
Bash
|
|
122
118
|
npm publish --access public
|
|
123
119
|
📝 Version History
|
|
120
|
+
v1.0.4 — refactor the code and edit readme
|
|
121
|
+
|
|
124
122
|
v1.0.4 — add generateInfrastructureFields, security access control and multi tenant boundary
|
|
123
|
+
|
|
125
124
|
v1.0.3 — add the base44Id
|
|
126
125
|
|
|
127
126
|
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.
|
package/dist/index.d.mts
CHANGED
|
@@ -506,7 +506,8 @@ declare function evaluateBaseAccess(resourcePath: string, session: {
|
|
|
506
506
|
declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
|
|
507
507
|
targetUserId?: string;
|
|
508
508
|
authScopeId?: string;
|
|
509
|
+
expectedScopeType?: 'company' | 'legalEntity' | 'facility';
|
|
509
510
|
}): Promise<boolean>;
|
|
510
|
-
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType): AccessLevelType;
|
|
511
|
+
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType | null): AccessLevelType;
|
|
511
512
|
|
|
512
513
|
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
|
@@ -506,7 +506,8 @@ declare function evaluateBaseAccess(resourcePath: string, session: {
|
|
|
506
506
|
declare function assertTenantBoundary(db: any, requesterCompanyId: string, targets: {
|
|
507
507
|
targetUserId?: string;
|
|
508
508
|
authScopeId?: string;
|
|
509
|
+
expectedScopeType?: 'company' | 'legalEntity' | 'facility';
|
|
509
510
|
}): Promise<boolean>;
|
|
510
|
-
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType): AccessLevelType;
|
|
511
|
+
declare function resolveEffectiveAccess(base: AccessLevelType, override?: AccessLevelType | null): AccessLevelType;
|
|
511
512
|
|
|
512
513
|
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
|
@@ -873,27 +873,43 @@ function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
|
873
873
|
return isHighRole ? "WRITE" : "READ";
|
|
874
874
|
}
|
|
875
875
|
async function assertTenantBoundary(db, requesterCompanyId, targets) {
|
|
876
|
-
if (!requesterCompanyId)
|
|
876
|
+
if (!requesterCompanyId) {
|
|
877
|
+
throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context for boundary check.");
|
|
878
|
+
}
|
|
877
879
|
if (targets.targetUserId) {
|
|
878
|
-
const
|
|
880
|
+
const targetUserAssignments = await db.UserRoleAssignment.filter({
|
|
879
881
|
userId: targets.targetUserId,
|
|
880
882
|
companyId: requesterCompanyId
|
|
881
883
|
});
|
|
882
|
-
if (
|
|
884
|
+
if (targetUserAssignments.length === 0) {
|
|
885
|
+
console.error(`[SECURITY BREACH ATTEMPT] User tried to modify targetUserId ${targets.targetUserId} outside their company ${requesterCompanyId}`);
|
|
886
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user or tenant boundary violation.");
|
|
887
|
+
}
|
|
883
888
|
}
|
|
884
889
|
if (targets.authScopeId) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
db.
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if (!
|
|
890
|
+
let matchFound = false;
|
|
891
|
+
if (targets.expectedScopeType === "company" || !targets.expectedScopeType) {
|
|
892
|
+
const c = await db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId });
|
|
893
|
+
if (c.length > 0) matchFound = true;
|
|
894
|
+
}
|
|
895
|
+
if (!matchFound && (targets.expectedScopeType === "legalEntity" || !targets.expectedScopeType)) {
|
|
896
|
+
const le = await db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
|
|
897
|
+
if (le.length > 0) matchFound = true;
|
|
898
|
+
}
|
|
899
|
+
if (!matchFound && (targets.expectedScopeType === "facility" || !targets.expectedScopeType)) {
|
|
900
|
+
const fac = await db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
|
|
901
|
+
if (fac.length > 0) matchFound = true;
|
|
902
|
+
}
|
|
903
|
+
if (!matchFound) {
|
|
904
|
+
console.error(`[SECURITY BREACH/VALIDATION ERROR] Scope ${targets.authScopeId} not found for type ${targets.expectedScopeType} in company ${requesterCompanyId}`);
|
|
905
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level or tenant boundary violation.");
|
|
906
|
+
}
|
|
891
907
|
}
|
|
892
908
|
return true;
|
|
893
909
|
}
|
|
894
910
|
function resolveEffectiveAccess(base, override) {
|
|
895
|
-
if (
|
|
896
|
-
return
|
|
911
|
+
if (override === void 0 || override === null) return base;
|
|
912
|
+
return override;
|
|
897
913
|
}
|
|
898
914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
899
915
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -779,27 +779,43 @@ function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
|
779
779
|
return isHighRole ? "WRITE" : "READ";
|
|
780
780
|
}
|
|
781
781
|
async function assertTenantBoundary(db, requesterCompanyId, targets) {
|
|
782
|
-
if (!requesterCompanyId)
|
|
782
|
+
if (!requesterCompanyId) {
|
|
783
|
+
throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context for boundary check.");
|
|
784
|
+
}
|
|
783
785
|
if (targets.targetUserId) {
|
|
784
|
-
const
|
|
786
|
+
const targetUserAssignments = await db.UserRoleAssignment.filter({
|
|
785
787
|
userId: targets.targetUserId,
|
|
786
788
|
companyId: requesterCompanyId
|
|
787
789
|
});
|
|
788
|
-
if (
|
|
790
|
+
if (targetUserAssignments.length === 0) {
|
|
791
|
+
console.error(`[SECURITY BREACH ATTEMPT] User tried to modify targetUserId ${targets.targetUserId} outside their company ${requesterCompanyId}`);
|
|
792
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user or tenant boundary violation.");
|
|
793
|
+
}
|
|
789
794
|
}
|
|
790
795
|
if (targets.authScopeId) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
db.
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (!
|
|
796
|
+
let matchFound = false;
|
|
797
|
+
if (targets.expectedScopeType === "company" || !targets.expectedScopeType) {
|
|
798
|
+
const c = await db.Company.filter({ canonicalId: targets.authScopeId, id: requesterCompanyId });
|
|
799
|
+
if (c.length > 0) matchFound = true;
|
|
800
|
+
}
|
|
801
|
+
if (!matchFound && (targets.expectedScopeType === "legalEntity" || !targets.expectedScopeType)) {
|
|
802
|
+
const le = await db.LegalEntity.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
|
|
803
|
+
if (le.length > 0) matchFound = true;
|
|
804
|
+
}
|
|
805
|
+
if (!matchFound && (targets.expectedScopeType === "facility" || !targets.expectedScopeType)) {
|
|
806
|
+
const fac = await db.Facility.filter({ canonicalId: targets.authScopeId, companyId: requesterCompanyId });
|
|
807
|
+
if (fac.length > 0) matchFound = true;
|
|
808
|
+
}
|
|
809
|
+
if (!matchFound) {
|
|
810
|
+
console.error(`[SECURITY BREACH/VALIDATION ERROR] Scope ${targets.authScopeId} not found for type ${targets.expectedScopeType} in company ${requesterCompanyId}`);
|
|
811
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level or tenant boundary violation.");
|
|
812
|
+
}
|
|
797
813
|
}
|
|
798
814
|
return true;
|
|
799
815
|
}
|
|
800
816
|
function resolveEffectiveAccess(base, override) {
|
|
801
|
-
if (
|
|
802
|
-
return
|
|
817
|
+
if (override === void 0 || override === null) return base;
|
|
818
|
+
return override;
|
|
803
819
|
}
|
|
804
820
|
export {
|
|
805
821
|
ACCESS_RANK,
|