toride 0.2.0 → 0.4.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/{chunk-24PMDTLE.js → chunk-GRFSE3QO.js} +90 -21
- package/dist/{chunk-475CNU63.js → chunk-S6DKDABO.js} +2 -1
- package/dist/cli.js +86 -2
- package/dist/{client-RqwW0K_-.d.ts → client-CBp3admQ.d.ts} +92 -11
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +94 -24
- package/dist/index.js +125 -7
- package/package.json +3 -2
- package/schema/.gitkeep +0 -0
- package/schema/policy.schema.json +1308 -0
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
// src/policy/schema.ts
|
|
2
2
|
import * as v from "valibot";
|
|
3
|
-
var AttributeTypeSchema = v.picklist(["string", "number", "boolean"]);
|
|
3
|
+
var AttributeTypeSchema = v.picklist(["string", "number", "boolean", "string[]", "number[]", "boolean[]"]);
|
|
4
|
+
var MAX_ATTRIBUTE_DEPTH = 3;
|
|
5
|
+
function getAttributeDepth(schema, currentDepth = 0) {
|
|
6
|
+
if (typeof schema !== "object" || schema === null) {
|
|
7
|
+
return currentDepth;
|
|
8
|
+
}
|
|
9
|
+
const obj = schema;
|
|
10
|
+
if (obj.kind === "primitive") {
|
|
11
|
+
return currentDepth;
|
|
12
|
+
}
|
|
13
|
+
if (obj.kind === "object" && typeof obj.fields === "object" && obj.fields !== null) {
|
|
14
|
+
const fields = obj.fields;
|
|
15
|
+
let maxDepth = currentDepth;
|
|
16
|
+
for (const field of Object.values(fields)) {
|
|
17
|
+
const fieldDepth = getAttributeDepth(field, currentDepth + 1);
|
|
18
|
+
if (fieldDepth > maxDepth) {
|
|
19
|
+
maxDepth = fieldDepth;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return maxDepth;
|
|
23
|
+
}
|
|
24
|
+
if (obj.kind === "array" && typeof obj.items === "object" && obj.items !== null) {
|
|
25
|
+
return getAttributeDepth(obj.items, currentDepth);
|
|
26
|
+
}
|
|
27
|
+
return currentDepth;
|
|
28
|
+
}
|
|
29
|
+
function checkAttributeDepth(input) {
|
|
30
|
+
return getAttributeDepth(input) <= MAX_ATTRIBUTE_DEPTH;
|
|
31
|
+
}
|
|
32
|
+
var PrimitiveAttributeSchemaNodeSchema = v.object({
|
|
33
|
+
kind: v.literal("primitive"),
|
|
34
|
+
type: AttributeTypeSchema
|
|
35
|
+
});
|
|
36
|
+
var ObjectAttributeSchemaNodeSchema = v.object({
|
|
37
|
+
kind: v.literal("object"),
|
|
38
|
+
fields: v.record(v.string(), v.lazy(() => AttributeSchemaNodeSchema))
|
|
39
|
+
});
|
|
40
|
+
var ArrayAttributeSchemaNodeSchema = v.object({
|
|
41
|
+
kind: v.literal("array"),
|
|
42
|
+
items: v.lazy(() => AttributeSchemaNodeSchema)
|
|
43
|
+
});
|
|
44
|
+
var AttributeSchemaNodeSchema = v.pipe(
|
|
45
|
+
v.union([
|
|
46
|
+
PrimitiveAttributeSchemaNodeSchema,
|
|
47
|
+
ObjectAttributeSchemaNodeSchema,
|
|
48
|
+
ArrayAttributeSchemaNodeSchema
|
|
49
|
+
]),
|
|
50
|
+
v.check(checkAttributeDepth, "Attribute schema exceeds maximum depth of 3")
|
|
51
|
+
);
|
|
4
52
|
var ActorDeclarationSchema = v.object({
|
|
5
|
-
attributes: v.record(v.string(),
|
|
53
|
+
attributes: v.record(v.string(), AttributeSchemaNodeSchema)
|
|
6
54
|
});
|
|
7
55
|
var ConditionOperatorSchema = v.union([
|
|
8
56
|
v.object({ eq: v.unknown() }),
|
|
@@ -54,7 +102,7 @@ var FieldAccessDefSchema = v.object({
|
|
|
54
102
|
var ResourceBlockSchema = v.object({
|
|
55
103
|
roles: v.array(v.string()),
|
|
56
104
|
permissions: v.array(v.string()),
|
|
57
|
-
attributes: v.optional(v.record(v.string(),
|
|
105
|
+
attributes: v.optional(v.record(v.string(), AttributeSchemaNodeSchema)),
|
|
58
106
|
relations: v.optional(v.record(v.string(), v.string())),
|
|
59
107
|
grants: v.optional(v.record(v.string(), v.array(v.string()))),
|
|
60
108
|
derived_roles: v.optional(v.array(DerivedRoleEntrySchema)),
|
|
@@ -122,6 +170,20 @@ var DepthLimitError = class extends Error {
|
|
|
122
170
|
this.limitType = limitType;
|
|
123
171
|
}
|
|
124
172
|
};
|
|
173
|
+
var ForbiddenError = class extends Error {
|
|
174
|
+
actor;
|
|
175
|
+
action;
|
|
176
|
+
resourceType;
|
|
177
|
+
constructor(actor, action, resourceType) {
|
|
178
|
+
super(
|
|
179
|
+
`Actor "${actor.type}:${actor.id}" is forbidden from performing "${action}" on resource type "${resourceType}"`
|
|
180
|
+
);
|
|
181
|
+
this.name = "ForbiddenError";
|
|
182
|
+
this.actor = actor;
|
|
183
|
+
this.action = action;
|
|
184
|
+
this.resourceType = resourceType;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
125
187
|
|
|
126
188
|
// src/policy/validator.ts
|
|
127
189
|
function extractActorAttributes(condition) {
|
|
@@ -1434,7 +1496,7 @@ var OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
1434
1496
|
async function buildConstraints(actor, action, resourceType, cache, policy, options) {
|
|
1435
1497
|
const resourceBlock = policy.resources[resourceType];
|
|
1436
1498
|
if (!resourceBlock) {
|
|
1437
|
-
return {
|
|
1499
|
+
return { ok: false };
|
|
1438
1500
|
}
|
|
1439
1501
|
const env = options?.env ?? {};
|
|
1440
1502
|
const rolesGrantingAction = findRolesGrantingAction(action, resourceBlock);
|
|
@@ -1443,7 +1505,7 @@ async function buildConstraints(actor, action, resourceType, cache, policy, opti
|
|
|
1443
1505
|
(r) => r.effect === "permit" && r.permissions.includes(action)
|
|
1444
1506
|
);
|
|
1445
1507
|
if (permitRules2.length === 0) {
|
|
1446
|
-
return {
|
|
1508
|
+
return { ok: false };
|
|
1447
1509
|
}
|
|
1448
1510
|
}
|
|
1449
1511
|
const roleConstraintCache = /* @__PURE__ */ new Map();
|
|
@@ -1504,7 +1566,7 @@ async function buildConstraints(actor, action, resourceType, cache, policy, opti
|
|
|
1504
1566
|
}
|
|
1505
1567
|
}
|
|
1506
1568
|
if (pathConstraints.length === 0) {
|
|
1507
|
-
return {
|
|
1569
|
+
return { ok: false };
|
|
1508
1570
|
}
|
|
1509
1571
|
let combined;
|
|
1510
1572
|
if (pathConstraints.length === 1) {
|
|
@@ -1526,12 +1588,12 @@ async function buildConstraints(actor, action, resourceType, cache, policy, opti
|
|
|
1526
1588
|
}
|
|
1527
1589
|
combined = simplify(combined);
|
|
1528
1590
|
if (combined.type === "always") {
|
|
1529
|
-
return {
|
|
1591
|
+
return { ok: true, constraint: null };
|
|
1530
1592
|
}
|
|
1531
1593
|
if (combined.type === "never") {
|
|
1532
|
-
return {
|
|
1594
|
+
return { ok: false };
|
|
1533
1595
|
}
|
|
1534
|
-
return {
|
|
1596
|
+
return { ok: true, constraint: combined };
|
|
1535
1597
|
}
|
|
1536
1598
|
function findRolesGrantingAction(action, resourceBlock) {
|
|
1537
1599
|
const grants = resourceBlock.grants ?? {};
|
|
@@ -1949,11 +2011,11 @@ function translateConstraints(constraint, adapter, _depth = 0) {
|
|
|
1949
2011
|
// Terminal nodes - should not reach the translator
|
|
1950
2012
|
case "always":
|
|
1951
2013
|
throw new Error(
|
|
1952
|
-
'Constraint node "always" should be simplified out before translation.
|
|
2014
|
+
'Constraint node "always" should be simplified out before translation. Check result.ok and result.constraint before calling translateConstraints().'
|
|
1953
2015
|
);
|
|
1954
2016
|
case "never":
|
|
1955
2017
|
throw new Error(
|
|
1956
|
-
'Constraint node "never" should be simplified out before translation.
|
|
2018
|
+
'Constraint node "never" should be simplified out before translation. Check result.ok and result.constraint before calling translateConstraints().'
|
|
1957
2019
|
);
|
|
1958
2020
|
default: {
|
|
1959
2021
|
const _exhaustive = constraint;
|
|
@@ -2001,16 +2063,17 @@ async function permittedFields(engine, actor, operation, resource, fieldAccess,
|
|
|
2001
2063
|
for (const fieldName of fieldNames) {
|
|
2002
2064
|
const fieldDef = fieldAccess[fieldName];
|
|
2003
2065
|
const allowedRoles = fieldDef?.[operation];
|
|
2066
|
+
const typedFieldName = fieldName;
|
|
2004
2067
|
if (allowedRoles) {
|
|
2005
2068
|
if (actorRoles.some((role) => allowedRoles.includes(role))) {
|
|
2006
|
-
permitted.push(
|
|
2069
|
+
permitted.push(typedFieldName);
|
|
2007
2070
|
}
|
|
2008
2071
|
} else {
|
|
2009
2072
|
if (hasResourcePermission === void 0) {
|
|
2010
2073
|
hasResourcePermission = await engine.can(actor, operation, resource, options);
|
|
2011
2074
|
}
|
|
2012
2075
|
if (hasResourcePermission) {
|
|
2013
|
-
permitted.push(
|
|
2076
|
+
permitted.push(typedFieldName);
|
|
2014
2077
|
}
|
|
2015
2078
|
}
|
|
2016
2079
|
}
|
|
@@ -2167,9 +2230,9 @@ var Toride = class {
|
|
|
2167
2230
|
const a = actor;
|
|
2168
2231
|
const sharedCache = new AttributeCache(this.resolvers);
|
|
2169
2232
|
const results = [];
|
|
2170
|
-
for (const
|
|
2171
|
-
const r =
|
|
2172
|
-
const act =
|
|
2233
|
+
for (const check2 of checks) {
|
|
2234
|
+
const r = check2.resource;
|
|
2235
|
+
const act = check2.action;
|
|
2173
2236
|
const result = await this.evaluateInternal(
|
|
2174
2237
|
a,
|
|
2175
2238
|
act,
|
|
@@ -2209,9 +2272,13 @@ var Toride = class {
|
|
|
2209
2272
|
/**
|
|
2210
2273
|
* T064: Translate constraint AST using an adapter.
|
|
2211
2274
|
* Dispatches each constraint node to the adapter's methods.
|
|
2275
|
+
*
|
|
2276
|
+
* Accepts Constraint (the AST) from buildConstraints() and returns
|
|
2277
|
+
* TQueryMap[R] — the adapter's mapped output type for resource R.
|
|
2278
|
+
* Callers must check result.ok and result.constraint before calling this method.
|
|
2212
2279
|
*/
|
|
2213
|
-
translateConstraints(
|
|
2214
|
-
return translateConstraints(
|
|
2280
|
+
translateConstraints(constraint, adapter) {
|
|
2281
|
+
return translateConstraints(constraint, adapter);
|
|
2215
2282
|
}
|
|
2216
2283
|
/**
|
|
2217
2284
|
* T072: Fire onDecision audit callback via microtask (non-blocking).
|
|
@@ -2250,10 +2317,10 @@ var Toride = class {
|
|
|
2250
2317
|
const callback = this.options.onQuery;
|
|
2251
2318
|
if (!callback) return;
|
|
2252
2319
|
let resultType;
|
|
2253
|
-
if (
|
|
2254
|
-
resultType = "unrestricted";
|
|
2255
|
-
} else if ("forbidden" in constraintResult && constraintResult.forbidden) {
|
|
2320
|
+
if (!constraintResult.ok) {
|
|
2256
2321
|
resultType = "forbidden";
|
|
2322
|
+
} else if (constraintResult.constraint === null) {
|
|
2323
|
+
resultType = "unrestricted";
|
|
2257
2324
|
} else {
|
|
2258
2325
|
resultType = "constrained";
|
|
2259
2326
|
}
|
|
@@ -2350,10 +2417,12 @@ async function runTestCases(policy, tests) {
|
|
|
2350
2417
|
}
|
|
2351
2418
|
|
|
2352
2419
|
export {
|
|
2420
|
+
ConditionExpressionSchema,
|
|
2353
2421
|
PolicySchema,
|
|
2354
2422
|
ValidationError,
|
|
2355
2423
|
CycleError,
|
|
2356
2424
|
DepthLimitError,
|
|
2425
|
+
ForbiddenError,
|
|
2357
2426
|
validatePolicy,
|
|
2358
2427
|
validatePolicyResult,
|
|
2359
2428
|
validatePolicyStrict,
|
|
@@ -4,7 +4,8 @@ var TorideClient = class {
|
|
|
4
4
|
permissions;
|
|
5
5
|
constructor(snapshot) {
|
|
6
6
|
this.permissions = /* @__PURE__ */ new Map();
|
|
7
|
-
|
|
7
|
+
const entries = snapshot;
|
|
8
|
+
for (const [key, actions] of Object.entries(entries)) {
|
|
8
9
|
this.permissions.set(key, new Set(actions));
|
|
9
10
|
}
|
|
10
11
|
}
|
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,96 @@ import {
|
|
|
7
7
|
runTestCases,
|
|
8
8
|
validatePolicyResult,
|
|
9
9
|
validatePolicyStrict
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-GRFSE3QO.js";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
14
14
|
import { resolve, dirname, join } from "path";
|
|
15
15
|
import * as YAML from "yaml";
|
|
16
16
|
import * as v from "valibot";
|
|
17
|
+
function normalizeAttributeValue(raw) {
|
|
18
|
+
if (raw === null || raw === void 0) return raw;
|
|
19
|
+
if (typeof raw === "string" && ["string", "number", "boolean"].includes(raw)) {
|
|
20
|
+
return { kind: "primitive", type: raw };
|
|
21
|
+
}
|
|
22
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
23
|
+
const obj = raw;
|
|
24
|
+
if ("kind" in obj && obj.kind !== void 0) {
|
|
25
|
+
if (obj.kind === "object" && typeof obj.fields === "object" && obj.fields !== null) {
|
|
26
|
+
const normalizedFields = {};
|
|
27
|
+
for (const [key, value] of Object.entries(obj.fields)) {
|
|
28
|
+
normalizedFields[key] = normalizeAttributeValue(value);
|
|
29
|
+
}
|
|
30
|
+
return { ...obj, fields: normalizedFields };
|
|
31
|
+
}
|
|
32
|
+
if (obj.kind === "array" && "items" in obj) {
|
|
33
|
+
return { ...obj, items: normalizeAttributeValue(obj.items) };
|
|
34
|
+
}
|
|
35
|
+
return obj;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
39
|
+
const obj = raw;
|
|
40
|
+
const normalized = {};
|
|
41
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
42
|
+
normalized[key] = normalizeAttributeValue(value);
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(raw)) {
|
|
47
|
+
return raw.map((item) => normalizeAttributeValue(item));
|
|
48
|
+
}
|
|
49
|
+
return raw;
|
|
50
|
+
}
|
|
51
|
+
function normalizeAttributeRecord(attrs) {
|
|
52
|
+
const normalizedAttrs = {};
|
|
53
|
+
for (const [attrKey, attrValue] of Object.entries(attrs)) {
|
|
54
|
+
normalizedAttrs[attrKey] = normalizeAttributeValue(attrValue);
|
|
55
|
+
}
|
|
56
|
+
return normalizedAttrs;
|
|
57
|
+
}
|
|
58
|
+
function normalizeAttributes(raw) {
|
|
59
|
+
if (raw === null || raw === void 0 || typeof raw !== "object" || Array.isArray(raw)) {
|
|
60
|
+
return raw;
|
|
61
|
+
}
|
|
62
|
+
const obj = raw;
|
|
63
|
+
const normalized = { ...obj };
|
|
64
|
+
if ("actors" in normalized && typeof normalized.actors === "object" && normalized.actors !== null) {
|
|
65
|
+
const actors = normalized.actors;
|
|
66
|
+
const normalizedActors = {};
|
|
67
|
+
for (const [actorName, actorDef] of Object.entries(actors)) {
|
|
68
|
+
if (typeof actorDef === "object" && actorDef !== null) {
|
|
69
|
+
const actor = actorDef;
|
|
70
|
+
const normalizedActor = { ...actor };
|
|
71
|
+
if ("attributes" in normalizedActor && typeof normalizedActor.attributes === "object" && normalizedActor.attributes !== null) {
|
|
72
|
+
normalizedActor.attributes = normalizeAttributeRecord(normalizedActor.attributes);
|
|
73
|
+
}
|
|
74
|
+
normalizedActors[actorName] = normalizedActor;
|
|
75
|
+
} else {
|
|
76
|
+
normalizedActors[actorName] = actorDef;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
normalized.actors = normalizedActors;
|
|
80
|
+
}
|
|
81
|
+
if ("resources" in normalized && typeof normalized.resources === "object" && normalized.resources !== null) {
|
|
82
|
+
const resources = normalized.resources;
|
|
83
|
+
const normalizedResources = {};
|
|
84
|
+
for (const [resName, resDef] of Object.entries(resources)) {
|
|
85
|
+
if (typeof resDef === "object" && resDef !== null) {
|
|
86
|
+
const resource = resDef;
|
|
87
|
+
const normalizedResource = { ...resource };
|
|
88
|
+
if ("attributes" in normalizedResource && typeof normalizedResource.attributes === "object" && normalizedResource.attributes !== null) {
|
|
89
|
+
normalizedResource.attributes = normalizeAttributeRecord(normalizedResource.attributes);
|
|
90
|
+
}
|
|
91
|
+
normalizedResources[resName] = normalizedResource;
|
|
92
|
+
} else {
|
|
93
|
+
normalizedResources[resName] = resDef;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
normalized.resources = normalizedResources;
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
17
100
|
function loadPolicyFile(filePath) {
|
|
18
101
|
const absPath = resolve(filePath);
|
|
19
102
|
let content;
|
|
@@ -42,7 +125,8 @@ function loadPolicyFile(filePath) {
|
|
|
42
125
|
);
|
|
43
126
|
}
|
|
44
127
|
}
|
|
45
|
-
const
|
|
128
|
+
const normalizedRaw = normalizeAttributes(raw);
|
|
129
|
+
const result = v.safeParse(PolicySchema, normalizedRaw);
|
|
46
130
|
if (!result.success) {
|
|
47
131
|
const issue = result.issues[0];
|
|
48
132
|
const path = issue?.path?.map((p) => String(p.key)).join(".") ?? "";
|
|
@@ -45,6 +45,58 @@ interface DefaultSchema extends TorideSchema {
|
|
|
45
45
|
actorAttributeMap: Record<string, Record<string, unknown>>;
|
|
46
46
|
relationMap: Record<string, Record<string, string>>;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Virtual field mapping configuration.
|
|
50
|
+
* Describes how a virtual field maps to a related resource query.
|
|
51
|
+
*/
|
|
52
|
+
interface VirtualFieldMapping {
|
|
53
|
+
/** The relation name to query */
|
|
54
|
+
readonly relation: string;
|
|
55
|
+
/** The field in the related resource to match against */
|
|
56
|
+
readonly matchField: string;
|
|
57
|
+
/** Optional filter to apply to the relation query */
|
|
58
|
+
readonly filter?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Extracts keys from T whose values are arrays.
|
|
62
|
+
*/
|
|
63
|
+
type ArrayKeys<T> = {
|
|
64
|
+
[K in keyof T]: T[K] extends unknown[] ? K : never;
|
|
65
|
+
}[keyof T];
|
|
66
|
+
type ModelScalars<T> = T extends {
|
|
67
|
+
scalars: infer S extends Record<string, unknown>;
|
|
68
|
+
} ? S : T extends Record<string, unknown> ? T : never;
|
|
69
|
+
type UnwrapRelation<T> = T extends readonly (infer U)[] ? U : NonNullable<T>;
|
|
70
|
+
type PayloadRelations<T> = T extends {
|
|
71
|
+
objects: infer O extends Record<string, unknown>;
|
|
72
|
+
} ? {
|
|
73
|
+
[K in keyof O & string]: ModelScalars<UnwrapRelation<O[K]>>;
|
|
74
|
+
} : Record<string, never>;
|
|
75
|
+
type VirtualFieldMappingFor<TRelations extends Record<string, Record<string, unknown>>> = {
|
|
76
|
+
[R in keyof TRelations & string]: {
|
|
77
|
+
readonly relation: R;
|
|
78
|
+
readonly matchField: keyof TRelations[R] & string;
|
|
79
|
+
readonly filter?: {
|
|
80
|
+
readonly [F in keyof TRelations[R]]?: TRelations[R][F];
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}[keyof TRelations & string];
|
|
84
|
+
/**
|
|
85
|
+
* Conditional type that uses model-aware detection when TModelMap is provided,
|
|
86
|
+
* falls back to ArrayKeys otherwise.
|
|
87
|
+
*/
|
|
88
|
+
type VirtualFieldKeysFor<S extends TorideSchema, R extends string, TModelMap> = [TModelMap] extends [never] ? S["resourceAttributeMap"][R] extends Record<string, unknown> ? Record<string, unknown> extends S["resourceAttributeMap"][R] ? string : ArrayKeys<S["resourceAttributeMap"][R]> : never : R extends keyof TModelMap ? Exclude<keyof S["resourceAttributeMap"][R] & string, keyof ModelScalars<TModelMap[R]> & string> : string;
|
|
89
|
+
/**
|
|
90
|
+
* Per-resource mapping of virtual field keys to VirtualFieldMapping.
|
|
91
|
+
* When TModelMap is provided, uses model-aware detection for virtual field keys.
|
|
92
|
+
*/
|
|
93
|
+
type VirtualFieldsConfig<S extends TorideSchema, TModelMap = never> = {
|
|
94
|
+
[R in S["resources"]]?: {
|
|
95
|
+
[K in VirtualFieldKeysFor<S, R & string, TModelMap>]?: R extends keyof TModelMap ? TModelMap[R] extends {
|
|
96
|
+
objects: Record<string, unknown>;
|
|
97
|
+
} ? VirtualFieldMappingFor<PayloadRelations<TModelMap[R]>> : VirtualFieldMapping : VirtualFieldMapping;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
48
100
|
/**
|
|
49
101
|
* Represents an entity performing actions.
|
|
50
102
|
* Generic discriminated union over actor types in S.
|
|
@@ -145,10 +197,27 @@ interface TorideOptions<S extends TorideSchema = DefaultSchema> {
|
|
|
145
197
|
readonly onQuery?: (event: QueryEvent) => void;
|
|
146
198
|
}
|
|
147
199
|
/** Attribute type for actor declarations. */
|
|
148
|
-
type AttributeType = "string" | "number" | "boolean";
|
|
200
|
+
type AttributeType = "string" | "number" | "boolean" | "string[]" | "number[]" | "boolean[]";
|
|
201
|
+
/** Schema for a primitive attribute. */
|
|
202
|
+
interface PrimitiveAttributeSchema {
|
|
203
|
+
readonly kind: "primitive";
|
|
204
|
+
readonly type: AttributeType;
|
|
205
|
+
}
|
|
206
|
+
/** Schema for a nested object attribute. */
|
|
207
|
+
interface ObjectAttributeSchema {
|
|
208
|
+
readonly kind: "object";
|
|
209
|
+
readonly fields: Record<string, AttributeSchema>;
|
|
210
|
+
}
|
|
211
|
+
/** Schema for an array attribute. */
|
|
212
|
+
interface ArrayAttributeSchema {
|
|
213
|
+
readonly kind: "array";
|
|
214
|
+
readonly items: AttributeSchema;
|
|
215
|
+
}
|
|
216
|
+
/** Discriminated union of all attribute schema types. */
|
|
217
|
+
type AttributeSchema = PrimitiveAttributeSchema | ObjectAttributeSchema | ArrayAttributeSchema;
|
|
149
218
|
/** Actor type declaration with attribute schema. */
|
|
150
219
|
interface ActorDeclaration {
|
|
151
|
-
readonly attributes: Record<string,
|
|
220
|
+
readonly attributes: Record<string, AttributeSchema>;
|
|
152
221
|
}
|
|
153
222
|
/** Global role definition derived from actor attributes. */
|
|
154
223
|
interface GlobalRole {
|
|
@@ -190,7 +259,7 @@ interface ResourceBlock {
|
|
|
190
259
|
readonly roles: string[];
|
|
191
260
|
readonly permissions: string[];
|
|
192
261
|
/** Optional typed attribute declarations for this resource type. */
|
|
193
|
-
readonly attributes?: Record<string,
|
|
262
|
+
readonly attributes?: Record<string, AttributeSchema>;
|
|
194
263
|
/** Relations map field names to target resource type names (simplified). */
|
|
195
264
|
readonly relations?: Record<string, string>;
|
|
196
265
|
readonly grants?: Record<string, string[]>;
|
|
@@ -324,19 +393,31 @@ declare class DepthLimitError extends Error {
|
|
|
324
393
|
readonly limitType: "condition" | "derivation";
|
|
325
394
|
constructor(message: string, limit: number, limitType: "condition" | "derivation");
|
|
326
395
|
}
|
|
396
|
+
/** Thrown when an actor is forbidden from performing an action. */
|
|
397
|
+
declare class ForbiddenError extends Error {
|
|
398
|
+
readonly actor: ActorRef;
|
|
399
|
+
readonly action: string;
|
|
400
|
+
readonly resourceType: string;
|
|
401
|
+
constructor(actor: ActorRef, action: string, resourceType: string);
|
|
402
|
+
}
|
|
327
403
|
|
|
328
404
|
/**
|
|
329
405
|
* A serializable map of permissions keyed by "Type:id".
|
|
330
406
|
* Values are arrays of permitted action strings.
|
|
331
407
|
* Suitable for JSON transport to client-side TorideClient.
|
|
408
|
+
*
|
|
409
|
+
* Generic over TorideSchema so that the type parameter flows through
|
|
410
|
+
* to TorideClient<S> on deserialization. The runtime structure is unchanged.
|
|
332
411
|
*/
|
|
333
|
-
type PermissionSnapshot = Record<string, string[]
|
|
412
|
+
type PermissionSnapshot<S extends TorideSchema = DefaultSchema> = Record<string, string[]> & {
|
|
413
|
+
readonly __schema?: S | undefined;
|
|
414
|
+
};
|
|
334
415
|
|
|
335
416
|
declare const CLIENT_VERSION = "0.0.1";
|
|
336
417
|
|
|
337
|
-
/** Minimal resource reference for client-side lookups. Generic over S for type narrowing. */
|
|
338
|
-
interface ClientResourceRef<S extends TorideSchema = DefaultSchema> {
|
|
339
|
-
readonly type:
|
|
418
|
+
/** Minimal resource reference for client-side lookups. Generic over S and R for type narrowing. */
|
|
419
|
+
interface ClientResourceRef<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> {
|
|
420
|
+
readonly type: R;
|
|
340
421
|
readonly id: string;
|
|
341
422
|
}
|
|
342
423
|
/**
|
|
@@ -351,18 +432,18 @@ interface ClientResourceRef<S extends TorideSchema = DefaultSchema> {
|
|
|
351
432
|
*/
|
|
352
433
|
declare class TorideClient<S extends TorideSchema = DefaultSchema> {
|
|
353
434
|
private readonly permissions;
|
|
354
|
-
constructor(snapshot: PermissionSnapshot);
|
|
435
|
+
constructor(snapshot: PermissionSnapshot<S>);
|
|
355
436
|
/**
|
|
356
437
|
* Synchronous permission check.
|
|
357
438
|
* Returns true if the action is permitted for the resource, false otherwise.
|
|
358
439
|
* Unknown resources return false (default-deny).
|
|
359
440
|
*/
|
|
360
|
-
can(action: S["
|
|
441
|
+
can<R extends S["resources"]>(action: S["permissionMap"][R], resource: ClientResourceRef<S, R>): boolean;
|
|
361
442
|
/**
|
|
362
443
|
* Return the list of permitted actions for a resource.
|
|
363
444
|
* Returns empty array for unknown resources.
|
|
364
445
|
*/
|
|
365
|
-
permittedActions(resource: ClientResourceRef<S>): S["
|
|
446
|
+
permittedActions<R extends S["resources"]>(resource: ClientResourceRef<S, R>): S["permissionMap"][R][];
|
|
366
447
|
}
|
|
367
448
|
|
|
368
|
-
export { type ActorRef as A, type BatchCheckItem as B, type CheckOptions as C, type DefaultSchema as D, type ExplainResult as E, type FieldAccessDef as F, type GlobalRole as G, type MatchedRule as M, type Policy as P, type QueryEvent as Q, type ResourceRef as R, type SimpleConditions as S, type TorideSchema as T, ValidationError as V, type TorideOptions as a, type PermissionSnapshot as b, type TestCase as c, type Resolvers as d, type ActorDeclaration as e, type
|
|
449
|
+
export { type ActorRef as A, type BatchCheckItem as B, type CheckOptions as C, type DefaultSchema as D, type ExplainResult as E, type FieldAccessDef as F, type GlobalRole as G, type VirtualFieldMapping as H, type VirtualFieldMappingFor as I, type VirtualFieldsConfig as J, CLIENT_VERSION as K, type MatchedRule as M, type ObjectAttributeSchema as O, type Policy as P, type QueryEvent as Q, type ResourceRef as R, type SimpleConditions as S, type TorideSchema as T, ValidationError as V, type TorideOptions as a, type PermissionSnapshot as b, type TestCase as c, type Resolvers as d, type ActorDeclaration as e, type ArrayAttributeSchema as f, type AttributeSchema as g, type AttributeType as h, type ClientResourceRef as i, type ConditionExpression as j, type ConditionOperator as k, type ConditionValue as l, CycleError as m, type DecisionEvent as n, DepthLimitError as o, type DerivedRoleEntry as p, type DerivedRoleTrace as q, type EvaluatorFn as r, ForbiddenError as s, type PayloadRelations as t, type PrimitiveAttributeSchema as u, type ResolvedRolesDetail as v, type ResourceBlock as w, type ResourceResolver as x, type Rule as y, TorideClient as z };
|
package/dist/client.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { K as CLIENT_VERSION, i as ClientResourceRef, b as PermissionSnapshot, z as TorideClient } from './client-CBp3admQ.js';
|