@zhama/tego-plugin-sdk 0.0.0-stage → 1.36.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/LICENSE +172 -0
- package/README.md +846 -2
- package/access-catalog.schema.json +304 -0
- package/access-fixtures/resolve.json +1359 -0
- package/bin/tego-plugin-pack.mjs +2 -0
- package/bin/tego-plugin.mjs +3 -0
- package/dist/access-drizzle.cjs +1 -0
- package/dist/access-drizzle.d.cts +726 -0
- package/dist/access-drizzle.d.ts +726 -0
- package/dist/access-drizzle.js +1 -0
- package/dist/access-hono.cjs +3 -0
- package/dist/access-hono.d.cts +108 -0
- package/dist/access-hono.d.ts +108 -0
- package/dist/access-hono.js +3 -0
- package/dist/access-implications.cjs +1 -0
- package/dist/access-implications.d.cts +25 -0
- package/dist/access-implications.d.ts +25 -0
- package/dist/access-implications.js +1 -0
- package/dist/access.cjs +26 -0
- package/dist/access.d.cts +884 -0
- package/dist/access.d.ts +884 -0
- package/dist/access.js +26 -0
- package/dist/catalog-CSSbrfEf.d.cts +361 -0
- package/dist/catalog-CSSbrfEf.d.ts +361 -0
- package/dist/chunk-3KUOBGJN.js +19 -0
- package/dist/chunk-3WA3M347.cjs +1 -0
- package/dist/chunk-5CU33JVY.js +1 -0
- package/dist/chunk-7K37PVGV.js +5 -0
- package/dist/chunk-7XHF3YSK.js +1 -0
- package/dist/chunk-AJY7ZHXK.cjs +3 -0
- package/dist/chunk-BN6TGHSB.cjs +1 -0
- package/dist/chunk-BTHXQWHT.js +1 -0
- package/dist/chunk-C2OCOPZQ.cjs +1 -0
- package/dist/chunk-IMAVYQ4U.cjs +4 -0
- package/dist/chunk-OP64Y4IN.cjs +1 -0
- package/dist/chunk-QKSPPMVN.js +1 -0
- package/dist/chunk-ROFUQLOZ.cjs +1 -0
- package/dist/chunk-SNUCO2MK.js +1 -0
- package/dist/chunk-THMWRUAT.cjs +19 -0
- package/dist/chunk-VMCGDDGP.js +3 -0
- package/dist/chunk-W7C357A6.cjs +5 -0
- package/dist/chunk-WLTASJF6.js +4 -0
- package/dist/egress.cjs +7 -0
- package/dist/egress.d.cts +9 -0
- package/dist/egress.d.ts +9 -0
- package/dist/egress.js +7 -0
- package/dist/frontend.cjs +2 -0
- package/dist/frontend.d.cts +353 -0
- package/dist/frontend.d.ts +353 -0
- package/dist/frontend.js +2 -0
- package/dist/gateway-signature.cjs +1 -0
- package/dist/gateway-signature.d.cts +112 -0
- package/dist/gateway-signature.d.ts +112 -0
- package/dist/gateway-signature.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +528 -0
- package/dist/index.d.ts +528 -0
- package/dist/index.js +1 -0
- package/dist/manifest-types-DQARCQJt.d.cts +627 -0
- package/dist/manifest-types-DQARCQJt.d.ts +627 -0
- package/dist/manifest.cjs +1 -0
- package/dist/manifest.d.cts +2824 -0
- package/dist/manifest.d.ts +2824 -0
- package/dist/manifest.js +1 -0
- package/dist/mcp-identity.cjs +2 -0
- package/dist/mcp-identity.d.cts +106 -0
- package/dist/mcp-identity.d.ts +106 -0
- package/dist/mcp-identity.js +2 -0
- package/dist/pack.cjs +1 -0
- package/dist/pack.d.cts +169 -0
- package/dist/pack.d.ts +169 -0
- package/dist/pack.js +1 -0
- package/dist/publish.cjs +1 -0
- package/dist/publish.d.cts +96 -0
- package/dist/publish.d.ts +96 -0
- package/dist/publish.js +1 -0
- package/dist/scaffold.cjs +523 -0
- package/dist/scaffold.d.cts +182 -0
- package/dist/scaffold.d.ts +182 -0
- package/dist/scaffold.js +523 -0
- package/dist/server-runtime.cjs +1 -0
- package/dist/server-runtime.d.cts +63 -0
- package/dist/server-runtime.d.ts +63 -0
- package/dist/server-runtime.js +1 -0
- package/dist/server.cjs +1 -0
- package/dist/server.d.cts +1808 -0
- package/dist/server.d.ts +1808 -0
- package/dist/server.js +1 -0
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.cts +92 -0
- package/dist/testing.d.ts +92 -0
- package/dist/testing.js +2 -0
- package/dist/vite.cjs +1 -0
- package/dist/vite.d.cts +133 -0
- package/dist/vite.d.ts +133 -0
- package/dist/vite.js +1 -0
- package/package.json +160 -4
- package/sql/access.sql +73 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
import { D as DelegationAction, G as Grants, A as AccessCatalog, a as GrantValue, T as TypedAccessCatalog, P as PermissionOf } from './catalog-CSSbrfEf.cjs';
|
|
2
|
+
export { b as AccessCatalogError, c as AccessCatalogInput, d as AgentPolicy, B as BUILTIN_PRINCIPAL_KINDS, C as CatalogIssue, e as CatalogPermission, f as CatalogRole, g as DELEGATION_ACTIONS, h as GLOBAL_DOMAIN, H as HOST_ADMIN_ROLE, R as RESERVED_GROUP, i as RESERVED_PERMISSIONS, j as accessCatalogFromJson, k as accessCatalogSchema, l as defineAccess, m as grantProblem, v as validateAccessCatalog } from './catalog-CSSbrfEf.cjs';
|
|
3
|
+
import { PluginUserContext } from './frontend.cjs';
|
|
4
|
+
import { PluginHostApi } from './server.cjs';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export { expandImplications, mergeGrant, scopeRank } from './access-implications.cjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The pure core of the decision engine: given who the caller is and what the
|
|
10
|
+
* store holds, which permissions do they have, and why.
|
|
11
|
+
*
|
|
12
|
+
* No I/O here. The store loads rows, the principal sources say which units or
|
|
13
|
+
* groups the caller belongs to, and this module only combines them. That is
|
|
14
|
+
* what lets the contract fixtures — shared with the Python SDK — and the
|
|
15
|
+
* property tests exercise the real semantics without a database.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface AccessSubject {
|
|
19
|
+
tenantId: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
/** The host's role names for the person, as the gateway signed them. */
|
|
22
|
+
hostRoles: readonly string[];
|
|
23
|
+
/** Set when a digital employee acts for `userId`. */
|
|
24
|
+
agent?: {
|
|
25
|
+
avatarId: string;
|
|
26
|
+
delegation: readonly DelegationAction[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
declare function subjectFromContext(ctx: PluginUserContext): AccessSubject;
|
|
30
|
+
interface AccessDomain {
|
|
31
|
+
type: string;
|
|
32
|
+
id: string;
|
|
33
|
+
}
|
|
34
|
+
interface AccessRole {
|
|
35
|
+
id: string;
|
|
36
|
+
/** The catalog key of a built-in role; null for a custom one. */
|
|
37
|
+
key: string | null;
|
|
38
|
+
builtin: boolean;
|
|
39
|
+
/** A custom role usable only inside this domain; null for a global role. */
|
|
40
|
+
domain: AccessDomain | null;
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
grants: Grants;
|
|
44
|
+
catalogVersion: number;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
updatedAt: string;
|
|
47
|
+
updatedBy: string | null;
|
|
48
|
+
}
|
|
49
|
+
interface AssignmentPrincipal {
|
|
50
|
+
kind: string;
|
|
51
|
+
id: string;
|
|
52
|
+
/** For a unit or a group: also everyone in the units below it. */
|
|
53
|
+
includeDescendants: boolean;
|
|
54
|
+
/** For a unit: only members holding one of these unit roles. Null for all. */
|
|
55
|
+
unitRoles: string[] | null;
|
|
56
|
+
}
|
|
57
|
+
interface AccessAssignment {
|
|
58
|
+
id: string;
|
|
59
|
+
roleId: string;
|
|
60
|
+
principal: AssignmentPrincipal;
|
|
61
|
+
domain: AccessDomain | null;
|
|
62
|
+
createdBy: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
}
|
|
65
|
+
/** A unit or group the subject belongs to. */
|
|
66
|
+
interface PrincipalMembership {
|
|
67
|
+
kind: string;
|
|
68
|
+
id: string;
|
|
69
|
+
/** Enclosing units, for assignments made with `includeDescendants`. */
|
|
70
|
+
ancestors: readonly string[];
|
|
71
|
+
/** The subject's role inside it, matched against `unitRoles`. */
|
|
72
|
+
role?: string;
|
|
73
|
+
}
|
|
74
|
+
type GrantSourceKind = 'assignment' | 'hostRole' | 'unassigned';
|
|
75
|
+
interface GrantSource {
|
|
76
|
+
roleId: string;
|
|
77
|
+
roleKey: string | null;
|
|
78
|
+
source: GrantSourceKind;
|
|
79
|
+
/** The assignment's principal, or the host role name. */
|
|
80
|
+
principal?: {
|
|
81
|
+
kind: string;
|
|
82
|
+
id: string;
|
|
83
|
+
};
|
|
84
|
+
hostRole?: string;
|
|
85
|
+
domain?: AccessDomain | null;
|
|
86
|
+
/** An assignment made at an ancestor of the resolved domain. */
|
|
87
|
+
inherited?: true;
|
|
88
|
+
}
|
|
89
|
+
type DecisionReason = 'granted' | 'not_granted' | 'scope_mismatch' | 'agent_forbidden' | 'delegation_missing';
|
|
90
|
+
interface Decision {
|
|
91
|
+
allowed: boolean;
|
|
92
|
+
permission: string;
|
|
93
|
+
/** The scope held, for a scoped permission that is granted. */
|
|
94
|
+
scope: string | null;
|
|
95
|
+
/** The scope that matched the resource, when a resource was checked. */
|
|
96
|
+
matchedScope?: string;
|
|
97
|
+
reason: DecisionReason;
|
|
98
|
+
via: GrantSource[];
|
|
99
|
+
}
|
|
100
|
+
interface ResolveInput {
|
|
101
|
+
catalog: AccessCatalog;
|
|
102
|
+
subject: AccessSubject;
|
|
103
|
+
/** Every role of the tenant, by id. */
|
|
104
|
+
roles: ReadonlyMap<string, AccessRole>;
|
|
105
|
+
/** Assignments naming the subject or any of its memberships, in any domain. */
|
|
106
|
+
assignments: readonly AccessAssignment[];
|
|
107
|
+
memberships: readonly PrincipalMembership[];
|
|
108
|
+
unassignedRoleId: string | null;
|
|
109
|
+
domain: AccessDomain | null;
|
|
110
|
+
/** For a domain of a hierarchical type: see `DomainInheritance`. */
|
|
111
|
+
inheritance?: DomainInheritance;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* How assignments made above a domain reach into it.
|
|
115
|
+
*
|
|
116
|
+
* `ancestors` are the resolved domain's, nearest first. An assignment at any of
|
|
117
|
+
* them applies here with the role's grants passed through `inherit`, which can
|
|
118
|
+
* only narrow: a grant it adds, or widens past the role's own, is ignored.
|
|
119
|
+
*/
|
|
120
|
+
interface DomainInheritance {
|
|
121
|
+
ancestors: readonly AccessDomain[];
|
|
122
|
+
inherit(grants: Grants): Grants;
|
|
123
|
+
}
|
|
124
|
+
interface HeldGrant {
|
|
125
|
+
value: GrantValue;
|
|
126
|
+
via: GrantSource[];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* What one subject holds in one domain.
|
|
130
|
+
*
|
|
131
|
+
* Grants are allow-only and combine monotonically: a union across roles, the
|
|
132
|
+
* widest scope wins, implications are expanded per role. A digital employee
|
|
133
|
+
* then keeps only what each permission's agent policy and the live delegation
|
|
134
|
+
* allow — never more than the person it acts for.
|
|
135
|
+
*/
|
|
136
|
+
declare class EffectiveGrants {
|
|
137
|
+
readonly catalog: AccessCatalog;
|
|
138
|
+
readonly subject: AccessSubject;
|
|
139
|
+
readonly domain: AccessDomain | null;
|
|
140
|
+
private readonly held;
|
|
141
|
+
private readonly withheld;
|
|
142
|
+
/** Every role that contributed, with how. */
|
|
143
|
+
readonly sources: readonly GrantSource[];
|
|
144
|
+
/** Ids of the domains in which the subject has explicit assignments. */
|
|
145
|
+
readonly assignedDomains: readonly AccessDomain[];
|
|
146
|
+
constructor(catalog: AccessCatalog, subject: AccessSubject, domain: AccessDomain | null, held: ReadonlyMap<string, HeldGrant>, withheld: ReadonlyMap<string, {
|
|
147
|
+
reason: 'agent_forbidden' | 'delegation_missing';
|
|
148
|
+
via: GrantSource[];
|
|
149
|
+
}>,
|
|
150
|
+
/** Every role that contributed, with how. */
|
|
151
|
+
sources: readonly GrantSource[],
|
|
152
|
+
/** Ids of the domains in which the subject has explicit assignments. */
|
|
153
|
+
assignedDomains: readonly AccessDomain[]);
|
|
154
|
+
/** The grant value, or undefined when the permission is not held. */
|
|
155
|
+
value(permission: string): GrantValue | undefined;
|
|
156
|
+
can(permission: string): boolean;
|
|
157
|
+
/** The scope held, or null for an unscoped permission or one not held. */
|
|
158
|
+
scope(permission: string): string | null;
|
|
159
|
+
/** Whether the permission is held at `scope` or wider. */
|
|
160
|
+
atLeast(permission: string, scope: string): boolean;
|
|
161
|
+
via(permission: string): GrantSource[];
|
|
162
|
+
/** Why a permission is not held: not granted, or withheld from the agent. */
|
|
163
|
+
denial(permission: string): DecisionReason | null;
|
|
164
|
+
permissions(): Record<string, GrantValue>;
|
|
165
|
+
/** Whether `access.manage` comes from a host role binding. */
|
|
166
|
+
managesThroughHostRole(): boolean;
|
|
167
|
+
/** The decision for a functional check, before any resource is looked at. */
|
|
168
|
+
decide(permission: string): Decision;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The decision for `permission` on `resource`.
|
|
172
|
+
*
|
|
173
|
+
* Without a resource, or for an unscoped permission, this is the functional
|
|
174
|
+
* check. Otherwise the resource must match the held scope or one narrower on
|
|
175
|
+
* the ladder, tried widest first so `all` short-circuits.
|
|
176
|
+
*/
|
|
177
|
+
declare function decideResource(grants: EffectiveGrants, permission: string, resource: unknown, matches: (scope: string) => boolean | Promise<boolean>): Promise<Decision>;
|
|
178
|
+
/** Whether an assignment applies when resolving in `domain`. */
|
|
179
|
+
declare function assignmentInDomain(assignment: AccessAssignment, domain: AccessDomain | null): boolean;
|
|
180
|
+
/** Whether an assignment names the subject, directly or through a membership. */
|
|
181
|
+
declare function assignmentMatches(assignment: AccessAssignment, subject: AccessSubject, memberships: readonly PrincipalMembership[]): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Drops grants the current catalog does not recognise.
|
|
184
|
+
*
|
|
185
|
+
* A custom role saved under an older catalog may name a permission since
|
|
186
|
+
* removed, or a scope since dropped from its ladder. Ignoring the grant —
|
|
187
|
+
* rather than failing the request or widening it — is the only reading that
|
|
188
|
+
* cannot hand anyone more than the catalog now describes.
|
|
189
|
+
*/
|
|
190
|
+
declare function validGrants(catalog: AccessCatalog, grants: Grants): Grants;
|
|
191
|
+
declare function resolveGrants(input: ResolveInput): EffectiveGrants;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* SQL fragments a scope contributes to a list query.
|
|
195
|
+
*
|
|
196
|
+
* Built with a tagged template so values can only ever be parameters: there
|
|
197
|
+
* is no way to splice one into the text. Identifiers (the caller's table
|
|
198
|
+
* alias and column names) go through {@link ident}, which accepts only plain
|
|
199
|
+
* lower-case names — they are the one thing a parameter cannot carry.
|
|
200
|
+
*
|
|
201
|
+
* Placeholders are numbered when the fragment is rendered, against the
|
|
202
|
+
* caller's own parameter list, so a fragment composes into any query without
|
|
203
|
+
* either side knowing the other's numbering.
|
|
204
|
+
*/
|
|
205
|
+
declare const IDENT: unique symbol;
|
|
206
|
+
declare const FRAGMENT: unique symbol;
|
|
207
|
+
interface Identifier {
|
|
208
|
+
readonly [IDENT]: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Only {@link sql} makes one: an interpolated object that merely has
|
|
212
|
+
* `strings` and `values` is a parameter, so its text never reaches the query.
|
|
213
|
+
*/
|
|
214
|
+
interface SqlFragment {
|
|
215
|
+
readonly [FRAGMENT]: true;
|
|
216
|
+
readonly strings: readonly string[];
|
|
217
|
+
readonly values: readonly unknown[];
|
|
218
|
+
}
|
|
219
|
+
/** A lower-case SQL identifier; anything else throws. */
|
|
220
|
+
declare function ident(name: string): Identifier;
|
|
221
|
+
/**
|
|
222
|
+
* Tagged template producing a fragment.
|
|
223
|
+
*
|
|
224
|
+
* Interpolated values become parameters, an {@link ident} becomes the
|
|
225
|
+
* identifier, and a nested fragment is inlined with its own parameters.
|
|
226
|
+
*/
|
|
227
|
+
declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment;
|
|
228
|
+
declare const TRUE_FRAGMENT: SqlFragment;
|
|
229
|
+
declare const FALSE_FRAGMENT: SqlFragment;
|
|
230
|
+
/** `(a) OR (b) …`, collapsing to TRUE/FALSE where that is already decided. */
|
|
231
|
+
declare function orFragments(fragments: readonly SqlFragment[]): SqlFragment;
|
|
232
|
+
/**
|
|
233
|
+
* Renders a fragment with `$n` placeholders starting after `offset`.
|
|
234
|
+
*
|
|
235
|
+
* `offset` is the number of parameters the surrounding query already uses;
|
|
236
|
+
* append the returned `params` to them.
|
|
237
|
+
*/
|
|
238
|
+
declare function renderFragment(fragment: SqlFragment, offset?: number): {
|
|
239
|
+
text: string;
|
|
240
|
+
params: unknown[];
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Splices a fragment into a query that already has `params`.
|
|
244
|
+
*
|
|
245
|
+
* Returns the fragment's text, numbered after the existing parameters, and
|
|
246
|
+
* the combined parameter list.
|
|
247
|
+
*/
|
|
248
|
+
declare function bindFragment(params: readonly unknown[], fragment: SqlFragment): {
|
|
249
|
+
text: string;
|
|
250
|
+
params: unknown[];
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Scope evaluators: what a scope name means for one resource and for a list.
|
|
255
|
+
*
|
|
256
|
+
* Each evaluator answers both questions — `matches` for a single resource,
|
|
257
|
+
* `filter` for the WHERE clause of a list — so the two cannot drift apart:
|
|
258
|
+
* the classic leak where a list shows rows the detail page refuses (or the
|
|
259
|
+
* other way round) comes from writing them separately.
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
/** Where the filtered table sits in the caller's query. */
|
|
263
|
+
interface ScopeRef {
|
|
264
|
+
/** The table alias the fragment qualifies its columns with. */
|
|
265
|
+
alias: string;
|
|
266
|
+
/** Logical column name → actual column, for evaluators that need one. */
|
|
267
|
+
columns?: Record<string, string>;
|
|
268
|
+
}
|
|
269
|
+
interface ScopeContext {
|
|
270
|
+
host: PluginHostApi;
|
|
271
|
+
permission: string;
|
|
272
|
+
}
|
|
273
|
+
interface ScopeEvaluator<R = unknown> {
|
|
274
|
+
matches(subject: AccessSubject, resource: R, context: ScopeContext): boolean | Promise<boolean>;
|
|
275
|
+
filter(subject: AccessSubject, ref: ScopeRef): SqlFragment;
|
|
276
|
+
}
|
|
277
|
+
/** The column an evaluator needs, validated as an identifier. */
|
|
278
|
+
declare function refColumn(ref: ScopeRef, name: string): ReturnType<typeof ident>;
|
|
279
|
+
/** Every resource. */
|
|
280
|
+
declare const allScope: ScopeEvaluator<unknown>;
|
|
281
|
+
/** Resources the subject owns: `resource.ownerId`, column `ownerId`. */
|
|
282
|
+
declare const ownScope: ScopeEvaluator<{
|
|
283
|
+
ownerId: string | null;
|
|
284
|
+
}>;
|
|
285
|
+
declare const BUILTIN_SCOPES: Record<string, ScopeEvaluator<never>>;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* A denied check, carrying the decision that denied it.
|
|
289
|
+
*
|
|
290
|
+
* 403 says "you cannot use this", which reveals nothing about any record.
|
|
291
|
+
* 404 is for a resource outside the caller's scope when the route asked to
|
|
292
|
+
* conceal it: confirming that a confidential record exists is itself a leak.
|
|
293
|
+
*/
|
|
294
|
+
declare class AccessDeniedError extends Error {
|
|
295
|
+
readonly decision: Decision;
|
|
296
|
+
readonly status: 403 | 404;
|
|
297
|
+
constructor(decision: Decision, status: 403 | 404);
|
|
298
|
+
}
|
|
299
|
+
/** A refused admin operation, with the status and a stable code. */
|
|
300
|
+
declare class AccessAdminError extends Error {
|
|
301
|
+
readonly status: 400 | 403 | 404 | 409;
|
|
302
|
+
readonly code: string;
|
|
303
|
+
constructor(status: 400 | 403 | 404 | 409, code: string, message: string);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Reads and writes of the `tego_access_*` tables.
|
|
308
|
+
*
|
|
309
|
+
* Plain parameterized SQL over the plugin's scoped connection, so it runs the
|
|
310
|
+
* same in every plugin whatever query layer the plugin itself uses. Every
|
|
311
|
+
* statement filters on `tenant_id`.
|
|
312
|
+
*/
|
|
313
|
+
|
|
314
|
+
interface Queryable {
|
|
315
|
+
query<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
|
|
316
|
+
}
|
|
317
|
+
interface AccessSettingsRow {
|
|
318
|
+
unassignedRoleId: string | null;
|
|
319
|
+
version: number;
|
|
320
|
+
epoch: string;
|
|
321
|
+
updatedAt: string;
|
|
322
|
+
updatedBy: string | null;
|
|
323
|
+
}
|
|
324
|
+
interface AuditEntry {
|
|
325
|
+
id: string;
|
|
326
|
+
actorId: string | null;
|
|
327
|
+
action: string;
|
|
328
|
+
targetType: string;
|
|
329
|
+
targetId: string;
|
|
330
|
+
before: unknown;
|
|
331
|
+
after: unknown;
|
|
332
|
+
at: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The decision point: `createAccess` binds a catalog to its scope evaluators
|
|
337
|
+
* and principal sources, and hands out a session per caller.
|
|
338
|
+
*
|
|
339
|
+
* A session is cheap and lives for one request (or one storage check). It
|
|
340
|
+
* reads the tenant's access version once, and everything derived from that —
|
|
341
|
+
* role definitions, the caller's assignments — is cached across requests
|
|
342
|
+
* under that version, so a change made in any process is seen by every other
|
|
343
|
+
* process on its next request without any invalidation message.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
interface PrincipalEntry {
|
|
347
|
+
id: string;
|
|
348
|
+
displayName: string;
|
|
349
|
+
parentId?: string | null;
|
|
350
|
+
active?: boolean;
|
|
351
|
+
}
|
|
352
|
+
interface PrincipalQuery {
|
|
353
|
+
search?: string;
|
|
354
|
+
ids?: string[];
|
|
355
|
+
offset: number;
|
|
356
|
+
limit: number;
|
|
357
|
+
}
|
|
358
|
+
/** A kind of principal roles can be assigned to, beyond a single user. */
|
|
359
|
+
interface PrincipalSource {
|
|
360
|
+
kind: string;
|
|
361
|
+
/** The subject's memberships of this kind. */
|
|
362
|
+
memberships(subject: AccessSubject, host: PluginHostApi): Promise<PrincipalMembership[]>;
|
|
363
|
+
/** Lookup and search, for the admin screens and for validating a grantee. */
|
|
364
|
+
list(host: PluginHostApi, query: PrincipalQuery): Promise<{
|
|
365
|
+
items: PrincipalEntry[];
|
|
366
|
+
hasMore: boolean;
|
|
367
|
+
}>;
|
|
368
|
+
}
|
|
369
|
+
/** The plugin's own records a domain type names (EHS's organizations). */
|
|
370
|
+
interface DomainSource {
|
|
371
|
+
/** Display names by id; an id missing from the result does not exist. */
|
|
372
|
+
describe(ids: string[], host: PluginHostApi): Promise<Map<string, string>>;
|
|
373
|
+
/**
|
|
374
|
+
* Required for a hierarchical domain type: the ancestors of `id`, nearest
|
|
375
|
+
* first. Leave out one that should confer nothing below it (a deactivated
|
|
376
|
+
* organization, say); the walk continues past it.
|
|
377
|
+
*/
|
|
378
|
+
ancestors?(id: string, host: PluginHostApi): Promise<string[]>;
|
|
379
|
+
/**
|
|
380
|
+
* Required for a hierarchical domain type: what a role assigned at an
|
|
381
|
+
* ancestor confers here, given the role's grants with implications
|
|
382
|
+
* expanded. It can only narrow; see `DomainInheritance`.
|
|
383
|
+
*/
|
|
384
|
+
inherit?(grants: Grants): Grants;
|
|
385
|
+
}
|
|
386
|
+
type AccessChange = {
|
|
387
|
+
type: 'role.created';
|
|
388
|
+
role: AccessRole;
|
|
389
|
+
} | {
|
|
390
|
+
type: 'role.updated';
|
|
391
|
+
role: AccessRole;
|
|
392
|
+
before: AccessRole;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Also when `reconcile` retires a built-in role the catalog dropped; its
|
|
396
|
+
* assignments go with it and are not reported one by one.
|
|
397
|
+
*/
|
|
398
|
+
| {
|
|
399
|
+
type: 'role.deleted';
|
|
400
|
+
role: AccessRole;
|
|
401
|
+
} | {
|
|
402
|
+
type: 'assignment.created';
|
|
403
|
+
assignment: AccessAssignment;
|
|
404
|
+
} | {
|
|
405
|
+
type: 'assignment.deleted';
|
|
406
|
+
assignment: AccessAssignment;
|
|
407
|
+
} | {
|
|
408
|
+
type: 'settings.updated';
|
|
409
|
+
before: {
|
|
410
|
+
unassignedRoleId: string | null;
|
|
411
|
+
};
|
|
412
|
+
after: {
|
|
413
|
+
unassignedRoleId: string | null;
|
|
414
|
+
};
|
|
415
|
+
} | {
|
|
416
|
+
type: 'catalog.reconciled';
|
|
417
|
+
report: ReconcileReport;
|
|
418
|
+
};
|
|
419
|
+
interface AccessHooks {
|
|
420
|
+
/**
|
|
421
|
+
* Runs inside the transaction that made the change, after it.
|
|
422
|
+
*
|
|
423
|
+
* For data derived from roles — compiled sharing rules, an index marked
|
|
424
|
+
* stale — that has to commit or roll back together with the change.
|
|
425
|
+
* Throwing rolls the change back.
|
|
426
|
+
*/
|
|
427
|
+
onChange?(tx: Queryable, change: AccessChange, context: {
|
|
428
|
+
tenantId: string;
|
|
429
|
+
host: PluginHostApi;
|
|
430
|
+
}): Promise<void>;
|
|
431
|
+
}
|
|
432
|
+
interface CreateAccessOptions<C extends AccessCatalog> {
|
|
433
|
+
catalog: C;
|
|
434
|
+
/** Evaluators for the catalog's scopes; `all` and `own` are built in. */
|
|
435
|
+
evaluators?: Record<string, ScopeEvaluator<never>>;
|
|
436
|
+
/** Principal kinds beyond `user` and `unit`. */
|
|
437
|
+
principals?: PrincipalSource[];
|
|
438
|
+
/** One source per domain type the catalog declares. */
|
|
439
|
+
domains?: Record<string, DomainSource>;
|
|
440
|
+
hooks?: AccessHooks;
|
|
441
|
+
/** Cached assignment sets across requests. Defaults to 2000. */
|
|
442
|
+
cacheSize?: number;
|
|
443
|
+
}
|
|
444
|
+
interface ReconcileReport {
|
|
445
|
+
created: string[];
|
|
446
|
+
updated: string[];
|
|
447
|
+
retired: string[];
|
|
448
|
+
/** Custom-role grants the catalog no longer recognises, ignored until saved. */
|
|
449
|
+
staleGrants: {
|
|
450
|
+
roleId: string;
|
|
451
|
+
permission: string;
|
|
452
|
+
}[];
|
|
453
|
+
}
|
|
454
|
+
interface AccessSnapshot {
|
|
455
|
+
userId: string;
|
|
456
|
+
agent: boolean;
|
|
457
|
+
catalogVersion: number;
|
|
458
|
+
domain: AccessDomain | null;
|
|
459
|
+
permissions: Record<string, GrantValue>;
|
|
460
|
+
/** Navigation key → visible. Keys the catalog does not gate are absent. */
|
|
461
|
+
menus: Record<string, boolean>;
|
|
462
|
+
roles: {
|
|
463
|
+
id: string;
|
|
464
|
+
key: string | null;
|
|
465
|
+
builtin: boolean;
|
|
466
|
+
name: string;
|
|
467
|
+
nameKey: string | null;
|
|
468
|
+
source: GrantSourceKind;
|
|
469
|
+
hostRole?: string;
|
|
470
|
+
}[];
|
|
471
|
+
/** Domain ids by type in which the caller has explicit assignments. */
|
|
472
|
+
domains: Record<string, string[]>;
|
|
473
|
+
}
|
|
474
|
+
interface TenantState {
|
|
475
|
+
settings: AccessSettingsRow;
|
|
476
|
+
roles: Map<string, AccessRole>;
|
|
477
|
+
kinds: string[];
|
|
478
|
+
}
|
|
479
|
+
/** Users are matched by id; this source exists for lookup. */
|
|
480
|
+
declare const userPrincipalSource: PrincipalSource;
|
|
481
|
+
/** The host's organization units, with the member's role in each. */
|
|
482
|
+
declare const unitPrincipalSource: PrincipalSource;
|
|
483
|
+
declare function domainKey(domain: AccessDomain | null | undefined): string;
|
|
484
|
+
interface CheckOptions {
|
|
485
|
+
domain?: AccessDomain | null;
|
|
486
|
+
}
|
|
487
|
+
declare class AccessEngine<P extends string = string> {
|
|
488
|
+
readonly catalog: AccessCatalog;
|
|
489
|
+
readonly evaluators: ReadonlyMap<string, ScopeEvaluator<never>>;
|
|
490
|
+
readonly principalSources: ReadonlyMap<string, PrincipalSource>;
|
|
491
|
+
readonly domainSources: ReadonlyMap<string, DomainSource>;
|
|
492
|
+
readonly hooks: AccessHooks;
|
|
493
|
+
private readonly tenantCache;
|
|
494
|
+
private readonly assignmentCache;
|
|
495
|
+
constructor(options: CreateAccessOptions<AccessCatalog>);
|
|
496
|
+
forRequest(ctx: PluginUserContext, host: PluginHostApi): AccessSession<P>;
|
|
497
|
+
forSubject(subject: AccessSubject, host: PluginHostApi): AccessSession<P>;
|
|
498
|
+
/**
|
|
499
|
+
* A person as the directory knows them now, or null when inactive.
|
|
500
|
+
*
|
|
501
|
+
* For the entry points that carry a user id but no signed context: the
|
|
502
|
+
* storage authorizer, and explaining someone else's access.
|
|
503
|
+
*/
|
|
504
|
+
subjectFor(userId: string, host: PluginHostApi): Promise<AccessSubject | null>;
|
|
505
|
+
/** @internal */
|
|
506
|
+
tenantState(host: PluginHostApi): Promise<TenantState>;
|
|
507
|
+
/** @internal */
|
|
508
|
+
assignments(host: PluginHostApi, settings: AccessSettingsRow, principals: {
|
|
509
|
+
kind: string;
|
|
510
|
+
id: string;
|
|
511
|
+
}[]): Promise<AccessAssignment[]>;
|
|
512
|
+
/**
|
|
513
|
+
* Brings the built-in roles in line with the catalog.
|
|
514
|
+
*
|
|
515
|
+
* Run from the backend's `start`: built-in roles are owned by the catalog,
|
|
516
|
+
* so their grants are rewritten whenever it changes, and a built-in role it
|
|
517
|
+
* no longer lists is removed with its assignments. Serialized across
|
|
518
|
+
* processes by an advisory lock, and a no-op — no version bump — when
|
|
519
|
+
* nothing differs.
|
|
520
|
+
*/
|
|
521
|
+
reconcile(host: PluginHostApi): Promise<ReconcileReport>;
|
|
522
|
+
}
|
|
523
|
+
declare class AccessSession<P extends string = string> {
|
|
524
|
+
readonly engine: AccessEngine<P>;
|
|
525
|
+
readonly subject: AccessSubject;
|
|
526
|
+
readonly host: PluginHostApi;
|
|
527
|
+
private state?;
|
|
528
|
+
private memberships?;
|
|
529
|
+
private readonly grantsByDomain;
|
|
530
|
+
constructor(engine: AccessEngine<P>, subject: AccessSubject, host: PluginHostApi);
|
|
531
|
+
/** @internal */
|
|
532
|
+
tenant(): Promise<TenantState>;
|
|
533
|
+
private loadMemberships;
|
|
534
|
+
private inheritance;
|
|
535
|
+
/**
|
|
536
|
+
* Everything the subject holds in `domain` (global when omitted), including
|
|
537
|
+
* what assignments at its ancestors pass down when its type is hierarchical.
|
|
538
|
+
*/
|
|
539
|
+
grants(domain?: AccessDomain | null): Promise<EffectiveGrants>;
|
|
540
|
+
can(permission: P, options?: CheckOptions): Promise<boolean>;
|
|
541
|
+
/**
|
|
542
|
+
* Whether `permission` is held globally or in any domain the subject has an
|
|
543
|
+
* assignment in. Inheritance only narrows what an assignment grants, so a
|
|
544
|
+
* domain below those cannot hold more.
|
|
545
|
+
*/
|
|
546
|
+
holdsAnywhere(permission: P): Promise<boolean>;
|
|
547
|
+
/** The scope held, or null when not held or the permission has no scopes. */
|
|
548
|
+
scope(permission: P, options?: CheckOptions): Promise<string | null>;
|
|
549
|
+
/**
|
|
550
|
+
* The decision for `permission`, and for `resource` when one is given.
|
|
551
|
+
*
|
|
552
|
+
* Without a resource this is the functional check: is the permission held
|
|
553
|
+
* at any scope. With one, a scoped permission must also match it under the
|
|
554
|
+
* held scope or one narrower.
|
|
555
|
+
*/
|
|
556
|
+
authorize(permission: P, resource?: unknown, options?: CheckOptions): Promise<Decision>;
|
|
557
|
+
/**
|
|
558
|
+
* {@link authorize}, throwing when denied.
|
|
559
|
+
*
|
|
560
|
+
* A permission not held at all is a 403 whatever `conceal` says: that
|
|
561
|
+
* reveals only that the caller cannot use the feature. A resource outside
|
|
562
|
+
* the held scope is a 404 under `conceal`, so its existence stays hidden.
|
|
563
|
+
*/
|
|
564
|
+
assert(permission: P, resource?: unknown, options?: CheckOptions & {
|
|
565
|
+
conceal?: boolean;
|
|
566
|
+
}): Promise<Decision>;
|
|
567
|
+
/**
|
|
568
|
+
* A WHERE-clause fragment selecting the rows `permission` reaches.
|
|
569
|
+
*
|
|
570
|
+
* FALSE when the permission is not held, TRUE when it is unscoped or held at
|
|
571
|
+
* a scope that covers everything, otherwise the OR of every scope up to the
|
|
572
|
+
* held one — the same reading {@link authorize} applies to one resource.
|
|
573
|
+
*/
|
|
574
|
+
filter(permission: P, ref: ScopeRef, options?: CheckOptions): Promise<SqlFragment>;
|
|
575
|
+
/** What `/access/me` answers: enough for a UI to gate itself. */
|
|
576
|
+
snapshot(options?: CheckOptions): Promise<AccessSnapshot>;
|
|
577
|
+
/** Whether the caller manages access (holds `access.manage`). */
|
|
578
|
+
manages(options?: CheckOptions): Promise<boolean>;
|
|
579
|
+
}
|
|
580
|
+
declare function createAccess<C extends TypedAccessCatalog<string>>(options: CreateAccessOptions<C>): AccessEngine<PermissionOf<C>>;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The access administration operations, independent of any HTTP framework.
|
|
584
|
+
*
|
|
585
|
+
* The Hono routes (and the Python SDK's FastAPI routes, which mirror this
|
|
586
|
+
* file) only parse input and map errors; every rule lives here:
|
|
587
|
+
*
|
|
588
|
+
* - every operation needs `access.manage` in the domain it touches, so an
|
|
589
|
+
* assignment of `access.manage` inside one domain delegates exactly that
|
|
590
|
+
* domain's administration;
|
|
591
|
+
* - no escalation: whoever writes a role, assigns it, removes an assignment
|
|
592
|
+
* of it or makes it the default must already hold everything it grants, at
|
|
593
|
+
* the same scope or wider. Someone managing access through a host role
|
|
594
|
+
* binding is exempt — that is the recovery path when nobody else can;
|
|
595
|
+
* - a role marked `assignableBy: host-admin` is assigned and unassigned only
|
|
596
|
+
* through such a host role;
|
|
597
|
+
* - the catalog's per-user role limits, and referential rules the schema
|
|
598
|
+
* cannot express (a domain role assigned in its own domain only).
|
|
599
|
+
*
|
|
600
|
+
* Every write runs in one transaction that bumps the tenant's access version,
|
|
601
|
+
* appends to the audit log and runs the plugin's change hook.
|
|
602
|
+
*/
|
|
603
|
+
|
|
604
|
+
declare const domainInputSchema: z.ZodObject<{
|
|
605
|
+
type: z.ZodString;
|
|
606
|
+
id: z.ZodString;
|
|
607
|
+
}, "strict", z.ZodTypeAny, {
|
|
608
|
+
type: string;
|
|
609
|
+
id: string;
|
|
610
|
+
}, {
|
|
611
|
+
type: string;
|
|
612
|
+
id: string;
|
|
613
|
+
}>;
|
|
614
|
+
declare const createRoleInputSchema: z.ZodObject<{
|
|
615
|
+
name: z.ZodString;
|
|
616
|
+
description: z.ZodDefault<z.ZodString>;
|
|
617
|
+
grants: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodLiteral<true>, z.ZodString]>>;
|
|
618
|
+
domain: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
619
|
+
type: z.ZodString;
|
|
620
|
+
id: z.ZodString;
|
|
621
|
+
}, "strict", z.ZodTypeAny, {
|
|
622
|
+
type: string;
|
|
623
|
+
id: string;
|
|
624
|
+
}, {
|
|
625
|
+
type: string;
|
|
626
|
+
id: string;
|
|
627
|
+
}>>>;
|
|
628
|
+
}, "strict", z.ZodTypeAny, {
|
|
629
|
+
grants: Record<string, string | true>;
|
|
630
|
+
domain: {
|
|
631
|
+
type: string;
|
|
632
|
+
id: string;
|
|
633
|
+
} | null;
|
|
634
|
+
name: string;
|
|
635
|
+
description: string;
|
|
636
|
+
}, {
|
|
637
|
+
grants: Record<string, string | true>;
|
|
638
|
+
name: string;
|
|
639
|
+
domain?: {
|
|
640
|
+
type: string;
|
|
641
|
+
id: string;
|
|
642
|
+
} | null | undefined;
|
|
643
|
+
description?: string | undefined;
|
|
644
|
+
}>;
|
|
645
|
+
declare const updateRoleInputSchema: z.ZodObject<{
|
|
646
|
+
name: z.ZodOptional<z.ZodString>;
|
|
647
|
+
description: z.ZodOptional<z.ZodString>;
|
|
648
|
+
grants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodLiteral<true>, z.ZodString]>>>;
|
|
649
|
+
}, "strict", z.ZodTypeAny, {
|
|
650
|
+
grants?: Record<string, string | true> | undefined;
|
|
651
|
+
name?: string | undefined;
|
|
652
|
+
description?: string | undefined;
|
|
653
|
+
}, {
|
|
654
|
+
grants?: Record<string, string | true> | undefined;
|
|
655
|
+
name?: string | undefined;
|
|
656
|
+
description?: string | undefined;
|
|
657
|
+
}>;
|
|
658
|
+
declare const cloneRoleInputSchema: z.ZodObject<{
|
|
659
|
+
name: z.ZodString;
|
|
660
|
+
domain: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
661
|
+
type: z.ZodString;
|
|
662
|
+
id: z.ZodString;
|
|
663
|
+
}, "strict", z.ZodTypeAny, {
|
|
664
|
+
type: string;
|
|
665
|
+
id: string;
|
|
666
|
+
}, {
|
|
667
|
+
type: string;
|
|
668
|
+
id: string;
|
|
669
|
+
}>>>;
|
|
670
|
+
}, "strict", z.ZodTypeAny, {
|
|
671
|
+
name: string;
|
|
672
|
+
domain?: {
|
|
673
|
+
type: string;
|
|
674
|
+
id: string;
|
|
675
|
+
} | null | undefined;
|
|
676
|
+
}, {
|
|
677
|
+
name: string;
|
|
678
|
+
domain?: {
|
|
679
|
+
type: string;
|
|
680
|
+
id: string;
|
|
681
|
+
} | null | undefined;
|
|
682
|
+
}>;
|
|
683
|
+
declare const createAssignmentInputSchema: z.ZodObject<{
|
|
684
|
+
roleId: z.ZodString;
|
|
685
|
+
principal: z.ZodObject<{
|
|
686
|
+
kind: z.ZodString;
|
|
687
|
+
id: z.ZodString;
|
|
688
|
+
includeDescendants: z.ZodDefault<z.ZodBoolean>;
|
|
689
|
+
unitRoles: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodEnum<["owner", "admin", "member", "viewer"]>, "many">>>;
|
|
690
|
+
}, "strict", z.ZodTypeAny, {
|
|
691
|
+
kind: string;
|
|
692
|
+
id: string;
|
|
693
|
+
includeDescendants: boolean;
|
|
694
|
+
unitRoles: ("admin" | "owner" | "member" | "viewer")[] | null;
|
|
695
|
+
}, {
|
|
696
|
+
kind: string;
|
|
697
|
+
id: string;
|
|
698
|
+
includeDescendants?: boolean | undefined;
|
|
699
|
+
unitRoles?: ("admin" | "owner" | "member" | "viewer")[] | null | undefined;
|
|
700
|
+
}>;
|
|
701
|
+
domain: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
702
|
+
type: z.ZodString;
|
|
703
|
+
id: z.ZodString;
|
|
704
|
+
}, "strict", z.ZodTypeAny, {
|
|
705
|
+
type: string;
|
|
706
|
+
id: string;
|
|
707
|
+
}, {
|
|
708
|
+
type: string;
|
|
709
|
+
id: string;
|
|
710
|
+
}>>>;
|
|
711
|
+
}, "strict", z.ZodTypeAny, {
|
|
712
|
+
domain: {
|
|
713
|
+
type: string;
|
|
714
|
+
id: string;
|
|
715
|
+
} | null;
|
|
716
|
+
principal: {
|
|
717
|
+
kind: string;
|
|
718
|
+
id: string;
|
|
719
|
+
includeDescendants: boolean;
|
|
720
|
+
unitRoles: ("admin" | "owner" | "member" | "viewer")[] | null;
|
|
721
|
+
};
|
|
722
|
+
roleId: string;
|
|
723
|
+
}, {
|
|
724
|
+
principal: {
|
|
725
|
+
kind: string;
|
|
726
|
+
id: string;
|
|
727
|
+
includeDescendants?: boolean | undefined;
|
|
728
|
+
unitRoles?: ("admin" | "owner" | "member" | "viewer")[] | null | undefined;
|
|
729
|
+
};
|
|
730
|
+
roleId: string;
|
|
731
|
+
domain?: {
|
|
732
|
+
type: string;
|
|
733
|
+
id: string;
|
|
734
|
+
} | null | undefined;
|
|
735
|
+
}>;
|
|
736
|
+
declare const assignmentQuerySchema: z.ZodObject<{
|
|
737
|
+
roleId: z.ZodOptional<z.ZodString>;
|
|
738
|
+
principalKind: z.ZodOptional<z.ZodString>;
|
|
739
|
+
principalId: z.ZodOptional<z.ZodString>;
|
|
740
|
+
}, "strict", z.ZodTypeAny, {
|
|
741
|
+
roleId?: string | undefined;
|
|
742
|
+
principalKind?: string | undefined;
|
|
743
|
+
principalId?: string | undefined;
|
|
744
|
+
}, {
|
|
745
|
+
roleId?: string | undefined;
|
|
746
|
+
principalKind?: string | undefined;
|
|
747
|
+
principalId?: string | undefined;
|
|
748
|
+
}>;
|
|
749
|
+
declare const principalQuerySchema: z.ZodObject<{
|
|
750
|
+
kind: z.ZodString;
|
|
751
|
+
search: z.ZodOptional<z.ZodString>;
|
|
752
|
+
ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
753
|
+
offset: z.ZodDefault<z.ZodNumber>;
|
|
754
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
755
|
+
}, "strict", z.ZodTypeAny, {
|
|
756
|
+
kind: string;
|
|
757
|
+
offset: number;
|
|
758
|
+
limit: number;
|
|
759
|
+
search?: string | undefined;
|
|
760
|
+
ids?: string[] | undefined;
|
|
761
|
+
}, {
|
|
762
|
+
kind: string;
|
|
763
|
+
search?: string | undefined;
|
|
764
|
+
ids?: string[] | undefined;
|
|
765
|
+
offset?: number | undefined;
|
|
766
|
+
limit?: number | undefined;
|
|
767
|
+
}>;
|
|
768
|
+
declare const updateSettingsInputSchema: z.ZodObject<{
|
|
769
|
+
unassignedRoleId: z.ZodNullable<z.ZodString>;
|
|
770
|
+
}, "strict", z.ZodTypeAny, {
|
|
771
|
+
unassignedRoleId: string | null;
|
|
772
|
+
}, {
|
|
773
|
+
unassignedRoleId: string | null;
|
|
774
|
+
}>;
|
|
775
|
+
declare const auditQuerySchema: z.ZodObject<{
|
|
776
|
+
offset: z.ZodDefault<z.ZodNumber>;
|
|
777
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
778
|
+
}, "strict", z.ZodTypeAny, {
|
|
779
|
+
offset: number;
|
|
780
|
+
limit: number;
|
|
781
|
+
}, {
|
|
782
|
+
offset?: number | undefined;
|
|
783
|
+
limit?: number | undefined;
|
|
784
|
+
}>;
|
|
785
|
+
interface RoleView {
|
|
786
|
+
id: string;
|
|
787
|
+
key: string | null;
|
|
788
|
+
builtin: boolean;
|
|
789
|
+
domain: AccessDomain | null;
|
|
790
|
+
name: string;
|
|
791
|
+
nameKey: string | null;
|
|
792
|
+
description: string;
|
|
793
|
+
descriptionKey: string | null;
|
|
794
|
+
grants: Grants;
|
|
795
|
+
assignableBy: 'access-manager' | 'host-admin';
|
|
796
|
+
exemptFromRoleLimit: boolean;
|
|
797
|
+
assignmentCount: number;
|
|
798
|
+
isDefault: boolean;
|
|
799
|
+
/** Grants the current catalog no longer recognises; dropped on next save. */
|
|
800
|
+
staleGrants: string[];
|
|
801
|
+
updatedAt: string;
|
|
802
|
+
updatedBy: string | null;
|
|
803
|
+
}
|
|
804
|
+
interface AssignmentView extends AccessAssignment {
|
|
805
|
+
principalName: string | null;
|
|
806
|
+
domainName: string | null;
|
|
807
|
+
}
|
|
808
|
+
interface ExplainView {
|
|
809
|
+
userId: string;
|
|
810
|
+
domain: AccessDomain | null;
|
|
811
|
+
roles: (GrantSource & {
|
|
812
|
+
name: string;
|
|
813
|
+
nameKey: string | null;
|
|
814
|
+
})[];
|
|
815
|
+
permissions: Pick<Decision, 'permission' | 'allowed' | 'scope' | 'reason' | 'via'>[];
|
|
816
|
+
}
|
|
817
|
+
interface AuditEntryView extends AuditEntry {
|
|
818
|
+
actorName: string | null;
|
|
819
|
+
}
|
|
820
|
+
interface SettingsView {
|
|
821
|
+
unassignedRoleId: string | null;
|
|
822
|
+
version: number;
|
|
823
|
+
updatedAt: string;
|
|
824
|
+
updatedBy: string | null;
|
|
825
|
+
}
|
|
826
|
+
/** The grants of `grants` the actor does not hold at the same scope or wider. */
|
|
827
|
+
declare function uncoveredGrants(catalog: AccessCatalog, actor: EffectiveGrants, grants: Grants): string[];
|
|
828
|
+
declare class AccessAdmin {
|
|
829
|
+
private readonly session;
|
|
830
|
+
constructor(session: AccessSession);
|
|
831
|
+
private get engine();
|
|
832
|
+
private get catalog();
|
|
833
|
+
private get host();
|
|
834
|
+
private get tenantId();
|
|
835
|
+
private get actorId();
|
|
836
|
+
/** The caller's grants in `domain`, refusing anyone without `access.manage`. */
|
|
837
|
+
private manager;
|
|
838
|
+
private assertCovers;
|
|
839
|
+
private assertDomain;
|
|
840
|
+
private validateGrants;
|
|
841
|
+
private write;
|
|
842
|
+
private role;
|
|
843
|
+
private view;
|
|
844
|
+
catalogView(domain: AccessDomain | null): Promise<AccessCatalog>;
|
|
845
|
+
/** Global roles, plus the roles of `domain` when one is given. */
|
|
846
|
+
listRoles(domain: AccessDomain | null): Promise<RoleView[]>;
|
|
847
|
+
createRole(input: z.output<typeof createRoleInputSchema>): Promise<RoleView>;
|
|
848
|
+
private assertNameFree;
|
|
849
|
+
updateRole(id: string, input: z.output<typeof updateRoleInputSchema>): Promise<RoleView>;
|
|
850
|
+
deleteRole(id: string): Promise<void>;
|
|
851
|
+
cloneRole(id: string, input: z.output<typeof cloneRoleInputSchema>): Promise<RoleView>;
|
|
852
|
+
private principalNames;
|
|
853
|
+
private domainNames;
|
|
854
|
+
listAssignments(domain: AccessDomain | null, query: z.output<typeof assignmentQuerySchema>): Promise<AssignmentView[]>;
|
|
855
|
+
/** Refuses a default role that manages access: every user would. */
|
|
856
|
+
private assertDefaultMayHold;
|
|
857
|
+
/** The checks every grant or removal of `role` in `domain` must pass. */
|
|
858
|
+
private assertMayAssign;
|
|
859
|
+
createAssignment(input: z.output<typeof createAssignmentInputSchema>): Promise<AssignmentView>;
|
|
860
|
+
deleteAssignment(id: string): Promise<void>;
|
|
861
|
+
principals(domain: AccessDomain | null, query: z.output<typeof principalQuerySchema>): Promise<{
|
|
862
|
+
items: PrincipalEntry[];
|
|
863
|
+
hasMore: boolean;
|
|
864
|
+
}>;
|
|
865
|
+
/** Every permission of another user, with where each one comes from. */
|
|
866
|
+
explain(userId: string, domain: AccessDomain | null): Promise<ExplainView>;
|
|
867
|
+
settings(): Promise<SettingsView>;
|
|
868
|
+
updateSettings(input: z.output<typeof updateSettingsInputSchema>): Promise<SettingsView>;
|
|
869
|
+
audit(query: z.output<typeof auditQuerySchema>): Promise<{
|
|
870
|
+
items: AuditEntryView[];
|
|
871
|
+
hasMore: boolean;
|
|
872
|
+
}>;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* The catalog's JSON Schema, for editors.
|
|
877
|
+
*
|
|
878
|
+
* Shipped as `@tego/plugin-sdk/access-catalog.schema.json`; an `access.json`
|
|
879
|
+
* pointing its `$schema` there gets completion and inline errors. The schema
|
|
880
|
+
* only covers shape — cross-references are checked by the pack step.
|
|
881
|
+
*/
|
|
882
|
+
declare function accessCatalogJsonSchema(): Record<string, unknown>;
|
|
883
|
+
|
|
884
|
+
export { AccessAdmin, AccessAdminError, type AccessAssignment, AccessCatalog, type AccessChange, AccessDeniedError, type AccessDomain, AccessEngine, type AccessHooks, type AccessRole, AccessSession, type AccessSettingsRow, type AccessSnapshot, type AccessSubject, type AssignmentPrincipal, type AssignmentView, type AuditEntry, type AuditEntryView, BUILTIN_SCOPES, type CheckOptions, type CreateAccessOptions, type Decision, type DecisionReason, DelegationAction, type DomainInheritance, type DomainSource, EffectiveGrants, type ExplainView, FALSE_FRAGMENT, type GrantSource, type GrantSourceKind, GrantValue, Grants, PermissionOf, type PrincipalEntry, type PrincipalMembership, type PrincipalQuery, type PrincipalSource, type Queryable, type ReconcileReport, type ResolveInput, type RoleView, type ScopeContext, type ScopeEvaluator, type ScopeRef, type SettingsView, type SqlFragment, TRUE_FRAGMENT, TypedAccessCatalog, accessCatalogJsonSchema, allScope, assignmentInDomain, assignmentMatches, assignmentQuerySchema, auditQuerySchema, bindFragment, cloneRoleInputSchema, createAccess, createAssignmentInputSchema, createRoleInputSchema, decideResource, domainInputSchema, domainKey, ident, orFragments, ownScope, principalQuerySchema, refColumn, renderFragment, resolveGrants, sql, subjectFromContext, uncoveredGrants, unitPrincipalSource, updateRoleInputSchema, updateSettingsInputSchema, userPrincipalSource, validGrants };
|