@embedpdf/engine-core 3.0.0-next.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 +192 -0
- package/README.md +24 -0
- package/dist/AbortError-ByBflvEl.cjs +38 -0
- package/dist/AbortError-ByBflvEl.cjs.map +1 -0
- package/dist/AbortError-CjSasIlL.js +27 -0
- package/dist/AbortError-CjSasIlL.js.map +1 -0
- package/dist/AnnotationListSnapshot-W0RCsyDE.d.cts +5043 -0
- package/dist/AnnotationListSnapshot-W0RCsyDE.d.ts +5043 -0
- package/dist/DocumentSecurityService-B-tULKNY.d.ts +332 -0
- package/dist/DocumentSecurityService-Df79fgpe.d.cts +332 -0
- package/dist/Engine-B7Oe7f5i.d.ts +1013 -0
- package/dist/Engine-D9loFZc1.d.cts +1013 -0
- package/dist/builders-BvikWRuO.cjs +1684 -0
- package/dist/builders-BvikWRuO.cjs.map +1 -0
- package/dist/builders-C548U47M.js +1367 -0
- package/dist/builders-C548U47M.js.map +1 -0
- package/dist/conformance.cjs +5414 -0
- package/dist/conformance.cjs.map +1 -0
- package/dist/conformance.d.cts +530 -0
- package/dist/conformance.d.ts +530 -0
- package/dist/conformance.js +5394 -0
- package/dist/conformance.js.map +1 -0
- package/dist/fold-CB4fAOOy.js +175 -0
- package/dist/fold-CB4fAOOy.js.map +1 -0
- package/dist/fold-IaMph3CO.cjs +216 -0
- package/dist/fold-IaMph3CO.cjs.map +1 -0
- package/dist/index.cjs +82 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/resolver-DAZWVoJL.cjs +434 -0
- package/dist/resolver-DAZWVoJL.cjs.map +1 -0
- package/dist/resolver-DiPMTfcy.js +363 -0
- package/dist/resolver-DiPMTfcy.js.map +1 -0
- package/dist/runtime.cjs +453 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +1002 -0
- package/dist/runtime.d.ts +1002 -0
- package/dist/runtime.js +361 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schemas-9OpSSeM6.js +2493 -0
- package/dist/schemas-9OpSSeM6.js.map +1 -0
- package/dist/schemas-B_1VyFsm.cjs +3608 -0
- package/dist/schemas-B_1VyFsm.cjs.map +1 -0
- package/dist/subtype-BvAzLCGC.js +196 -0
- package/dist/subtype-BvAzLCGC.js.map +1 -0
- package/dist/subtype-qejjVr6h.cjs +231 -0
- package/dist/subtype-qejjVr6h.cjs.map +1 -0
- package/dist/wire.cjs +1516 -0
- package/dist/wire.cjs.map +1 -0
- package/dist/wire.d.cts +1546 -0
- package/dist/wire.d.ts +1546 -0
- package/dist/wire.js +1280 -0
- package/dist/wire.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
//#region src/auth/scope/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link parseScope} / {@link validateScopeArray} when a scope
|
|
4
|
+
* string does not match any known grammar (capability, collab, virtual,
|
|
5
|
+
* or wildcard).
|
|
6
|
+
*
|
|
7
|
+
* Carries the offending scope verbatim so route/JWT-layer error
|
|
8
|
+
* handlers can echo it to the customer.
|
|
9
|
+
*/
|
|
10
|
+
var InvalidScope = class extends Error {
|
|
11
|
+
constructor(scope, reason) {
|
|
12
|
+
super(`invalid scope "${scope}": ${reason}`);
|
|
13
|
+
this.scope = scope;
|
|
14
|
+
this.name = "InvalidScope";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Thrown by route guards and engine-local enforcement when a capability
|
|
19
|
+
* or collab action is denied for the current request/handle.
|
|
20
|
+
*
|
|
21
|
+
* `required` names what the caller needed (e.g., "doc.render" or
|
|
22
|
+
* "annotations:update"). `context` is an optional label such as "local"
|
|
23
|
+
* or "/v1/docs/.../pages/2/render".
|
|
24
|
+
*/
|
|
25
|
+
var PermissionDenied = class extends Error {
|
|
26
|
+
constructor(required, context) {
|
|
27
|
+
super(`permission denied${context ? ` (${context})` : ""}: ${required}`);
|
|
28
|
+
this.required = required;
|
|
29
|
+
this.context = context;
|
|
30
|
+
this.name = "PermissionDenied";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Thrown at engine-local open time when the supplied scope includes
|
|
35
|
+
* collab filters (`:self`, `:group=...`) but the identity claims needed
|
|
36
|
+
* to resolve them are missing. Fails loudly at open so the configuration
|
|
37
|
+
* mistake is visible immediately instead of producing silent denies at
|
|
38
|
+
* every annotation mutation.
|
|
39
|
+
*/
|
|
40
|
+
var MissingIdentity = class extends Error {
|
|
41
|
+
constructor(scope) {
|
|
42
|
+
super(`scope "${scope}" requires identity claims (user_id and/or groups)`);
|
|
43
|
+
this.name = "MissingIdentity";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/auth/scope/parser.ts
|
|
48
|
+
/**
|
|
49
|
+
* Closed set of recognized capability strings. Membership is the
|
|
50
|
+
* authoritative source of truth — adding a new capability requires
|
|
51
|
+
* extending the `DocCapability` union AND adding it here. The parser
|
|
52
|
+
* rejects anything outside this set.
|
|
53
|
+
*
|
|
54
|
+
* Removed legacy names (e.g., `doc.read`, `doc.edit-pages`, `doc.save`)
|
|
55
|
+
* are deliberately absent so any JWT carrying them fails at verify
|
|
56
|
+
* time with a clear error.
|
|
57
|
+
*/
|
|
58
|
+
const KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
59
|
+
"doc.open",
|
|
60
|
+
"doc.render",
|
|
61
|
+
"doc.text.select",
|
|
62
|
+
"doc.text.copy",
|
|
63
|
+
"doc.text.search",
|
|
64
|
+
"doc.content.copy",
|
|
65
|
+
"doc.download",
|
|
66
|
+
"doc.download.flattened",
|
|
67
|
+
"doc.print",
|
|
68
|
+
"doc.print.high",
|
|
69
|
+
"doc.pages.modify",
|
|
70
|
+
"doc.pages.assemble",
|
|
71
|
+
"doc.forms.read",
|
|
72
|
+
"doc.forms.fill",
|
|
73
|
+
"doc.forms.modify",
|
|
74
|
+
"doc.annotate.read",
|
|
75
|
+
"doc.annotate.modify",
|
|
76
|
+
"doc.metadata.modify",
|
|
77
|
+
"doc.attachments.modify",
|
|
78
|
+
"doc.redact"
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* Parse a single scope string into a structured `ParsedScope`.
|
|
82
|
+
*
|
|
83
|
+
* Recognised shapes:
|
|
84
|
+
* - `"*"` → wildcard
|
|
85
|
+
* - `"pdf.permissions"` → virtual (expands at resolve time)
|
|
86
|
+
* - `"a.b.c..."` (dotted) → capability
|
|
87
|
+
* - `"entity:action:filter"` (colons) → collab
|
|
88
|
+
*
|
|
89
|
+
* Throws {@link InvalidScope} for any string that fails to match one of
|
|
90
|
+
* those shapes, or whose capability/entity/action/filter is not in the
|
|
91
|
+
* closed enum.
|
|
92
|
+
*/
|
|
93
|
+
function parseScope(raw) {
|
|
94
|
+
if (raw === "*") return { kind: "wildcard" };
|
|
95
|
+
if (raw === "pdf.permissions") return {
|
|
96
|
+
kind: "virtual",
|
|
97
|
+
name: "pdf.permissions"
|
|
98
|
+
};
|
|
99
|
+
if (raw.includes(":")) return parseCollab(raw);
|
|
100
|
+
return parseCapability(raw);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Validate every entry in a scope array by attempting to parse it.
|
|
104
|
+
* Throws on the first invalid string. Discards the parse output —
|
|
105
|
+
* callers re-parse at resolution time to keep the array as the
|
|
106
|
+
* authoritative payload.
|
|
107
|
+
*/
|
|
108
|
+
function validateScopeArray(raw) {
|
|
109
|
+
for (const s of raw) parseScope(s);
|
|
110
|
+
}
|
|
111
|
+
function parseCollab(raw) {
|
|
112
|
+
const idx1 = raw.indexOf(":");
|
|
113
|
+
const idx2 = raw.indexOf(":", idx1 + 1);
|
|
114
|
+
if (idx1 === -1 || idx2 === -1) throw new InvalidScope(raw, "collab scope requires entity:action:filter");
|
|
115
|
+
const entity = raw.slice(0, idx1);
|
|
116
|
+
const action = raw.slice(idx1 + 1, idx2);
|
|
117
|
+
const filterStr = raw.slice(idx2 + 1);
|
|
118
|
+
if (entity !== "annotations") throw new InvalidScope(raw, `unknown collab entity: ${entity}`);
|
|
119
|
+
if (action !== "create" && action !== "update" && action !== "delete" && action !== "set-group" && action !== "*") throw new InvalidScope(raw, `unknown collab action: ${action}`);
|
|
120
|
+
const filter = parseFilter(filterStr, raw);
|
|
121
|
+
if (action === "set-group" && (filter.kind === "self" || filter.kind === "createdBy")) throw new InvalidScope(raw, `set-group only supports :all or :group=<id> filters (got :${filter.kind})`);
|
|
122
|
+
return {
|
|
123
|
+
kind: "collab",
|
|
124
|
+
entity,
|
|
125
|
+
action,
|
|
126
|
+
filter
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function parseFilter(s, raw) {
|
|
130
|
+
if (s === "all") return { kind: "all" };
|
|
131
|
+
if (s === "self") return { kind: "self" };
|
|
132
|
+
if (s.startsWith("createdBy=")) {
|
|
133
|
+
const v = s.slice(10);
|
|
134
|
+
if (!v) throw new InvalidScope(raw, "createdBy= requires a value");
|
|
135
|
+
return {
|
|
136
|
+
kind: "createdBy",
|
|
137
|
+
userId: v
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (s.startsWith("group=")) {
|
|
141
|
+
const v = s.slice(6);
|
|
142
|
+
if (!v) throw new InvalidScope(raw, "group= requires a value");
|
|
143
|
+
return {
|
|
144
|
+
kind: "group",
|
|
145
|
+
groupId: v
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
throw new InvalidScope(raw, `unknown filter: ${s}`);
|
|
149
|
+
}
|
|
150
|
+
const CAPABILITY_SHAPE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
151
|
+
function parseCapability(raw) {
|
|
152
|
+
if (!CAPABILITY_SHAPE.test(raw)) throw new InvalidScope(raw, "invalid capability name shape");
|
|
153
|
+
if (!KNOWN_CAPABILITIES.has(raw)) throw new InvalidScope(raw, `unknown capability: ${raw}`);
|
|
154
|
+
return {
|
|
155
|
+
kind: "capability",
|
|
156
|
+
name: raw
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/auth/scope/resolver.ts
|
|
161
|
+
/**
|
|
162
|
+
* True iff the given capability is granted by the scope array plus the
|
|
163
|
+
* PDF bits visible via `pdf.permissions` expansion.
|
|
164
|
+
*
|
|
165
|
+
* Wildcard `*` short-circuits to true. Anything not explicitly granted
|
|
166
|
+
* (or expanded from `pdf.permissions`) is denied.
|
|
167
|
+
*/
|
|
168
|
+
function checkCapability(capability, rawScope, pdfBits) {
|
|
169
|
+
const parsed = rawScope.map(parseScope);
|
|
170
|
+
if (parsed.some((s) => s.kind === "wildcard")) return true;
|
|
171
|
+
return expandedCapabilities(parsed, pdfBits).has(capability);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* True iff the scope grants AT LEAST ONE of `capabilities`. Convenience
|
|
175
|
+
* for routes like `/text` (`doc.text.copy` OR `doc.text.search`) and
|
|
176
|
+
* `/geometry` (`doc.text.select` OR `doc.text.search`).
|
|
177
|
+
*/
|
|
178
|
+
function checkAnyCapability(capabilities, rawScope, pdfBits) {
|
|
179
|
+
return capabilities.some((c) => checkCapability(c, rawScope, pdfBits));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* True iff the scope grants the collab action against the target record.
|
|
183
|
+
*
|
|
184
|
+
* Narrowing model. For each action independently:
|
|
185
|
+
* 1. wildcard `*` → allow (global escape hatch)
|
|
186
|
+
* 2. if any collab scope applies to this action → NARROW: only those
|
|
187
|
+
* collab filters decide. If none match the target, deny — even if
|
|
188
|
+
* `doc.annotate.modify` is also present. This is what makes
|
|
189
|
+
* `[modify, update:self]` correctly mean "edit own only" rather
|
|
190
|
+
* than "edit anyone via modify-bypass."
|
|
191
|
+
* 3. otherwise, if `doc.annotate.modify` is present → allow. This is
|
|
192
|
+
* the broad PDF-bit-derived default for create/update/delete when
|
|
193
|
+
* no per-action collab filter has been written.
|
|
194
|
+
* 4. otherwise → deny.
|
|
195
|
+
*
|
|
196
|
+
* For create: `target` should be built by the caller from JWT identity
|
|
197
|
+
* (`{ userId: caller.user_id, groupId: caller.group_id }`). `:self` and
|
|
198
|
+
* `:all` then trivially pass; `:group=X` is the meaningful filter (only
|
|
199
|
+
* matches when the caller's default group is X).
|
|
200
|
+
*
|
|
201
|
+
* For update / delete: `target` is the existing row's owner.
|
|
202
|
+
*/
|
|
203
|
+
function checkCollab(action, target, rawScope, identity, pdfBits) {
|
|
204
|
+
const parsed = rawScope.map(parseScope);
|
|
205
|
+
if (parsed.some((s) => s.kind === "wildcard")) return true;
|
|
206
|
+
const applicableCollab = parsed.filter((s) => s.kind === "collab" && s.entity === "annotations" && (s.action === action || s.action === "*"));
|
|
207
|
+
if (applicableCollab.length > 0) return applicableCollab.some((s) => filterMatches(s.filter, target, identity));
|
|
208
|
+
return expandedCapabilities(parsed, pdfBits).has("doc.annotate.modify");
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Compute the full set of granted capabilities after expanding
|
|
212
|
+
* `pdf.permissions` and applying implication rules.
|
|
213
|
+
*
|
|
214
|
+
* Implications applied:
|
|
215
|
+
* - `doc.annotate.modify` implies `doc.annotate.read` (you can't
|
|
216
|
+
* sensibly modify what you can't see).
|
|
217
|
+
* - `doc.forms.modify` implies `doc.forms.fill` and `doc.forms.read`.
|
|
218
|
+
* - `doc.forms.fill` implies `doc.forms.read`.
|
|
219
|
+
* - any annotation collab scope implies `doc.annotate.read`, because
|
|
220
|
+
* mutation routes need to see the target row to evaluate the
|
|
221
|
+
* collab filter against its current owner.
|
|
222
|
+
*
|
|
223
|
+
* Does NOT short-circuit on wildcard — callers do that themselves
|
|
224
|
+
* before calling this. Returning the expanded set is useful for the
|
|
225
|
+
* `/access` response's `effectiveScope` and for advisory UI surfacing.
|
|
226
|
+
*/
|
|
227
|
+
function expandedCapabilities(parsed, pdfBits) {
|
|
228
|
+
const out = /* @__PURE__ */ new Set();
|
|
229
|
+
let hasAnnotationCollab = false;
|
|
230
|
+
for (const s of parsed) if (s.kind === "capability") out.add(s.name);
|
|
231
|
+
else if (s.kind === "virtual" && s.name === "pdf.permissions") addPdfPermissions(out, pdfBits);
|
|
232
|
+
else if (s.kind === "collab" && s.entity === "annotations") hasAnnotationCollab = true;
|
|
233
|
+
if (out.has("doc.annotate.modify")) out.add("doc.annotate.read");
|
|
234
|
+
if (out.has("doc.forms.modify")) {
|
|
235
|
+
out.add("doc.forms.fill");
|
|
236
|
+
out.add("doc.forms.read");
|
|
237
|
+
}
|
|
238
|
+
if (out.has("doc.forms.fill")) out.add("doc.forms.read");
|
|
239
|
+
if (hasAnnotationCollab) out.add("doc.annotate.read");
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Convenience wrapper: takes the raw scope array, parses each entry,
|
|
244
|
+
* returns the expanded capability set. Exposed for the `/access`
|
|
245
|
+
* endpoint and for the SDK helper that surfaces `effectiveScope`.
|
|
246
|
+
*/
|
|
247
|
+
function expandRawScope(rawScope, pdfBits) {
|
|
248
|
+
return expandedCapabilities(rawScope.map(parseScope), pdfBits);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Test a single collab filter against a target record + the caller's
|
|
252
|
+
* identity. Pure function — no side effects, no implicit rules.
|
|
253
|
+
*
|
|
254
|
+
* all → always matches
|
|
255
|
+
* self → matches if identity.user_id === target.userId
|
|
256
|
+
* createdBy=<X> → matches if target.userId === X
|
|
257
|
+
* group=<X> → matches if target.groupId === X
|
|
258
|
+
* AND identity.groups includes X
|
|
259
|
+
*
|
|
260
|
+
* The group-membership check on `group=X` prevents a token from
|
|
261
|
+
* matching annotations in a group it doesn't belong to, even if the
|
|
262
|
+
* target row carries that groupId.
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* Authority to assign a specific groupId to an annotation.
|
|
266
|
+
*
|
|
267
|
+
* Set-group is decoupled from `doc.annotate.modify`. The reasoning:
|
|
268
|
+
* capabilities like `modify` describe row access — what kind of write
|
|
269
|
+
* you can do to which existing rows — and map to PDF permission bits.
|
|
270
|
+
* Set-group is a cloud-only *assignment authority*: which destination
|
|
271
|
+
* group can you put an annotation into? There is no PDF-bit
|
|
272
|
+
* counterpart, so it doesn't inherit from `modify`.
|
|
273
|
+
*
|
|
274
|
+
* Resolution order:
|
|
275
|
+
* 1. newGroupId === callerDefaultGroupId → true (no real reassignment
|
|
276
|
+
* is happening; the annotation gets the caller's default group)
|
|
277
|
+
* 2. wildcard `*` → true (global escape hatch)
|
|
278
|
+
* 3. `annotations:set-group:all` → true
|
|
279
|
+
* 4. `annotations:set-group:group=<newGroupId>` → true
|
|
280
|
+
* 5. `annotations:*:all` or `annotations:*:group=<newGroupId>` → true
|
|
281
|
+
* (action wildcard includes set-group)
|
|
282
|
+
* 6. otherwise → false
|
|
283
|
+
*
|
|
284
|
+
* Note: independent from membership. A user with
|
|
285
|
+
* `set-group:group=legal` can assign annotations to the legal group
|
|
286
|
+
* even if they're not a member — that's the whole point.
|
|
287
|
+
*/
|
|
288
|
+
function checkSetGroup(newGroupId, callerDefaultGroupId, rawScope, _pdfBits) {
|
|
289
|
+
if (newGroupId === callerDefaultGroupId) return true;
|
|
290
|
+
const parsed = rawScope.map(parseScope);
|
|
291
|
+
if (parsed.some((s) => s.kind === "wildcard")) return true;
|
|
292
|
+
for (const s of parsed) {
|
|
293
|
+
if (s.kind !== "collab") continue;
|
|
294
|
+
if (s.entity !== "annotations") continue;
|
|
295
|
+
if (s.action !== "set-group" && s.action !== "*") continue;
|
|
296
|
+
if (s.filter.kind === "all") return true;
|
|
297
|
+
if (s.filter.kind === "group" && s.filter.groupId === newGroupId) return true;
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
function filterMatches(filter, target, id) {
|
|
302
|
+
switch (filter.kind) {
|
|
303
|
+
case "all": return true;
|
|
304
|
+
case "self": return !!id.user_id && target.userId === id.user_id;
|
|
305
|
+
case "createdBy": return target.userId === filter.userId;
|
|
306
|
+
case "group": return target.groupId === filter.groupId && (id.groups?.includes(filter.groupId) ?? false);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Translate `pdf.permissions` (virtual scope) into the concrete
|
|
311
|
+
* capabilities it represents under the current PDF bit configuration.
|
|
312
|
+
*
|
|
313
|
+
* Always adds `doc.open` and `doc.render` — these are cloud-only
|
|
314
|
+
* capabilities with no PDF bit, but `pdf.permissions` is meant to be
|
|
315
|
+
* the "give the user a working session" shorthand. Without them, a
|
|
316
|
+
* token with just `['pdf.permissions']` would be useless.
|
|
317
|
+
*
|
|
318
|
+
* Reads are unconditional: ISO 32000 / Acrobat let any reader see
|
|
319
|
+
* existing annotations and form values regardless of permission bits.
|
|
320
|
+
* Bit 6 governs *writing*, not visibility.
|
|
321
|
+
*
|
|
322
|
+
* Bit-derived expansions follow ISO 32000:
|
|
323
|
+
* bit 5 → doc.text.{select, copy, search}, doc.content.copy
|
|
324
|
+
* bit 3 → doc.print
|
|
325
|
+
* bit 12 → doc.print.high (requires bit 3 also set)
|
|
326
|
+
* bit 4 → doc.pages.modify, doc.redact, doc.metadata.modify, doc.attachments.modify
|
|
327
|
+
* bit 11 → doc.pages.assemble
|
|
328
|
+
* bit 6 → doc.annotate.modify
|
|
329
|
+
* bit 6/9 → doc.forms.fill
|
|
330
|
+
* bit 6+4 → doc.forms.modify
|
|
331
|
+
*
|
|
332
|
+
* Note: this same expansion lives in builders.ts as
|
|
333
|
+
* `materializePdfPermissions` for SDK-side use. The two MUST stay in
|
|
334
|
+
* sync; a test in resolver.test.ts pins them together.
|
|
335
|
+
*/
|
|
336
|
+
function addPdfPermissions(out, b) {
|
|
337
|
+
out.add("doc.open");
|
|
338
|
+
out.add("doc.render");
|
|
339
|
+
out.add("doc.annotate.read");
|
|
340
|
+
out.add("doc.forms.read");
|
|
341
|
+
if (b.bit5) {
|
|
342
|
+
out.add("doc.text.select");
|
|
343
|
+
out.add("doc.text.copy");
|
|
344
|
+
out.add("doc.text.search");
|
|
345
|
+
out.add("doc.content.copy");
|
|
346
|
+
}
|
|
347
|
+
if (b.bit3) out.add("doc.print");
|
|
348
|
+
if (b.bit12 && b.bit3) out.add("doc.print.high");
|
|
349
|
+
if (b.bit4) {
|
|
350
|
+
out.add("doc.pages.modify");
|
|
351
|
+
out.add("doc.redact");
|
|
352
|
+
out.add("doc.metadata.modify");
|
|
353
|
+
out.add("doc.attachments.modify");
|
|
354
|
+
}
|
|
355
|
+
if (b.bit11) out.add("doc.pages.assemble");
|
|
356
|
+
if (b.bit6) out.add("doc.annotate.modify");
|
|
357
|
+
if (b.bit6 || b.bit9) out.add("doc.forms.fill");
|
|
358
|
+
if (b.bit6 && b.bit4) out.add("doc.forms.modify");
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
361
|
+
Object.defineProperty(exports, "InvalidScope", {
|
|
362
|
+
enumerable: true,
|
|
363
|
+
get: function() {
|
|
364
|
+
return InvalidScope;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
Object.defineProperty(exports, "MissingIdentity", {
|
|
368
|
+
enumerable: true,
|
|
369
|
+
get: function() {
|
|
370
|
+
return MissingIdentity;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
Object.defineProperty(exports, "PermissionDenied", {
|
|
374
|
+
enumerable: true,
|
|
375
|
+
get: function() {
|
|
376
|
+
return PermissionDenied;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
Object.defineProperty(exports, "checkAnyCapability", {
|
|
380
|
+
enumerable: true,
|
|
381
|
+
get: function() {
|
|
382
|
+
return checkAnyCapability;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
Object.defineProperty(exports, "checkCapability", {
|
|
386
|
+
enumerable: true,
|
|
387
|
+
get: function() {
|
|
388
|
+
return checkCapability;
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
Object.defineProperty(exports, "checkCollab", {
|
|
392
|
+
enumerable: true,
|
|
393
|
+
get: function() {
|
|
394
|
+
return checkCollab;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
Object.defineProperty(exports, "checkSetGroup", {
|
|
398
|
+
enumerable: true,
|
|
399
|
+
get: function() {
|
|
400
|
+
return checkSetGroup;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
Object.defineProperty(exports, "expandRawScope", {
|
|
404
|
+
enumerable: true,
|
|
405
|
+
get: function() {
|
|
406
|
+
return expandRawScope;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
Object.defineProperty(exports, "expandedCapabilities", {
|
|
410
|
+
enumerable: true,
|
|
411
|
+
get: function() {
|
|
412
|
+
return expandedCapabilities;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
Object.defineProperty(exports, "filterMatches", {
|
|
416
|
+
enumerable: true,
|
|
417
|
+
get: function() {
|
|
418
|
+
return filterMatches;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
Object.defineProperty(exports, "parseScope", {
|
|
422
|
+
enumerable: true,
|
|
423
|
+
get: function() {
|
|
424
|
+
return parseScope;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
Object.defineProperty(exports, "validateScopeArray", {
|
|
428
|
+
enumerable: true,
|
|
429
|
+
get: function() {
|
|
430
|
+
return validateScopeArray;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
//# sourceMappingURL=resolver-DAZWVoJL.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver-DAZWVoJL.cjs","names":[],"sources":["../src/auth/scope/errors.ts","../src/auth/scope/parser.ts","../src/auth/scope/resolver.ts"],"sourcesContent":["/**\n * Thrown by {@link parseScope} / {@link validateScopeArray} when a scope\n * string does not match any known grammar (capability, collab, virtual,\n * or wildcard).\n *\n * Carries the offending scope verbatim so route/JWT-layer error\n * handlers can echo it to the customer.\n */\nexport class InvalidScope extends Error {\n constructor(\n public readonly scope: string,\n reason: string,\n ) {\n super(`invalid scope \"${scope}\": ${reason}`);\n this.name = 'InvalidScope';\n }\n}\n\n/**\n * Thrown by route guards and engine-local enforcement when a capability\n * or collab action is denied for the current request/handle.\n *\n * `required` names what the caller needed (e.g., \"doc.render\" or\n * \"annotations:update\"). `context` is an optional label such as \"local\"\n * or \"/v1/docs/.../pages/2/render\".\n */\nexport class PermissionDenied extends Error {\n constructor(\n public readonly required: string,\n public readonly context?: string,\n ) {\n super(`permission denied${context ? ` (${context})` : ''}: ${required}`);\n this.name = 'PermissionDenied';\n }\n}\n\n/**\n * Thrown at engine-local open time when the supplied scope includes\n * collab filters (`:self`, `:group=...`) but the identity claims needed\n * to resolve them are missing. Fails loudly at open so the configuration\n * mistake is visible immediately instead of producing silent denies at\n * every annotation mutation.\n */\nexport class MissingIdentity extends Error {\n constructor(scope: string) {\n super(`scope \"${scope}\" requires identity claims (user_id and/or groups)`);\n this.name = 'MissingIdentity';\n }\n}\n","import { InvalidScope } from './errors';\nimport type { CollabAction, CollabFilter, DocCapability, ParsedScope } from './types';\n\n/**\n * Closed set of recognized capability strings. Membership is the\n * authoritative source of truth — adding a new capability requires\n * extending the `DocCapability` union AND adding it here. The parser\n * rejects anything outside this set.\n *\n * Removed legacy names (e.g., `doc.read`, `doc.edit-pages`, `doc.save`)\n * are deliberately absent so any JWT carrying them fails at verify\n * time with a clear error.\n */\nconst KNOWN_CAPABILITIES: ReadonlySet<DocCapability> = new Set([\n 'doc.open',\n 'doc.render',\n 'doc.text.select',\n 'doc.text.copy',\n 'doc.text.search',\n 'doc.content.copy',\n 'doc.download',\n 'doc.download.flattened',\n 'doc.print',\n 'doc.print.high',\n 'doc.pages.modify',\n 'doc.pages.assemble',\n 'doc.forms.read',\n 'doc.forms.fill',\n 'doc.forms.modify',\n 'doc.annotate.read',\n 'doc.annotate.modify',\n 'doc.metadata.modify',\n 'doc.attachments.modify',\n 'doc.redact',\n]);\n\n/**\n * Parse a single scope string into a structured `ParsedScope`.\n *\n * Recognised shapes:\n * - `\"*\"` → wildcard\n * - `\"pdf.permissions\"` → virtual (expands at resolve time)\n * - `\"a.b.c...\"` (dotted) → capability\n * - `\"entity:action:filter\"` (colons) → collab\n *\n * Throws {@link InvalidScope} for any string that fails to match one of\n * those shapes, or whose capability/entity/action/filter is not in the\n * closed enum.\n */\nexport function parseScope(raw: string): ParsedScope {\n if (raw === '*') return { kind: 'wildcard' };\n if (raw === 'pdf.permissions') return { kind: 'virtual', name: 'pdf.permissions' };\n if (raw.includes(':')) return parseCollab(raw);\n return parseCapability(raw);\n}\n\n/**\n * Validate every entry in a scope array by attempting to parse it.\n * Throws on the first invalid string. Discards the parse output —\n * callers re-parse at resolution time to keep the array as the\n * authoritative payload.\n */\nexport function validateScopeArray(raw: ReadonlyArray<string>): void {\n for (const s of raw) parseScope(s);\n}\n\nfunction parseCollab(raw: string): ParsedScope {\n // Split on the FIRST two colons only. Filter values may contain\n // colons (UUIDs with `urn:uuid:...`, subject ids like `auth0|user:1`).\n const idx1 = raw.indexOf(':');\n const idx2 = raw.indexOf(':', idx1 + 1);\n if (idx1 === -1 || idx2 === -1) {\n throw new InvalidScope(raw, 'collab scope requires entity:action:filter');\n }\n const entity = raw.slice(0, idx1);\n const action = raw.slice(idx1 + 1, idx2);\n const filterStr = raw.slice(idx2 + 1);\n\n if (entity !== 'annotations') {\n throw new InvalidScope(raw, `unknown collab entity: ${entity}`);\n }\n if (\n action !== 'create' &&\n action !== 'update' &&\n action !== 'delete' &&\n action !== 'set-group' &&\n action !== '*'\n ) {\n throw new InvalidScope(raw, `unknown collab action: ${action}`);\n }\n const filter = parseFilter(filterStr, raw);\n\n // `set-group` is a group-assignment authority, not a per-record collab\n // filter. The only meaningful filter values are `all` (assign to any\n // group) and `group=X` (assign to group X). `:self` and `:createdBy=Y`\n // can't be sensibly applied to \"what group is this annotation in\", so\n // we reject them at parse time rather than silently always-denying.\n if (action === 'set-group' && (filter.kind === 'self' || filter.kind === 'createdBy')) {\n throw new InvalidScope(\n raw,\n `set-group only supports :all or :group=<id> filters (got :${filter.kind})`,\n );\n }\n\n return {\n kind: 'collab',\n entity,\n action: action as CollabAction | '*',\n filter,\n };\n}\n\nfunction parseFilter(s: string, raw: string): CollabFilter {\n if (s === 'all') return { kind: 'all' };\n if (s === 'self') return { kind: 'self' };\n if (s.startsWith('createdBy=')) {\n const v = s.slice('createdBy='.length);\n if (!v) throw new InvalidScope(raw, 'createdBy= requires a value');\n return { kind: 'createdBy', userId: v };\n }\n if (s.startsWith('group=')) {\n const v = s.slice('group='.length);\n if (!v) throw new InvalidScope(raw, 'group= requires a value');\n return { kind: 'group', groupId: v };\n }\n throw new InvalidScope(raw, `unknown filter: ${s}`);\n}\n\n// Capability shape: lowercase dotted segments, e.g. \"doc.text.copy\".\n// Validates shape first, then membership in the closed set.\nconst CAPABILITY_SHAPE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/;\n\nfunction parseCapability(raw: string): ParsedScope {\n if (!CAPABILITY_SHAPE.test(raw)) {\n throw new InvalidScope(raw, 'invalid capability name shape');\n }\n if (!KNOWN_CAPABILITIES.has(raw as DocCapability)) {\n throw new InvalidScope(raw, `unknown capability: ${raw}`);\n }\n return { kind: 'capability', name: raw as DocCapability };\n}\n","import { parseScope } from './parser';\nimport type {\n CollabAction,\n CollabFilter,\n DocCapability,\n IdentityClaims,\n ParsedScope,\n PdfBits,\n} from './types';\n\n/**\n * Resolved collab subject — the per-record identity bits used to test\n * collab filters against. Sourced from the target record's stored\n * `/EMBD_Metadata/UserID` and `/GroupID` at mutation time.\n */\nexport interface CollabTarget {\n userId?: string;\n groupId?: string;\n}\n\n/**\n * True iff the given capability is granted by the scope array plus the\n * PDF bits visible via `pdf.permissions` expansion.\n *\n * Wildcard `*` short-circuits to true. Anything not explicitly granted\n * (or expanded from `pdf.permissions`) is denied.\n */\nexport function checkCapability(\n capability: DocCapability,\n rawScope: ReadonlyArray<string>,\n pdfBits: PdfBits,\n): boolean {\n const parsed = rawScope.map(parseScope);\n if (parsed.some((s) => s.kind === 'wildcard')) return true;\n return expandedCapabilities(parsed, pdfBits).has(capability);\n}\n\n/**\n * True iff the scope grants AT LEAST ONE of `capabilities`. Convenience\n * for routes like `/text` (`doc.text.copy` OR `doc.text.search`) and\n * `/geometry` (`doc.text.select` OR `doc.text.search`).\n */\nexport function checkAnyCapability(\n capabilities: ReadonlyArray<DocCapability>,\n rawScope: ReadonlyArray<string>,\n pdfBits: PdfBits,\n): boolean {\n return capabilities.some((c) => checkCapability(c, rawScope, pdfBits));\n}\n\n/**\n * True iff the scope grants the collab action against the target record.\n *\n * Narrowing model. For each action independently:\n * 1. wildcard `*` → allow (global escape hatch)\n * 2. if any collab scope applies to this action → NARROW: only those\n * collab filters decide. If none match the target, deny — even if\n * `doc.annotate.modify` is also present. This is what makes\n * `[modify, update:self]` correctly mean \"edit own only\" rather\n * than \"edit anyone via modify-bypass.\"\n * 3. otherwise, if `doc.annotate.modify` is present → allow. This is\n * the broad PDF-bit-derived default for create/update/delete when\n * no per-action collab filter has been written.\n * 4. otherwise → deny.\n *\n * For create: `target` should be built by the caller from JWT identity\n * (`{ userId: caller.user_id, groupId: caller.group_id }`). `:self` and\n * `:all` then trivially pass; `:group=X` is the meaningful filter (only\n * matches when the caller's default group is X).\n *\n * For update / delete: `target` is the existing row's owner.\n */\nexport function checkCollab(\n action: CollabAction,\n target: CollabTarget,\n rawScope: ReadonlyArray<string>,\n identity: IdentityClaims,\n pdfBits: PdfBits,\n): boolean {\n const parsed = rawScope.map(parseScope);\n if (parsed.some((s) => s.kind === 'wildcard')) return true;\n\n const applicableCollab = parsed.filter(\n (s): s is Extract<ParsedScope, { kind: 'collab' }> =>\n s.kind === 'collab' &&\n s.entity === 'annotations' &&\n (s.action === action || s.action === '*'),\n );\n\n if (applicableCollab.length > 0) {\n // Narrow: presence of any applicable collab scope shadows\n // `modify`-as-default for this action.\n return applicableCollab.some((s) => filterMatches(s.filter, target, identity));\n }\n\n // No collab scope for this action — fall back to the broad default.\n return expandedCapabilities(parsed, pdfBits).has('doc.annotate.modify');\n}\n\n/**\n * Compute the full set of granted capabilities after expanding\n * `pdf.permissions` and applying implication rules.\n *\n * Implications applied:\n * - `doc.annotate.modify` implies `doc.annotate.read` (you can't\n * sensibly modify what you can't see).\n * - `doc.forms.modify` implies `doc.forms.fill` and `doc.forms.read`.\n * - `doc.forms.fill` implies `doc.forms.read`.\n * - any annotation collab scope implies `doc.annotate.read`, because\n * mutation routes need to see the target row to evaluate the\n * collab filter against its current owner.\n *\n * Does NOT short-circuit on wildcard — callers do that themselves\n * before calling this. Returning the expanded set is useful for the\n * `/access` response's `effectiveScope` and for advisory UI surfacing.\n */\nexport function expandedCapabilities(\n parsed: ReadonlyArray<ParsedScope>,\n pdfBits: PdfBits,\n): Set<DocCapability> {\n const out = new Set<DocCapability>();\n let hasAnnotationCollab = false;\n\n for (const s of parsed) {\n if (s.kind === 'capability') {\n out.add(s.name);\n } else if (s.kind === 'virtual' && s.name === 'pdf.permissions') {\n addPdfPermissions(out, pdfBits);\n } else if (s.kind === 'collab' && s.entity === 'annotations') {\n hasAnnotationCollab = true;\n }\n }\n\n // Implications (apply after the explicit additions above)\n if (out.has('doc.annotate.modify')) out.add('doc.annotate.read');\n if (out.has('doc.forms.modify')) {\n out.add('doc.forms.fill');\n out.add('doc.forms.read');\n }\n if (out.has('doc.forms.fill')) out.add('doc.forms.read');\n if (hasAnnotationCollab) out.add('doc.annotate.read');\n\n return out;\n}\n\n/**\n * Convenience wrapper: takes the raw scope array, parses each entry,\n * returns the expanded capability set. Exposed for the `/access`\n * endpoint and for the SDK helper that surfaces `effectiveScope`.\n */\nexport function expandRawScope(\n rawScope: ReadonlyArray<string>,\n pdfBits: PdfBits,\n): Set<DocCapability> {\n return expandedCapabilities(rawScope.map(parseScope), pdfBits);\n}\n\n/**\n * Test a single collab filter against a target record + the caller's\n * identity. Pure function — no side effects, no implicit rules.\n *\n * all → always matches\n * self → matches if identity.user_id === target.userId\n * createdBy=<X> → matches if target.userId === X\n * group=<X> → matches if target.groupId === X\n * AND identity.groups includes X\n *\n * The group-membership check on `group=X` prevents a token from\n * matching annotations in a group it doesn't belong to, even if the\n * target row carries that groupId.\n */\n/**\n * Authority to assign a specific groupId to an annotation.\n *\n * Set-group is decoupled from `doc.annotate.modify`. The reasoning:\n * capabilities like `modify` describe row access — what kind of write\n * you can do to which existing rows — and map to PDF permission bits.\n * Set-group is a cloud-only *assignment authority*: which destination\n * group can you put an annotation into? There is no PDF-bit\n * counterpart, so it doesn't inherit from `modify`.\n *\n * Resolution order:\n * 1. newGroupId === callerDefaultGroupId → true (no real reassignment\n * is happening; the annotation gets the caller's default group)\n * 2. wildcard `*` → true (global escape hatch)\n * 3. `annotations:set-group:all` → true\n * 4. `annotations:set-group:group=<newGroupId>` → true\n * 5. `annotations:*:all` or `annotations:*:group=<newGroupId>` → true\n * (action wildcard includes set-group)\n * 6. otherwise → false\n *\n * Note: independent from membership. A user with\n * `set-group:group=legal` can assign annotations to the legal group\n * even if they're not a member — that's the whole point.\n */\nexport function checkSetGroup(\n newGroupId: string,\n callerDefaultGroupId: string | undefined,\n rawScope: ReadonlyArray<string>,\n _pdfBits: PdfBits,\n): boolean {\n // No authority needed when the caller is assigning their default group.\n if (newGroupId === callerDefaultGroupId) return true;\n\n const parsed = rawScope.map(parseScope);\n if (parsed.some((s) => s.kind === 'wildcard')) return true;\n\n for (const s of parsed) {\n if (s.kind !== 'collab') continue;\n if (s.entity !== 'annotations') continue;\n if (s.action !== 'set-group' && s.action !== '*') continue;\n if (s.filter.kind === 'all') return true;\n if (s.filter.kind === 'group' && s.filter.groupId === newGroupId) return true;\n // :self and :createdBy are rejected at parse time for set-group, so\n // they never appear here. The action-wildcard path could carry those\n // filters legitimately for other actions — silently skip those, they\n // can't satisfy a set-group check.\n }\n return false;\n}\n\nexport function filterMatches(\n filter: CollabFilter,\n target: CollabTarget,\n id: IdentityClaims,\n): boolean {\n switch (filter.kind) {\n case 'all':\n return true;\n case 'self':\n return !!id.user_id && target.userId === id.user_id;\n case 'createdBy':\n return target.userId === filter.userId;\n case 'group':\n return target.groupId === filter.groupId && (id.groups?.includes(filter.groupId) ?? false);\n }\n}\n\n/**\n * Translate `pdf.permissions` (virtual scope) into the concrete\n * capabilities it represents under the current PDF bit configuration.\n *\n * Always adds `doc.open` and `doc.render` — these are cloud-only\n * capabilities with no PDF bit, but `pdf.permissions` is meant to be\n * the \"give the user a working session\" shorthand. Without them, a\n * token with just `['pdf.permissions']` would be useless.\n *\n * Reads are unconditional: ISO 32000 / Acrobat let any reader see\n * existing annotations and form values regardless of permission bits.\n * Bit 6 governs *writing*, not visibility.\n *\n * Bit-derived expansions follow ISO 32000:\n * bit 5 → doc.text.{select, copy, search}, doc.content.copy\n * bit 3 → doc.print\n * bit 12 → doc.print.high (requires bit 3 also set)\n * bit 4 → doc.pages.modify, doc.redact, doc.metadata.modify, doc.attachments.modify\n * bit 11 → doc.pages.assemble\n * bit 6 → doc.annotate.modify\n * bit 6/9 → doc.forms.fill\n * bit 6+4 → doc.forms.modify\n *\n * Note: this same expansion lives in builders.ts as\n * `materializePdfPermissions` for SDK-side use. The two MUST stay in\n * sync; a test in resolver.test.ts pins them together.\n */\nfunction addPdfPermissions(out: Set<DocCapability>, b: PdfBits): void {\n // Always — pdf.permissions means \"give me a working session\"\n out.add('doc.open');\n out.add('doc.render');\n // Reading existing annotations and form values is unconditional —\n // PDF bit 6 governs writes, not visibility.\n out.add('doc.annotate.read');\n out.add('doc.forms.read');\n\n if (b.bit5) {\n out.add('doc.text.select');\n out.add('doc.text.copy');\n out.add('doc.text.search');\n out.add('doc.content.copy');\n }\n if (b.bit3) out.add('doc.print');\n if (b.bit12 && b.bit3) out.add('doc.print.high');\n if (b.bit4) {\n out.add('doc.pages.modify');\n out.add('doc.redact');\n out.add('doc.metadata.modify');\n out.add('doc.attachments.modify');\n }\n if (b.bit11) out.add('doc.pages.assemble');\n if (b.bit6) {\n out.add('doc.annotate.modify');\n }\n if (b.bit6 || b.bit9) out.add('doc.forms.fill');\n if (b.bit6 && b.bit4) out.add('doc.forms.modify');\n}\n"],"mappings":"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"}
|