@astram/guard 0.5.1 → 0.6.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/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +3 -0
- package/dist/auth/auth.controller.d.ts +90 -1
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +245 -7
- package/dist/auth/auth.module.d.ts +21 -0
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +28 -0
- package/dist/auth/dto.d.ts +32 -0
- package/dist/auth/dto.d.ts.map +1 -1
- package/dist/auth/ports/membership-checker.d.ts +41 -0
- package/dist/auth/ports/membership-checker.d.ts.map +1 -0
- package/dist/auth/ports/membership-checker.js +32 -0
- package/dist/auth/ports/scope-subtree-resolver.d.ts +48 -0
- package/dist/auth/ports/scope-subtree-resolver.d.ts.map +1 -0
- package/dist/auth/ports/scope-subtree-resolver.js +38 -0
- package/dist/auth/tokens.d.ts +25 -0
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +26 -1
- package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-profile-writer.js +10 -1
- package/dist/authz/list-scope-sql.d.ts +125 -0
- package/dist/authz/list-scope-sql.d.ts.map +1 -0
- package/dist/authz/list-scope-sql.js +477 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +28 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +49 -0
- package/dist/authz/mariadb-list-scope-adapter.d.ts +61 -0
- package/dist/authz/mariadb-list-scope-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-list-scope-adapter.js +157 -0
- package/dist/authz/mariadb-policy-store.d.ts +118 -0
- package/dist/authz/mariadb-policy-store.d.ts.map +1 -0
- package/dist/authz/mariadb-policy-store.js +225 -0
- package/dist/authz/mariadb-record-rule-adapter.d.ts +7 -3
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-record-rule-adapter.js +156 -13
- package/dist/authz/record-share-writer.d.ts +22 -0
- package/dist/authz/record-share-writer.d.ts.map +1 -1
- package/dist/authz/record-share-writer.js +41 -0
- package/dist/authz/wire-record-authorization.d.ts +35 -1
- package/dist/authz/wire-record-authorization.d.ts.map +1 -1
- package/dist/authz/wire-record-authorization.js +75 -0
- package/dist/identity/identity-actions.d.ts +7 -0
- package/dist/identity/identity-actions.d.ts.map +1 -1
- package/dist/identity/identity-actions.js +8 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -3
- package/dist/module/shield.module.d.ts +33 -0
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +12 -0
- package/dist/request-auth/request-principal.d.ts.map +1 -1
- package/dist/request-auth/request-principal.js +12 -0
- package/dist/request-auth/session-token-binder.d.ts +35 -1
- package/dist/request-auth/session-token-binder.d.ts.map +1 -1
- package/dist/request-auth/session-token-binder.js +123 -8
- package/dist/scope/closure-builder.d.ts +75 -0
- package/dist/scope/closure-builder.d.ts.map +1 -0
- package/dist/scope/closure-builder.js +134 -0
- package/migrations/1710000000009-CreateRecordPermissionRule.cjs +56 -0
- package/migrations/1710000000014-CreateRecordShare.cjs +49 -0
- package/migrations/1710000000015-CreateScopeClosure.cjs +46 -0
- package/migrations/1710000000016-AlterCompanyParentFkRestrict.cjs +71 -0
- package/package.json +3 -3
|
@@ -35,9 +35,13 @@
|
|
|
35
35
|
* ── SAFETY: every rule row is matched on `(tenant, type_key, action)` with all values
|
|
36
36
|
* BOUND as `?` params (no interpolation). The action match is exact OR the rule's
|
|
37
37
|
* wildcard `*`. Field conditions are evaluated in JS against the record attributes
|
|
38
|
-
* core supplies (design §11.1 builder shape) — the operator set is
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* core supplies (design §11.1 builder shape) — the operator set is the FULL
|
|
39
|
+
* 14-operator {@link import('@astram/core').PolicyOperator} model (unified with
|
|
40
|
+
* the guided policy builder, Task 0.2): `eq/ne` (dual-accept legacy
|
|
41
|
+
* `equals`/`not_equals`), `in/not_in`, `gt/lt/gte/lte`, `contains/not_contains`,
|
|
42
|
+
* `is_null/is_not_null`, `is_owner/is_not_owner`. An unknown operator makes the
|
|
43
|
+
* rule NOT match (fail-closed, never a spurious allow) — see `conditionHolds`
|
|
44
|
+
* below for the exact per-operator semantics and the documented edge-case choices.
|
|
41
45
|
*/
|
|
42
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
47
|
exports.MariaDbRecordPermissionRuleAdapter = exports.SHIELD_RECORD_RULE_PORT = void 0;
|
|
@@ -69,10 +73,76 @@ function resolveValue(raw, input) {
|
|
|
69
73
|
function fieldValue(field, input) {
|
|
70
74
|
return input.attributes?.[field];
|
|
71
75
|
}
|
|
76
|
+
/** Whether a record attribute value counts as "present" — not absent, not null. */
|
|
77
|
+
function isPresent(value) {
|
|
78
|
+
return value !== undefined && value !== null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Ordered compare of the record attribute vs. the resolved rule value (for
|
|
82
|
+
* `gt`/`lt`/`gte`/`lte`). RULE (documented, design §Task-0.2): if BOTH sides
|
|
83
|
+
* parse as finite numbers → NUMERIC compare; otherwise → LEXICAL (string)
|
|
84
|
+
* compare. Returns `null` when the attribute is absent/null — the condition
|
|
85
|
+
* does NOT hold for any ordered operator in that case (fail-closed; no
|
|
86
|
+
* comparison against a missing value).
|
|
87
|
+
*/
|
|
88
|
+
function orderedCompare(actual, expected) {
|
|
89
|
+
if (!isPresent(actual))
|
|
90
|
+
return null;
|
|
91
|
+
const actualNum = Number(actual);
|
|
92
|
+
const expectedNum = Number(expected);
|
|
93
|
+
const bothNumeric = actual !== '' &&
|
|
94
|
+
expected !== '' &&
|
|
95
|
+
Number.isFinite(actualNum) &&
|
|
96
|
+
Number.isFinite(expectedNum);
|
|
97
|
+
if (bothNumeric) {
|
|
98
|
+
return actualNum < expectedNum ? -1 : actualNum > expectedNum ? 1 : 0;
|
|
99
|
+
}
|
|
100
|
+
const actualStr = String(actual);
|
|
101
|
+
const expectedStr = String(expected);
|
|
102
|
+
return actualStr < expectedStr ? -1 : actualStr > expectedStr ? 1 : 0;
|
|
103
|
+
}
|
|
72
104
|
/**
|
|
73
105
|
* Evaluate a rule's field condition. A rule with NO field (`rpr_field` null) is
|
|
74
106
|
* UNCONDITIONAL (applies whenever subject+action match). The operator set is a fixed
|
|
75
|
-
* allow-list
|
|
107
|
+
* allow-list covering the full 14-operator {@link import('@astram/core').PolicyOperator}
|
|
108
|
+
* model — unifying this enforcement path with the guided policy builder. An unknown
|
|
109
|
+
* operator → the condition does NOT hold (fail-closed).
|
|
110
|
+
*
|
|
111
|
+
* ── DUAL-ACCEPT (back-compat): this table may hold either the core canonical
|
|
112
|
+
* operator names (`eq`/`ne`, as the Task-0.3 policy store persists) or the
|
|
113
|
+
* legacy astram3 names (`equals`/`not_equals`) from any pre-existing row —
|
|
114
|
+
* both are accepted and behave identically. `in`/`not_in` are shared by both
|
|
115
|
+
* naming schemes already.
|
|
116
|
+
* ── `in`/`not_in`: comma-separated token-set membership. `in` on an
|
|
117
|
+
* absent/null attribute already does NOT hold (its stringified form is
|
|
118
|
+
* never in a real authored set). `not_in` is the MIRROR of `ne` — an
|
|
119
|
+
* absent/null attribute must NOT be treated as "not in the set" (that
|
|
120
|
+
* would trivially hold via `!set.includes('undefined')` and WIDEN the
|
|
121
|
+
* policy for every record missing the attribute); guarded the same way:
|
|
122
|
+
* does NOT hold unless the attribute is present.
|
|
123
|
+
* ── `eq`/`equals`: absent/null attribute never equals a resolved value (an
|
|
124
|
+
* absent attribute simply is not `===` to anything) — already fail-closed.
|
|
125
|
+
* ── `ne`/`not_equals`: the MIRROR of `eq` — an absent/null attribute must
|
|
126
|
+
* NOT be treated as "not equal" (that would trivially hold via
|
|
127
|
+
* `undefined !== expected` and WIDEN the policy for every record missing
|
|
128
|
+
* the attribute). Guarded symmetrically with every other operator: does
|
|
129
|
+
* NOT hold unless the attribute is present.
|
|
130
|
+
* ── `gt`/`lt`/`gte`/`lte`: numeric compare when both sides parse as finite
|
|
131
|
+
* numbers, else lexical (string) compare. An absent/null attribute never
|
|
132
|
+
* holds for any ordered operator (fail-closed — no comparison against
|
|
133
|
+
* nothing).
|
|
134
|
+
* ── `contains`/`not_contains`: substring match on the string form. An
|
|
135
|
+
* absent/null attribute means the record has no such value to test — the
|
|
136
|
+
* SAFER choice is taken: the condition does NOT hold for EITHER `contains`
|
|
137
|
+
* OR `not_contains` when the attribute is absent (no accidental widen via
|
|
138
|
+
* "it doesn't contain what's missing").
|
|
139
|
+
* ── `is_null`/`is_not_null`: take no operand (the rule's value is ignored);
|
|
140
|
+
* test only whether the attribute is absent/null vs. present-and-non-null.
|
|
141
|
+
* ── `is_owner`/`is_not_owner`: the OWNERSHIP predicate — the field's value
|
|
142
|
+
* (a reference/owner-id attribute) compared to `input.principal.id`; the
|
|
143
|
+
* rule's value operand is ignored. An absent/null OR EMPTY-STRING attribute
|
|
144
|
+
* → does NOT hold for either (fail-closed — no owner to compare, and an
|
|
145
|
+
* empty attribute must never "match" an empty `principal.id`).
|
|
76
146
|
*/
|
|
77
147
|
function conditionHolds(row, input) {
|
|
78
148
|
if (row.rpr_field === null)
|
|
@@ -81,10 +151,15 @@ function conditionHolds(row, input) {
|
|
|
81
151
|
const expectedRaw = row.rpr_value ?? '';
|
|
82
152
|
const expected = resolveValue(expectedRaw, input);
|
|
83
153
|
switch (row.rpr_operator) {
|
|
154
|
+
case 'eq':
|
|
84
155
|
case 'equals':
|
|
85
156
|
return actual === expected;
|
|
157
|
+
case 'ne':
|
|
86
158
|
case 'not_equals':
|
|
87
|
-
|
|
159
|
+
// absent attribute → does NOT hold (fail-closed; consistent with every
|
|
160
|
+
// other operator — `undefined !== expected` would otherwise be trivially
|
|
161
|
+
// true and WIDEN the policy for every record missing this attribute).
|
|
162
|
+
return isPresent(actual) && actual !== expected;
|
|
88
163
|
case 'in': {
|
|
89
164
|
// comma-separated token list; membership by string comparison.
|
|
90
165
|
const set = String(expectedRaw)
|
|
@@ -93,11 +168,50 @@ function conditionHolds(row, input) {
|
|
|
93
168
|
return set.includes(String(actual));
|
|
94
169
|
}
|
|
95
170
|
case 'not_in': {
|
|
171
|
+
// absent attribute → does NOT hold (fail-closed; consistent with `ne`/
|
|
172
|
+
// `not_contains`/`is_not_owner` — otherwise `!set.includes('undefined')`
|
|
173
|
+
// would trivially hold and WIDEN the policy for every record missing the field).
|
|
174
|
+
if (!isPresent(actual))
|
|
175
|
+
return false;
|
|
96
176
|
const set = String(expectedRaw)
|
|
97
177
|
.split(',')
|
|
98
178
|
.map((s) => s.trim());
|
|
99
179
|
return !set.includes(String(actual));
|
|
100
180
|
}
|
|
181
|
+
case 'gt': {
|
|
182
|
+
const cmp = orderedCompare(actual, expected);
|
|
183
|
+
return cmp !== null && cmp > 0;
|
|
184
|
+
}
|
|
185
|
+
case 'gte': {
|
|
186
|
+
const cmp = orderedCompare(actual, expected);
|
|
187
|
+
return cmp !== null && cmp >= 0;
|
|
188
|
+
}
|
|
189
|
+
case 'lt': {
|
|
190
|
+
const cmp = orderedCompare(actual, expected);
|
|
191
|
+
return cmp !== null && cmp < 0;
|
|
192
|
+
}
|
|
193
|
+
case 'lte': {
|
|
194
|
+
const cmp = orderedCompare(actual, expected);
|
|
195
|
+
return cmp !== null && cmp <= 0;
|
|
196
|
+
}
|
|
197
|
+
case 'contains':
|
|
198
|
+
// absent attribute → does NOT hold (no accidental widen).
|
|
199
|
+
return isPresent(actual) && String(actual).includes(String(expected));
|
|
200
|
+
case 'not_contains':
|
|
201
|
+
// absent attribute → does NOT hold either (the safer, non-widening choice).
|
|
202
|
+
return isPresent(actual) && !String(actual).includes(String(expected));
|
|
203
|
+
case 'is_null':
|
|
204
|
+
return !isPresent(actual);
|
|
205
|
+
case 'is_not_null':
|
|
206
|
+
return isPresent(actual);
|
|
207
|
+
case 'is_owner':
|
|
208
|
+
// absent OR empty-string → does NOT hold (fail-closed; an empty attribute
|
|
209
|
+
// must never "match" an empty principal.id).
|
|
210
|
+
return isPresent(actual) && actual !== '' && actual === input.principal.id;
|
|
211
|
+
case 'is_not_owner':
|
|
212
|
+
// absent OR empty-string → does NOT hold (fail-closed; same hardening,
|
|
213
|
+
// mirrored for the negative ownership operator).
|
|
214
|
+
return isPresent(actual) && actual !== '' && actual !== input.principal.id;
|
|
101
215
|
default:
|
|
102
216
|
return false; // unknown operator → fail-closed (rule does not apply)
|
|
103
217
|
}
|
|
@@ -119,7 +233,7 @@ class MariaDbRecordPermissionRuleAdapter {
|
|
|
119
233
|
const tenantId = input.resource.tenantId ?? input.principal.tenantId;
|
|
120
234
|
let rows;
|
|
121
235
|
try {
|
|
122
|
-
rows = (await this.db.query(`SELECT rpr_effect, rpr_subject_kind, rpr_subject_value, rpr_field, rpr_operator, rpr_value
|
|
236
|
+
rows = (await this.db.query(`SELECT rpr_policy_uuid, rpr_effect, rpr_subject_kind, rpr_subject_value, rpr_field, rpr_operator, rpr_value
|
|
123
237
|
FROM \`${this.schema}\`.\`record_permission_rule\`
|
|
124
238
|
WHERE rpr_type_key = ?
|
|
125
239
|
AND (rpr_tenant_uuid = ? OR rpr_tenant_uuid IS NULL)
|
|
@@ -130,18 +244,47 @@ class MariaDbRecordPermissionRuleAdapter {
|
|
|
130
244
|
throw new core_1.AuthorizationUnavailableError(`record_permission_rule store unavailable for ` +
|
|
131
245
|
`${input.resource.typeKey}:${input.action.key}: ${err.message}`);
|
|
132
246
|
}
|
|
247
|
+
// ── AND-GROUPING BY rpr_policy_uuid (author-what-enforces, Task 0.3) ──────────
|
|
248
|
+
// A guided policy has N AND-composed conditions persisted as N rows sharing one
|
|
249
|
+
// `rpr_policy_uuid`. A group (policy) APPLIES iff EVERY row in it subject-matches
|
|
250
|
+
// AND its condition holds — the AND. A legacy single-row rule (`rpr_policy_uuid`
|
|
251
|
+
// NULL) keeps the historical INDEPENDENT-row behavior: each such row is its OWN
|
|
252
|
+
// singleton group. This prevents the over-widening a naive per-row OR fold would
|
|
253
|
+
// cause on a multi-condition policy (allow when EITHER holds).
|
|
254
|
+
//
|
|
255
|
+
// Grouping: rows sharing a non-null policy uuid form ONE group; every NULL-policy
|
|
256
|
+
// row is a distinct singleton group (keyed by a unique sentinel per row).
|
|
257
|
+
const groups = new Map();
|
|
258
|
+
let nullGroupSeq = 0;
|
|
259
|
+
for (const row of rows) {
|
|
260
|
+
const key = row.rpr_policy_uuid != null // null OR undefined ⇒ legacy single-row rule
|
|
261
|
+
? `p:${row.rpr_policy_uuid}`
|
|
262
|
+
: `n:${nullGroupSeq++}`; // each legacy row → its own singleton group
|
|
263
|
+
const bucket = groups.get(key);
|
|
264
|
+
if (bucket)
|
|
265
|
+
bucket.push(row);
|
|
266
|
+
else
|
|
267
|
+
groups.set(key, [row]);
|
|
268
|
+
}
|
|
133
269
|
let allow = false;
|
|
134
270
|
let explicitDeny = false;
|
|
135
|
-
for (const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
271
|
+
for (const groupRows of groups.values()) {
|
|
272
|
+
const head = groupRows[0];
|
|
273
|
+
if (head === undefined)
|
|
274
|
+
continue; // a group always has ≥1 row; guard for the type
|
|
275
|
+
// The group (policy) applies IFF every row subject-matches AND its condition holds.
|
|
276
|
+
// Its effect is consistent across rows (the store writes one effect per policy);
|
|
277
|
+
// we take the group's effect from its first row and ignore any anomalous mix
|
|
278
|
+
// fail-closed (a non-'allow'/'deny' effect contributes nothing).
|
|
279
|
+
const applies = groupRows.every((row) => subjectMatches(row, input) && conditionHolds(row, input));
|
|
280
|
+
if (!applies)
|
|
139
281
|
continue;
|
|
140
|
-
|
|
141
|
-
|
|
282
|
+
const effect = head.rpr_effect;
|
|
283
|
+
if (effect === 'deny') {
|
|
284
|
+
// Deny-wins — one applying deny group is final for this tier.
|
|
142
285
|
explicitDeny = true;
|
|
143
286
|
}
|
|
144
|
-
else if (
|
|
287
|
+
else if (effect === 'allow') {
|
|
145
288
|
allow = true;
|
|
146
289
|
}
|
|
147
290
|
// any other effect string is ignored (fail-closed — no permit inferred).
|
|
@@ -156,6 +156,28 @@ export declare class RecordShareWriter {
|
|
|
156
156
|
* row(s); a no-match delete is a benign no-op. Emits `share.removed` on success.
|
|
157
157
|
*/
|
|
158
158
|
unshare(input: ShareInput): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* List the shares CURRENTLY on a record — the owner-facing "who can see this" view. The SAME
|
|
161
|
+
* §6.2 owner bound applies: only the record's owner (in the sharer's tenant) may list its
|
|
162
|
+
* shares (a grantee cannot enumerate a record they don't own). Returns the raw share rows
|
|
163
|
+
* (grantee kind + uuid + action + sharer); the CALLER resolves grantee uuids → names before
|
|
164
|
+
* they reach the UI (no raw uuid rule — the identity module wires the DirectoryLabelResolver).
|
|
165
|
+
*
|
|
166
|
+
* @throws {@link ShareNotPermittedError} if the caller is not the record's owner (or the type
|
|
167
|
+
* is not owned / the record is not in their tenant) — same fail-closed bound as share().
|
|
168
|
+
* @throws {@link ShareStoreUnavailableError} on a transient store failure.
|
|
169
|
+
*/
|
|
170
|
+
listSharesForRecord(input: {
|
|
171
|
+
tenantId: string;
|
|
172
|
+
typeKey: string;
|
|
173
|
+
recordId: string;
|
|
174
|
+
sharerId: string;
|
|
175
|
+
}): Promise<ReadonlyArray<{
|
|
176
|
+
granteeKind: ShareGranteeKind;
|
|
177
|
+
granteeUuid: string;
|
|
178
|
+
action: string;
|
|
179
|
+
grantedBy: string;
|
|
180
|
+
}>>;
|
|
159
181
|
/**
|
|
160
182
|
* Enforce the §6.2 who-may-share bound (steps 1–3) and return the resolved type info.
|
|
161
183
|
* Fetches the record owner tenant-scoped and proves `sharerId` owns it. Throws
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-share-writer.d.ts","sourceRoot":"","sources":["../../src/authz/record-share-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAKxE,8CAA8C;AAC9C,eAAO,MAAM,0BAA0B,eAAuC,CAAC;AAE/E,4DAA4D;AAC5D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6FAA6F;IAC7F,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;CACrD;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAAC;IACnD,mCAAmC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;QACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH;AAED,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,oCAAoC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,iFAAiF;AACjF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,6EAA6E;AAC7E,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAI5B;AAOD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE;QAChB,EAAE,EAAE,cAAc,CAAC;QACnB,KAAK,EAAE,iBAAiB,CAAC;QACzB,kFAAkF;QAClF,KAAK,CAAC,EAAE,cAAc,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,+DAA+D;QAC/D,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;KACtB;IAQD;;;;;;;;OAQG;IACG,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B7C;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"record-share-writer.d.ts","sourceRoot":"","sources":["../../src/authz/record-share-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -137,6 +137,47 @@ class RecordShareWriter {
|
|
|
137
137
|
}
|
|
138
138
|
await this.emit('share.removed', input);
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* List the shares CURRENTLY on a record — the owner-facing "who can see this" view. The SAME
|
|
142
|
+
* §6.2 owner bound applies: only the record's owner (in the sharer's tenant) may list its
|
|
143
|
+
* shares (a grantee cannot enumerate a record they don't own). Returns the raw share rows
|
|
144
|
+
* (grantee kind + uuid + action + sharer); the CALLER resolves grantee uuids → names before
|
|
145
|
+
* they reach the UI (no raw uuid rule — the identity module wires the DirectoryLabelResolver).
|
|
146
|
+
*
|
|
147
|
+
* @throws {@link ShareNotPermittedError} if the caller is not the record's owner (or the type
|
|
148
|
+
* is not owned / the record is not in their tenant) — same fail-closed bound as share().
|
|
149
|
+
* @throws {@link ShareStoreUnavailableError} on a transient store failure.
|
|
150
|
+
*/
|
|
151
|
+
async listSharesForRecord(input) {
|
|
152
|
+
// Reuse the owner bound (steps 1–3) — a non-owner NEVER sees a record's share list.
|
|
153
|
+
await this.assertMayShare({
|
|
154
|
+
tenantId: input.tenantId,
|
|
155
|
+
typeKey: input.typeKey,
|
|
156
|
+
recordId: input.recordId,
|
|
157
|
+
sharerId: input.sharerId,
|
|
158
|
+
// grantee/action are irrelevant to the owner check; supply inert placeholders that the
|
|
159
|
+
// bound ignores (it reads only type/record/tenant/sharer).
|
|
160
|
+
granteeKind: 'user',
|
|
161
|
+
granteeUuid: '',
|
|
162
|
+
action: '',
|
|
163
|
+
});
|
|
164
|
+
let rows;
|
|
165
|
+
try {
|
|
166
|
+
rows = (await this.db.query(`SELECT rs_grantee_kind, rs_grantee_uuid, rs_action, rs_granted_by` +
|
|
167
|
+
` FROM \`${this.schema}\`.\`record_share\`` +
|
|
168
|
+
` WHERE rs_tenant_uuid = ? AND rs_type_key = ? AND rs_record_uuid = ?` +
|
|
169
|
+
` ORDER BY rs_created_at`, [input.tenantId, input.typeKey, input.recordId]));
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
throw new ShareStoreUnavailableError(`record_share list failed for ${input.typeKey}: ${err.message}`);
|
|
173
|
+
}
|
|
174
|
+
return rows.map((r) => ({
|
|
175
|
+
granteeKind: r.rs_grantee_kind === 'role' ? 'role' : 'user',
|
|
176
|
+
granteeUuid: r.rs_grantee_uuid,
|
|
177
|
+
action: r.rs_action,
|
|
178
|
+
grantedBy: r.rs_granted_by,
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
140
181
|
// --- the bound -------------------------------------------------------------
|
|
141
182
|
/**
|
|
142
183
|
* Enforce the §6.2 who-may-share bound (steps 1–3) and return the resolved type info.
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* import inside the `SHIELD_DECISION_AUTHORITY` async factory — which runs ONLY when a
|
|
24
24
|
* consumer opts into `recordAuthorization`. This file (and thus core) loads only then.
|
|
25
25
|
*/
|
|
26
|
-
import type { DecisionAuthority, ModuleRegistry, PermissionFactsPort, SessionFactsReader } from '@astram/core';
|
|
26
|
+
import type { DecisionAuthority, ModuleRegistry, PermissionFactsPort, Principal, SessionFactsReader } from '@astram/core';
|
|
27
|
+
import type { ObjectType } from '@astram/engine';
|
|
27
28
|
import { type RuleQueryable } from './mariadb-record-rule-adapter.js';
|
|
28
29
|
import { type OperationManifestEntry } from './manifest-operation-authority-adapter.js';
|
|
29
30
|
/**
|
|
@@ -63,4 +64,37 @@ export interface RecordAuthorizationInputs {
|
|
|
63
64
|
* pool, same registry, same ports) — this is a wiring MOVE, not a logic change.
|
|
64
65
|
*/
|
|
65
66
|
export declare function buildRecordAuthorization(inputs: RecordAuthorizationInputs): DecisionAuthority;
|
|
67
|
+
/**
|
|
68
|
+
* The engine-facing list-scope fn (Task 2.2). Given the engine's registered
|
|
69
|
+
* {@link ObjectType}, the requesting {@link Principal}, and the list action key, it returns
|
|
70
|
+
* the SAFE, parameterized WHERE fragment describing the visible set. The engine's `list()`
|
|
71
|
+
* (Task 3.1) ANDs `sql` into its WHERE and appends `params`.
|
|
72
|
+
*
|
|
73
|
+
* A transient store outage (an `AuthorizationUnavailableError` from the list-scope port)
|
|
74
|
+
* PROPAGATES through the returned promise — the engine's `list()` fails rather than falling
|
|
75
|
+
* open to an unbounded set (Invariant #8 / ADR-0016).
|
|
76
|
+
*/
|
|
77
|
+
export type ListScopeFn = (reg: ObjectType, actor: Principal, action: string) => Promise<{
|
|
78
|
+
sql: string;
|
|
79
|
+
params: unknown[];
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Construct the engine-facing {@link ListScopeFn} — the LIST-form sibling to
|
|
83
|
+
* `buildRecordAuthorization`'s per-record authority. It is a SEPARATE exported factory
|
|
84
|
+
* (Option A in the task report) that the module wiring calls ALONGSIDE
|
|
85
|
+
* `buildRecordAuthorization`: both take {@link RecordAuthorizationInputs} and both construct
|
|
86
|
+
* their adapters over the SAME un-overridable `inputs.pool`. This keeps
|
|
87
|
+
* `buildRecordAuthorization`'s `DecisionAuthority` return type unchanged (least blast radius).
|
|
88
|
+
*
|
|
89
|
+
* ── THE UN-OVERRIDABLE SEAM (the SECURITY POINT). The {@link MariaDbListScopeAdapter} is
|
|
90
|
+
* constructed HERE, over `inputs.pool` — the SAME pool the four record-tier read adapters
|
|
91
|
+
* use — and is DELIBERATELY NOT a field of {@link RecordAuthorizationInputs}. A consumer
|
|
92
|
+
* supplies ONLY the pool; it can NOT inject a fail-OPEN scope port (the exact hole this
|
|
93
|
+
* closes, mirroring the four record-tier adapters).
|
|
94
|
+
*
|
|
95
|
+
* ── FAIL-CLOSED. Nothing in the returned fn catches or swallows: a transient throw at ANY
|
|
96
|
+
* step (the port's store outage → `AuthorizationUnavailableError`, or the translator's
|
|
97
|
+
* unknown-field rejection) PROPAGATES so the read fails, NEVER falls open to unbounded.
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildListScopeFn(inputs: RecordAuthorizationInputs): ListScopeFn;
|
|
66
100
|
//# sourceMappingURL=wire-record-authorization.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire-record-authorization.d.ts","sourceRoot":"","sources":["../../src/authz/wire-record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,
|
|
1
|
+
{"version":3,"file":"wire-record-authorization.d.ts","sourceRoot":"","sources":["../../src/authz/wire-record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EAEd,mBAAmB,EACnB,SAAS,EAET,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAK1C,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,2CAA2C,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAC9C,6FAA6F;IAC7F,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,yBAAyB,GAAG,iBAAiB,CAmB7F;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,yBAAyB,GAAG,WAAW,CAkE/E"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildRecordAuthorization = buildRecordAuthorization;
|
|
4
|
+
exports.buildListScopeFn = buildListScopeFn;
|
|
4
5
|
const core_1 = require("@astram/core");
|
|
5
6
|
const mariadb_record_rule_adapter_js_1 = require("./mariadb-record-rule-adapter.js");
|
|
6
7
|
const mariadb_record_share_adapter_js_1 = require("./mariadb-record-share-adapter.js");
|
|
7
8
|
const mariadb_cross_tenant_grant_adapter_js_1 = require("./mariadb-cross-tenant-grant-adapter.js");
|
|
9
|
+
const mariadb_list_scope_adapter_js_1 = require("./mariadb-list-scope-adapter.js");
|
|
10
|
+
const list_scope_sql_js_1 = require("./list-scope-sql.js");
|
|
8
11
|
const manifest_operation_authority_adapter_js_1 = require("./manifest-operation-authority-adapter.js");
|
|
9
12
|
/**
|
|
10
13
|
* Construct the four framework-owned record-tier adapters over the supplied pool +
|
|
@@ -32,3 +35,75 @@ function buildRecordAuthorization(inputs) {
|
|
|
32
35
|
operationAuthority: new manifest_operation_authority_adapter_js_1.ManifestOperationAuthorityAdapter(inputs.operationManifest ?? []),
|
|
33
36
|
});
|
|
34
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Construct the engine-facing {@link ListScopeFn} — the LIST-form sibling to
|
|
40
|
+
* `buildRecordAuthorization`'s per-record authority. It is a SEPARATE exported factory
|
|
41
|
+
* (Option A in the task report) that the module wiring calls ALONGSIDE
|
|
42
|
+
* `buildRecordAuthorization`: both take {@link RecordAuthorizationInputs} and both construct
|
|
43
|
+
* their adapters over the SAME un-overridable `inputs.pool`. This keeps
|
|
44
|
+
* `buildRecordAuthorization`'s `DecisionAuthority` return type unchanged (least blast radius).
|
|
45
|
+
*
|
|
46
|
+
* ── THE UN-OVERRIDABLE SEAM (the SECURITY POINT). The {@link MariaDbListScopeAdapter} is
|
|
47
|
+
* constructed HERE, over `inputs.pool` — the SAME pool the four record-tier read adapters
|
|
48
|
+
* use — and is DELIBERATELY NOT a field of {@link RecordAuthorizationInputs}. A consumer
|
|
49
|
+
* supplies ONLY the pool; it can NOT inject a fail-OPEN scope port (the exact hole this
|
|
50
|
+
* closes, mirroring the four record-tier adapters).
|
|
51
|
+
*
|
|
52
|
+
* ── FAIL-CLOSED. Nothing in the returned fn catches or swallows: a transient throw at ANY
|
|
53
|
+
* step (the port's store outage → `AuthorizationUnavailableError`, or the translator's
|
|
54
|
+
* unknown-field rejection) PROPAGATES so the read fails, NEVER falls open to unbounded.
|
|
55
|
+
*/
|
|
56
|
+
function buildListScopeFn(inputs) {
|
|
57
|
+
// Framework-constructed over `inputs.pool` — UN-overridable (not a field of the inputs).
|
|
58
|
+
const listScopePort = new mariadb_list_scope_adapter_js_1.MariaDbListScopeAdapter(inputs.pool);
|
|
59
|
+
// The cross-tenant grant adapter — reused HERE to resolve the OWNING tenants a grantee
|
|
60
|
+
// reaches, so the list can apply the OWNING tenant's deny rules to the cross-tenant branch
|
|
61
|
+
// (deny-wins per the resource's tenant — the fail-open leak the Phase-4 review found). Same
|
|
62
|
+
// un-overridable `inputs.pool`.
|
|
63
|
+
const crossTenantAdapter = new mariadb_cross_tenant_grant_adapter_js_1.MariaDbCrossTenantGrantAdapter(inputs.pool);
|
|
64
|
+
return async (reg, actor, action) => {
|
|
65
|
+
// Bridge the engine `ObjectType` → the core `ComputeListScopeDeps.registry` shape.
|
|
66
|
+
// `computeListScope` reads ONLY `descriptor.access` for the owner-baseline posture
|
|
67
|
+
// (see list-scope.ts `ownerBaselineFor`), so `label`/`fields` are inert — the engine's
|
|
68
|
+
// `reg.access` (`AccessDeclaration`) is structurally core's `RecordAccessPosture`.
|
|
69
|
+
const registry = {
|
|
70
|
+
resolveObjectType(typeKey) {
|
|
71
|
+
return typeKey === reg.typeKey
|
|
72
|
+
? { typeKey: reg.typeKey, label: reg.typeKey, fields: [], access: reg.access }
|
|
73
|
+
: undefined;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
// A transient throw here PROPAGATES (fail-closed — NOT caught).
|
|
77
|
+
const spec = await (0, core_1.computeListScope)({ registry, listScope: listScopePort }, { principal: actor, action, typeKey: reg.typeKey, tenantId: actor.tenantId });
|
|
78
|
+
// ── OWNING-TENANT DENY (Task 4.2 — the fail-open leak fix) ────────────────────────────
|
|
79
|
+
// A cross-tenant grant reaches the OWNING tenant's records; `decide()` evaluates those at
|
|
80
|
+
// the RESOURCE's (owning) tenant, so the owning tenant's DENY rules apply (deny-wins,
|
|
81
|
+
// ADR-0005). The same-tenant deny (in `spec.policyDeny`) does NOT cover them — it is scoped
|
|
82
|
+
// to the actor's own subtree. So resolve the active grants' owning tenants and fetch THEIR
|
|
83
|
+
// deny groups, to be applied ONLY to the cross-tenant branch (per-branch deny scoping — a
|
|
84
|
+
// global deny would over-restrict the same-tenant set). Fires ONLY for a NON-ROOT grantee
|
|
85
|
+
// with `crossTenant.kind === 'granted'`; root has no cross-tenant branch. A transient throw
|
|
86
|
+
// PROPAGATES (fail-closed — never drop the owning-tenant deny). Global (NULL-tenant) deny
|
|
87
|
+
// rules come back in BOTH fetches, so they apply to both branches (as `decide()` does).
|
|
88
|
+
let owningTenantDeny = [];
|
|
89
|
+
if (spec.crossTenant.kind === 'granted') {
|
|
90
|
+
const owningTenants = await crossTenantAdapter.listActiveOwningTenants({
|
|
91
|
+
grantee: actor.id,
|
|
92
|
+
roleIds: actor.roleIds,
|
|
93
|
+
typeKey: reg.typeKey,
|
|
94
|
+
action,
|
|
95
|
+
});
|
|
96
|
+
if (owningTenants.length > 0) {
|
|
97
|
+
const owningPolicy = await listScopePort.listPolicyConditions(reg.typeKey, owningTenants, action, {
|
|
98
|
+
principalId: actor.id,
|
|
99
|
+
roleIds: actor.roleIds,
|
|
100
|
+
});
|
|
101
|
+
owningTenantDeny = owningPolicy.deny; // ONLY the deny — the GRANT is the permit source
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Translate the abstract spec → SAFE parameterized WHERE fragment. An unknown-field
|
|
105
|
+
// rejection here also PROPAGATES (fail-closed).
|
|
106
|
+
const translated = (0, list_scope_sql_js_1.translateListScope)(spec, reg, { principalId: actor.id, principalTenantId: actor.tenantId }, { owningTenantDeny });
|
|
107
|
+
return { sql: translated.sql, params: translated.params };
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -17,6 +17,13 @@ export type IdentityAction = (typeof IDENTITY_ACTIONS)[number];
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const CROSS_TENANT_GRANT_ACTIONS: readonly ["share", "accept", "revoke"];
|
|
19
19
|
export type CrossTenantGrantAction = (typeof CROSS_TENANT_GRANT_ACTIONS)[number];
|
|
20
|
+
/**
|
|
21
|
+
* `record_share`'s action vocabulary (list-scoping engine Phase 6, §9.2) — NOT the CRUD verbs.
|
|
22
|
+
* `share` gates the create/revoke routes (a per-record owner action; the writer's owner-bound is
|
|
23
|
+
* the real wall), `read` gates the list-a-record's-shares route.
|
|
24
|
+
*/
|
|
25
|
+
export declare const RECORD_SHARE_ACTIONS: readonly ["share", "read"];
|
|
26
|
+
export type RecordShareAction = (typeof RECORD_SHARE_ACTIONS)[number];
|
|
20
27
|
/** The action vocabulary for `typeKey` — the per-type override if one exists, else the default. */
|
|
21
28
|
export declare function actionsFor(typeKey: string): readonly string[];
|
|
22
29
|
//# sourceMappingURL=identity-actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-actions.d.ts","sourceRoot":"","sources":["../../src/identity/identity-actions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,aAAa,CAAC;AACrC,eAAO,MAAM,gBAAgB,iDAAkD,CAAC;AAChF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,wCAAyC,CAAC;AACjF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"identity-actions.d.ts","sourceRoot":"","sources":["../../src/identity/identity-actions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,aAAa,CAAC;AACrC,eAAO,MAAM,gBAAgB,iDAAkD,CAAC;AAChF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,wCAAyC,CAAC;AACjF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4BAA6B,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAWtE,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAE7D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CROSS_TENANT_GRANT_ACTIONS = exports.IDENTITY_ACTIONS = exports.MODULE_KEY = void 0;
|
|
3
|
+
exports.RECORD_SHARE_ACTIONS = exports.CROSS_TENANT_GRANT_ACTIONS = exports.IDENTITY_ACTIONS = exports.MODULE_KEY = void 0;
|
|
4
4
|
exports.actionsFor = actionsFor;
|
|
5
5
|
/**
|
|
6
6
|
* The fixed action vocabulary + module key for the framework's @astram/identity objects.
|
|
@@ -19,12 +19,19 @@ exports.IDENTITY_ACTIONS = ['read', 'create', 'update', 'delete'];
|
|
|
19
19
|
* revoke -> 'revoke'). Mirrors astram3's `authz/actions.ts`'s `CROSS_TENANT_GRANT_ACTIONS`.
|
|
20
20
|
*/
|
|
21
21
|
exports.CROSS_TENANT_GRANT_ACTIONS = ['share', 'accept', 'revoke'];
|
|
22
|
+
/**
|
|
23
|
+
* `record_share`'s action vocabulary (list-scoping engine Phase 6, §9.2) — NOT the CRUD verbs.
|
|
24
|
+
* `share` gates the create/revoke routes (a per-record owner action; the writer's owner-bound is
|
|
25
|
+
* the real wall), `read` gates the list-a-record's-shares route.
|
|
26
|
+
*/
|
|
27
|
+
exports.RECORD_SHARE_ACTIONS = ['share', 'read'];
|
|
22
28
|
/**
|
|
23
29
|
* Per-type action-vocabulary override. Every type not listed here defaults to
|
|
24
30
|
* {@link IDENTITY_ACTIONS} (the CRUD verbs).
|
|
25
31
|
*/
|
|
26
32
|
const ACTIONS_BY_TYPE = {
|
|
27
33
|
cross_tenant_grant: exports.CROSS_TENANT_GRANT_ACTIONS,
|
|
34
|
+
record_share: exports.RECORD_SHARE_ACTIONS,
|
|
28
35
|
};
|
|
29
36
|
/** The action vocabulary for `typeKey` — the per-type override if one exists, else the default. */
|
|
30
37
|
function actionsFor(typeKey) {
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,9 @@ export { stubDecisionAuthority, type Effect, type CoreDecisionAuthority, type Co
|
|
|
33
33
|
export { Argon2idPasswordHasher, argon2idHasherProvider, SHIELD_PASSWORD_HASHER, type Argon2idParams, } from './authentication/argon2id-password-hasher.js';
|
|
34
34
|
export { MariaDbSessionRecordStore, SHIELD_SESSION_RECORD_STORE, RedisHotSessionStore, SHIELD_HOT_SESSION_STORE, CryptoTokenGenerator, Sha256RefreshTokenHasher, SystemClock, cryptoTokenGeneratorProvider, refreshHasherProvider, systemClockProvider, SHIELD_TOKEN_GENERATOR, SHIELD_REFRESH_HASHER, SHIELD_CLOCK, } from './session/index.js';
|
|
35
35
|
export { MariaDbRecordPermissionRuleAdapter, SHIELD_RECORD_RULE_PORT, type RuleQueryable, } from './authz/mariadb-record-rule-adapter.js';
|
|
36
|
+
export { MariaDbPolicyStore, SHIELD_POLICY_STORE, type RuleQueryable as PolicyStoreQueryable, } from './authz/mariadb-policy-store.js';
|
|
37
|
+
export { MariaDbListScopeAdapter, SHIELD_LIST_SCOPE_PORT, type ListScopeQueryable, } from './authz/mariadb-list-scope-adapter.js';
|
|
38
|
+
export { translateListScope, UnknownScopeFieldError, type TranslatedListScope, type ListScopePrincipal, } from './authz/list-scope-sql.js';
|
|
36
39
|
export { MariaDbRecordShareAdapter, SHIELD_RECORD_SHARE_PORT, type ShareQueryable, } from './authz/mariadb-record-share-adapter.js';
|
|
37
40
|
export { RecordShareWriter, SHIELD_RECORD_SHARE_WRITER, ShareNotPermittedError, ShareStoreUnavailableError, type ShareInput, type ShareGranteeKind, type ShareTypeInfo, type ShareTypeResolver, type ShareAuditEvent, type ShareAuditSink, } from './authz/record-share-writer.js';
|
|
38
41
|
export { makeShareDeleteCascade, type RecordDeletedHook, type ShareQueryFn, } from './authz/record-share-cascade.js';
|
|
@@ -40,6 +43,7 @@ export { MariaDbCrossTenantGrantAdapter, SHIELD_CROSS_TENANT_GRANT_PORT, type Gr
|
|
|
40
43
|
export { CrossTenantGrantWriter, SHIELD_CROSS_TENANT_GRANT_WRITER, GrantNotPermittedError, InvalidGrantError, GrantStoreUnavailableError, type GrantAuthorizeFn, type GrantAuditEvent, type GrantAuditSink, type GrantGranteeKind, type GrantScope, type OfferGrantInput, type AcceptGrantInput, type RevokeGrantInput, } from './authz/cross-tenant-grant-writer.js';
|
|
41
44
|
export { ManifestOperationAuthorityAdapter, SHIELD_OPERATION_AUTHORITY_PORT, type OperationManifestEntry, } from './authz/manifest-operation-authority-adapter.js';
|
|
42
45
|
export { buildRecordAuthorization } from './authz/wire-record-authorization.js';
|
|
46
|
+
export { buildListScopeFn, type ListScopeFn } from './authz/wire-record-authorization.js';
|
|
43
47
|
export { BootSequence, assertPoolBudget } from './boot/boot-sequence.js';
|
|
44
48
|
export { APP_CONFIG } from './config/config.module.js';
|
|
45
49
|
export { loadAppConfig, loadPoolBudget, evaluatePoolBudget, DEFAULT_POOL_BUDGET, SCHEMA_SVC_POOL_HARD_CAP, type AppConfig, type PoolBudgetConfig, type PoolBudgetEvaluation, type RuntimeAccountPools, loadDbConfig, loadRedisConfig, loadSessionLifetimes, loadRefreshHmacKey, assertRefreshHmacKey, DEFAULT_DB_CONFIG, DEFAULT_REDIS_URL, type DbConfig, type RedisConfig, type SessionLifetimeOverrides, } from './config/app-config.js';
|
|
@@ -62,8 +66,11 @@ export type { MfaDeviceStore, MfaDevice } from './auth/ports/mfa-device-store.js
|
|
|
62
66
|
export type { AuditSink, AuthAuditEvent } from './auth/ports/audit-sink.js';
|
|
63
67
|
export type { SessionFactsProjector } from './auth/ports/session-facts-projector.js';
|
|
64
68
|
export { SHIELD_SESSION_FACTS_PROJECTOR } from './auth/tokens.js';
|
|
69
|
+
export type { ScopeSubtreeResolver } from './auth/ports/scope-subtree-resolver.js';
|
|
70
|
+
export { SHIELD_SCOPE_SUBTREE_RESOLVER } from './auth/tokens.js';
|
|
65
71
|
export { AuthController } from './auth/auth.controller.js';
|
|
66
72
|
export { PendingMfaStore } from './auth/pending-mfa-store.js';
|
|
67
73
|
export { generateTotpSecret, buildOtpauthUri, generateRecoveryCodes, } from './auth/totp-enrollment.js';
|
|
68
74
|
export { AccountController } from './auth/account.controller.js';
|
|
75
|
+
export { buildClosureRows, ScopeCycleError, ScopeDepthError, ScopeUnknownParentError, type ScopeEdge, type ClosureRow, } from './scope/closure-builder.js';
|
|
69
76
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAG1F,OAAO,EACL,YAAY,EACZ,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAI7B,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,MAAM,EACN,UAAU,EACV,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,2CAA2C,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAKhF,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AACjG,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,eAAe,GAChB,MAAM,uCAAuC,CAAC;AAM/C,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,YAAY,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,qCAAqC,CAAC;AAS7C,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,iBAAiB,GACvB,MAAM,qCAAqC,CAAC;AAU7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAKnF,OAAO,EACL,qBAAqB,EACrB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,cAAc,GACpB,MAAM,8CAA8C,CAAC;AAOtD,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,kCAAkC,EAClC,uBAAuB,EACvB,KAAK,aAAa,GACnB,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|