toride 0.1.0 → 0.3.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-QE4FJZEG.js → chunk-2AWXNP37.js} +74 -25
- package/dist/chunk-S6DKDABO.js +38 -0
- package/dist/cli.js +1 -1
- package/dist/client-aExMng5T.d.ts +373 -0
- package/dist/client.d.ts +1 -33
- package/dist/client.js +4 -32
- package/dist/index.d.ts +40 -252
- package/dist/index.js +5 -1
- package/package.json +7 -3
- package/dist/snapshot-CcCYeU0Q.d.ts +0 -8
|
@@ -54,6 +54,7 @@ var FieldAccessDefSchema = v.object({
|
|
|
54
54
|
var ResourceBlockSchema = v.object({
|
|
55
55
|
roles: v.array(v.string()),
|
|
56
56
|
permissions: v.array(v.string()),
|
|
57
|
+
attributes: v.optional(v.record(v.string(), AttributeTypeSchema)),
|
|
57
58
|
relations: v.optional(v.record(v.string(), v.string())),
|
|
58
59
|
grants: v.optional(v.record(v.string(), v.array(v.string()))),
|
|
59
60
|
derived_roles: v.optional(v.array(DerivedRoleEntrySchema)),
|
|
@@ -2000,16 +2001,17 @@ async function permittedFields(engine, actor, operation, resource, fieldAccess,
|
|
|
2000
2001
|
for (const fieldName of fieldNames) {
|
|
2001
2002
|
const fieldDef = fieldAccess[fieldName];
|
|
2002
2003
|
const allowedRoles = fieldDef?.[operation];
|
|
2004
|
+
const typedFieldName = fieldName;
|
|
2003
2005
|
if (allowedRoles) {
|
|
2004
2006
|
if (actorRoles.some((role) => allowedRoles.includes(role))) {
|
|
2005
|
-
permitted.push(
|
|
2007
|
+
permitted.push(typedFieldName);
|
|
2006
2008
|
}
|
|
2007
2009
|
} else {
|
|
2008
2010
|
if (hasResourcePermission === void 0) {
|
|
2009
2011
|
hasResourcePermission = await engine.can(actor, operation, resource, options);
|
|
2010
2012
|
}
|
|
2011
2013
|
if (hasResourcePermission) {
|
|
2012
|
-
permitted.push(
|
|
2014
|
+
permitted.push(typedFieldName);
|
|
2013
2015
|
}
|
|
2014
2016
|
}
|
|
2015
2017
|
}
|
|
@@ -2031,8 +2033,11 @@ var Toride = class {
|
|
|
2031
2033
|
* Default-deny: returns false if resource type is unknown or no grants match.
|
|
2032
2034
|
*/
|
|
2033
2035
|
async can(actor, action, resource, options) {
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
+
const a = actor;
|
|
2037
|
+
const r = resource;
|
|
2038
|
+
const act = action;
|
|
2039
|
+
const result = await this.evaluateInternal(a, act, r, options);
|
|
2040
|
+
this.fireDecisionEvent(a, act, r, result);
|
|
2036
2041
|
return result.allowed;
|
|
2037
2042
|
}
|
|
2038
2043
|
/**
|
|
@@ -2048,8 +2053,11 @@ var Toride = class {
|
|
|
2048
2053
|
* granted permissions, matched rules, and human-readable final decision.
|
|
2049
2054
|
*/
|
|
2050
2055
|
async explain(actor, action, resource, options) {
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2056
|
+
const a = actor;
|
|
2057
|
+
const r = resource;
|
|
2058
|
+
const act = action;
|
|
2059
|
+
const result = await this.evaluateInternal(a, act, r, options);
|
|
2060
|
+
this.fireDecisionEvent(a, act, r, result);
|
|
2053
2061
|
return result;
|
|
2054
2062
|
}
|
|
2055
2063
|
/**
|
|
@@ -2057,7 +2065,9 @@ var Toride = class {
|
|
|
2057
2065
|
* Uses a shared cache across all per-action evaluations.
|
|
2058
2066
|
*/
|
|
2059
2067
|
async permittedActions(actor, resource, options) {
|
|
2060
|
-
const
|
|
2068
|
+
const a = actor;
|
|
2069
|
+
const r = resource;
|
|
2070
|
+
const resourceBlock = this.policy.resources[r.type];
|
|
2061
2071
|
if (!resourceBlock) {
|
|
2062
2072
|
return [];
|
|
2063
2073
|
}
|
|
@@ -2065,9 +2075,9 @@ var Toride = class {
|
|
|
2065
2075
|
const permitted = [];
|
|
2066
2076
|
for (const action of resourceBlock.permissions) {
|
|
2067
2077
|
const result = await this.evaluateInternal(
|
|
2068
|
-
|
|
2078
|
+
a,
|
|
2069
2079
|
action,
|
|
2070
|
-
|
|
2080
|
+
r,
|
|
2071
2081
|
options,
|
|
2072
2082
|
sharedCache
|
|
2073
2083
|
);
|
|
@@ -2084,7 +2094,12 @@ var Toride = class {
|
|
|
2084
2094
|
* Suitable for serializing to the client via TorideClient.
|
|
2085
2095
|
*/
|
|
2086
2096
|
async snapshot(actor, resources, options) {
|
|
2087
|
-
return snapshot(
|
|
2097
|
+
return snapshot(
|
|
2098
|
+
this,
|
|
2099
|
+
actor,
|
|
2100
|
+
resources,
|
|
2101
|
+
options
|
|
2102
|
+
);
|
|
2088
2103
|
}
|
|
2089
2104
|
/**
|
|
2090
2105
|
* T095: Check if an actor can perform a field-level operation on a specific field.
|
|
@@ -2092,33 +2107,52 @@ var Toride = class {
|
|
|
2092
2107
|
* Unlisted fields are unrestricted: any actor with the resource-level permission can access them.
|
|
2093
2108
|
*/
|
|
2094
2109
|
async canField(actor, operation, resource, field, options) {
|
|
2095
|
-
const
|
|
2110
|
+
const r = resource;
|
|
2111
|
+
const resourceBlock = this.policy.resources[r.type];
|
|
2096
2112
|
if (!resourceBlock) {
|
|
2097
2113
|
return false;
|
|
2098
2114
|
}
|
|
2099
|
-
return canField(
|
|
2115
|
+
return canField(
|
|
2116
|
+
this,
|
|
2117
|
+
actor,
|
|
2118
|
+
operation,
|
|
2119
|
+
r,
|
|
2120
|
+
field,
|
|
2121
|
+
resourceBlock.field_access,
|
|
2122
|
+
options
|
|
2123
|
+
);
|
|
2100
2124
|
}
|
|
2101
2125
|
/**
|
|
2102
2126
|
* T095: Return the list of declared field_access field names the actor can access
|
|
2103
2127
|
* for the given operation. Only returns explicitly declared fields.
|
|
2104
2128
|
*/
|
|
2105
2129
|
async permittedFields(actor, operation, resource, options) {
|
|
2106
|
-
const
|
|
2130
|
+
const r = resource;
|
|
2131
|
+
const resourceBlock = this.policy.resources[r.type];
|
|
2107
2132
|
if (!resourceBlock) {
|
|
2108
2133
|
return [];
|
|
2109
2134
|
}
|
|
2110
|
-
return permittedFields(
|
|
2135
|
+
return permittedFields(
|
|
2136
|
+
this,
|
|
2137
|
+
actor,
|
|
2138
|
+
operation,
|
|
2139
|
+
r,
|
|
2140
|
+
resourceBlock.field_access,
|
|
2141
|
+
options
|
|
2142
|
+
);
|
|
2111
2143
|
}
|
|
2112
2144
|
/**
|
|
2113
2145
|
* T070: Return flat deduplicated list of all resolved roles (direct + derived).
|
|
2114
2146
|
*/
|
|
2115
2147
|
async resolvedRoles(actor, resource, options) {
|
|
2116
|
-
const
|
|
2148
|
+
const a = actor;
|
|
2149
|
+
const r = resource;
|
|
2150
|
+
const resourceBlock = this.policy.resources[r.type];
|
|
2117
2151
|
if (!resourceBlock) {
|
|
2118
2152
|
return [];
|
|
2119
2153
|
}
|
|
2120
2154
|
const action = resourceBlock.permissions[0] ?? "__resolvedRoles__";
|
|
2121
|
-
const result = await this.evaluateInternal(
|
|
2155
|
+
const result = await this.evaluateInternal(a, action, r, options);
|
|
2122
2156
|
const directRoles = result.resolvedRoles.direct;
|
|
2123
2157
|
const derivedRoleNames = result.resolvedRoles.derived.map((d) => d.role);
|
|
2124
2158
|
return [.../* @__PURE__ */ new Set([...directRoles, ...derivedRoleNames])];
|
|
@@ -2131,17 +2165,20 @@ var Toride = class {
|
|
|
2131
2165
|
if (checks.length === 0) {
|
|
2132
2166
|
return [];
|
|
2133
2167
|
}
|
|
2168
|
+
const a = actor;
|
|
2134
2169
|
const sharedCache = new AttributeCache(this.resolvers);
|
|
2135
2170
|
const results = [];
|
|
2136
2171
|
for (const check of checks) {
|
|
2172
|
+
const r = check.resource;
|
|
2173
|
+
const act = check.action;
|
|
2137
2174
|
const result = await this.evaluateInternal(
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2175
|
+
a,
|
|
2176
|
+
act,
|
|
2177
|
+
r,
|
|
2141
2178
|
options,
|
|
2142
2179
|
sharedCache
|
|
2143
2180
|
);
|
|
2144
|
-
this.fireDecisionEvent(
|
|
2181
|
+
this.fireDecisionEvent(a, act, r, result);
|
|
2145
2182
|
results.push(result.allowed);
|
|
2146
2183
|
}
|
|
2147
2184
|
return results;
|
|
@@ -2151,11 +2188,14 @@ var Toride = class {
|
|
|
2151
2188
|
* Returns ConstraintResult with unrestricted/forbidden sentinels or constraint AST.
|
|
2152
2189
|
*/
|
|
2153
2190
|
async buildConstraints(actor, action, resourceType, options) {
|
|
2191
|
+
const a = actor;
|
|
2192
|
+
const act = action;
|
|
2193
|
+
const rt = resourceType;
|
|
2154
2194
|
const cache = new AttributeCache(this.resolvers);
|
|
2155
2195
|
const constraintResult = await buildConstraints(
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2196
|
+
a,
|
|
2197
|
+
act,
|
|
2198
|
+
rt,
|
|
2159
2199
|
cache,
|
|
2160
2200
|
this.policy,
|
|
2161
2201
|
{
|
|
@@ -2164,15 +2204,24 @@ var Toride = class {
|
|
|
2164
2204
|
customEvaluators: this.options.customEvaluators
|
|
2165
2205
|
}
|
|
2166
2206
|
);
|
|
2167
|
-
this.fireQueryEvent(
|
|
2207
|
+
this.fireQueryEvent(a, act, rt, constraintResult);
|
|
2168
2208
|
return constraintResult;
|
|
2169
2209
|
}
|
|
2170
2210
|
/**
|
|
2171
2211
|
* T064: Translate constraint AST using an adapter.
|
|
2172
2212
|
* Dispatches each constraint node to the adapter's methods.
|
|
2213
|
+
*
|
|
2214
|
+
* Accepts ConstraintResult<R> from buildConstraints() and returns
|
|
2215
|
+
* TQueryMap[R] — the adapter's mapped output type for resource R.
|
|
2216
|
+
* The resource type R is inferred from the ConstraintResult phantom type.
|
|
2173
2217
|
*/
|
|
2174
2218
|
translateConstraints(constraints, adapter) {
|
|
2175
|
-
|
|
2219
|
+
if ("unrestricted" in constraints || "forbidden" in constraints) {
|
|
2220
|
+
throw new Error(
|
|
2221
|
+
"Cannot translate unrestricted or forbidden ConstraintResult. Check for 'constraints' property before calling translateConstraints()."
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
return translateConstraints(constraints.constraints, adapter);
|
|
2176
2225
|
}
|
|
2177
2226
|
/**
|
|
2178
2227
|
* T072: Fire onDecision audit callback via microtask (non-blocking).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var CLIENT_VERSION = "0.0.1";
|
|
3
|
+
var TorideClient = class {
|
|
4
|
+
permissions;
|
|
5
|
+
constructor(snapshot) {
|
|
6
|
+
this.permissions = /* @__PURE__ */ new Map();
|
|
7
|
+
const entries = snapshot;
|
|
8
|
+
for (const [key, actions] of Object.entries(entries)) {
|
|
9
|
+
this.permissions.set(key, new Set(actions));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous permission check.
|
|
14
|
+
* Returns true if the action is permitted for the resource, false otherwise.
|
|
15
|
+
* Unknown resources return false (default-deny).
|
|
16
|
+
*/
|
|
17
|
+
can(action, resource) {
|
|
18
|
+
const key = `${resource.type}:${resource.id}`;
|
|
19
|
+
const actions = this.permissions.get(key);
|
|
20
|
+
if (!actions) return false;
|
|
21
|
+
return actions.has(action);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Return the list of permitted actions for a resource.
|
|
25
|
+
* Returns empty array for unknown resources.
|
|
26
|
+
*/
|
|
27
|
+
permittedActions(resource) {
|
|
28
|
+
const key = `${resource.type}:${resource.id}`;
|
|
29
|
+
const actions = this.permissions.get(key);
|
|
30
|
+
if (!actions) return [];
|
|
31
|
+
return [...actions];
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
CLIENT_VERSION,
|
|
37
|
+
TorideClient
|
|
38
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape constraint for Toride's type parameter.
|
|
3
|
+
* Each property is a union or mapped type that codegen fills with literals.
|
|
4
|
+
*/
|
|
5
|
+
interface TorideSchema {
|
|
6
|
+
/** Union of all resource type names (e.g., "Document" | "Organization") */
|
|
7
|
+
resources: string;
|
|
8
|
+
/** Global union of all action/permission names across all resources */
|
|
9
|
+
actions: string;
|
|
10
|
+
/** Union of all actor type names (e.g., "User" | "ServiceAccount") */
|
|
11
|
+
actorTypes: string;
|
|
12
|
+
/** Per-resource permission unions: { Document: "read" | "write"; ... } */
|
|
13
|
+
permissionMap: {
|
|
14
|
+
[R in string]: string;
|
|
15
|
+
};
|
|
16
|
+
/** Per-resource role unions: { Document: "admin" | "editor"; ... } */
|
|
17
|
+
roleMap: {
|
|
18
|
+
[R in string]: string;
|
|
19
|
+
};
|
|
20
|
+
/** Per-resource attribute shapes: { Document: { status: string; ownerId: string }; ... } */
|
|
21
|
+
resourceAttributeMap: {
|
|
22
|
+
[R in string]: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
/** Per-actor attribute shapes: { User: { email: string; is_admin: boolean }; ... } */
|
|
25
|
+
actorAttributeMap: {
|
|
26
|
+
[A in string]: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
/** Per-resource relation maps: { Document: { org: "Organization" }; ... } */
|
|
29
|
+
relationMap: {
|
|
30
|
+
[R in string]: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Default schema where everything is string / Record<string, unknown>.
|
|
35
|
+
* Used when Toride is instantiated without a type parameter.
|
|
36
|
+
* Provides full backward compatibility with the current untyped API.
|
|
37
|
+
*/
|
|
38
|
+
interface DefaultSchema extends TorideSchema {
|
|
39
|
+
resources: string;
|
|
40
|
+
actions: string;
|
|
41
|
+
actorTypes: string;
|
|
42
|
+
permissionMap: Record<string, string>;
|
|
43
|
+
roleMap: Record<string, string>;
|
|
44
|
+
resourceAttributeMap: Record<string, Record<string, unknown>>;
|
|
45
|
+
actorAttributeMap: Record<string, Record<string, unknown>>;
|
|
46
|
+
relationMap: Record<string, Record<string, string>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Represents an entity performing actions.
|
|
50
|
+
* Generic discriminated union over actor types in S.
|
|
51
|
+
* When S = DefaultSchema, collapses to the original untyped shape.
|
|
52
|
+
*/
|
|
53
|
+
type ActorRef<S extends TorideSchema = DefaultSchema> = {
|
|
54
|
+
[A in S["actorTypes"]]: {
|
|
55
|
+
readonly type: A;
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly attributes: S["actorAttributeMap"][A];
|
|
58
|
+
};
|
|
59
|
+
}[S["actorTypes"]];
|
|
60
|
+
/**
|
|
61
|
+
* Represents a protected entity being accessed.
|
|
62
|
+
* Generic over schema S and resource type R.
|
|
63
|
+
* When S = DefaultSchema, collapses to the original untyped shape.
|
|
64
|
+
*/
|
|
65
|
+
type ResourceRef<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> = {
|
|
66
|
+
readonly type: R;
|
|
67
|
+
readonly id: string;
|
|
68
|
+
/** Pre-fetched attributes. Inline values take precedence over resolver results. */
|
|
69
|
+
readonly attributes?: S["resourceAttributeMap"][R];
|
|
70
|
+
};
|
|
71
|
+
/** Optional per-check configuration. */
|
|
72
|
+
interface CheckOptions {
|
|
73
|
+
readonly env?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
/** A single item in a canBatch() call. Action narrowed to global actions union. */
|
|
76
|
+
interface BatchCheckItem<S extends TorideSchema = DefaultSchema> {
|
|
77
|
+
readonly action: S["actions"];
|
|
78
|
+
readonly resource: ResourceRef<S>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Per-type resolver function.
|
|
82
|
+
* Called when the engine needs attributes not available inline.
|
|
83
|
+
* Called at most once per unique resource per evaluation (cached).
|
|
84
|
+
*
|
|
85
|
+
* Registering a resolver is **optional** per resource type. When no resolver is
|
|
86
|
+
* registered, inline {@link ResourceRef.attributes} are used as the sole data
|
|
87
|
+
* source — this is referred to as "default resolver" behavior (analogous to
|
|
88
|
+
* GraphQL's default field resolver, which returns `parent[fieldName]`).
|
|
89
|
+
*
|
|
90
|
+
* A resolver is only needed when attributes must be fetched from an external
|
|
91
|
+
* source (e.g., a database). When both inline attributes and a resolver are
|
|
92
|
+
* present, inline attributes take precedence field-by-field over resolver
|
|
93
|
+
* results.
|
|
94
|
+
*/
|
|
95
|
+
type ResourceResolver<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> = (ref: ResourceRef<S, R>) => Promise<Record<string, unknown>>;
|
|
96
|
+
/**
|
|
97
|
+
* Map of resource type names to their resolver functions.
|
|
98
|
+
*
|
|
99
|
+
* Not all types need resolvers. Types without a registered resolver use
|
|
100
|
+
* **default resolver** behavior (also called "trivial resolution"): the engine
|
|
101
|
+
* reads attribute values directly from the inline {@link ResourceRef.attributes}
|
|
102
|
+
* passed at the call site. Fields not present inline resolve to `undefined`,
|
|
103
|
+
* causing conditions that reference them to fail (default-deny).
|
|
104
|
+
*
|
|
105
|
+
* This mirrors GraphQL's default field resolver pattern, where an unresolved
|
|
106
|
+
* field simply returns `parent[fieldName]` — here, inline attributes play the
|
|
107
|
+
* role of the `parent` object.
|
|
108
|
+
*/
|
|
109
|
+
type Resolvers<S extends TorideSchema = DefaultSchema> = {
|
|
110
|
+
[R in S["resources"]]?: ResourceResolver<S, R>;
|
|
111
|
+
};
|
|
112
|
+
/** Custom evaluator function signature. */
|
|
113
|
+
type EvaluatorFn = (actor: ActorRef, resource: ResourceRef, env: Record<string, unknown>) => Promise<boolean>;
|
|
114
|
+
/** Engine construction options. */
|
|
115
|
+
interface TorideOptions<S extends TorideSchema = DefaultSchema> {
|
|
116
|
+
readonly policy: Policy;
|
|
117
|
+
/**
|
|
118
|
+
* Per-type resolver map.
|
|
119
|
+
*
|
|
120
|
+
* Optional — the engine works without any resolvers when all required data is
|
|
121
|
+
* provided inline via {@link ResourceRef.attributes}. This "default resolver"
|
|
122
|
+
* mode is the simplest way to use toride and requires no async data fetching.
|
|
123
|
+
*
|
|
124
|
+
* When both inline attributes and a resolver are present for the same resource
|
|
125
|
+
* type, **inline attributes take precedence** over resolver results on a
|
|
126
|
+
* field-by-field basis.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* // Inline-only mode — no resolvers needed
|
|
131
|
+
* const toride = new Toride({ policy });
|
|
132
|
+
*
|
|
133
|
+
* const allowed = await toride.can(actor, "read", {
|
|
134
|
+
* type: "Document",
|
|
135
|
+
* id: "doc-1",
|
|
136
|
+
* attributes: { status: "published", ownerId: "user-42" },
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
readonly resolvers?: Resolvers<S>;
|
|
141
|
+
readonly maxConditionDepth?: number;
|
|
142
|
+
readonly maxDerivedRoleDepth?: number;
|
|
143
|
+
readonly customEvaluators?: Record<string, EvaluatorFn>;
|
|
144
|
+
readonly onDecision?: (event: DecisionEvent) => void;
|
|
145
|
+
readonly onQuery?: (event: QueryEvent) => void;
|
|
146
|
+
}
|
|
147
|
+
/** Attribute type for actor declarations. */
|
|
148
|
+
type AttributeType = "string" | "number" | "boolean";
|
|
149
|
+
/** Actor type declaration with attribute schema. */
|
|
150
|
+
interface ActorDeclaration {
|
|
151
|
+
readonly attributes: Record<string, AttributeType>;
|
|
152
|
+
}
|
|
153
|
+
/** Global role definition derived from actor attributes. */
|
|
154
|
+
interface GlobalRole {
|
|
155
|
+
readonly actor_type: string;
|
|
156
|
+
readonly when: ConditionExpression;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Derived role entry. Exactly one derivation pattern per entry.
|
|
160
|
+
* Patterns:
|
|
161
|
+
* 1. from_global_role
|
|
162
|
+
* 2. from_role + on_relation
|
|
163
|
+
* 3. from_relation
|
|
164
|
+
* 4. actor_type + when (conditional)
|
|
165
|
+
* 5. when only
|
|
166
|
+
*/
|
|
167
|
+
interface DerivedRoleEntry {
|
|
168
|
+
readonly role: string;
|
|
169
|
+
readonly from_global_role?: string;
|
|
170
|
+
readonly from_role?: string;
|
|
171
|
+
readonly on_relation?: string;
|
|
172
|
+
readonly from_relation?: string;
|
|
173
|
+
readonly actor_type?: string;
|
|
174
|
+
readonly when?: ConditionExpression;
|
|
175
|
+
}
|
|
176
|
+
/** Conditional rule (permit or forbid). */
|
|
177
|
+
interface Rule {
|
|
178
|
+
readonly effect: "permit" | "forbid";
|
|
179
|
+
readonly roles?: string[];
|
|
180
|
+
readonly permissions: string[];
|
|
181
|
+
readonly when: ConditionExpression;
|
|
182
|
+
}
|
|
183
|
+
/** Field-level access control definition. */
|
|
184
|
+
interface FieldAccessDef {
|
|
185
|
+
readonly read?: string[];
|
|
186
|
+
readonly update?: string[];
|
|
187
|
+
}
|
|
188
|
+
/** Resource block definition. */
|
|
189
|
+
interface ResourceBlock {
|
|
190
|
+
readonly roles: string[];
|
|
191
|
+
readonly permissions: string[];
|
|
192
|
+
/** Optional typed attribute declarations for this resource type. */
|
|
193
|
+
readonly attributes?: Record<string, AttributeType>;
|
|
194
|
+
/** Relations map field names to target resource type names (simplified). */
|
|
195
|
+
readonly relations?: Record<string, string>;
|
|
196
|
+
readonly grants?: Record<string, string[]>;
|
|
197
|
+
readonly derived_roles?: DerivedRoleEntry[];
|
|
198
|
+
readonly rules?: Rule[];
|
|
199
|
+
readonly field_access?: Record<string, FieldAccessDef>;
|
|
200
|
+
}
|
|
201
|
+
/** Operator-based condition value. */
|
|
202
|
+
type ConditionOperator = {
|
|
203
|
+
readonly eq: unknown;
|
|
204
|
+
} | {
|
|
205
|
+
readonly neq: unknown;
|
|
206
|
+
} | {
|
|
207
|
+
readonly gt: unknown;
|
|
208
|
+
} | {
|
|
209
|
+
readonly gte: unknown;
|
|
210
|
+
} | {
|
|
211
|
+
readonly lt: unknown;
|
|
212
|
+
} | {
|
|
213
|
+
readonly lte: unknown;
|
|
214
|
+
} | {
|
|
215
|
+
readonly in: unknown[] | string;
|
|
216
|
+
} | {
|
|
217
|
+
readonly includes: unknown;
|
|
218
|
+
} | {
|
|
219
|
+
readonly exists: boolean;
|
|
220
|
+
} | {
|
|
221
|
+
readonly startsWith: string;
|
|
222
|
+
} | {
|
|
223
|
+
readonly endsWith: string;
|
|
224
|
+
} | {
|
|
225
|
+
readonly contains: string;
|
|
226
|
+
} | {
|
|
227
|
+
readonly custom: string;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Condition value: either a primitive (equality shorthand),
|
|
231
|
+
* a cross-reference string ($actor.x, $resource.x, $env.x),
|
|
232
|
+
* or an operator object.
|
|
233
|
+
*/
|
|
234
|
+
type ConditionValue = string | number | boolean | ConditionOperator;
|
|
235
|
+
/** Simple conditions: all key-value pairs ANDed together. */
|
|
236
|
+
type SimpleConditions = Record<string, ConditionValue>;
|
|
237
|
+
/**
|
|
238
|
+
* Recursive condition expression.
|
|
239
|
+
* Either simple conditions (Record<string, ConditionValue>),
|
|
240
|
+
* or a logical combinator ({ any: ... } or { all: ... }).
|
|
241
|
+
*/
|
|
242
|
+
type ConditionExpression = SimpleConditions | {
|
|
243
|
+
readonly any: ConditionExpression[];
|
|
244
|
+
} | {
|
|
245
|
+
readonly all: ConditionExpression[];
|
|
246
|
+
};
|
|
247
|
+
/** Test case for declarative YAML tests. */
|
|
248
|
+
interface TestCase {
|
|
249
|
+
readonly name: string;
|
|
250
|
+
readonly actor: ActorRef;
|
|
251
|
+
/** Mock resolver data: keyed by "Type:id", values are attribute objects. */
|
|
252
|
+
readonly resolvers?: Record<string, Record<string, unknown>>;
|
|
253
|
+
readonly action: string;
|
|
254
|
+
readonly resource: ResourceRef;
|
|
255
|
+
readonly expected: "allow" | "deny";
|
|
256
|
+
}
|
|
257
|
+
/** Top-level policy object. */
|
|
258
|
+
interface Policy {
|
|
259
|
+
readonly version: "1";
|
|
260
|
+
readonly actors: Record<string, ActorDeclaration>;
|
|
261
|
+
readonly global_roles?: Record<string, GlobalRole>;
|
|
262
|
+
readonly resources: Record<string, ResourceBlock>;
|
|
263
|
+
readonly tests?: TestCase[];
|
|
264
|
+
}
|
|
265
|
+
/** Trace for a derived role showing derivation path. */
|
|
266
|
+
interface DerivedRoleTrace {
|
|
267
|
+
readonly role: string;
|
|
268
|
+
readonly via: string;
|
|
269
|
+
}
|
|
270
|
+
/** Resolved roles detail with direct and derived breakdown. */
|
|
271
|
+
interface ResolvedRolesDetail {
|
|
272
|
+
readonly direct: string[];
|
|
273
|
+
readonly derived: DerivedRoleTrace[];
|
|
274
|
+
}
|
|
275
|
+
/** A matched rule with evaluation context. */
|
|
276
|
+
interface MatchedRule {
|
|
277
|
+
readonly effect: "permit" | "forbid";
|
|
278
|
+
readonly matched: boolean;
|
|
279
|
+
readonly rule: Rule;
|
|
280
|
+
readonly resolvedValues: Record<string, unknown>;
|
|
281
|
+
}
|
|
282
|
+
/** Full decision trace from explain(). */
|
|
283
|
+
interface ExplainResult<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> {
|
|
284
|
+
readonly allowed: boolean;
|
|
285
|
+
readonly resolvedRoles: ResolvedRolesDetail;
|
|
286
|
+
readonly grantedPermissions: S["permissionMap"][R][];
|
|
287
|
+
readonly matchedRules: MatchedRule[];
|
|
288
|
+
readonly finalDecision: string;
|
|
289
|
+
}
|
|
290
|
+
/** Audit event for authorization checks. */
|
|
291
|
+
interface DecisionEvent {
|
|
292
|
+
readonly actor: ActorRef;
|
|
293
|
+
readonly action: string;
|
|
294
|
+
readonly resource: ResourceRef;
|
|
295
|
+
readonly allowed: boolean;
|
|
296
|
+
readonly resolvedRoles: string[];
|
|
297
|
+
readonly matchedRules: {
|
|
298
|
+
effect: string;
|
|
299
|
+
matched: boolean;
|
|
300
|
+
}[];
|
|
301
|
+
readonly timestamp: Date;
|
|
302
|
+
}
|
|
303
|
+
/** Audit event for constraint queries. */
|
|
304
|
+
interface QueryEvent {
|
|
305
|
+
readonly actor: ActorRef;
|
|
306
|
+
readonly action: string;
|
|
307
|
+
readonly resourceType: string;
|
|
308
|
+
readonly resultType: "unrestricted" | "forbidden" | "constrained";
|
|
309
|
+
readonly timestamp: Date;
|
|
310
|
+
}
|
|
311
|
+
/** Thrown when policy validation fails. */
|
|
312
|
+
declare class ValidationError extends Error {
|
|
313
|
+
readonly path: string;
|
|
314
|
+
constructor(message: string, path: string);
|
|
315
|
+
}
|
|
316
|
+
/** Thrown when a cycle is detected in relation traversal. */
|
|
317
|
+
declare class CycleError extends Error {
|
|
318
|
+
readonly path: string[];
|
|
319
|
+
constructor(message: string, path: string[]);
|
|
320
|
+
}
|
|
321
|
+
/** Thrown when depth limit is exceeded. */
|
|
322
|
+
declare class DepthLimitError extends Error {
|
|
323
|
+
readonly limit: number;
|
|
324
|
+
readonly limitType: "condition" | "derivation";
|
|
325
|
+
constructor(message: string, limit: number, limitType: "condition" | "derivation");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* A serializable map of permissions keyed by "Type:id".
|
|
330
|
+
* Values are arrays of permitted action strings.
|
|
331
|
+
* Suitable for JSON transport to client-side TorideClient.
|
|
332
|
+
*
|
|
333
|
+
* Generic over TorideSchema so that the type parameter flows through
|
|
334
|
+
* to TorideClient<S> on deserialization. The runtime structure is unchanged.
|
|
335
|
+
*/
|
|
336
|
+
type PermissionSnapshot<S extends TorideSchema = DefaultSchema> = Record<string, string[]> & {
|
|
337
|
+
readonly __schema?: S | undefined;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
declare const CLIENT_VERSION = "0.0.1";
|
|
341
|
+
|
|
342
|
+
/** Minimal resource reference for client-side lookups. Generic over S and R for type narrowing. */
|
|
343
|
+
interface ClientResourceRef<S extends TorideSchema = DefaultSchema, R extends S["resources"] = S["resources"]> {
|
|
344
|
+
readonly type: R;
|
|
345
|
+
readonly id: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Client-side permission checker that provides instant synchronous checks
|
|
349
|
+
* against a PermissionSnapshot received from the server.
|
|
350
|
+
*
|
|
351
|
+
* Generic over TorideSchema so that action names and resource types
|
|
352
|
+
* are validated at compile time when a concrete schema is provided.
|
|
353
|
+
*
|
|
354
|
+
* Default-deny: unknown resources or actions return false.
|
|
355
|
+
* The snapshot is defensively copied to prevent external mutation.
|
|
356
|
+
*/
|
|
357
|
+
declare class TorideClient<S extends TorideSchema = DefaultSchema> {
|
|
358
|
+
private readonly permissions;
|
|
359
|
+
constructor(snapshot: PermissionSnapshot<S>);
|
|
360
|
+
/**
|
|
361
|
+
* Synchronous permission check.
|
|
362
|
+
* Returns true if the action is permitted for the resource, false otherwise.
|
|
363
|
+
* Unknown resources return false (default-deny).
|
|
364
|
+
*/
|
|
365
|
+
can<R extends S["resources"]>(action: S["permissionMap"][R], resource: ClientResourceRef<S, R>): boolean;
|
|
366
|
+
/**
|
|
367
|
+
* Return the list of permitted actions for a resource.
|
|
368
|
+
* Returns empty array for unknown resources.
|
|
369
|
+
*/
|
|
370
|
+
permittedActions<R extends S["resources"]>(resource: ClientResourceRef<S, R>): S["permissionMap"][R][];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
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 AttributeType as f, type ClientResourceRef as g, type ConditionExpression as h, type ConditionOperator as i, type ConditionValue as j, CycleError as k, type DecisionEvent as l, DepthLimitError as m, type DerivedRoleEntry as n, type DerivedRoleTrace as o, type EvaluatorFn as p, type ResolvedRolesDetail as q, type ResourceBlock as r, type ResourceResolver as s, type Rule as t, TorideClient as u, CLIENT_VERSION as v };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
declare const CLIENT_VERSION = "0.0.1";
|
|
4
|
-
|
|
5
|
-
/** Minimal resource reference for client-side lookups. */
|
|
6
|
-
interface ClientResourceRef {
|
|
7
|
-
readonly type: string;
|
|
8
|
-
readonly id: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Client-side permission checker that provides instant synchronous checks
|
|
12
|
-
* against a PermissionSnapshot received from the server.
|
|
13
|
-
*
|
|
14
|
-
* Default-deny: unknown resources or actions return false.
|
|
15
|
-
* The snapshot is defensively copied to prevent external mutation.
|
|
16
|
-
*/
|
|
17
|
-
declare class TorideClient {
|
|
18
|
-
private readonly permissions;
|
|
19
|
-
constructor(snapshot: PermissionSnapshot);
|
|
20
|
-
/**
|
|
21
|
-
* Synchronous permission check.
|
|
22
|
-
* Returns true if the action is permitted for the resource, false otherwise.
|
|
23
|
-
* Unknown resources return false (default-deny).
|
|
24
|
-
*/
|
|
25
|
-
can(action: string, resource: ClientResourceRef): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Return the list of permitted actions for a resource.
|
|
28
|
-
* Returns empty array for unknown resources.
|
|
29
|
-
*/
|
|
30
|
-
permittedActions(resource: ClientResourceRef): string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export { CLIENT_VERSION, type ClientResourceRef, PermissionSnapshot, TorideClient };
|
|
1
|
+
export { v as CLIENT_VERSION, g as ClientResourceRef, b as PermissionSnapshot, u as TorideClient } from './client-aExMng5T.js';
|
package/dist/client.js
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
constructor(snapshot) {
|
|
6
|
-
this.permissions = /* @__PURE__ */ new Map();
|
|
7
|
-
for (const [key, actions] of Object.entries(snapshot)) {
|
|
8
|
-
this.permissions.set(key, new Set(actions));
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Synchronous permission check.
|
|
13
|
-
* Returns true if the action is permitted for the resource, false otherwise.
|
|
14
|
-
* Unknown resources return false (default-deny).
|
|
15
|
-
*/
|
|
16
|
-
can(action, resource) {
|
|
17
|
-
const key = `${resource.type}:${resource.id}`;
|
|
18
|
-
const actions = this.permissions.get(key);
|
|
19
|
-
if (!actions) return false;
|
|
20
|
-
return actions.has(action);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Return the list of permitted actions for a resource.
|
|
24
|
-
* Returns empty array for unknown resources.
|
|
25
|
-
*/
|
|
26
|
-
permittedActions(resource) {
|
|
27
|
-
const key = `${resource.type}:${resource.id}`;
|
|
28
|
-
const actions = this.permissions.get(key);
|
|
29
|
-
if (!actions) return [];
|
|
30
|
-
return [...actions];
|
|
31
|
-
}
|
|
32
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
CLIENT_VERSION,
|
|
3
|
+
TorideClient
|
|
4
|
+
} from "./chunk-S6DKDABO.js";
|
|
33
5
|
export {
|
|
34
6
|
CLIENT_VERSION,
|
|
35
7
|
TorideClient
|
package/dist/index.d.ts
CHANGED
|
@@ -1,230 +1,5 @@
|
|
|
1
|
-
import { P as PermissionSnapshot } from './
|
|
2
|
-
|
|
3
|
-
/** Represents an entity performing actions. */
|
|
4
|
-
interface ActorRef {
|
|
5
|
-
readonly type: string;
|
|
6
|
-
readonly id: string;
|
|
7
|
-
readonly attributes: Record<string, unknown>;
|
|
8
|
-
}
|
|
9
|
-
/** Represents a protected entity being accessed. */
|
|
10
|
-
interface ResourceRef {
|
|
11
|
-
readonly type: string;
|
|
12
|
-
readonly id: string;
|
|
13
|
-
/** Pre-fetched attributes. Inline values take precedence over resolver results. */
|
|
14
|
-
readonly attributes?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
/** Optional per-check configuration. */
|
|
17
|
-
interface CheckOptions {
|
|
18
|
-
readonly env?: Record<string, unknown>;
|
|
19
|
-
}
|
|
20
|
-
/** A single item in a canBatch() call. */
|
|
21
|
-
interface BatchCheckItem {
|
|
22
|
-
readonly action: string;
|
|
23
|
-
readonly resource: ResourceRef;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Per-type resolver function.
|
|
27
|
-
* Called when the engine needs attributes not available inline.
|
|
28
|
-
* Called at most once per unique resource per evaluation (cached).
|
|
29
|
-
*/
|
|
30
|
-
type ResourceResolver = (ref: ResourceRef) => Promise<Record<string, unknown>>;
|
|
31
|
-
/**
|
|
32
|
-
* Map of resource type names to their resolver functions.
|
|
33
|
-
* Not all types need resolvers — types without resolvers use trivial resolution
|
|
34
|
-
* (fields are undefined unless provided inline).
|
|
35
|
-
*/
|
|
36
|
-
type Resolvers = Record<string, ResourceResolver>;
|
|
37
|
-
/** Custom evaluator function signature. */
|
|
38
|
-
type EvaluatorFn = (actor: ActorRef, resource: ResourceRef, env: Record<string, unknown>) => Promise<boolean>;
|
|
39
|
-
/** Engine construction options. */
|
|
40
|
-
interface TorideOptions {
|
|
41
|
-
readonly policy: Policy;
|
|
42
|
-
/** Per-type resolver map. Optional — engine works without resolvers if all data is inline. */
|
|
43
|
-
readonly resolvers?: Resolvers;
|
|
44
|
-
readonly maxConditionDepth?: number;
|
|
45
|
-
readonly maxDerivedRoleDepth?: number;
|
|
46
|
-
readonly customEvaluators?: Record<string, EvaluatorFn>;
|
|
47
|
-
readonly onDecision?: (event: DecisionEvent) => void;
|
|
48
|
-
readonly onQuery?: (event: QueryEvent) => void;
|
|
49
|
-
}
|
|
50
|
-
/** Attribute type for actor declarations. */
|
|
51
|
-
type AttributeType = "string" | "number" | "boolean";
|
|
52
|
-
/** Actor type declaration with attribute schema. */
|
|
53
|
-
interface ActorDeclaration {
|
|
54
|
-
readonly attributes: Record<string, AttributeType>;
|
|
55
|
-
}
|
|
56
|
-
/** Global role definition derived from actor attributes. */
|
|
57
|
-
interface GlobalRole {
|
|
58
|
-
readonly actor_type: string;
|
|
59
|
-
readonly when: ConditionExpression;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Derived role entry. Exactly one derivation pattern per entry.
|
|
63
|
-
* Patterns:
|
|
64
|
-
* 1. from_global_role
|
|
65
|
-
* 2. from_role + on_relation
|
|
66
|
-
* 3. from_relation
|
|
67
|
-
* 4. actor_type + when (conditional)
|
|
68
|
-
* 5. when only
|
|
69
|
-
*/
|
|
70
|
-
interface DerivedRoleEntry {
|
|
71
|
-
readonly role: string;
|
|
72
|
-
readonly from_global_role?: string;
|
|
73
|
-
readonly from_role?: string;
|
|
74
|
-
readonly on_relation?: string;
|
|
75
|
-
readonly from_relation?: string;
|
|
76
|
-
readonly actor_type?: string;
|
|
77
|
-
readonly when?: ConditionExpression;
|
|
78
|
-
}
|
|
79
|
-
/** Conditional rule (permit or forbid). */
|
|
80
|
-
interface Rule {
|
|
81
|
-
readonly effect: "permit" | "forbid";
|
|
82
|
-
readonly roles?: string[];
|
|
83
|
-
readonly permissions: string[];
|
|
84
|
-
readonly when: ConditionExpression;
|
|
85
|
-
}
|
|
86
|
-
/** Field-level access control definition. */
|
|
87
|
-
interface FieldAccessDef {
|
|
88
|
-
readonly read?: string[];
|
|
89
|
-
readonly update?: string[];
|
|
90
|
-
}
|
|
91
|
-
/** Resource block definition. */
|
|
92
|
-
interface ResourceBlock {
|
|
93
|
-
readonly roles: string[];
|
|
94
|
-
readonly permissions: string[];
|
|
95
|
-
/** Relations map field names to target resource type names (simplified). */
|
|
96
|
-
readonly relations?: Record<string, string>;
|
|
97
|
-
readonly grants?: Record<string, string[]>;
|
|
98
|
-
readonly derived_roles?: DerivedRoleEntry[];
|
|
99
|
-
readonly rules?: Rule[];
|
|
100
|
-
readonly field_access?: Record<string, FieldAccessDef>;
|
|
101
|
-
}
|
|
102
|
-
/** Operator-based condition value. */
|
|
103
|
-
type ConditionOperator = {
|
|
104
|
-
readonly eq: unknown;
|
|
105
|
-
} | {
|
|
106
|
-
readonly neq: unknown;
|
|
107
|
-
} | {
|
|
108
|
-
readonly gt: unknown;
|
|
109
|
-
} | {
|
|
110
|
-
readonly gte: unknown;
|
|
111
|
-
} | {
|
|
112
|
-
readonly lt: unknown;
|
|
113
|
-
} | {
|
|
114
|
-
readonly lte: unknown;
|
|
115
|
-
} | {
|
|
116
|
-
readonly in: unknown[] | string;
|
|
117
|
-
} | {
|
|
118
|
-
readonly includes: unknown;
|
|
119
|
-
} | {
|
|
120
|
-
readonly exists: boolean;
|
|
121
|
-
} | {
|
|
122
|
-
readonly startsWith: string;
|
|
123
|
-
} | {
|
|
124
|
-
readonly endsWith: string;
|
|
125
|
-
} | {
|
|
126
|
-
readonly contains: string;
|
|
127
|
-
} | {
|
|
128
|
-
readonly custom: string;
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* Condition value: either a primitive (equality shorthand),
|
|
132
|
-
* a cross-reference string ($actor.x, $resource.x, $env.x),
|
|
133
|
-
* or an operator object.
|
|
134
|
-
*/
|
|
135
|
-
type ConditionValue = string | number | boolean | ConditionOperator;
|
|
136
|
-
/** Simple conditions: all key-value pairs ANDed together. */
|
|
137
|
-
type SimpleConditions = Record<string, ConditionValue>;
|
|
138
|
-
/**
|
|
139
|
-
* Recursive condition expression.
|
|
140
|
-
* Either simple conditions (Record<string, ConditionValue>),
|
|
141
|
-
* or a logical combinator ({ any: ... } or { all: ... }).
|
|
142
|
-
*/
|
|
143
|
-
type ConditionExpression = SimpleConditions | {
|
|
144
|
-
readonly any: ConditionExpression[];
|
|
145
|
-
} | {
|
|
146
|
-
readonly all: ConditionExpression[];
|
|
147
|
-
};
|
|
148
|
-
/** Test case for declarative YAML tests. */
|
|
149
|
-
interface TestCase {
|
|
150
|
-
readonly name: string;
|
|
151
|
-
readonly actor: ActorRef;
|
|
152
|
-
/** Mock resolver data: keyed by "Type:id", values are attribute objects. */
|
|
153
|
-
readonly resolvers?: Record<string, Record<string, unknown>>;
|
|
154
|
-
readonly action: string;
|
|
155
|
-
readonly resource: ResourceRef;
|
|
156
|
-
readonly expected: "allow" | "deny";
|
|
157
|
-
}
|
|
158
|
-
/** Top-level policy object. */
|
|
159
|
-
interface Policy {
|
|
160
|
-
readonly version: "1";
|
|
161
|
-
readonly actors: Record<string, ActorDeclaration>;
|
|
162
|
-
readonly global_roles?: Record<string, GlobalRole>;
|
|
163
|
-
readonly resources: Record<string, ResourceBlock>;
|
|
164
|
-
readonly tests?: TestCase[];
|
|
165
|
-
}
|
|
166
|
-
/** Trace for a derived role showing derivation path. */
|
|
167
|
-
interface DerivedRoleTrace {
|
|
168
|
-
readonly role: string;
|
|
169
|
-
readonly via: string;
|
|
170
|
-
}
|
|
171
|
-
/** Resolved roles detail with direct and derived breakdown. */
|
|
172
|
-
interface ResolvedRolesDetail {
|
|
173
|
-
readonly direct: string[];
|
|
174
|
-
readonly derived: DerivedRoleTrace[];
|
|
175
|
-
}
|
|
176
|
-
/** A matched rule with evaluation context. */
|
|
177
|
-
interface MatchedRule {
|
|
178
|
-
readonly effect: "permit" | "forbid";
|
|
179
|
-
readonly matched: boolean;
|
|
180
|
-
readonly rule: Rule;
|
|
181
|
-
readonly resolvedValues: Record<string, unknown>;
|
|
182
|
-
}
|
|
183
|
-
/** Full decision trace from explain(). */
|
|
184
|
-
interface ExplainResult {
|
|
185
|
-
readonly allowed: boolean;
|
|
186
|
-
readonly resolvedRoles: ResolvedRolesDetail;
|
|
187
|
-
readonly grantedPermissions: string[];
|
|
188
|
-
readonly matchedRules: MatchedRule[];
|
|
189
|
-
readonly finalDecision: string;
|
|
190
|
-
}
|
|
191
|
-
/** Audit event for authorization checks. */
|
|
192
|
-
interface DecisionEvent {
|
|
193
|
-
readonly actor: ActorRef;
|
|
194
|
-
readonly action: string;
|
|
195
|
-
readonly resource: ResourceRef;
|
|
196
|
-
readonly allowed: boolean;
|
|
197
|
-
readonly resolvedRoles: string[];
|
|
198
|
-
readonly matchedRules: {
|
|
199
|
-
effect: string;
|
|
200
|
-
matched: boolean;
|
|
201
|
-
}[];
|
|
202
|
-
readonly timestamp: Date;
|
|
203
|
-
}
|
|
204
|
-
/** Audit event for constraint queries. */
|
|
205
|
-
interface QueryEvent {
|
|
206
|
-
readonly actor: ActorRef;
|
|
207
|
-
readonly action: string;
|
|
208
|
-
readonly resourceType: string;
|
|
209
|
-
readonly resultType: "unrestricted" | "forbidden" | "constrained";
|
|
210
|
-
readonly timestamp: Date;
|
|
211
|
-
}
|
|
212
|
-
/** Thrown when policy validation fails. */
|
|
213
|
-
declare class ValidationError extends Error {
|
|
214
|
-
readonly path: string;
|
|
215
|
-
constructor(message: string, path: string);
|
|
216
|
-
}
|
|
217
|
-
/** Thrown when a cycle is detected in relation traversal. */
|
|
218
|
-
declare class CycleError extends Error {
|
|
219
|
-
readonly path: string[];
|
|
220
|
-
constructor(message: string, path: string[]);
|
|
221
|
-
}
|
|
222
|
-
/** Thrown when depth limit is exceeded. */
|
|
223
|
-
declare class DepthLimitError extends Error {
|
|
224
|
-
readonly limit: number;
|
|
225
|
-
readonly limitType: "condition" | "derivation";
|
|
226
|
-
constructor(message: string, limit: number, limitType: "condition" | "derivation");
|
|
227
|
-
}
|
|
1
|
+
import { P as Policy, T as TorideSchema, D as DefaultSchema, a as TorideOptions, A as ActorRef, R as ResourceRef, C as CheckOptions, E as ExplainResult, b as PermissionSnapshot, B as BatchCheckItem, c as TestCase, d as Resolvers } from './client-aExMng5T.js';
|
|
2
|
+
export { e as ActorDeclaration, f as AttributeType, g as ClientResourceRef, h as ConditionExpression, i as ConditionOperator, j as ConditionValue, k as CycleError, l as DecisionEvent, m as DepthLimitError, n as DerivedRoleEntry, o as DerivedRoleTrace, p as EvaluatorFn, F as FieldAccessDef, G as GlobalRole, M as MatchedRule, Q as QueryEvent, q as ResolvedRolesDetail, r as ResourceBlock, s as ResourceResolver, t as Rule, S as SimpleConditions, u as TorideClient, V as ValidationError } from './client-aExMng5T.js';
|
|
228
3
|
|
|
229
4
|
/**
|
|
230
5
|
* Parse and validate a YAML string into a typed Policy object.
|
|
@@ -374,23 +149,32 @@ interface NeverConstraint {
|
|
|
374
149
|
type Constraint = FieldEqConstraint | FieldNeqConstraint | FieldGtConstraint | FieldGteConstraint | FieldLtConstraint | FieldLteConstraint | FieldInConstraint | FieldNinConstraint | FieldExistsConstraint | FieldIncludesConstraint | FieldContainsConstraint | RelationConstraint | HasRoleConstraint | UnknownConstraint | AndConstraint | OrConstraint | NotConstraint | AlwaysConstraint | NeverConstraint;
|
|
375
150
|
/** Leaf constraint subset for ConstraintAdapter.translate(). */
|
|
376
151
|
type LeafConstraint = FieldEqConstraint | FieldNeqConstraint | FieldGtConstraint | FieldGteConstraint | FieldLtConstraint | FieldLteConstraint | FieldInConstraint | FieldNinConstraint | FieldExistsConstraint | FieldIncludesConstraint | FieldContainsConstraint;
|
|
377
|
-
/** Result of partial evaluation. */
|
|
378
|
-
type ConstraintResult = {
|
|
152
|
+
/** Result of partial evaluation, tagged with resource type R (phantom). */
|
|
153
|
+
type ConstraintResult<R extends string = string> = {
|
|
379
154
|
readonly unrestricted: true;
|
|
155
|
+
readonly __resource?: R;
|
|
380
156
|
} | {
|
|
381
157
|
readonly forbidden: true;
|
|
158
|
+
readonly __resource?: R;
|
|
382
159
|
} | {
|
|
383
160
|
readonly constraints: Constraint;
|
|
161
|
+
readonly __resource?: R;
|
|
384
162
|
};
|
|
385
|
-
/**
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
163
|
+
/**
|
|
164
|
+
* User-provided adapter for translating constraint ASTs to queries.
|
|
165
|
+
* TQueryMap maps resource type names to their query output types.
|
|
166
|
+
*
|
|
167
|
+
* BREAKING CHANGE: Previously ConstraintAdapter<TQuery> with a single query type.
|
|
168
|
+
* Now uses a resource-to-query-type map for per-resource output typing.
|
|
169
|
+
*/
|
|
170
|
+
interface ConstraintAdapter<TQueryMap extends Record<string, unknown> = Record<string, unknown>> {
|
|
171
|
+
translate(constraint: LeafConstraint): TQueryMap[string];
|
|
172
|
+
relation(field: string, resourceType: string, childQuery: TQueryMap[string]): TQueryMap[string];
|
|
173
|
+
hasRole(actorId: string, actorType: string, role: string): TQueryMap[string];
|
|
174
|
+
unknown(name: string): TQueryMap[string];
|
|
175
|
+
and(queries: TQueryMap[string][]): TQueryMap[string];
|
|
176
|
+
or(queries: TQueryMap[string][]): TQueryMap[string];
|
|
177
|
+
not(query: TQueryMap[string]): TQueryMap[string];
|
|
394
178
|
}
|
|
395
179
|
|
|
396
180
|
/**
|
|
@@ -398,16 +182,16 @@ interface ConstraintAdapter<TQuery> {
|
|
|
398
182
|
* Creates a per-check cache, resolves direct roles, checks grants,
|
|
399
183
|
* and returns boolean with default-deny semantics.
|
|
400
184
|
*/
|
|
401
|
-
declare class Toride {
|
|
185
|
+
declare class Toride<S extends TorideSchema = DefaultSchema> {
|
|
402
186
|
private policy;
|
|
403
187
|
private readonly resolvers;
|
|
404
188
|
private readonly options;
|
|
405
|
-
constructor(options: TorideOptions);
|
|
189
|
+
constructor(options: TorideOptions<S>);
|
|
406
190
|
/**
|
|
407
191
|
* Check if an actor can perform an action on a resource.
|
|
408
192
|
* Default-deny: returns false if resource type is unknown or no grants match.
|
|
409
193
|
*/
|
|
410
|
-
can(actor: ActorRef
|
|
194
|
+
can<R extends S["resources"]>(actor: ActorRef<S>, action: S["permissionMap"][R], resource: ResourceRef<S, R>, options?: CheckOptions): Promise<boolean>;
|
|
411
195
|
/**
|
|
412
196
|
* T097: Atomic policy swap. In-flight checks capture the resource block
|
|
413
197
|
* at the start of evaluateInternal, so they complete with the old policy.
|
|
@@ -418,49 +202,53 @@ declare class Toride {
|
|
|
418
202
|
* T068: Return full ExplainResult with role derivation traces,
|
|
419
203
|
* granted permissions, matched rules, and human-readable final decision.
|
|
420
204
|
*/
|
|
421
|
-
explain(actor: ActorRef
|
|
205
|
+
explain<R extends S["resources"]>(actor: ActorRef<S>, action: S["permissionMap"][R], resource: ResourceRef<S, R>, options?: CheckOptions): Promise<ExplainResult<S, R>>;
|
|
422
206
|
/**
|
|
423
207
|
* T069: Check all declared permissions for a resource and return permitted ones.
|
|
424
208
|
* Uses a shared cache across all per-action evaluations.
|
|
425
209
|
*/
|
|
426
|
-
permittedActions(actor: ActorRef
|
|
210
|
+
permittedActions<R extends S["resources"]>(actor: ActorRef<S>, resource: ResourceRef<S, R>, options?: CheckOptions): Promise<S["permissionMap"][R][]>;
|
|
427
211
|
/**
|
|
428
212
|
* T083: Generate a PermissionSnapshot for a list of resources.
|
|
429
213
|
* Calls permittedActions() for each resource and returns a map
|
|
430
214
|
* keyed by "Type:id" with arrays of permitted action strings.
|
|
431
215
|
* Suitable for serializing to the client via TorideClient.
|
|
432
216
|
*/
|
|
433
|
-
snapshot(actor: ActorRef
|
|
217
|
+
snapshot(actor: ActorRef<S>, resources: ResourceRef<S>[], options?: CheckOptions): Promise<PermissionSnapshot<S>>;
|
|
434
218
|
/**
|
|
435
219
|
* T095: Check if an actor can perform a field-level operation on a specific field.
|
|
436
220
|
* Restricted fields require the actor to have a role listed in field_access.
|
|
437
221
|
* Unlisted fields are unrestricted: any actor with the resource-level permission can access them.
|
|
438
222
|
*/
|
|
439
|
-
canField(actor: ActorRef
|
|
223
|
+
canField<R extends S["resources"]>(actor: ActorRef<S>, operation: "read" | "update", resource: ResourceRef<S, R>, field: keyof S["resourceAttributeMap"][R] & string, options?: CheckOptions): Promise<boolean>;
|
|
440
224
|
/**
|
|
441
225
|
* T095: Return the list of declared field_access field names the actor can access
|
|
442
226
|
* for the given operation. Only returns explicitly declared fields.
|
|
443
227
|
*/
|
|
444
|
-
permittedFields(actor: ActorRef
|
|
228
|
+
permittedFields<R extends S["resources"]>(actor: ActorRef<S>, operation: "read" | "update", resource: ResourceRef<S, R>, options?: CheckOptions): Promise<(keyof S["resourceAttributeMap"][R] & string)[]>;
|
|
445
229
|
/**
|
|
446
230
|
* T070: Return flat deduplicated list of all resolved roles (direct + derived).
|
|
447
231
|
*/
|
|
448
|
-
resolvedRoles(actor: ActorRef
|
|
232
|
+
resolvedRoles<R extends S["resources"]>(actor: ActorRef<S>, resource: ResourceRef<S, R>, options?: CheckOptions): Promise<S["roleMap"][R][]>;
|
|
449
233
|
/**
|
|
450
234
|
* T071: Evaluate multiple checks for the same actor with a shared resolver cache.
|
|
451
235
|
* Returns boolean[] in the same order as the input checks.
|
|
452
236
|
*/
|
|
453
|
-
canBatch(actor: ActorRef
|
|
237
|
+
canBatch(actor: ActorRef<S>, checks: BatchCheckItem<S>[], options?: CheckOptions): Promise<boolean[]>;
|
|
454
238
|
/**
|
|
455
239
|
* T064: Build constraint AST for partial evaluation / data filtering.
|
|
456
240
|
* Returns ConstraintResult with unrestricted/forbidden sentinels or constraint AST.
|
|
457
241
|
*/
|
|
458
|
-
buildConstraints(actor: ActorRef
|
|
242
|
+
buildConstraints<R extends S["resources"]>(actor: ActorRef<S>, action: S["permissionMap"][R], resourceType: R, options?: CheckOptions): Promise<ConstraintResult<R>>;
|
|
459
243
|
/**
|
|
460
244
|
* T064: Translate constraint AST using an adapter.
|
|
461
245
|
* Dispatches each constraint node to the adapter's methods.
|
|
246
|
+
*
|
|
247
|
+
* Accepts ConstraintResult<R> from buildConstraints() and returns
|
|
248
|
+
* TQueryMap[R] — the adapter's mapped output type for resource R.
|
|
249
|
+
* The resource type R is inferred from the ConstraintResult phantom type.
|
|
462
250
|
*/
|
|
463
|
-
translateConstraints<
|
|
251
|
+
translateConstraints<R extends string, TQueryMap extends Record<string, unknown>>(constraints: ConstraintResult<R>, adapter: ConstraintAdapter<TQueryMap>): TQueryMap[R];
|
|
464
252
|
/**
|
|
465
253
|
* T072: Fire onDecision audit callback via microtask (non-blocking).
|
|
466
254
|
* Errors are silently swallowed to prevent audit failures from affecting authorization.
|
|
@@ -480,7 +268,7 @@ declare class Toride {
|
|
|
480
268
|
/**
|
|
481
269
|
* Typed factory function for creating a Toride instance.
|
|
482
270
|
*/
|
|
483
|
-
declare function createToride(options: TorideOptions): Toride
|
|
271
|
+
declare function createToride<S extends TorideSchema = DefaultSchema>(options: TorideOptions<S>): Toride<S>;
|
|
484
272
|
|
|
485
273
|
/**
|
|
486
274
|
* Constructs a Resolvers map from test case mock data.
|
|
@@ -531,4 +319,4 @@ declare function runTestCases(policy: Policy, tests: TestCase[]): Promise<TestRe
|
|
|
531
319
|
|
|
532
320
|
declare const VERSION = "0.0.1";
|
|
533
321
|
|
|
534
|
-
export {
|
|
322
|
+
export { ActorRef, BatchCheckItem, CheckOptions, type Constraint, type ConstraintAdapter, type ConstraintResult, DefaultSchema, ExplainResult, type LeafConstraint, PermissionSnapshot, Policy, Resolvers, ResourceRef, type StrictValidationResult, TestCase, type TestFileResult, type TestResult, Toride, TorideOptions, TorideSchema, VERSION, type ValidationDiagnostic, type ValidationResult, createMockResolver, createToride, loadJson, loadYaml, mergePolicies, parseInlineTests, parseTestFile, runTestCases, validatePolicy, validatePolicyResult, validatePolicyStrict };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
validatePolicy,
|
|
13
13
|
validatePolicyResult,
|
|
14
14
|
validatePolicyStrict
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2AWXNP37.js";
|
|
16
|
+
import {
|
|
17
|
+
TorideClient
|
|
18
|
+
} from "./chunk-S6DKDABO.js";
|
|
16
19
|
|
|
17
20
|
// src/policy/parser.ts
|
|
18
21
|
import * as YAML from "yaml";
|
|
@@ -226,6 +229,7 @@ export {
|
|
|
226
229
|
CycleError,
|
|
227
230
|
DepthLimitError,
|
|
228
231
|
Toride,
|
|
232
|
+
TorideClient,
|
|
229
233
|
VERSION,
|
|
230
234
|
ValidationError,
|
|
231
235
|
createMockResolver,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toride",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Relation-aware authorization engine for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
]
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"valibot": "^1.2.0",
|
|
31
|
+
"yaml": "^2.3.0"
|
|
32
32
|
},
|
|
33
33
|
"keywords": [
|
|
34
34
|
"authorization",
|
|
@@ -42,9 +42,13 @@
|
|
|
42
42
|
"type": "git",
|
|
43
43
|
"url": "https://github.com/toride-auth/toride"
|
|
44
44
|
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"tsd": "^0.33.0"
|
|
47
|
+
},
|
|
45
48
|
"scripts": {
|
|
46
49
|
"build": "tsup",
|
|
47
50
|
"test": "vitest run",
|
|
51
|
+
"typetest": "tsd --files 'src/__typetests__/*.test-d.ts'",
|
|
48
52
|
"lint": "tsc --noEmit",
|
|
49
53
|
"bench": "vitest bench"
|
|
50
54
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A serializable map of permissions keyed by "Type:id".
|
|
3
|
-
* Values are arrays of permitted action strings.
|
|
4
|
-
* Suitable for JSON transport to client-side TorideClient.
|
|
5
|
-
*/
|
|
6
|
-
type PermissionSnapshot = Record<string, string[]>;
|
|
7
|
-
|
|
8
|
-
export type { PermissionSnapshot as P };
|