@api-client/core 0.20.1 → 0.20.3
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/build/src/modeling/RuntimeApiModel.d.ts +14 -18
- package/build/src/modeling/RuntimeApiModel.d.ts.map +1 -1
- package/build/src/modeling/RuntimeApiModel.js +14 -52
- package/build/src/modeling/RuntimeApiModel.js.map +1 -1
- package/build/src/modeling/Semantics.d.ts +0 -252
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +0 -356
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/ai/tools/Semantic.tools.d.ts +0 -3
- package/build/src/modeling/ai/tools/Semantic.tools.d.ts.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/modeling/RuntimeApiModel.ts +19 -61
- package/src/modeling/Semantics.ts +40 -520
- package/tests/unit/modeling/RuntimeApiModel.spec.ts +40 -108
- package/tests/unit/modeling/client_ip_address_semantic.spec.ts +0 -22
- package/tests/unit/modeling/semantics.spec.ts +0 -65
- package/tests/unit/modeling/username_semantic.spec.ts +0 -32
- package/build/src/runtime/modeling/Semantics.d.ts +0 -84
- package/build/src/runtime/modeling/Semantics.d.ts.map +0 -1
- package/build/src/runtime/modeling/Semantics.js +0 -124
- package/build/src/runtime/modeling/Semantics.js.map +0 -1
- package/src/runtime/modeling/Semantics.ts +0 -196
- package/tests/unit/modeling/semantic_runtime.spec.ts +0 -113
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-client/core",
|
|
3
3
|
"description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
|
|
4
|
-
"version": "0.20.
|
|
4
|
+
"version": "0.20.3",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./browser.js": {
|
|
@@ -154,7 +154,8 @@
|
|
|
154
154
|
"build:node": "tsc --project tsconfig.node.json",
|
|
155
155
|
"build": "npm run build:ts && npm run copy:assets",
|
|
156
156
|
"prepare": "husky && npm run build:ts",
|
|
157
|
-
"tsc": "tsc",
|
|
157
|
+
"tsc": "tsc --project tsconfig.json",
|
|
158
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
158
159
|
"tsc:tests": "tsc --project tsconfig.browser.json",
|
|
159
160
|
"tsc:watch": "tsc --watch --project tsconfig.json",
|
|
160
161
|
"test:browser": "wtr --playwright --browsers chromium",
|
|
@@ -39,19 +39,20 @@ export interface RuntimeApiModelSchema extends ApiModelSchema {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface RuntimeResolvedAction {
|
|
42
|
-
|
|
42
|
+
exposure: ExposedEntity
|
|
43
|
+
entity: DomainEntity
|
|
43
44
|
action: Action
|
|
44
45
|
params: Record<string, string>
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export type RuleEvaluator = (rule: AccessRule) => Promise<boolean | undefined> | boolean | undefined
|
|
48
49
|
|
|
49
|
-
interface PhaseRules {
|
|
50
|
+
export interface PhaseRules {
|
|
50
51
|
permissionRules: AccessRule[]
|
|
51
52
|
mandatoryRules: AccessRule[]
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
interface ActionRulesCache {
|
|
55
|
+
export interface ActionRulesCache {
|
|
55
56
|
preFetch: PhaseRules
|
|
56
57
|
fetch: PhaseRules
|
|
57
58
|
postFetch: PhaseRules
|
|
@@ -281,17 +282,23 @@ export class RuntimeApiModel extends ApiModel {
|
|
|
281
282
|
|
|
282
283
|
const params = exec(path, matchedRoute)
|
|
283
284
|
|
|
284
|
-
const
|
|
285
|
-
if (!
|
|
286
|
-
|
|
285
|
+
const exposure = this.exposes.get(def.lookup.exposedEntityKey)
|
|
286
|
+
if (!exposure) {
|
|
287
|
+
throw new Exception('Missing exposure ' + def.lookup.exposedEntityKey, { code: 'API_MODEL_ERROR', status: 500 })
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
const action =
|
|
290
|
+
const action = exposure.actions.find((a) => a.kind === def.lookup.actionKind)
|
|
290
291
|
if (!action) {
|
|
291
|
-
|
|
292
|
+
throw new Exception('Missing action ' + def.lookup.actionKind, { code: 'API_MODEL_ERROR', status: 500 })
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const entity = this.domain?.findEntity(exposure.entity.key, exposure.entity.domain)
|
|
296
|
+
if (!entity) {
|
|
297
|
+
throw new Exception('Missing entity ' + exposure.entity.key, { code: 'API_MODEL_ERROR', status: 500 })
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
return {
|
|
301
|
+
exposure,
|
|
295
302
|
entity,
|
|
296
303
|
action,
|
|
297
304
|
params,
|
|
@@ -299,64 +306,15 @@ export class RuntimeApiModel extends ApiModel {
|
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* The evaluation process follows two phases per execution phase (PRE_FETCH, FETCH, POST_FETCH):
|
|
305
|
-
* 1. Mandatory Phase: All rules marked as `mandatory: true` across all levels must return true.
|
|
306
|
-
* If any fail, the request is immediately rejected.
|
|
307
|
-
* 2. Permission Phase: Evaluation follows the hierarchy from most specific to most general:
|
|
308
|
-
* Action -> Endpoint (ExposedEntity) -> API Model.
|
|
309
|
-
* If an explicit allow (true) or deny (false) is hit, evaluation stops and returns the result.
|
|
310
|
-
* If no rules match (all return undefined), the request is rejected by default.
|
|
311
|
-
*
|
|
312
|
-
* @param action The resolved action to evaluate.
|
|
313
|
-
* @param evaluator A callback that evaluates a single rule. Should return true (allow),
|
|
314
|
-
* false (deny), or undefined (no hit).
|
|
315
|
-
* @param phase The execution phase to evaluate.
|
|
316
|
-
* @returns A promise that resolves to true if access is granted, false if denied.
|
|
309
|
+
* Retrieves the precomputed and shadowed effective rules for a given action.
|
|
317
310
|
*/
|
|
318
|
-
|
|
319
|
-
action: RuntimeResolvedAction,
|
|
320
|
-
evaluator: RuleEvaluator,
|
|
321
|
-
phase: AccessRuleExecutionPhase
|
|
322
|
-
): Promise<boolean> {
|
|
311
|
+
getEffectiveRules(action: RuntimeResolvedAction): ActionRulesCache {
|
|
323
312
|
let cachedRules = this.#actionRulesCache.get(action.action)
|
|
324
313
|
if (!cachedRules) {
|
|
325
|
-
|
|
326
|
-
cachedRules = this.#computeEffectiveRules(action.action, action.entity)
|
|
314
|
+
cachedRules = this.#computeEffectiveRules(action.action, action.exposure)
|
|
327
315
|
this.#actionRulesCache.set(action.action, cachedRules)
|
|
328
316
|
}
|
|
329
|
-
|
|
330
|
-
let rulesForPhase
|
|
331
|
-
if (phase === AccessRuleExecutionPhase.POST_FETCH) {
|
|
332
|
-
rulesForPhase = cachedRules.postFetch
|
|
333
|
-
} else if (phase === AccessRuleExecutionPhase.FETCH) {
|
|
334
|
-
rulesForPhase = cachedRules.fetch
|
|
335
|
-
} else {
|
|
336
|
-
rulesForPhase = cachedRules.preFetch
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Step 1: Mandatory Phase
|
|
340
|
-
for (const rule of rulesForPhase.mandatoryRules) {
|
|
341
|
-
const result = await evaluator(rule)
|
|
342
|
-
if (result !== true) {
|
|
343
|
-
return false // Immediately reject if any mandatory rule fails
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Step 2-4: Permission Phase
|
|
348
|
-
for (const rule of rulesForPhase.permissionRules) {
|
|
349
|
-
const result = await evaluator(rule)
|
|
350
|
-
if (result === true) {
|
|
351
|
-
return true
|
|
352
|
-
}
|
|
353
|
-
if (result === false) {
|
|
354
|
-
return false
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Default: no hit
|
|
359
|
-
return false
|
|
317
|
+
return cachedRules
|
|
360
318
|
}
|
|
361
319
|
|
|
362
320
|
override toJSON(): RuntimeApiModelSchema {
|