@carlonicora/nestjs-neo4jsonapi 1.63.0 → 1.64.0
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/dist/bootstrap/bootstrap.options.d.ts +7 -0
- package/dist/bootstrap/bootstrap.options.d.ts.map +1 -1
- package/dist/foundations/rbac/controllers/rbac-dev.controller.d.ts +50 -0
- package/dist/foundations/rbac/controllers/rbac-dev.controller.d.ts.map +1 -0
- package/dist/foundations/rbac/controllers/rbac-dev.controller.js +172 -0
- package/dist/foundations/rbac/controllers/rbac-dev.controller.js.map +1 -0
- package/dist/foundations/rbac/dsl/define-rbac.d.ts +15 -0
- package/dist/foundations/rbac/dsl/define-rbac.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/define-rbac.js +19 -0
- package/dist/foundations/rbac/dsl/define-rbac.js.map +1 -0
- package/dist/foundations/rbac/dsl/index.d.ts +6 -0
- package/dist/foundations/rbac/dsl/index.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/index.js +30 -0
- package/dist/foundations/rbac/dsl/index.js.map +1 -0
- package/dist/foundations/rbac/dsl/perm.d.ts +15 -0
- package/dist/foundations/rbac/dsl/perm.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/perm.js +24 -0
- package/dist/foundations/rbac/dsl/perm.js.map +1 -0
- package/dist/foundations/rbac/dsl/resolver.d.ts +24 -0
- package/dist/foundations/rbac/dsl/resolver.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/resolver.js +55 -0
- package/dist/foundations/rbac/dsl/resolver.js.map +1 -0
- package/dist/foundations/rbac/dsl/to-permissions-json.d.ts +13 -0
- package/dist/foundations/rbac/dsl/to-permissions-json.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/to-permissions-json.js +43 -0
- package/dist/foundations/rbac/dsl/to-permissions-json.js.map +1 -0
- package/dist/foundations/rbac/dsl/types.d.ts +55 -0
- package/dist/foundations/rbac/dsl/types.d.ts.map +1 -0
- package/dist/foundations/rbac/dsl/types.js +6 -0
- package/dist/foundations/rbac/dsl/types.js.map +1 -0
- package/dist/foundations/rbac/dump.d.ts +116 -0
- package/dist/foundations/rbac/dump.d.ts.map +1 -0
- package/dist/foundations/rbac/dump.js +154 -0
- package/dist/foundations/rbac/dump.js.map +1 -0
- package/dist/foundations/rbac/index.d.ts +6 -0
- package/dist/foundations/rbac/index.d.ts.map +1 -1
- package/dist/foundations/rbac/index.js +23 -1
- package/dist/foundations/rbac/index.js.map +1 -1
- package/dist/foundations/rbac/rbac.module.d.ts +4 -1
- package/dist/foundations/rbac/rbac.module.d.ts.map +1 -1
- package/dist/foundations/rbac/rbac.module.js +25 -11
- package/dist/foundations/rbac/rbac.module.js.map +1 -1
- package/dist/foundations/rbac/rbac.tokens.d.ts +11 -0
- package/dist/foundations/rbac/rbac.tokens.d.ts.map +1 -0
- package/dist/foundations/rbac/rbac.tokens.js +14 -0
- package/dist/foundations/rbac/rbac.tokens.js.map +1 -0
- package/dist/foundations/rbac/serializer/matrix-to-ts.d.ts +13 -0
- package/dist/foundations/rbac/serializer/matrix-to-ts.d.ts.map +1 -0
- package/dist/foundations/rbac/serializer/matrix-to-ts.js +74 -0
- package/dist/foundations/rbac/serializer/matrix-to-ts.js.map +1 -0
- package/dist/foundations/rbac/services/rbac-reconciler.service.d.ts +30 -0
- package/dist/foundations/rbac/services/rbac-reconciler.service.d.ts.map +1 -0
- package/dist/foundations/rbac/services/rbac-reconciler.service.js +192 -0
- package/dist/foundations/rbac/services/rbac-reconciler.service.js.map +1 -0
- package/dist/tools/generate-rbac-paths/index.js +24 -19
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.RbacReconcilerService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const neo4j_service_1 = require("../../../core/neo4j/services/neo4j.service");
|
|
18
|
+
const logging_service_1 = require("../../../core/logging/services/logging.service");
|
|
19
|
+
const system_roles_1 = require("../../../common/constants/system.roles");
|
|
20
|
+
const rbac_tokens_1 = require("../rbac.tokens");
|
|
21
|
+
const resolver_1 = require("../dsl/resolver");
|
|
22
|
+
const ADMINISTRATOR_ID = system_roles_1.SystemRoles.Administrator;
|
|
23
|
+
/**
|
|
24
|
+
* Applies the declared `RbacMatrix` to Neo4j at application bootstrap.
|
|
25
|
+
*
|
|
26
|
+
* Behaviour (per spec §6.3):
|
|
27
|
+
* - Administrator role is never written as an HAS_PERMISSIONS edge — the
|
|
28
|
+
* security layer short-circuits for it.
|
|
29
|
+
* - Module defaults are stored on `Module.permissions`.
|
|
30
|
+
* - Role-specific permissions are stored on `(Role)-[:HAS_PERMISSIONS]->(Module)`.
|
|
31
|
+
* - Deletions are scoped to modules declared in the matrix: edges for
|
|
32
|
+
* undeclared modules are left untouched.
|
|
33
|
+
* - Preflight aborts with a clear error if any referenced role or module is
|
|
34
|
+
* missing from the DB (seed migrations must run first).
|
|
35
|
+
*/
|
|
36
|
+
let RbacReconcilerService = class RbacReconcilerService {
|
|
37
|
+
constructor(neo4j, matrix, logger) {
|
|
38
|
+
this.neo4j = neo4j;
|
|
39
|
+
this.matrix = matrix;
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
}
|
|
42
|
+
async onApplicationBootstrap() {
|
|
43
|
+
if (!this.matrix) {
|
|
44
|
+
this.logger.log("RBAC reconciler: no matrix configured, skipping");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await this.preflight();
|
|
48
|
+
const actual = await this.readActualState();
|
|
49
|
+
const diff = this.computeDiff(actual);
|
|
50
|
+
if (diff.operations.length === 0) {
|
|
51
|
+
this.logger.log("RBAC reconcile: no changes");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await this.apply(diff.operations);
|
|
55
|
+
this.logger.log(`RBAC reconcile: ${diff.defaultsChanged} defaults changed, ${diff.edgesUpserted} edges upserted, ${diff.edgesRemoved} edges removed`);
|
|
56
|
+
}
|
|
57
|
+
async preflight() {
|
|
58
|
+
const moduleIds = new Set();
|
|
59
|
+
const roleIds = new Set();
|
|
60
|
+
for (const moduleId of (0, resolver_1.iterateDeclaredModules)(this.matrix)) {
|
|
61
|
+
moduleIds.add(moduleId);
|
|
62
|
+
}
|
|
63
|
+
for (const { roleId } of (0, resolver_1.iterateDeclaredEdges)(this.matrix)) {
|
|
64
|
+
if (roleId === ADMINISTRATOR_ID)
|
|
65
|
+
continue;
|
|
66
|
+
roleIds.add(roleId);
|
|
67
|
+
}
|
|
68
|
+
const missingRoles = await this.findMissing("Role", Array.from(roleIds));
|
|
69
|
+
const missingModules = await this.findMissing("Module", Array.from(moduleIds));
|
|
70
|
+
if (missingRoles.length > 0 || missingModules.length > 0) {
|
|
71
|
+
throw new Error(`RBAC reconcile aborted - referenced entities not found in DB. ` +
|
|
72
|
+
`Roles missing: ${missingRoles.join(", ") || "none"}. ` +
|
|
73
|
+
`Modules missing: ${missingModules.join(", ") || "none"}. ` +
|
|
74
|
+
`Apply seed migrations before declaring these in the matrix.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async findMissing(label, ids) {
|
|
78
|
+
if (ids.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const result = await this.neo4j.read(`MATCH (n:${label}) WHERE n.id IN $ids RETURN n.id AS id`, { ids });
|
|
81
|
+
const found = new Set(result.records.map((r) => r.get("id")));
|
|
82
|
+
return ids.filter((id) => !found.has(id));
|
|
83
|
+
}
|
|
84
|
+
async readActualState() {
|
|
85
|
+
const modulesResult = await this.neo4j.read(`MATCH (m:Module) RETURN m.id AS id, m.permissions AS permissions`, {});
|
|
86
|
+
const edgesResult = await this.neo4j.read(`MATCH (r:Role)-[p:HAS_PERMISSIONS]->(m:Module) RETURN r.id AS roleId, m.id AS moduleId, p.permissions AS permissions`, {});
|
|
87
|
+
const moduleDefaults = {};
|
|
88
|
+
for (const rec of modulesResult.records) {
|
|
89
|
+
moduleDefaults[rec.get("id")] = rec.get("permissions");
|
|
90
|
+
}
|
|
91
|
+
const edges = [];
|
|
92
|
+
for (const rec of edgesResult.records) {
|
|
93
|
+
edges.push({
|
|
94
|
+
roleId: rec.get("roleId"),
|
|
95
|
+
moduleId: rec.get("moduleId"),
|
|
96
|
+
permissions: rec.get("permissions"),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return { moduleDefaults, edges };
|
|
100
|
+
}
|
|
101
|
+
computeDiff(actual) {
|
|
102
|
+
const operations = [];
|
|
103
|
+
let defaultsChanged = 0;
|
|
104
|
+
let edgesUpserted = 0;
|
|
105
|
+
let edgesRemoved = 0;
|
|
106
|
+
const declaredModules = new Set(Array.from((0, resolver_1.iterateDeclaredModules)(this.matrix)));
|
|
107
|
+
const sortedModules = Array.from(declaredModules).sort();
|
|
108
|
+
// Defaults
|
|
109
|
+
for (const moduleId of sortedModules) {
|
|
110
|
+
const expected = (0, resolver_1.resolveDefault)(this.matrix, moduleId);
|
|
111
|
+
if (expected === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
if (actual.moduleDefaults[moduleId] !== expected) {
|
|
114
|
+
operations.push({ kind: "setDefault", params: { moduleId, permissions: expected } });
|
|
115
|
+
defaultsChanged++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Edges — expected (Administrator excluded)
|
|
119
|
+
const expectedEdgeKeys = new Set();
|
|
120
|
+
const sortedEdges = Array.from((0, resolver_1.iterateDeclaredEdges)(this.matrix))
|
|
121
|
+
.filter((e) => e.roleId !== ADMINISTRATOR_ID)
|
|
122
|
+
.sort((a, b) => (a.moduleId + a.roleId).localeCompare(b.moduleId + b.roleId));
|
|
123
|
+
const actualByKey = new Map();
|
|
124
|
+
for (const edge of actual.edges) {
|
|
125
|
+
actualByKey.set(`${edge.roleId}|${edge.moduleId}`, edge.permissions);
|
|
126
|
+
}
|
|
127
|
+
for (const { roleId, moduleId } of sortedEdges) {
|
|
128
|
+
const expected = (0, resolver_1.resolveForRole)(this.matrix, roleId, moduleId);
|
|
129
|
+
if (expected === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
const key = `${roleId}|${moduleId}`;
|
|
132
|
+
expectedEdgeKeys.add(key);
|
|
133
|
+
if (actualByKey.get(key) !== expected) {
|
|
134
|
+
operations.push({
|
|
135
|
+
kind: "upsertEdge",
|
|
136
|
+
params: { roleId, moduleId, permissions: expected },
|
|
137
|
+
});
|
|
138
|
+
edgesUpserted++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Edges — to delete (scoped to declared modules only; Administrator never managed)
|
|
142
|
+
for (const edge of actual.edges) {
|
|
143
|
+
if (!declaredModules.has(edge.moduleId))
|
|
144
|
+
continue;
|
|
145
|
+
if (edge.roleId === ADMINISTRATOR_ID)
|
|
146
|
+
continue;
|
|
147
|
+
const key = `${edge.roleId}|${edge.moduleId}`;
|
|
148
|
+
if (!expectedEdgeKeys.has(key)) {
|
|
149
|
+
operations.push({
|
|
150
|
+
kind: "deleteEdge",
|
|
151
|
+
params: { roleId: edge.roleId, moduleId: edge.moduleId },
|
|
152
|
+
});
|
|
153
|
+
edgesRemoved++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { operations, defaultsChanged, edgesUpserted, edgesRemoved };
|
|
157
|
+
}
|
|
158
|
+
async apply(operations) {
|
|
159
|
+
const queries = operations.map((op) => {
|
|
160
|
+
if (op.kind === "setDefault") {
|
|
161
|
+
return {
|
|
162
|
+
query: `MATCH (m:Module {id: $moduleId}) SET m.permissions = $permissions`,
|
|
163
|
+
params: op.params,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (op.kind === "upsertEdge") {
|
|
167
|
+
return {
|
|
168
|
+
query: `MATCH (role:Role {id: $roleId}) ` +
|
|
169
|
+
`MATCH (module:Module {id: $moduleId}) ` +
|
|
170
|
+
`MERGE (role)-[permissions:HAS_PERMISSIONS]->(module) ` +
|
|
171
|
+
`ON CREATE SET permissions.permissions = $permissions ` +
|
|
172
|
+
`ON MATCH SET permissions.permissions = $permissions`,
|
|
173
|
+
params: op.params,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// deleteEdge
|
|
177
|
+
return {
|
|
178
|
+
query: `MATCH (role:Role {id: $roleId})-[p:HAS_PERMISSIONS]->(module:Module {id: $moduleId}) DELETE p`,
|
|
179
|
+
params: op.params,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
await this.neo4j.executeInTransaction(queries);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
exports.RbacReconcilerService = RbacReconcilerService;
|
|
186
|
+
exports.RbacReconcilerService = RbacReconcilerService = __decorate([
|
|
187
|
+
(0, common_1.Injectable)(),
|
|
188
|
+
__param(1, (0, common_1.Optional)()),
|
|
189
|
+
__param(1, (0, common_1.Inject)(rbac_tokens_1.RBAC_MATRIX_TOKEN)),
|
|
190
|
+
__metadata("design:paramtypes", [neo4j_service_1.Neo4jService, Object, logging_service_1.AppLoggingService])
|
|
191
|
+
], RbacReconcilerService);
|
|
192
|
+
//# sourceMappingURL=rbac-reconciler.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac-reconciler.service.js","sourceRoot":"","sources":["../../../../src/foundations/rbac/services/rbac-reconciler.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,7 @@ program
|
|
|
50
50
|
.option("--output <path>", "Output file path", "src/features/rbac/module-relationships.map.ts")
|
|
51
51
|
.option("--skip <names>", "Comma-separated entity names to skip", "")
|
|
52
52
|
.option("--max-depth <n>", "BFS max depth", "4")
|
|
53
|
+
.option("--module-id-map <path>", "Path to labelName→UUID JSON map (required for UUID-keyed output)", "")
|
|
53
54
|
.parse();
|
|
54
55
|
function scanDescriptors(dir, skipEntities) {
|
|
55
56
|
const entities = [];
|
|
@@ -141,19 +142,6 @@ function bfsToUser(graph, startEntity, maxDepth) {
|
|
|
141
142
|
}
|
|
142
143
|
return paths;
|
|
143
144
|
}
|
|
144
|
-
function generateOutput(moduleUserPaths) {
|
|
145
|
-
const entries = Object.entries(moduleUserPaths)
|
|
146
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
147
|
-
.map(([key, paths]) => ` ${JSON.stringify(key)}: ${JSON.stringify(paths)}`)
|
|
148
|
-
.join(",\n");
|
|
149
|
-
return `// Auto-generated by generate-rbac-paths CLI tool
|
|
150
|
-
// Do not edit manually - regenerate with: pnpm generate:rbac-paths
|
|
151
|
-
|
|
152
|
-
export const MODULE_USER_PATHS: Record<string, string[]> = {
|
|
153
|
-
${entries}
|
|
154
|
-
};
|
|
155
|
-
`;
|
|
156
|
-
}
|
|
157
145
|
async function main() {
|
|
158
146
|
const options = program.opts();
|
|
159
147
|
const dir = options.dir;
|
|
@@ -164,31 +152,48 @@ async function main() {
|
|
|
164
152
|
.map((s) => s.trim())
|
|
165
153
|
.filter(Boolean);
|
|
166
154
|
const skipEntities = [...DEFAULT_SKIP_ENTITIES, ...extraSkip];
|
|
155
|
+
const moduleIdMapPath = options.moduleIdMap;
|
|
156
|
+
if (!moduleIdMapPath) {
|
|
157
|
+
console.error("--module-id-map is required. See apps/api/scripts/build-module-id-map.ts.");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const moduleIdMap = JSON.parse(fs.readFileSync(moduleIdMapPath, "utf8"));
|
|
167
161
|
console.info("\nRBAC Path Generation CLI");
|
|
168
162
|
console.info("========================\n");
|
|
169
163
|
console.info(`Scanning: ${dir}`);
|
|
170
164
|
console.info(`Output: ${output}`);
|
|
171
165
|
console.info(`Max depth: ${maxDepth}`);
|
|
166
|
+
console.info(`Module id map: ${moduleIdMapPath}`);
|
|
172
167
|
console.info(`Skip entities: ${skipEntities.join(", ")}\n`);
|
|
173
168
|
const entities = scanDescriptors(dir, skipEntities);
|
|
174
169
|
console.info(`Found ${entities.length} entities\n`);
|
|
175
170
|
const graph = buildGraph(entities);
|
|
176
|
-
const
|
|
171
|
+
const entitiesByLabel = new Map();
|
|
172
|
+
const pathsByEntity = new Map();
|
|
177
173
|
for (const entity of entities) {
|
|
178
|
-
|
|
174
|
+
entitiesByLabel.set(entity.labelName, entity);
|
|
179
175
|
const paths = bfsToUser(graph, entity.labelName, maxDepth);
|
|
176
|
+
pathsByEntity.set(entity.labelName, paths);
|
|
180
177
|
if (paths.length > 0) {
|
|
181
|
-
|
|
182
|
-
console.info(` ${normalizedName}: [${paths.join(", ")}]`);
|
|
178
|
+
console.info(` ${entity.labelName}: [${paths.join(", ")}]`);
|
|
183
179
|
}
|
|
184
180
|
}
|
|
185
|
-
|
|
181
|
+
// Ensure every mapped module has an entry (possibly empty)
|
|
182
|
+
const finalMap = {};
|
|
183
|
+
for (const [labelName, uuid] of Object.entries(moduleIdMap)) {
|
|
184
|
+
const entity = entitiesByLabel.get(labelName);
|
|
185
|
+
finalMap[uuid] = entity ? Array.from(pathsByEntity.get(labelName) ?? []) : [];
|
|
186
|
+
}
|
|
187
|
+
const content = `// Auto-generated by generate-rbac-paths CLI tool\n` +
|
|
188
|
+
`// Do not edit manually - regenerate with: pnpm generate:rbac-paths\n\n` +
|
|
189
|
+
`export const MODULE_USER_PATHS = ${JSON.stringify(finalMap, null, 2)} as const;\n\n` +
|
|
190
|
+
`export type ModuleUserPathsType = typeof MODULE_USER_PATHS;\n`;
|
|
186
191
|
const outputDir = path.dirname(output);
|
|
187
192
|
if (!fs.existsSync(outputDir)) {
|
|
188
193
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
189
194
|
}
|
|
190
195
|
fs.writeFileSync(output, content, "utf-8");
|
|
191
|
-
console.info(`\nGenerated ${output} with ${Object.keys(
|
|
196
|
+
console.info(`\nGenerated ${output} with ${Object.keys(finalMap).length} module entries\n`);
|
|
192
197
|
}
|
|
193
198
|
main().catch((error) => {
|
|
194
199
|
console.error("\nGeneration failed:", error.message);
|
package/package.json
CHANGED