endpoint-permissions-kit 0.1.0 → 0.2.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/README.md +27 -20
- package/bin/pkit.mjs +2 -2
- package/dist/cjs/index.cjs +716 -473
- package/dist/cjs/index.cjs.map +16 -14
- package/dist/esm/cli/child.js +131 -376
- package/dist/esm/cli/child.js.map +10 -11
- package/dist/esm/cli/generate.js +78 -420
- package/dist/esm/cli/generate.js.map +5 -9
- package/dist/esm/index.js +716 -473
- package/dist/esm/index.js.map +16 -14
- package/dist/types/cli/generate.d.ts +6 -4
- package/dist/types/cli/protocol.d.ts +10 -7
- package/dist/types/constants.d.ts +9 -6
- package/dist/types/context.d.ts +5 -6
- package/dist/types/definitions.d.ts +15 -0
- package/dist/types/errors.d.ts +12 -11
- package/dist/types/identifiers.d.ts +21 -0
- package/dist/types/index.d.ts +16 -17
- package/dist/types/permissions.d.ts +10 -5
- package/dist/types/properties.d.ts +17 -0
- package/dist/types/registry.d.ts +4 -1
- package/dist/types/resolve.d.ts +50 -6
- package/dist/types/seal.d.ts +8 -1
- package/dist/types/state.d.ts +12 -9
- package/dist/types/types.d.ts +25 -34
- package/dist/types/validate.d.ts +10 -3
- package/package.json +1 -1
- package/dist/types/validators.d.ts +0 -53
package/dist/esm/cli/generate.js
CHANGED
|
@@ -6,375 +6,70 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { parseArgs, promisify } from "node:util";
|
|
7
7
|
|
|
8
8
|
// src/cli/protocol.ts
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var
|
|
16
|
-
var GENERAL_ROLE = "general";
|
|
17
|
-
var GLOBAL_HOOK_OWNER = "*";
|
|
18
|
-
var ALL_FIELDS = "*";
|
|
19
|
-
var MODULE_SEPARATOR = ".";
|
|
20
|
-
var PERMISSION_ID_SEPARATOR = "::";
|
|
21
|
-
|
|
22
|
-
// src/errors.ts
|
|
23
|
-
function pkitError(code, message) {
|
|
24
|
-
return Object.assign(new Error(message), { name: "PkitError", code });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/resolve.ts
|
|
28
|
-
var DIRECT_AUTHORIZATION = Object.freeze({ direct: true, grantedBy: Object.freeze([]) });
|
|
29
|
-
var DISABLED_ACCESS = Object.freeze({ status: "disabled" });
|
|
30
|
-
var UNASSIGNED_ACCESS = Object.freeze({ status: "unassigned" });
|
|
31
|
-
function permissionIdOf(role, action, name) {
|
|
32
|
-
return [role, action, name].join(PERMISSION_ID_SEPARATOR);
|
|
33
|
-
}
|
|
34
|
-
function resolveAccess(nameEntry, permissionId, role, method, assigned) {
|
|
35
|
-
if (assigned.has(permissionId))
|
|
36
|
-
return resolveDirectAccess(nameEntry, role, method);
|
|
37
|
-
return resolveGrantedAccess(nameEntry, method, assigned);
|
|
38
|
-
}
|
|
39
|
-
function resolveDirectAccess(nameEntry, role, method) {
|
|
40
|
-
const definition = nameEntry.actions.get(role)?.[method];
|
|
41
|
-
if (!definition?.enabled)
|
|
42
|
-
return DISABLED_ACCESS;
|
|
43
|
-
return { status: "granted", properties: definition.properties, authorization: DIRECT_AUTHORIZATION };
|
|
44
|
-
}
|
|
45
|
-
function resolveGrantedAccess(nameEntry, method, assigned) {
|
|
46
|
-
const grantedBy = [];
|
|
47
|
-
const properties = new Set;
|
|
48
|
-
let reachable = false;
|
|
49
|
-
for (const [enablingId, grant] of nameEntry.grants) {
|
|
50
|
-
if (!assigned.has(enablingId))
|
|
51
|
-
continue;
|
|
52
|
-
reachable = true;
|
|
53
|
-
const definition = grant.actions[method];
|
|
54
|
-
if (!definition)
|
|
55
|
-
continue;
|
|
56
|
-
grantedBy.push(enablingId);
|
|
57
|
-
for (const field of definition.properties)
|
|
58
|
-
properties.add(field);
|
|
59
|
-
}
|
|
60
|
-
if (grantedBy.length === 0)
|
|
61
|
-
return reachable ? DISABLED_ACCESS : UNASSIGNED_ACCESS;
|
|
62
|
-
const authorization = Object.freeze({ direct: false, grantedBy: Object.freeze(grantedBy.sort()) });
|
|
63
|
-
return { status: "granted", properties: Object.freeze([...properties]), authorization };
|
|
64
|
-
}
|
|
9
|
+
var protocol = {
|
|
10
|
+
EXIT_CODE: { success: 0, failure: 1, usage: 2 },
|
|
11
|
+
CATALOG_MARKER: "__PKIT_CATALOG__",
|
|
12
|
+
CHILD_TIMEOUT_MS: 30000,
|
|
13
|
+
CHILD_MAX_BUFFER_BYTES: 1048576
|
|
14
|
+
};
|
|
15
|
+
var protocol_default = protocol;
|
|
65
16
|
|
|
66
|
-
// src/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
function validateOpenRegistry(state) {
|
|
86
|
-
if (state.snapshot)
|
|
87
|
-
throw pkitError("SEALED", "pkit.seal() was already called: no more registrations allowed");
|
|
88
|
-
}
|
|
89
|
-
function validateSnapshot(snapshot) {
|
|
90
|
-
if (snapshot === null) {
|
|
91
|
-
throw pkitError("NOT_SEALED", "pkit.seal() has not been called: call it after importing every permission file");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function validateRole(role, roles, code) {
|
|
95
|
-
if (typeof role === "string" && roles.has(role))
|
|
96
|
-
return;
|
|
97
|
-
if (code === "ROLE_NOT_DECLARED") {
|
|
98
|
-
throw pkitError(code, `role "${String(role)}" is not declared. Available roles: ${[...roles].join(", ")}.
|
|
99
|
-
` + "Is pkit.context.set('roles', [...]) missing from pkit.config.js, or was it imported after this file?");
|
|
100
|
-
}
|
|
101
|
-
throw pkitError(code, `role "${String(role)}" is not declared`);
|
|
102
|
-
}
|
|
103
|
-
function validateRoleCatalog(roles, state) {
|
|
104
|
-
validateOpenRegistry(state);
|
|
105
|
-
if (state.modules.size) {
|
|
106
|
-
throw pkitError("INVALID_DEFINITION", "roles must be declared before registering permissions: import pkit.config.js first");
|
|
107
|
-
}
|
|
108
|
-
validateStrings(roles, {
|
|
109
|
-
code: "INVALID_DEFINITION",
|
|
110
|
-
message: "roles must be an array of non-empty strings",
|
|
111
|
-
minimumLength: 1
|
|
112
|
-
});
|
|
113
|
-
if (roles.length === 0)
|
|
114
|
-
throw pkitError("INVALID_DEFINITION", "roles must declare at least one role");
|
|
115
|
-
for (const role of roles)
|
|
116
|
-
validateRoleSegment(role);
|
|
117
|
-
}
|
|
118
|
-
function isCleanSegment(value) {
|
|
119
|
-
return value.length > 0 && value.trim() === value && !value.includes(":");
|
|
120
|
-
}
|
|
121
|
-
function validateRoleSegment(role) {
|
|
122
|
-
if (typeof role === "string" && isCleanSegment(role) && role !== GLOBAL_HOOK_OWNER)
|
|
123
|
-
return;
|
|
124
|
-
throw pkitError("INVALID_DEFINITION", `invalid role: "${String(role)}" (non-empty string, no ":" or surrounding whitespace; "${GLOBAL_HOOK_OWNER}" is reserved for global hooks)`);
|
|
125
|
-
}
|
|
126
|
-
function validateModuleName(moduleName) {
|
|
127
|
-
if (typeof moduleName === "string" && isCleanSegment(moduleName) && !moduleName.includes(MODULE_SEPARATOR))
|
|
128
|
-
return;
|
|
129
|
-
throw pkitError("INVALID_DEFINITION", `invalid module name: "${String(moduleName)}" (non-empty string, no dots, no ":" or surrounding whitespace)`);
|
|
130
|
-
}
|
|
131
|
-
function validatePermissionName(name) {
|
|
132
|
-
if (typeof name === "string" && isCleanSegment(name) && name !== GLOBAL_HOOK_OWNER)
|
|
133
|
-
return;
|
|
134
|
-
throw pkitError("INVALID_DEFINITION", `invalid permission name: "${String(name)}" (non-empty string, no ":" or surrounding whitespace; "*" is reserved)`);
|
|
135
|
-
}
|
|
136
|
-
function parsePermissionId(value, failure) {
|
|
137
|
-
if (typeof value !== "string")
|
|
138
|
-
throw pkitError(failure.code, failure.message);
|
|
139
|
-
const parts = value.split(PERMISSION_ID_SEPARATOR);
|
|
140
|
-
const [role, action, name] = parts;
|
|
141
|
-
if (parts.length !== 3 || role === undefined || action === undefined || name === undefined) {
|
|
142
|
-
throw pkitError(failure.code, failure.message);
|
|
143
|
-
}
|
|
144
|
-
const isValidRole = isCleanSegment(role) && role !== GLOBAL_HOOK_OWNER;
|
|
145
|
-
const isValidName = isCleanSegment(name) && name !== GLOBAL_HOOK_OWNER;
|
|
146
|
-
const isValidAction = action.split(MODULE_SEPARATOR).every(isCleanSegment);
|
|
147
|
-
if (!isValidRole || !isValidName || !isValidAction)
|
|
148
|
-
throw pkitError(failure.code, failure.message);
|
|
149
|
-
return { role, action, name };
|
|
150
|
-
}
|
|
151
|
-
function validateMethod(method, failure) {
|
|
152
|
-
if (METHODS.includes(method))
|
|
153
|
-
return;
|
|
154
|
-
throw pkitError(failure.code, `${failure.message}: method "${String(method)}" does not exist. Methods: ${METHODS.join(", ")}`);
|
|
155
|
-
}
|
|
156
|
-
function readActionDefinition(definition, permissionPath) {
|
|
157
|
-
validateObject(definition, { code: "INVALID_DEFINITION", message: `${permissionPath}: must be an object` });
|
|
158
|
-
if (typeof definition.enabled !== "boolean") {
|
|
159
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}: enabled must be a boolean`);
|
|
160
|
-
}
|
|
161
|
-
const { properties } = definition;
|
|
162
|
-
if (properties === ALL_FIELDS)
|
|
163
|
-
return Object.freeze({ enabled: definition.enabled, properties });
|
|
164
|
-
validateStrings(properties, {
|
|
165
|
-
code: "INVALID_DEFINITION",
|
|
166
|
-
message: `${permissionPath}: properties must be string[] or '*'`,
|
|
167
|
-
minimumLength: 1
|
|
168
|
-
});
|
|
169
|
-
if (properties.includes(ALL_FIELDS)) {
|
|
170
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}: '${ALL_FIELDS}' is only allowed as the whole properties value`);
|
|
171
|
-
}
|
|
172
|
-
if (new Set(properties).size !== properties.length) {
|
|
173
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}: properties contains duplicate fields`);
|
|
174
|
-
}
|
|
175
|
-
return Object.freeze({ enabled: definition.enabled, properties: Object.freeze([...properties]) });
|
|
176
|
-
}
|
|
177
|
-
function readActionDefinitions(actions, permissionPath) {
|
|
178
|
-
validateObject(actions, { code: "INVALID_DEFINITION", message: `${permissionPath}: registerActions expects an object` });
|
|
179
|
-
const registeredActions = Object.create(null);
|
|
180
|
-
for (const [method, definition] of Object.entries(actions)) {
|
|
181
|
-
validateMethod(method, { code: "INVALID_DEFINITION", message: permissionPath });
|
|
182
|
-
registeredActions[method] = readActionDefinition(definition, `${permissionPath}.${method}`);
|
|
183
|
-
}
|
|
184
|
-
return Object.freeze(registeredActions);
|
|
185
|
-
}
|
|
186
|
-
function validateActions(actions, registration, state) {
|
|
187
|
-
const { action, name, role } = registration;
|
|
188
|
-
validateOpenRegistry(state);
|
|
189
|
-
validateRole(role, state.roles, "ROLE_NOT_DECLARED");
|
|
190
|
-
if (state.modules.get(action)?.names.get(name)?.actions.has(role)) {
|
|
191
|
-
throw pkitError("DUPLICATE_REGISTRATION", `"${action}::${name}" already has actions registered for role "${role}"`);
|
|
192
|
-
}
|
|
193
|
-
return readActionDefinitions(actions, `${action}::${name} [${role}]`);
|
|
194
|
-
}
|
|
195
|
-
function validateGrantActions(actions, registration, state) {
|
|
196
|
-
const { action, name, permissionId } = registration;
|
|
197
|
-
validateOpenRegistry(state);
|
|
198
|
-
const source = parsePermissionId(permissionId, {
|
|
199
|
-
code: "INVALID_DEFINITION",
|
|
200
|
-
message: `"${action}::${name}": invalid grantTo identifier: "${String(permissionId)}" (format role::module::name)`
|
|
201
|
-
});
|
|
202
|
-
validateRole(source.role, state.roles, "ROLE_NOT_DECLARED");
|
|
203
|
-
if (source.action === action && source.name === name) {
|
|
204
|
-
throw pkitError("INVALID_DEFINITION", `"${permissionId}" cannot grant to itself`);
|
|
205
|
-
}
|
|
206
|
-
if (state.modules.get(action)?.names.get(name)?.grants.has(permissionId)) {
|
|
207
|
-
throw pkitError("DUPLICATE_REGISTRATION", `"${action}::${name}" already has a grant for "${permissionId}"`);
|
|
208
|
-
}
|
|
209
|
-
const permissionPath = `${action}::${name} [grantTo ${permissionId}]`;
|
|
210
|
-
const registeredActions = readActionDefinitions(actions, permissionPath);
|
|
211
|
-
for (const [method, definition] of Object.entries(registeredActions)) {
|
|
212
|
-
if (definition.enabled !== true) {
|
|
213
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}.${method}: a grant does not allow enabled: false`);
|
|
214
|
-
}
|
|
215
|
-
if (definition.properties === ALL_FIELDS) {
|
|
216
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}.${method}: a grant requires an explicit properties list`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return { source, actions: registeredActions };
|
|
220
|
-
}
|
|
221
|
-
function validateHook(hook, registration, state) {
|
|
222
|
-
const { action, name, role, method } = registration;
|
|
223
|
-
validateOpenRegistry(state);
|
|
224
|
-
if (role !== undefined && role !== GLOBAL_HOOK_OWNER)
|
|
225
|
-
validateRole(role, state.roles, "ROLE_NOT_DECLARED");
|
|
226
|
-
const permissionPath = name === undefined ? action : `${action}::${name}`;
|
|
227
|
-
validateMethod(method, { code: "INVALID_DEFINITION", message: permissionPath });
|
|
228
|
-
if (typeof hook !== "function")
|
|
229
|
-
throw pkitError("INVALID_DEFINITION", `${permissionPath}: hook("${method}") expects a function`);
|
|
230
|
-
}
|
|
231
|
-
function declaresMethod(nameEntry, method) {
|
|
232
|
-
for (const actions of nameEntry.actions.values()) {
|
|
233
|
-
if (Object.hasOwn(actions, method))
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
function hasAccessPath(nameEntry, role, method) {
|
|
239
|
-
if (nameEntry.actions.get(role)?.[method])
|
|
240
|
-
return true;
|
|
241
|
-
for (const grant of nameEntry.grants.values()) {
|
|
242
|
-
if (grant.source.role === role && grant.actions[method])
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
function validateGrantReferences(permissionPath, nameEntry, modules) {
|
|
248
|
-
for (const [permissionId, grant] of nameEntry.grants) {
|
|
249
|
-
const sourceEntry = modules.get(grant.source.action)?.names.get(grant.source.name);
|
|
250
|
-
if (!sourceEntry?.actions.has(grant.source.role)) {
|
|
251
|
-
throw pkitError("INVALID_DEFINITION", `"${permissionPath}": grantTo "${permissionId}" references a permission with no registered actions`);
|
|
252
|
-
}
|
|
253
|
-
for (const method of Object.keys(grant.actions)) {
|
|
254
|
-
if (declaresMethod(nameEntry, method))
|
|
255
|
-
continue;
|
|
256
|
-
throw pkitError("INVALID_DEFINITION", `"${permissionPath}": grantTo "${permissionId}" grants "${method}", which no role declares on that permission`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
function validateRoleHooks(permissionPath, nameEntry) {
|
|
261
|
-
for (const [hookRole, methodHooks] of nameEntry.hooks) {
|
|
262
|
-
if (hookRole === GLOBAL_HOOK_OWNER)
|
|
263
|
-
continue;
|
|
264
|
-
for (const method of methodHooks.keys()) {
|
|
265
|
-
if (hasAccessPath(nameEntry, hookRole, method))
|
|
266
|
-
continue;
|
|
267
|
-
throw pkitError("INVALID_DEFINITION", `"${permissionPath}": hook for "${hookRole}" on "${method}" has no registered actions or grant for that role`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
function validateSealedRegistry(modules) {
|
|
272
|
-
for (const [action, registeredModule] of modules) {
|
|
273
|
-
if (registeredModule.names.size === 0) {
|
|
274
|
-
throw pkitError("INVALID_DEFINITION", `"${action}" has hooks but no name with registered actions`);
|
|
17
|
+
// src/cli/generate.ts
|
|
18
|
+
var generator = {
|
|
19
|
+
async generate(options) {
|
|
20
|
+
const { content, ...generationResult } = await loadRoleDeclaration(options);
|
|
21
|
+
writeFileSync(generationResult.outPath, content);
|
|
22
|
+
return generationResult;
|
|
23
|
+
},
|
|
24
|
+
async checkGenerated(options) {
|
|
25
|
+
const { content, ...generationResult } = await loadRoleDeclaration(options);
|
|
26
|
+
const isStale = !existsSync(generationResult.outPath) || readFileSync(generationResult.outPath, "utf8") !== content;
|
|
27
|
+
return { ...generationResult, isStale };
|
|
28
|
+
},
|
|
29
|
+
async main(commandArguments) {
|
|
30
|
+
const request = readRequest(commandArguments);
|
|
31
|
+
if (request === null) {
|
|
32
|
+
console.error("usage: pkit generate [--config pkit.config.js] [--out pkit.generated.d.ts] [--check]");
|
|
33
|
+
process.exitCode = protocol_default.EXIT_CODE.usage;
|
|
34
|
+
return;
|
|
275
35
|
}
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
|
|
36
|
+
try {
|
|
37
|
+
const { options } = request;
|
|
38
|
+
if (request.isCheck) {
|
|
39
|
+
const checkResult = await generator.checkGenerated(options);
|
|
40
|
+
console.log(checkResult.isStale ? `pkit: ${checkResult.outPath} is stale, run pkit generate` : `pkit: ${checkResult.outPath} is up to date`);
|
|
41
|
+
process.exitCode = checkResult.isStale ? protocol_default.EXIT_CODE.failure : protocol_default.EXIT_CODE.success;
|
|
42
|
+
return;
|
|
280
43
|
}
|
|
281
|
-
|
|
282
|
-
|
|
44
|
+
const generationResult = await generator.generate(options);
|
|
45
|
+
console.log(`pkit: ${generationResult.roles.length} roles written to ${generationResult.outPath}`);
|
|
46
|
+
process.exitCode = protocol_default.EXIT_CODE.success;
|
|
47
|
+
} catch (cause) {
|
|
48
|
+
console.error(`pkit: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
49
|
+
process.exitCode = protocol_default.EXIT_CODE.failure;
|
|
283
50
|
}
|
|
284
51
|
}
|
|
285
|
-
}
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
message: `invalid permission identifier: "${permissionId}" (format role::module::name)`
|
|
52
|
+
};
|
|
53
|
+
function readRequest(commandArguments) {
|
|
54
|
+
try {
|
|
55
|
+
const { positionals, values } = parseArgs({
|
|
56
|
+
args: [...commandArguments],
|
|
57
|
+
allowPositionals: true,
|
|
58
|
+
options: { config: { type: "string" }, out: { type: "string" }, check: { type: "boolean" } }
|
|
293
59
|
});
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
assigned.add(permissionId);
|
|
301
|
-
}
|
|
302
|
-
return assigned;
|
|
303
|
-
}
|
|
304
|
-
function validateIdentity(assignments, state) {
|
|
305
|
-
validateSnapshot(state.snapshot);
|
|
306
|
-
const { role, permissions } = assignments;
|
|
307
|
-
if (typeof role !== "string")
|
|
308
|
-
throw pkitError("INVALID_INPUT", "role is required");
|
|
309
|
-
validateRole(role, state.roles, "UNKNOWN_ROLE");
|
|
310
|
-
return { role, assigned: validateAssignments(permissions, role, state.snapshot.assignable) };
|
|
311
|
-
}
|
|
312
|
-
function trimSelection(select, properties) {
|
|
313
|
-
validateStrings(select, { code: "INVALID_INPUT", message: "select must be an array of strings", minimumLength: 0 });
|
|
314
|
-
const selectedFields = [];
|
|
315
|
-
for (const field of select) {
|
|
316
|
-
if (properties.includes(field))
|
|
317
|
-
selectedFields.push(field);
|
|
60
|
+
if (positionals[0] !== "generate" || positionals.length !== 1)
|
|
61
|
+
return null;
|
|
62
|
+
return { options: { cwd: process.cwd(), config: values.config, out: values.out }, isCheck: values.check === true };
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
318
65
|
}
|
|
319
|
-
return selectedFields;
|
|
320
66
|
}
|
|
321
|
-
function
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
if (forbiddenFields.length === 0)
|
|
328
|
-
return;
|
|
329
|
-
throw Object.assign(pkitError("PROPERTIES_NOT_ALLOWED", `fields not allowed in "${permissionPath}": ${forbiddenFields.join(", ")}`), { fields: forbiddenFields });
|
|
330
|
-
}
|
|
331
|
-
function validateRequest(input, state) {
|
|
332
|
-
validateSnapshot(state.snapshot);
|
|
333
|
-
const { action, name, method } = input;
|
|
334
|
-
validateMethod(method, { code: "INVALID_INPUT", message: "method is required" });
|
|
335
|
-
if (method !== "find" && Object.hasOwn(input, "select"))
|
|
336
|
-
throw pkitError("INVALID_INPUT", "select only applies to find");
|
|
337
|
-
if (typeof action !== "string" || typeof name !== "string")
|
|
338
|
-
throw pkitError("INVALID_INPUT", "action and name are required");
|
|
339
|
-
const { role, assigned } = validateIdentity(input, state);
|
|
340
|
-
const registeredModule = state.modules.get(action);
|
|
341
|
-
if (!registeredModule)
|
|
342
|
-
throw pkitError("UNKNOWN_ACTION", `module "${action}" is not registered`);
|
|
343
|
-
const nameEntry = registeredModule.names.get(name);
|
|
344
|
-
if (!nameEntry)
|
|
345
|
-
throw pkitError("UNKNOWN_PERMISSION", `permission "${action}::${name}" is not registered`);
|
|
346
|
-
const permissionId = permissionIdOf(role, action, name);
|
|
347
|
-
const access = resolveAccess(nameEntry, permissionId, role, method, assigned);
|
|
348
|
-
if (access.status === "unassigned") {
|
|
349
|
-
throw pkitError("PERMISSION_NOT_ASSIGNED", `"${permissionId}" is not assigned or granted by the user permissions`);
|
|
350
|
-
}
|
|
351
|
-
if (access.status === "disabled")
|
|
352
|
-
throw pkitError("METHOD_DISABLED", `"${permissionId}.${method}" is not enabled`);
|
|
353
|
-
const { data, context } = input;
|
|
354
|
-
if (context !== undefined)
|
|
355
|
-
validateObject(context, { code: "INVALID_INPUT", message: "context must be an object" });
|
|
356
|
-
const { properties, authorization } = access;
|
|
357
|
-
const permission = Object.freeze({ role, action, name, permissionId, method, enabled: true, properties, authorization });
|
|
358
|
-
const request = { registeredModule, nameEntry, permission, data, context };
|
|
359
|
-
if (method === "find") {
|
|
360
|
-
if (data !== undefined)
|
|
361
|
-
validateObject(data, { code: "INVALID_INPUT", message: "data must be an object" });
|
|
362
|
-
const { select } = input;
|
|
363
|
-
if (select === undefined)
|
|
364
|
-
return { ...request, result: properties };
|
|
365
|
-
if (properties === ALL_FIELDS) {
|
|
366
|
-
validateStrings(select, { code: "INVALID_INPUT", message: "select must be an array of strings", minimumLength: 0 });
|
|
367
|
-
return { ...request, result: select };
|
|
368
|
-
}
|
|
369
|
-
return { ...request, result: trimSelection(select, properties) };
|
|
370
|
-
}
|
|
371
|
-
validateObject(data, { code: "INVALID_INPUT", message: "data must be an object" });
|
|
372
|
-
if (properties !== ALL_FIELDS)
|
|
373
|
-
rejectForbiddenFields(data, properties, `${permissionId}.${method}`);
|
|
374
|
-
return { ...request, result: data };
|
|
67
|
+
async function loadRoleDeclaration(options) {
|
|
68
|
+
const configPath = resolveConfigPath(options.cwd, options.config);
|
|
69
|
+
const roles = await readRoleCatalog(configPath, options.cwd);
|
|
70
|
+
const outPath = resolve(options.cwd, options.out === undefined ? "pkit.generated.d.ts" : options.out);
|
|
71
|
+
return { configPath, outPath, roles, content: renderRoleDeclarations(roles) };
|
|
375
72
|
}
|
|
376
|
-
|
|
377
|
-
// src/cli/generate.ts
|
|
378
73
|
function resolveConfigPath(workingDirectory, config) {
|
|
379
74
|
if (config !== undefined)
|
|
380
75
|
return resolve(workingDirectory, config);
|
|
@@ -394,24 +89,33 @@ async function readRoleCatalog(configPath, workingDirectory) {
|
|
|
394
89
|
childArguments.unshift("--experimental-strip-types");
|
|
395
90
|
const { stdout } = await promisify(execFile)(process.execPath, childArguments, {
|
|
396
91
|
cwd: workingDirectory,
|
|
397
|
-
timeout: CHILD_TIMEOUT_MS,
|
|
92
|
+
timeout: protocol_default.CHILD_TIMEOUT_MS,
|
|
398
93
|
killSignal: "SIGKILL",
|
|
399
|
-
maxBuffer: CHILD_MAX_BUFFER_BYTES
|
|
94
|
+
maxBuffer: protocol_default.CHILD_MAX_BUFFER_BYTES
|
|
400
95
|
});
|
|
401
96
|
for (const outputLine of stdout.split(`
|
|
402
97
|
`)) {
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
const catalog = JSON.parse(outputLine.slice(CATALOG_MARKER.length));
|
|
406
|
-
validateStrings(catalog.roles, {
|
|
407
|
-
code: "INVALID_DEFINITION",
|
|
408
|
-
message: "child process returned an invalid role catalog",
|
|
409
|
-
minimumLength: 1
|
|
410
|
-
});
|
|
411
|
-
return catalog.roles;
|
|
98
|
+
if (outputLine.startsWith(protocol_default.CATALOG_MARKER))
|
|
99
|
+
return parseRoleCatalog(outputLine.slice(protocol_default.CATALOG_MARKER.length));
|
|
412
100
|
}
|
|
413
101
|
throw new Error("child process did not return the role catalog");
|
|
414
102
|
}
|
|
103
|
+
function parseRoleCatalog(payload) {
|
|
104
|
+
let catalog = null;
|
|
105
|
+
try {
|
|
106
|
+
catalog = JSON.parse(payload);
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error("child process returned a malformed role catalog");
|
|
109
|
+
}
|
|
110
|
+
const roles = catalog !== null && typeof catalog === "object" ? catalog.roles : undefined;
|
|
111
|
+
if (!Array.isArray(roles))
|
|
112
|
+
throw new Error("child process returned an invalid role catalog");
|
|
113
|
+
for (const role of roles) {
|
|
114
|
+
if (typeof role !== "string" || role.length === 0)
|
|
115
|
+
throw new Error("child process returned an invalid role catalog");
|
|
116
|
+
}
|
|
117
|
+
return roles;
|
|
118
|
+
}
|
|
415
119
|
function renderRoleDeclarations(roles) {
|
|
416
120
|
const declarations = [
|
|
417
121
|
"import 'endpoint-permissions-kit/types';",
|
|
@@ -425,55 +129,9 @@ function renderRoleDeclarations(roles) {
|
|
|
425
129
|
return declarations.join(`
|
|
426
130
|
`);
|
|
427
131
|
}
|
|
428
|
-
|
|
429
|
-
const { cwd: workingDirectory, config, out } = options;
|
|
430
|
-
const configPath = resolveConfigPath(workingDirectory, config);
|
|
431
|
-
const roles = await readRoleCatalog(configPath, workingDirectory);
|
|
432
|
-
const outPath = out === undefined ? resolve(workingDirectory, "pkit.generated.d.ts") : resolve(workingDirectory, out);
|
|
433
|
-
return { configPath, outPath, roles, content: renderRoleDeclarations(roles) };
|
|
434
|
-
}
|
|
435
|
-
async function generate(options) {
|
|
436
|
-
const { content, ...generationResult } = await loadRoleDeclaration(options);
|
|
437
|
-
writeFileSync(generationResult.outPath, content);
|
|
438
|
-
return generationResult;
|
|
439
|
-
}
|
|
440
|
-
async function checkGenerated(options) {
|
|
441
|
-
const { content, ...generationResult } = await loadRoleDeclaration(options);
|
|
442
|
-
if (!existsSync(generationResult.outPath))
|
|
443
|
-
return { ...generationResult, isStale: true };
|
|
444
|
-
return { ...generationResult, isStale: readFileSync(generationResult.outPath, "utf8") !== content };
|
|
445
|
-
}
|
|
446
|
-
async function main(commandArguments) {
|
|
447
|
-
try {
|
|
448
|
-
const { positionals, values } = parseArgs({
|
|
449
|
-
args: [...commandArguments],
|
|
450
|
-
allowPositionals: true,
|
|
451
|
-
options: { config: { type: "string" }, out: { type: "string" }, check: { type: "boolean" } }
|
|
452
|
-
});
|
|
453
|
-
if (positionals[0] !== "generate" || positionals.length !== 1) {
|
|
454
|
-
console.error("usage: pkit generate [--config pkit.config.js] [--out pkit.generated.d.ts] [--check]");
|
|
455
|
-
process.exitCode = EXIT_CODE.usage;
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
const options = { cwd: process.cwd(), config: values.config, out: values.out };
|
|
459
|
-
if (values.check) {
|
|
460
|
-
const checkResult = await checkGenerated(options);
|
|
461
|
-
console.log(checkResult.isStale ? `pkit: ${checkResult.outPath} is stale, run pkit generate` : `pkit: ${checkResult.outPath} is up to date`);
|
|
462
|
-
process.exitCode = checkResult.isStale ? EXIT_CODE.failure : EXIT_CODE.success;
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const generationResult = await generate(options);
|
|
466
|
-
console.log(`pkit: ${generationResult.roles.length} roles written to ${generationResult.outPath}`);
|
|
467
|
-
process.exitCode = EXIT_CODE.success;
|
|
468
|
-
} catch (cause) {
|
|
469
|
-
console.error(`pkit: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
470
|
-
process.exitCode = EXIT_CODE.failure;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
132
|
+
var generate_default = generator;
|
|
473
133
|
export {
|
|
474
|
-
|
|
475
|
-
generate,
|
|
476
|
-
main
|
|
134
|
+
generate_default as default
|
|
477
135
|
};
|
|
478
136
|
|
|
479
|
-
//# debugId=
|
|
137
|
+
//# debugId=1516241B0551E2F164756E2164756E21
|