@metalabel/dfos-protocol 0.53.0 → 0.54.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/README.md +7 -0
- package/dist/chain/index.d.ts +2 -2
- package/dist/chain/index.js +2 -2
- package/dist/{chunk-WBFWGQTS.js → chunk-QIM3KZXZ.js} +1 -1
- package/dist/{chunk-VEKRLXZ5.js → chunk-RPA44ND4.js} +116 -19
- package/dist/credentials/index.d.ts +95 -14
- package/dist/credentials/index.js +13 -1
- package/dist/{dfos-credential-j-kHtFvG.d.ts → dfos-credential-DgT8XVTm.d.ts} +37 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -2
- package/examples/api-resource-coverage.json +413 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@ import { signKeyProof, verifyKeyProof } from '@metalabel/dfos-protocol/key-proof
|
|
|
36
36
|
| `@metalabel/dfos-protocol/key-proof` | KEY-PROOF envelopes — compose/sign and verify a challenge-bound proof that a candidate key is held |
|
|
37
37
|
| `@metalabel/dfos-protocol/fold` | Canonical linearization and LWW-map folds for index documents |
|
|
38
38
|
|
|
39
|
+
## `api:` resources and the `jti` member
|
|
40
|
+
|
|
41
|
+
An `api:` resource takes one of two forms: `api:<host>`, the whole API surface, and `api:<host>/spaces/<id>`, one space on it. A bare host is the ancestor of every space at that host, so a grant naming the host covers a space-addressed request while a space-scoped grant covers only its own space. `parseApiResource` and `apiResourceCovers` are that rule on its own; `isAttenuated` and `matchesResource` apply it. Every other resource type is exact byte equality, and a malformed `api:` id covers nothing — itself included.
|
|
42
|
+
|
|
43
|
+
`jti` is the envelope's one registered additive member: a per-request unique string of at most `MAX_JTI_BYTES` UTF-8 bytes, emitted after the canonical members. Pass it typed to `signApiRequest` or `signApiIdentityRequest` (`generateJti()` mints one), set `requireJti` on a verifier to refuse a proof without it, and read the verified value back off the envelope to key a replay cache. A proof carrying none has the bytes it always had.
|
|
44
|
+
|
|
39
45
|
## Specifications
|
|
40
46
|
|
|
41
47
|
| Document | Description |
|
|
@@ -62,6 +68,7 @@ The `examples/` directory contains deterministic reference fixtures that can be
|
|
|
62
68
|
- `credential-write.json` — DFOS write credential (broad + content-narrowed)
|
|
63
69
|
- `credential-read.json` — DFOS read credential
|
|
64
70
|
- `identity-services.json` — genesis publishing a services set (relay locator + content/artifact anchors)
|
|
71
|
+
- `api-resource-coverage.json` — the `api:` hierarchy's parse, coverage, attenuation, and matching rows, read by the TypeScript and Go suites alike
|
|
65
72
|
|
|
66
73
|
## License
|
|
67
74
|
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as IdentityOperation, v as Signer, x as VerifiedIdentity, S as ServiceEntry, c as ContentOperation, R as ResolvedIdentity, r as RevocationChecker, d as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-
|
|
2
|
-
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, s as RevocationPayload, t as ServicesArray, u as SignRequestPayload, y as VoidKeyMembership,
|
|
1
|
+
import { I as IdentityOperation, v as Signer, x as VerifiedIdentity, S as ServiceEntry, c as ContentOperation, R as ResolvedIdentity, r as RevocationChecker, d as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-DgT8XVTm.js';
|
|
2
|
+
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, s as RevocationPayload, t as ServicesArray, u as SignRequestPayload, y as VoidKeyMembership, J as parseProtocolTimestampUnix } from '../dfos-credential-DgT8XVTm.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/** Ed25519 public key multicodec value */
|
package/dist/chain/index.js
CHANGED
|
@@ -53,8 +53,8 @@ import {
|
|
|
53
53
|
verifyIdentityExtensionFromTrustedState,
|
|
54
54
|
verifyRevocation,
|
|
55
55
|
verifySignRequest
|
|
56
|
-
} from "../chunk-
|
|
57
|
-
import "../chunk-
|
|
56
|
+
} from "../chunk-QIM3KZXZ.js";
|
|
57
|
+
import "../chunk-RPA44ND4.js";
|
|
58
58
|
import "../chunk-ISHAX5WN.js";
|
|
59
59
|
import {
|
|
60
60
|
ED25519_PRIV_MULTICODEC,
|
|
@@ -43,6 +43,7 @@ var REQUEST_PROOF_JWS_TYP = "did:dfos:request-proof";
|
|
|
43
43
|
var IDENTITY_PROOF_JWS_TYP = "did:dfos:identity-proof";
|
|
44
44
|
var EMPTY_BODY_SHA256 = "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU";
|
|
45
45
|
var MAX_REQUEST_PROOF_SIZE = 4096;
|
|
46
|
+
var MAX_JTI_BYTES = 256;
|
|
46
47
|
var DEFAULT_PROOF_WINDOW_SECONDS = 60;
|
|
47
48
|
var DEFAULT_PROOF_SKEW_SECONDS = 60;
|
|
48
49
|
var MAX_PROOF_FRESHNESS_SPAN_SECONDS = 300;
|
|
@@ -66,6 +67,27 @@ var CTL_OR_SPACE = /[\u0000-\u0020\u007f]/;
|
|
|
66
67
|
var BASE64URL_32 = /^[A-Za-z0-9_-]{43}$/;
|
|
67
68
|
var EXTRA_MEMBER_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
68
69
|
var CANONICAL_MEMBERS = /* @__PURE__ */ new Set(["method", "host", "path", "bodyHash", "credentialCID", "iat"]);
|
|
70
|
+
var assertJtiBound = (jti, label) => {
|
|
71
|
+
if (jti === "" || encoder.encode(jti).length > MAX_JTI_BYTES) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`invalid ${label}: jti must be a non-empty string of at most ${MAX_JTI_BYTES} bytes`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var withJti = (extra, jti, label) => {
|
|
78
|
+
if (jti === void 0) return extra;
|
|
79
|
+
if (extra && "jti" in extra) {
|
|
80
|
+
throw new Error(`invalid ${label}: jti is named twice \u2014 pass it typed or in extraMembers`);
|
|
81
|
+
}
|
|
82
|
+
assertJtiBound(jti, label);
|
|
83
|
+
return { ...extra, jti };
|
|
84
|
+
};
|
|
85
|
+
var emittedExtraMembers = (parsed, extra, label) => canonicalExtraMembers(withJti(extra, parsed.jti, label), label);
|
|
86
|
+
var generateJti = () => {
|
|
87
|
+
const bytes = new Uint8Array(16);
|
|
88
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
89
|
+
return base64urlEncode(bytes);
|
|
90
|
+
};
|
|
69
91
|
var assertNoLoneSurrogate = (value, field, label) => {
|
|
70
92
|
if (LONE_SURROGATE.test(value)) {
|
|
71
93
|
throw new Error(`invalid ${label}: ${field} must be well-formed Unicode`);
|
|
@@ -113,7 +135,26 @@ var validateProofPayload = (value, shape) => {
|
|
|
113
135
|
if (typeof iat !== "number" || !Number.isSafeInteger(iat) || iat <= 0) {
|
|
114
136
|
throw new Error(`invalid ${label}: iat must be a positive integer`);
|
|
115
137
|
}
|
|
116
|
-
|
|
138
|
+
const rawJti = raw["jti"];
|
|
139
|
+
let jti;
|
|
140
|
+
if (rawJti !== void 0) {
|
|
141
|
+
if (typeof rawJti !== "string") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`invalid ${label}: jti must be a non-empty string of at most ${MAX_JTI_BYTES} bytes`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
assertJtiBound(rawJti, label);
|
|
147
|
+
jti = rawJti;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
method,
|
|
151
|
+
host,
|
|
152
|
+
path,
|
|
153
|
+
bodyHash,
|
|
154
|
+
...shape.credentialed ? { credentialCID: raw["credentialCID"] } : {},
|
|
155
|
+
iat,
|
|
156
|
+
...jti !== void 0 ? { jti } : {}
|
|
157
|
+
};
|
|
117
158
|
};
|
|
118
159
|
var validateRequestProofPayload = (value) => {
|
|
119
160
|
const parsed = validateProofPayload(value, REQUEST_PROOF_SHAPE);
|
|
@@ -123,7 +164,8 @@ var validateRequestProofPayload = (value) => {
|
|
|
123
164
|
path: parsed.path,
|
|
124
165
|
bodyHash: parsed.bodyHash,
|
|
125
166
|
credentialCID: parsed.credentialCID,
|
|
126
|
-
iat: parsed.iat
|
|
167
|
+
iat: parsed.iat,
|
|
168
|
+
...parsed.jti !== void 0 ? { jti: parsed.jti } : {}
|
|
127
169
|
};
|
|
128
170
|
};
|
|
129
171
|
var validateIdentityProofPayload = (value) => {
|
|
@@ -133,7 +175,8 @@ var validateIdentityProofPayload = (value) => {
|
|
|
133
175
|
host: parsed.host,
|
|
134
176
|
path: parsed.path,
|
|
135
177
|
bodyHash: parsed.bodyHash,
|
|
136
|
-
iat: parsed.iat
|
|
178
|
+
iat: parsed.iat,
|
|
179
|
+
...parsed.jti !== void 0 ? { jti: parsed.jti } : {}
|
|
137
180
|
};
|
|
138
181
|
};
|
|
139
182
|
var canonicalExtraMembers = (extra, label) => {
|
|
@@ -171,16 +214,22 @@ var proofPayloadObject = (parsed, shape, extra) => {
|
|
|
171
214
|
for (const [name, value] of extra) out[name] = value;
|
|
172
215
|
return out;
|
|
173
216
|
};
|
|
174
|
-
var apiRequestSigningInput = (payload, extraMembers) =>
|
|
175
|
-
validateProofPayload(payload, REQUEST_PROOF_SHAPE)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
217
|
+
var apiRequestSigningInput = (payload, extraMembers) => {
|
|
218
|
+
const parsed = validateProofPayload(payload, REQUEST_PROOF_SHAPE);
|
|
219
|
+
return proofSigningInput(
|
|
220
|
+
parsed,
|
|
221
|
+
REQUEST_PROOF_SHAPE,
|
|
222
|
+
emittedExtraMembers(parsed, extraMembers, REQUEST_PROOF_SHAPE.label)
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
var apiIdentitySigningInput = (payload, extraMembers) => {
|
|
226
|
+
const parsed = validateProofPayload(payload, IDENTITY_PROOF_SHAPE);
|
|
227
|
+
return proofSigningInput(
|
|
228
|
+
parsed,
|
|
229
|
+
IDENTITY_PROOF_SHAPE,
|
|
230
|
+
emittedExtraMembers(parsed, extraMembers, IDENTITY_PROOF_SHAPE.label)
|
|
231
|
+
);
|
|
232
|
+
};
|
|
184
233
|
var sha256BodyHash = (body) => base64urlEncode(sha256(body));
|
|
185
234
|
var parseDfosAuthorization = (header) => {
|
|
186
235
|
if (!header) return null;
|
|
@@ -199,12 +248,13 @@ var signApiRequest = async (input) => {
|
|
|
199
248
|
path: input.path,
|
|
200
249
|
bodyHash: sha256BodyHash(input.body ?? EMPTY_BODY),
|
|
201
250
|
credentialCID: input.credentialCID,
|
|
202
|
-
iat: input.iat ?? Math.floor(Date.now() / 1e3)
|
|
251
|
+
iat: input.iat ?? Math.floor(Date.now() / 1e3),
|
|
252
|
+
...input.jti !== void 0 ? { jti: input.jti } : {}
|
|
203
253
|
});
|
|
204
254
|
if (!input.kid.includes("#")) {
|
|
205
255
|
throw new Error("invalid request proof: kid must be a DID URL");
|
|
206
256
|
}
|
|
207
|
-
const extra =
|
|
257
|
+
const extra = emittedExtraMembers(payload, input.extraMembers, REQUEST_PROOF_SHAPE.label);
|
|
208
258
|
const proof = await createJws({
|
|
209
259
|
header: { alg: "EdDSA", typ: REQUEST_PROOF_JWS_TYP, kid: input.kid },
|
|
210
260
|
payload: proofPayloadObject(payload, REQUEST_PROOF_SHAPE, extra),
|
|
@@ -221,12 +271,13 @@ var signApiIdentityRequest = async (input) => {
|
|
|
221
271
|
host: input.host,
|
|
222
272
|
path: input.path,
|
|
223
273
|
bodyHash: sha256BodyHash(input.body ?? EMPTY_BODY),
|
|
224
|
-
iat: input.iat ?? Math.floor(Date.now() / 1e3)
|
|
274
|
+
iat: input.iat ?? Math.floor(Date.now() / 1e3),
|
|
275
|
+
...input.jti !== void 0 ? { jti: input.jti } : {}
|
|
225
276
|
});
|
|
226
277
|
if (!input.kid.includes("#")) {
|
|
227
278
|
throw new Error("invalid identity proof: kid must be a DID URL");
|
|
228
279
|
}
|
|
229
|
-
const extra =
|
|
280
|
+
const extra = emittedExtraMembers(payload, input.extraMembers, IDENTITY_PROOF_SHAPE.label);
|
|
230
281
|
const proof = await createJws({
|
|
231
282
|
header: { alg: "EdDSA", typ: IDENTITY_PROOF_JWS_TYP, kid: input.kid },
|
|
232
283
|
payload: proofPayloadObject(payload, IDENTITY_PROOF_SHAPE, extra),
|
|
@@ -247,7 +298,7 @@ var buildApiIdentityHeaders = (input) => ({
|
|
|
247
298
|
var ApiRequestVerifyError = class extends Error {
|
|
248
299
|
reason;
|
|
249
300
|
phase;
|
|
250
|
-
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid, 503 unverifiable, 500 config. */
|
|
301
|
+
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid or uncovered, 409 replayed, 503 unverifiable, 500 config. */
|
|
251
302
|
status;
|
|
252
303
|
constructor(reason, phase, status, message) {
|
|
253
304
|
super(message);
|
|
@@ -258,6 +309,8 @@ var ApiRequestVerifyError = class extends Error {
|
|
|
258
309
|
}
|
|
259
310
|
};
|
|
260
311
|
var invalidProof = (message) => new ApiRequestVerifyError("invalid", "proof", 401, message);
|
|
312
|
+
var replayedProof = (message) => new ApiRequestVerifyError("replayed", "proof", 409, message);
|
|
313
|
+
var uncoveredProof = (message) => new ApiRequestVerifyError("uncovered", "credential", 403, message);
|
|
261
314
|
var unverifiableProof = (message) => new ApiRequestVerifyError("unverifiable", "proof", 503, message);
|
|
262
315
|
var misconfiguredProof = (message) => new ApiRequestVerifyError("config", "config", 500, message);
|
|
263
316
|
var assertProofVerifierConfig = (input) => {
|
|
@@ -319,6 +372,9 @@ var verifyProofEnvelope = async (input, shape, resolvePresenter) => {
|
|
|
319
372
|
} catch (err) {
|
|
320
373
|
throw invalidProof(err instanceof Error ? err.message : `invalid ${shape.label} payload`);
|
|
321
374
|
}
|
|
375
|
+
if (input.requireJti === true && payload.jti === void 0) {
|
|
376
|
+
throw invalidProof(`invalid ${shape.label}: jti is required on this route`);
|
|
377
|
+
}
|
|
322
378
|
const now = Math.floor((input.now ? input.now() : Date.now()) / 1e3);
|
|
323
379
|
if (now - payload.iat > window) throw invalidProof(`${shape.label} is stale`);
|
|
324
380
|
if (payload.iat - now > skew) {
|
|
@@ -362,7 +418,15 @@ var verifyProofEnvelope = async (input, shape, resolvePresenter) => {
|
|
|
362
418
|
} catch (err) {
|
|
363
419
|
throw invalidProof(err instanceof Error ? err.message : `invalid ${shape.label} signature`);
|
|
364
420
|
}
|
|
365
|
-
return {
|
|
421
|
+
return {
|
|
422
|
+
payload,
|
|
423
|
+
rawPayload,
|
|
424
|
+
presenterDID,
|
|
425
|
+
kid,
|
|
426
|
+
now,
|
|
427
|
+
...payload.jti !== void 0 ? { jti: payload.jti } : {},
|
|
428
|
+
...key.roles !== void 0 ? { keyRoles: key.roles } : {}
|
|
429
|
+
};
|
|
366
430
|
};
|
|
367
431
|
var verifyIdentityProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, IDENTITY_PROOF_SHAPE, resolvePresenter);
|
|
368
432
|
var verifyRequestProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, REQUEST_PROOF_SHAPE, resolvePresenter);
|
|
@@ -538,6 +602,26 @@ var parseResource = (resource) => {
|
|
|
538
602
|
if (colonIdx < 0) return null;
|
|
539
603
|
return { type: resource.substring(0, colonIdx), id: resource.substring(colonIdx + 1) };
|
|
540
604
|
};
|
|
605
|
+
var SPACE_ID_RE = /^[2346789acdefhknrtvz]{31}$/;
|
|
606
|
+
var parseApiResource = (resource) => {
|
|
607
|
+
const parsed = parseResource(resource);
|
|
608
|
+
if (!parsed || parsed.type !== "api") return null;
|
|
609
|
+
const slash = parsed.id.indexOf("/");
|
|
610
|
+
if (slash < 0) return parsed.id === "" ? null : { host: parsed.id };
|
|
611
|
+
const host = parsed.id.substring(0, slash);
|
|
612
|
+
if (host === "") return null;
|
|
613
|
+
const child = parsed.id.substring(slash + 1);
|
|
614
|
+
if (!child.startsWith("spaces/")) return null;
|
|
615
|
+
const spaceId = child.substring("spaces/".length);
|
|
616
|
+
if (!SPACE_ID_RE.test(spaceId)) return null;
|
|
617
|
+
return { host, spaceId };
|
|
618
|
+
};
|
|
619
|
+
var apiResourceCovers = (entryResource, requiredResource) => {
|
|
620
|
+
const entry = parseApiResource(entryResource);
|
|
621
|
+
const required = parseApiResource(requiredResource);
|
|
622
|
+
if (!entry || !required || entry.host !== required.host) return false;
|
|
623
|
+
return entry.spaceId === void 0 || entry.spaceId === required.spaceId;
|
|
624
|
+
};
|
|
541
625
|
var ASCII_TRIM_RE = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
|
|
542
626
|
var parseActions = (action) => new Set(
|
|
543
627
|
action.split(",").map((a) => a.replace(ASCII_TRIM_RE, "")).filter((a) => a !== "")
|
|
@@ -563,6 +647,9 @@ var isAttenuated = (parentAtt, childAtt) => {
|
|
|
563
647
|
if (childRes.type === "chain" && parentRes.type === "chain") {
|
|
564
648
|
return childRes.id === parentRes.id;
|
|
565
649
|
}
|
|
650
|
+
if (childRes.type === "api" && parentRes.type === "api") {
|
|
651
|
+
return apiResourceCovers(parentEntry.resource, childEntry.resource) || childEntry.resource === parentEntry.resource;
|
|
652
|
+
}
|
|
566
653
|
if (childRes.type !== "chain" && parentRes.type !== "chain") {
|
|
567
654
|
return childEntry.resource === parentEntry.resource;
|
|
568
655
|
}
|
|
@@ -589,6 +676,10 @@ var matchesResource = async (att, resource, action) => {
|
|
|
589
676
|
if (entryRes.type === "chain" && entryRes.id === "*" && requestedRes.type === "chain") {
|
|
590
677
|
return true;
|
|
591
678
|
}
|
|
679
|
+
if (entryRes.type === "api" || requestedRes.type === "api") {
|
|
680
|
+
if (apiResourceCovers(entry.resource, resource)) return true;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
592
683
|
if (entryRes.type === requestedRes.type && entryRes.id === requestedRes.id) {
|
|
593
684
|
return true;
|
|
594
685
|
}
|
|
@@ -623,11 +714,13 @@ export {
|
|
|
623
714
|
IDENTITY_PROOF_JWS_TYP,
|
|
624
715
|
EMPTY_BODY_SHA256,
|
|
625
716
|
MAX_REQUEST_PROOF_SIZE,
|
|
717
|
+
MAX_JTI_BYTES,
|
|
626
718
|
DEFAULT_PROOF_WINDOW_SECONDS,
|
|
627
719
|
DEFAULT_PROOF_SKEW_SECONDS,
|
|
628
720
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
629
721
|
MAX_BODY_BYTES,
|
|
630
722
|
DFOS_AUTH_SCHEME,
|
|
723
|
+
generateJti,
|
|
631
724
|
canonicalExtraMembers,
|
|
632
725
|
apiRequestSigningInput,
|
|
633
726
|
apiIdentitySigningInput,
|
|
@@ -639,6 +732,8 @@ export {
|
|
|
639
732
|
buildApiIdentityHeaders,
|
|
640
733
|
ApiRequestVerifyError,
|
|
641
734
|
invalidProof,
|
|
735
|
+
replayedProof,
|
|
736
|
+
uncoveredProof,
|
|
642
737
|
unverifiableProof,
|
|
643
738
|
misconfiguredProof,
|
|
644
739
|
assertProofVerifierConfig,
|
|
@@ -647,6 +742,8 @@ export {
|
|
|
647
742
|
createDFOSCredential,
|
|
648
743
|
verifyDFOSCredential,
|
|
649
744
|
verifyDelegationChain,
|
|
745
|
+
parseApiResource,
|
|
746
|
+
apiResourceCovers,
|
|
650
747
|
isAttenuated,
|
|
651
748
|
matchesResource,
|
|
652
749
|
decodeDFOSCredentialUnsafe,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as ResolvedIdentity, r as RevocationChecker, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, z as
|
|
1
|
+
export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as ResolvedIdentity, r as RevocationChecker, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, z as apiResourceCovers, B as createDFOSCredential, E as decodeDFOSCredentialUnsafe, F as isAttenuated, G as matchesResource, H as parseApiResource, K as verifyDFOSCredential, L as verifyDelegationChain } from '../dfos-credential-DgT8XVTm.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -27,6 +27,11 @@ declare const IDENTITY_PROOF_JWS_TYP = "did:dfos:identity-proof";
|
|
|
27
27
|
declare const EMPTY_BODY_SHA256 = "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU";
|
|
28
28
|
/** Size cap on the serialized proof token, checked BEFORE any decode. */
|
|
29
29
|
declare const MAX_REQUEST_PROOF_SIZE = 4096;
|
|
30
|
+
/**
|
|
31
|
+
* Cap on the `jti` member, in UTF-8 bytes. There is no floor: uniqueness is the
|
|
32
|
+
* member's job, and a verifier's replay cache is what enforces it.
|
|
33
|
+
*/
|
|
34
|
+
declare const MAX_JTI_BYTES = 256;
|
|
30
35
|
/** RECOMMENDED acceptance window `W` — how old a proof may be, in seconds. */
|
|
31
36
|
declare const DEFAULT_PROOF_WINDOW_SECONDS = 60;
|
|
32
37
|
/** RECOMMENDED clock-skew allowance `S` — how forward-dated a proof may be. */
|
|
@@ -64,6 +69,11 @@ interface RequestProofPayload {
|
|
|
64
69
|
credentialCID: string;
|
|
65
70
|
/** Issued-at — unix seconds (positive integer). */
|
|
66
71
|
iat: number;
|
|
72
|
+
/**
|
|
73
|
+
* The registered additive member: a per-request unique value, at most
|
|
74
|
+
* `MAX_JTI_BYTES` UTF-8 bytes. Emitted after `iat`, in the additive block.
|
|
75
|
+
*/
|
|
76
|
+
jti?: string;
|
|
67
77
|
}
|
|
68
78
|
/**
|
|
69
79
|
* The identity proof's payload: the request proof's five members MINUS
|
|
@@ -81,6 +91,11 @@ interface IdentityProofPayload {
|
|
|
81
91
|
bodyHash: string;
|
|
82
92
|
/** Issued-at — unix seconds (positive integer). */
|
|
83
93
|
iat: number;
|
|
94
|
+
/**
|
|
95
|
+
* The registered additive member: a per-request unique value, at most
|
|
96
|
+
* `MAX_JTI_BYTES` UTF-8 bytes. Emitted after `iat`, in the additive block.
|
|
97
|
+
*/
|
|
98
|
+
jti?: string;
|
|
84
99
|
}
|
|
85
100
|
/**
|
|
86
101
|
* ADDITIVE MEMBERS, appended AFTER the canonical order.
|
|
@@ -108,7 +123,15 @@ interface ParsedProofPayload {
|
|
|
108
123
|
bodyHash: string;
|
|
109
124
|
credentialCID?: string;
|
|
110
125
|
iat: number;
|
|
126
|
+
/** The registered additive member, when the proof carries one. */
|
|
127
|
+
jti?: string;
|
|
111
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Mint a `jti`: 128 bits from the platform CSPRNG, unpadded base64url (22
|
|
131
|
+
* characters). Uniqueness is per (presenter DID, freshness window), which this
|
|
132
|
+
* much randomness makes a collision no deployment will observe.
|
|
133
|
+
*/
|
|
134
|
+
declare const generateJti: () => string;
|
|
112
135
|
/**
|
|
113
136
|
* Validate and normalize additive members into their canonical emission order:
|
|
114
137
|
* lexicographic by name, after every canonical member. Returns `[]` for the
|
|
@@ -171,10 +194,17 @@ interface SignApiRequestInput {
|
|
|
171
194
|
sign: (message: Uint8Array) => Promise<Uint8Array>;
|
|
172
195
|
/** Issued-at override — unix seconds. Default `Math.floor(Date.now() / 1000)`. */
|
|
173
196
|
iat?: number;
|
|
197
|
+
/**
|
|
198
|
+
* The registered additive member, per-request unique and at most
|
|
199
|
+
* `MAX_JTI_BYTES` bytes — required by a deployment that gates WRITES with this
|
|
200
|
+
* envelope (INTEGRATIONS.md, API security notes). `generateJti()` mints one.
|
|
201
|
+
* It emits the same bytes as `extraMembers: { jti }`, and naming it both ways
|
|
202
|
+
* throws.
|
|
203
|
+
*/
|
|
204
|
+
jti?: string;
|
|
174
205
|
/**
|
|
175
206
|
* ADDITIVE members, appended after the canonical order in lexicographic name
|
|
176
|
-
* order. `{ jti }` is the registered one
|
|
177
|
-
* WRITES with this envelope (INTEGRATIONS.md, API security notes).
|
|
207
|
+
* order. `{ jti }` is the registered one, and has the typed field above.
|
|
178
208
|
*/
|
|
179
209
|
extraMembers?: ProofExtraMembers;
|
|
180
210
|
}
|
|
@@ -209,10 +239,17 @@ interface SignApiIdentityRequestInput {
|
|
|
209
239
|
sign: (message: Uint8Array) => Promise<Uint8Array>;
|
|
210
240
|
/** Issued-at override — unix seconds. Default `Math.floor(Date.now() / 1000)`. */
|
|
211
241
|
iat?: number;
|
|
242
|
+
/**
|
|
243
|
+
* The registered additive member, per-request unique and at most
|
|
244
|
+
* `MAX_JTI_BYTES` bytes. A WRITE-SHAPED surface — relay ingestion, blob
|
|
245
|
+
* upload — REQUIRES it (INTEGRATIONS.md, API security notes). `generateJti()`
|
|
246
|
+
* mints one. It emits the same bytes as `extraMembers: { jti }`, and naming it
|
|
247
|
+
* both ways throws.
|
|
248
|
+
*/
|
|
249
|
+
jti?: string;
|
|
212
250
|
/**
|
|
213
251
|
* ADDITIVE members, appended after the canonical order in lexicographic name
|
|
214
|
-
* order. `{ jti }` is the registered one, and
|
|
215
|
-
* ingestion, blob upload — REQUIRES it (INTEGRATIONS.md, API security notes).
|
|
252
|
+
* order. `{ jti }` is the registered one, and has the typed field above.
|
|
216
253
|
*/
|
|
217
254
|
extraMembers?: ProofExtraMembers;
|
|
218
255
|
}
|
|
@@ -261,13 +298,21 @@ declare const buildApiIdentityHeaders: (input: {
|
|
|
261
298
|
* The verdict class. Branch on `reason`, never on message text.
|
|
262
299
|
*
|
|
263
300
|
* - `invalid` — checked and failed.
|
|
301
|
+
* - `replayed` — checked, VALID, and already spent: this `jti` was seen inside
|
|
302
|
+
* the proof's own freshness window. Its own verdict rather than an `invalid`
|
|
303
|
+
* one because the caller's answer differs — re-read state, do not re-sign the
|
|
304
|
+
* same request.
|
|
305
|
+
* - `uncovered` — the credential is VALID and simply does not reach this route's
|
|
306
|
+
* resource and action. Distinct from `invalid` because nothing is wrong with
|
|
307
|
+
* the artifact: a route offering optional authentication serves its anonymous
|
|
308
|
+
* projection on an `uncovered` verdict, and still refuses an `invalid` one.
|
|
264
309
|
* - `unverifiable` — could not check (an unresolvable presenter, an unreachable
|
|
265
310
|
* revocation source). A transient resolution failure is the server's
|
|
266
311
|
* condition, not the caller's.
|
|
267
312
|
* - `config` — the DEPLOYMENT is misconfigured (a `W + S` over the 300-second
|
|
268
313
|
* ceiling, or an empty required action). Not a judgment about the artifact.
|
|
269
314
|
*/
|
|
270
|
-
type RequestProofFailureReason = 'invalid' | 'unverifiable' | 'config';
|
|
315
|
+
type RequestProofFailureReason = 'invalid' | 'replayed' | 'uncovered' | 'unverifiable' | 'config';
|
|
271
316
|
/**
|
|
272
317
|
* The verification phase a failure arose in. Load-bearing for HTTP mapping: an
|
|
273
318
|
* `invalid` proof-layer failure is a 401 (with a `WWW-Authenticate: DFOS`
|
|
@@ -279,12 +324,26 @@ type RequestProofFailurePhase = 'proof' | 'credential' | 'config';
|
|
|
279
324
|
declare class ApiRequestVerifyError extends Error {
|
|
280
325
|
readonly reason: RequestProofFailureReason;
|
|
281
326
|
readonly phase: RequestProofFailurePhase;
|
|
282
|
-
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid, 503 unverifiable, 500 config. */
|
|
327
|
+
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid or uncovered, 409 replayed, 503 unverifiable, 500 config. */
|
|
283
328
|
readonly status: number;
|
|
284
329
|
constructor(reason: RequestProofFailureReason, phase: RequestProofFailurePhase, status: number, message: string);
|
|
285
330
|
}
|
|
286
331
|
/** invalid, proof phase → 401. */
|
|
287
332
|
declare const invalidProof: (message: string) => ApiRequestVerifyError;
|
|
333
|
+
/**
|
|
334
|
+
* replayed, proof phase → 409. The proof verified; its `jti` was already spent.
|
|
335
|
+
* The cache belongs to the deployment, so this is the constructor every consumer
|
|
336
|
+
* classifies a replay with rather than a status each one picks.
|
|
337
|
+
*/
|
|
338
|
+
declare const replayedProof: (message: string) => ApiRequestVerifyError;
|
|
339
|
+
/**
|
|
340
|
+
* uncovered, credential phase → 403. The credential verified and simply does not
|
|
341
|
+
* reach this route's resource and action. Its own verdict because a route
|
|
342
|
+
* offering optional authentication answers it with the anonymous projection a
|
|
343
|
+
* caller presenting nothing would get — a credential only ever ADDS — while an
|
|
344
|
+
* `invalid` credential is refused outright.
|
|
345
|
+
*/
|
|
346
|
+
declare const uncoveredProof: (message: string) => ApiRequestVerifyError;
|
|
288
347
|
/** unverifiable, proof phase → 503. */
|
|
289
348
|
declare const unverifiableProof: (message: string) => ApiRequestVerifyError;
|
|
290
349
|
/** the DEPLOYMENT is misconfigured → 500. Never a judgment about the artifact. */
|
|
@@ -301,10 +360,16 @@ declare const misconfiguredProof: (message: string) => ApiRequestVerifyError;
|
|
|
301
360
|
interface ProofPresenterState {
|
|
302
361
|
/** Current-state deletion. A deleted presenter's proofs are INVALID (401). */
|
|
303
362
|
isDeleted: boolean;
|
|
304
|
-
/**
|
|
363
|
+
/**
|
|
364
|
+
* Current keys, any role. `roles` is OPTIONAL and the resolver's to supply: a
|
|
365
|
+
* key may hold several, and a deployment gating writes decides for itself
|
|
366
|
+
* which role may sign. The verifier never reads it — it hands the matched
|
|
367
|
+
* key's roles back on the verified envelope.
|
|
368
|
+
*/
|
|
305
369
|
keys: readonly {
|
|
306
370
|
id: string;
|
|
307
371
|
publicKeyMultibase: string;
|
|
372
|
+
roles?: readonly ('auth' | 'assert' | 'controller')[];
|
|
308
373
|
}[];
|
|
309
374
|
}
|
|
310
375
|
/**
|
|
@@ -345,6 +410,12 @@ interface ProofEnvelopeInput {
|
|
|
345
410
|
skewSeconds?: number;
|
|
346
411
|
/** Clock injection (unix ms). Default `Date.now()`. */
|
|
347
412
|
now?: () => number;
|
|
413
|
+
/**
|
|
414
|
+
* Require the registered `jti` member. Default false. A verifier gating WRITES
|
|
415
|
+
* sets it on every write-shaped route and records the value in its own replay
|
|
416
|
+
* cache; a read-shaped route may ignore the member entirely.
|
|
417
|
+
*/
|
|
418
|
+
requireJti?: boolean;
|
|
348
419
|
}
|
|
349
420
|
/**
|
|
350
421
|
* INTEGRATIONS.md, Verification algorithm step 4's freshness configuration,
|
|
@@ -372,11 +443,10 @@ interface VerifiedProofEnvelope {
|
|
|
372
443
|
/**
|
|
373
444
|
* The DECODED payload object, unknown members included.
|
|
374
445
|
*
|
|
375
|
-
* ADDITIVE MEMBERS ARE READ FROM HERE, at the consuming layer,
|
|
376
|
-
* verification — the signature already covers them, and the canonical
|
|
377
|
-
* set stays closed. `jti`
|
|
378
|
-
*
|
|
379
|
-
* off this object and applies its own replay discipline.
|
|
446
|
+
* UNREGISTERED ADDITIVE MEMBERS ARE READ FROM HERE, at the consuming layer,
|
|
447
|
+
* AFTER verification — the signature already covers them, and the canonical
|
|
448
|
+
* member set stays closed. The registered `jti` has its own field above; every
|
|
449
|
+
* other additive member is ignored per MUST-ignore-unknown and survives here.
|
|
380
450
|
*/
|
|
381
451
|
rawPayload: Record<string, unknown>;
|
|
382
452
|
/** THE PRINCIPAL — the `kid`'s DID. */
|
|
@@ -385,6 +455,17 @@ interface VerifiedProofEnvelope {
|
|
|
385
455
|
kid: string;
|
|
386
456
|
/** The integer unix seconds the freshness check used. */
|
|
387
457
|
now: number;
|
|
458
|
+
/**
|
|
459
|
+
* The registered `jti`, when the proof carried one — the value a write-gating
|
|
460
|
+
* deployment records in its replay cache, keyed with `presenterDID`.
|
|
461
|
+
*/
|
|
462
|
+
jti?: string;
|
|
463
|
+
/**
|
|
464
|
+
* The matched key's roles, when the resolver supplied them. A deployment
|
|
465
|
+
* gating writes with an identity proof applies its own rule here: a key whose
|
|
466
|
+
* only effective role is `controller` should not stand for a write.
|
|
467
|
+
*/
|
|
468
|
+
keyRoles?: readonly ('auth' | 'assert' | 'controller')[];
|
|
388
469
|
}
|
|
389
470
|
/**
|
|
390
471
|
* Verify an IDENTITY proof's envelope — for this artifact steps 1–7 ARE the whole
|
|
@@ -406,4 +487,4 @@ declare const verifyIdentityProofEnvelope: (input: ProofEnvelopeInput, resolvePr
|
|
|
406
487
|
*/
|
|
407
488
|
declare const verifyRequestProofEnvelope: (input: ProofEnvelopeInput, resolvePresenter: ResolveProofPresenter) => Promise<VerifiedProofEnvelope>;
|
|
408
489
|
|
|
409
|
-
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, type IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, type ParsedProofPayload, type ProofEnvelopeInput, type ProofExtraMembers, type ProofPresenterState, REQUEST_PROOF_JWS_TYP, type RequestProofFailurePhase, type RequestProofFailureReason, type RequestProofPayload, type ResolveProofPresenter, type SignApiIdentityRequestInput, type SignApiRequestInput, type VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, invalidProof, misconfiguredProof, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope };
|
|
490
|
+
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, type IdentityProofPayload, MAX_BODY_BYTES, MAX_JTI_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, type ParsedProofPayload, type ProofEnvelopeInput, type ProofExtraMembers, type ProofPresenterState, REQUEST_PROOF_JWS_TYP, type RequestProofFailurePhase, type RequestProofFailureReason, type RequestProofPayload, type ResolveProofPresenter, type SignApiIdentityRequestInput, type SignApiRequestInput, type VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, generateJti, invalidProof, misconfiguredProof, parseDfosAuthorization, replayedProof, sha256BodyHash, signApiIdentityRequest, signApiRequest, uncoveredProof, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope };
|
|
@@ -10,31 +10,37 @@ import {
|
|
|
10
10
|
IDENTITY_PROOF_JWS_TYP,
|
|
11
11
|
MAX_BODY_BYTES,
|
|
12
12
|
MAX_CREDENTIAL_SIZE,
|
|
13
|
+
MAX_JTI_BYTES,
|
|
13
14
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
14
15
|
MAX_REQUEST_PROOF_SIZE,
|
|
15
16
|
REQUEST_PROOF_JWS_TYP,
|
|
16
17
|
apiIdentitySigningInput,
|
|
17
18
|
apiRequestSigningInput,
|
|
19
|
+
apiResourceCovers,
|
|
18
20
|
assertProofVerifierConfig,
|
|
19
21
|
buildApiAuthHeaders,
|
|
20
22
|
buildApiIdentityHeaders,
|
|
21
23
|
canonicalExtraMembers,
|
|
22
24
|
createDFOSCredential,
|
|
23
25
|
decodeDFOSCredentialUnsafe,
|
|
26
|
+
generateJti,
|
|
24
27
|
invalidProof,
|
|
25
28
|
isAttenuated,
|
|
26
29
|
matchesResource,
|
|
27
30
|
misconfiguredProof,
|
|
31
|
+
parseApiResource,
|
|
28
32
|
parseDfosAuthorization,
|
|
33
|
+
replayedProof,
|
|
29
34
|
sha256BodyHash,
|
|
30
35
|
signApiIdentityRequest,
|
|
31
36
|
signApiRequest,
|
|
37
|
+
uncoveredProof,
|
|
32
38
|
unverifiableProof,
|
|
33
39
|
verifyDFOSCredential,
|
|
34
40
|
verifyDelegationChain,
|
|
35
41
|
verifyIdentityProofEnvelope,
|
|
36
42
|
verifyRequestProofEnvelope
|
|
37
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-RPA44ND4.js";
|
|
38
44
|
import "../chunk-F7RHI2CK.js";
|
|
39
45
|
import "../chunk-NUIIVAZ3.js";
|
|
40
46
|
export {
|
|
@@ -49,25 +55,31 @@ export {
|
|
|
49
55
|
IDENTITY_PROOF_JWS_TYP,
|
|
50
56
|
MAX_BODY_BYTES,
|
|
51
57
|
MAX_CREDENTIAL_SIZE,
|
|
58
|
+
MAX_JTI_BYTES,
|
|
52
59
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
53
60
|
MAX_REQUEST_PROOF_SIZE,
|
|
54
61
|
REQUEST_PROOF_JWS_TYP,
|
|
55
62
|
apiIdentitySigningInput,
|
|
56
63
|
apiRequestSigningInput,
|
|
64
|
+
apiResourceCovers,
|
|
57
65
|
assertProofVerifierConfig,
|
|
58
66
|
buildApiAuthHeaders,
|
|
59
67
|
buildApiIdentityHeaders,
|
|
60
68
|
canonicalExtraMembers,
|
|
61
69
|
createDFOSCredential,
|
|
62
70
|
decodeDFOSCredentialUnsafe,
|
|
71
|
+
generateJti,
|
|
63
72
|
invalidProof,
|
|
64
73
|
isAttenuated,
|
|
65
74
|
matchesResource,
|
|
66
75
|
misconfiguredProof,
|
|
76
|
+
parseApiResource,
|
|
67
77
|
parseDfosAuthorization,
|
|
78
|
+
replayedProof,
|
|
68
79
|
sha256BodyHash,
|
|
69
80
|
signApiIdentityRequest,
|
|
70
81
|
signApiRequest,
|
|
82
|
+
uncoveredProof,
|
|
71
83
|
unverifiableProof,
|
|
72
84
|
verifyDFOSCredential,
|
|
73
85
|
verifyDelegationChain,
|
|
@@ -648,6 +648,26 @@ declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, option
|
|
|
648
648
|
*/
|
|
649
649
|
nowUnix?: number;
|
|
650
650
|
}) => Promise<VerifiedDelegationChain>;
|
|
651
|
+
/**
|
|
652
|
+
* Parse an `api:` resource into its host and, when present, its space id.
|
|
653
|
+
* Returns `null` for any other resource type and for a malformed `api:` id —
|
|
654
|
+
* `null` IS the malformed verdict, and a malformed resource covers nothing.
|
|
655
|
+
*
|
|
656
|
+
* Two forms are well-formed and no others: `api:<host>` with a non-empty host
|
|
657
|
+
* carrying no `/`, and `api:<host>/spaces/<id>`. The id half splits on the FIRST
|
|
658
|
+
* `/`, so a host keeps its port and anything past `spaces/<id>` is malformed.
|
|
659
|
+
*/
|
|
660
|
+
declare const parseApiResource: (resource: string) => {
|
|
661
|
+
host: string;
|
|
662
|
+
spaceId?: string;
|
|
663
|
+
} | null;
|
|
664
|
+
/**
|
|
665
|
+
* Whether one `api:` resource covers another: same host, and either the entry is
|
|
666
|
+
* the bare host (the registered ancestor of every space on it) or the two name
|
|
667
|
+
* the same space. False when either side is malformed or is not `api:` —
|
|
668
|
+
* coverage never crosses hosts and never crosses resource types.
|
|
669
|
+
*/
|
|
670
|
+
declare const apiResourceCovers: (entryResource: string, requiredResource: string) => boolean;
|
|
651
671
|
/**
|
|
652
672
|
* Check if `childAtt` is a valid attenuation of `parentAtt`
|
|
653
673
|
*
|
|
@@ -658,11 +678,14 @@ declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, option
|
|
|
658
678
|
* - `chain:X` covered by `chain:*` (narrowing from wildcard — valid)
|
|
659
679
|
* - `chain:*` covered by `chain:*` (exact match)
|
|
660
680
|
* - `chain:*` NOT covered by `chain:X` (widening — invalid)
|
|
661
|
-
* -
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
* resource
|
|
681
|
+
* - `api:<host>/spaces/<id>` covered by `api:<host>` (narrowing onto one space)
|
|
682
|
+
* or by the same child. A MALFORMED `api:` id narrows only from a
|
|
683
|
+
* byte-identical parent, under the rule below — a bare host does not carry it.
|
|
684
|
+
* - Other types (`mailbox:<id>`, and any form a future capability registers):
|
|
685
|
+
* exact byte equality of the full resource string, nothing else. The wildcard
|
|
686
|
+
* is a `chain:`-only concept — a literal `*` id in any other type is an
|
|
687
|
+
* ordinary id covering only itself — and coverage never crosses resource
|
|
688
|
+
* types. See CREDENTIALS.md "Resource Types".
|
|
666
689
|
* - Actions: child action set must be a subset of parent action set
|
|
667
690
|
*/
|
|
668
691
|
declare const isAttenuated: (parentAtt: Attenuation[], childAtt: Attenuation[]) => boolean;
|
|
@@ -670,7 +693,14 @@ declare const isAttenuated: (parentAtt: Attenuation[], childAtt: Attenuation[])
|
|
|
670
693
|
* Check if an `att` array covers a requested resource
|
|
671
694
|
*
|
|
672
695
|
* Used at the relay to determine if a credential authorizes access to a
|
|
673
|
-
* specific content chain
|
|
696
|
+
* specific content chain, and by an API verifier to decide whether a grant
|
|
697
|
+
* reaches the route's resource.
|
|
698
|
+
*
|
|
699
|
+
* `chain:*` covers any `chain:` request; an `api:` entry covers an `api:`
|
|
700
|
+
* request under the host/space hierarchy; every other form is exact byte
|
|
701
|
+
* equality. A malformed `api:` resource covers nothing and is covered by
|
|
702
|
+
* nothing, itself included — a resource a verifier cannot parse must not
|
|
703
|
+
* authorize a request. Every entry is scanned, so any one of them may cover.
|
|
674
704
|
*/
|
|
675
705
|
declare const matchesResource: (att: Attenuation[], resource: string, action: string) => Promise<boolean>;
|
|
676
706
|
/**
|
|
@@ -691,4 +721,4 @@ declare class CredentialVerificationError extends Error {
|
|
|
691
721
|
constructor(message: string);
|
|
692
722
|
}
|
|
693
723
|
|
|
694
|
-
export { ARTIFACT_CID_ANCHOR_RE as A,
|
|
724
|
+
export { ARTIFACT_CID_ANCHOR_RE as A, createDFOSCredential as B, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, decodeDFOSCredentialUnsafe as E, isAttenuated as F, matchesResource as G, parseApiResource as H, IdentityOperation as I, parseProtocolTimestampUnix as J, verifyDFOSCredential as K, verifyDelegationChain as L, MAX_ARTIFACT_PAYLOAD_SIZE as M, type ResolvedIdentity as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, ContentOperation as c, CountersignPayload as d, CredentialVerificationError as e, CreditClaimPayload as f, DeclaredKeyState as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_KEY_PROOFS as k, MAX_OPERATION_SIZE as l, MAX_SERVICES_ENTRIES as m, MAX_SERVICES_PAYLOAD_SIZE as n, MAX_SIGN_REQUEST_PAYLOAD_SIZE as o, MAX_SIGN_REQUEST_SIZE as p, MultikeyPublicKey as q, type RevocationChecker as r, RevocationPayload as s, ServicesArray as t, SignRequestPayload as u, type Signer as v, type VerifiedDelegationChain as w, VerifiedIdentity as x, VoidKeyMembership as y, apiResourceCovers as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertCanonicalJsonText, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
|
|
2
|
-
export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as ResolvedIdentity, r as RevocationChecker, s as RevocationPayload, S as ServiceEntry, t as ServicesArray, u as SignRequestPayload, v as Signer, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, x as VerifiedIdentity, y as VoidKeyMembership, z as
|
|
2
|
+
export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as ResolvedIdentity, r as RevocationChecker, s as RevocationPayload, S as ServiceEntry, t as ServicesArray, u as SignRequestPayload, v as Signer, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, x as VerifiedIdentity, y as VoidKeyMembership, z as apiResourceCovers, B as createDFOSCredential, E as decodeDFOSCredentialUnsafe, F as isAttenuated, G as matchesResource, H as parseApiResource, J as parseProtocolTimestampUnix, K as verifyDFOSCredential, L as verifyDelegationChain } from './dfos-credential-DgT8XVTm.js';
|
|
3
3
|
export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityStateNoSeenKeysError, RECOGNIZED_SERVICE_TYPES, SignRequestFailureReason, SignRequestVerifyError, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeEd25519PublicMultikey, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest } from './chain/index.js';
|
|
4
|
-
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ParsedProofPayload, ProofEnvelopeInput, ProofExtraMembers, ProofPresenterState, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, ResolveProofPresenter, SignApiIdentityRequestInput, SignApiRequestInput, VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, invalidProof, misconfiguredProof, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope } from './credentials/index.js';
|
|
4
|
+
export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_JTI_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ParsedProofPayload, ProofEnvelopeInput, ProofExtraMembers, ProofPresenterState, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, ResolveProofPresenter, SignApiIdentityRequestInput, SignApiRequestInput, VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, generateJti, invalidProof, misconfiguredProof, parseDfosAuthorization, replayedProof, sha256BodyHash, signApiIdentityRequest, signApiRequest, uncoveredProof, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope } from './credentials/index.js';
|
|
5
5
|
export { DEFAULT_KEY_PROOF_SKEW_SECONDS, KEY_ADD_JWS_TYP, KEY_ROLES, KeyProofFailureReason, KeyProofPayload, KeyProofVerifyError, KeyRole, MAX_KEY_PROOF_SIZE, SignKeyProofInput, VerifiedKeyProof, VerifyChainKeyProofOptions, VerifyKeyProofOptions, isCanonicalRoleSet, keyProofSigningInput, keyWordFingerprint, parseRoleSet, roleSetCovers, serializeRoleSet, signKeyProof, unsafeKeyProofSubject, verifyChainKeyProof, verifyKeyProof } from './key-proof/index.js';
|
|
6
6
|
export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
|
|
7
7
|
import 'multiformats';
|
package/dist/index.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
verifyIdentityExtensionFromTrustedState,
|
|
54
54
|
verifyRevocation,
|
|
55
55
|
verifySignRequest
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-QIM3KZXZ.js";
|
|
57
57
|
import {
|
|
58
58
|
ApiRequestVerifyError,
|
|
59
59
|
Attenuation,
|
|
@@ -66,11 +66,13 @@ import {
|
|
|
66
66
|
IDENTITY_PROOF_JWS_TYP,
|
|
67
67
|
MAX_BODY_BYTES,
|
|
68
68
|
MAX_CREDENTIAL_SIZE,
|
|
69
|
+
MAX_JTI_BYTES,
|
|
69
70
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
70
71
|
MAX_REQUEST_PROOF_SIZE,
|
|
71
72
|
REQUEST_PROOF_JWS_TYP,
|
|
72
73
|
apiIdentitySigningInput,
|
|
73
74
|
apiRequestSigningInput,
|
|
75
|
+
apiResourceCovers,
|
|
74
76
|
assertProofVerifierConfig,
|
|
75
77
|
buildApiAuthHeaders,
|
|
76
78
|
buildApiIdentityHeaders,
|
|
@@ -78,22 +80,26 @@ import {
|
|
|
78
80
|
carryDependencyMissing,
|
|
79
81
|
createDFOSCredential,
|
|
80
82
|
decodeDFOSCredentialUnsafe,
|
|
83
|
+
generateJti,
|
|
81
84
|
invalidProof,
|
|
82
85
|
isAttenuated,
|
|
83
86
|
isDependencyMissing,
|
|
84
87
|
markDependencyMissing,
|
|
85
88
|
matchesResource,
|
|
86
89
|
misconfiguredProof,
|
|
90
|
+
parseApiResource,
|
|
87
91
|
parseDfosAuthorization,
|
|
92
|
+
replayedProof,
|
|
88
93
|
sha256BodyHash,
|
|
89
94
|
signApiIdentityRequest,
|
|
90
95
|
signApiRequest,
|
|
96
|
+
uncoveredProof,
|
|
91
97
|
unverifiableProof,
|
|
92
98
|
verifyDFOSCredential,
|
|
93
99
|
verifyDelegationChain,
|
|
94
100
|
verifyIdentityProofEnvelope,
|
|
95
101
|
verifyRequestProofEnvelope
|
|
96
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-RPA44ND4.js";
|
|
97
103
|
import {
|
|
98
104
|
DEFAULT_KEY_PROOF_SKEW_SECONDS,
|
|
99
105
|
KEY_ADD_JWS_TYP,
|
|
@@ -187,6 +193,7 @@ export {
|
|
|
187
193
|
MAX_BODY_BYTES,
|
|
188
194
|
MAX_CREDENTIAL_SIZE,
|
|
189
195
|
MAX_CREDIT_CLAIM_SIZE,
|
|
196
|
+
MAX_JTI_BYTES,
|
|
190
197
|
MAX_KEY_PROOFS,
|
|
191
198
|
MAX_KEY_PROOF_SIZE,
|
|
192
199
|
MAX_OPERATION_SIZE,
|
|
@@ -209,6 +216,7 @@ export {
|
|
|
209
216
|
anchorsByLabel,
|
|
210
217
|
apiIdentitySigningInput,
|
|
211
218
|
apiRequestSigningInput,
|
|
219
|
+
apiResourceCovers,
|
|
212
220
|
assertCanonicalJsonText,
|
|
213
221
|
assertCanonicalSignRequestPayload,
|
|
214
222
|
assertJwsProfile,
|
|
@@ -242,6 +250,7 @@ export {
|
|
|
242
250
|
foldLwwMap,
|
|
243
251
|
generateId,
|
|
244
252
|
generateIdNoPrefix,
|
|
253
|
+
generateJti,
|
|
245
254
|
importEd25519Keypair,
|
|
246
255
|
invalidProof,
|
|
247
256
|
isAttenuated,
|
|
@@ -258,11 +267,13 @@ export {
|
|
|
258
267
|
matchesResource,
|
|
259
268
|
misconfiguredProof,
|
|
260
269
|
normalizedId,
|
|
270
|
+
parseApiResource,
|
|
261
271
|
parseDagCborCID,
|
|
262
272
|
parseDfosAuthorization,
|
|
263
273
|
parseProtocolTimestampUnix,
|
|
264
274
|
parseRoleSet,
|
|
265
275
|
relayEndpoints,
|
|
276
|
+
replayedProof,
|
|
266
277
|
roleSetCovers,
|
|
267
278
|
serializeRoleSet,
|
|
268
279
|
sha256,
|
|
@@ -277,6 +288,7 @@ export {
|
|
|
277
288
|
signKeyProof,
|
|
278
289
|
signPayloadEd25519,
|
|
279
290
|
signRevocation,
|
|
291
|
+
uncoveredProof,
|
|
280
292
|
unsafeKeyProofSubject,
|
|
281
293
|
unverifiableProof,
|
|
282
294
|
verifyArtifact,
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Reference vectors for the api: resource family — parse, coverage, attenuation, and request matching. Consumed by the TS credentials tests and the Go delegation tests (both twins read this file; neither inlines a row). Space ids are 31 characters of the did:dfos identifier alphabet [2346789acdefhknrtvz]; hosts are lowercase authorities, port included when non-default.",
|
|
3
|
+
"ids": {
|
|
4
|
+
"spaceA": "9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
5
|
+
"spaceB": "cv7n8vkvr64cctf3294h9k4eanhff8z"
|
|
6
|
+
},
|
|
7
|
+
"parse": [
|
|
8
|
+
{ "resource": "api:api.dfos.com", "valid": true, "host": "api.dfos.com", "spaceId": null },
|
|
9
|
+
{
|
|
10
|
+
"resource": "api:api.example.org:8443",
|
|
11
|
+
"valid": true,
|
|
12
|
+
"host": "api.example.org:8443",
|
|
13
|
+
"spaceId": null
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
17
|
+
"valid": true,
|
|
18
|
+
"host": "api.dfos.com",
|
|
19
|
+
"spaceId": "9ctvrdn9vedda7efetrhcdakfh4cr2k"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"resource": "api:api.example.org:8443/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
23
|
+
"valid": true,
|
|
24
|
+
"host": "api.example.org:8443",
|
|
25
|
+
"spaceId": "9ctvrdn9vedda7efetrhcdakfh4cr2k"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"resource": "api:*",
|
|
29
|
+
"valid": true,
|
|
30
|
+
"host": "*",
|
|
31
|
+
"spaceId": null,
|
|
32
|
+
"note": "an ordinary id covering only itself; never a served host"
|
|
33
|
+
},
|
|
34
|
+
{ "resource": "api:", "valid": false, "note": "empty host" },
|
|
35
|
+
{
|
|
36
|
+
"resource": "api:/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
37
|
+
"valid": false,
|
|
38
|
+
"note": "empty host"
|
|
39
|
+
},
|
|
40
|
+
{ "resource": "api:api.dfos.com/", "valid": false, "note": "trailing separator, no child" },
|
|
41
|
+
{
|
|
42
|
+
"resource": "api:api.dfos.com/spaces",
|
|
43
|
+
"valid": false,
|
|
44
|
+
"note": "child segment without an id"
|
|
45
|
+
},
|
|
46
|
+
{ "resource": "api:api.dfos.com/spaces/", "valid": false, "note": "empty id" },
|
|
47
|
+
{
|
|
48
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2",
|
|
49
|
+
"valid": false,
|
|
50
|
+
"note": "30-character id"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k2",
|
|
54
|
+
"valid": false,
|
|
55
|
+
"note": "32-character id"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2K",
|
|
59
|
+
"valid": false,
|
|
60
|
+
"note": "uppercase is outside the identifier alphabet"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr1b",
|
|
64
|
+
"valid": false,
|
|
65
|
+
"note": "1 and b are outside the identifier alphabet"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k/extra",
|
|
69
|
+
"valid": false,
|
|
70
|
+
"note": "the hierarchy is one level deep"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"resource": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
74
|
+
"valid": false,
|
|
75
|
+
"note": "topics is not a registered child"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"resource": "api:api.dfos.com/SPACES/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
79
|
+
"valid": false,
|
|
80
|
+
"note": "the child segment is lowercase"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"resource": "api:api.dfos.com/spaces/did:dfos:9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
84
|
+
"valid": false,
|
|
85
|
+
"note": "the id is prefix-stripped, never a full DID"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"covers": [
|
|
89
|
+
{
|
|
90
|
+
"entry": "api:api.dfos.com",
|
|
91
|
+
"required": "api:api.dfos.com",
|
|
92
|
+
"covers": true,
|
|
93
|
+
"note": "bare covers bare"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"entry": "api:api.dfos.com",
|
|
97
|
+
"required": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
98
|
+
"covers": true,
|
|
99
|
+
"note": "bare host is the registered ancestor of every space at that host"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"entry": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
103
|
+
"required": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
104
|
+
"covers": true,
|
|
105
|
+
"note": "child covers itself"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"entry": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
109
|
+
"required": "api:api.dfos.com",
|
|
110
|
+
"covers": false,
|
|
111
|
+
"note": "child never covers the bare host"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"entry": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
115
|
+
"required": "api:api.dfos.com/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
116
|
+
"covers": false,
|
|
117
|
+
"note": "sibling space"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"entry": "api:api.dfos.com",
|
|
121
|
+
"required": "api:api.example.org/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
122
|
+
"covers": false,
|
|
123
|
+
"note": "coverage never crosses hosts"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"entry": "api:api.dfos.com",
|
|
127
|
+
"required": "api:api.dfos.com:8443",
|
|
128
|
+
"covers": false,
|
|
129
|
+
"note": "a non-default port is part of the host"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"entry": "api:api.dfos.com:8443",
|
|
133
|
+
"required": "api:api.dfos.com:8443/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
134
|
+
"covers": true,
|
|
135
|
+
"note": "the port travels with the host into the child"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"entry": "api:*",
|
|
139
|
+
"required": "api:api.dfos.com",
|
|
140
|
+
"covers": false,
|
|
141
|
+
"note": "api:* is an ordinary id, not a wildcard"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"entry": "api:*",
|
|
145
|
+
"required": "api:*",
|
|
146
|
+
"covers": true,
|
|
147
|
+
"note": "an ordinary id covers itself"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"entry": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
151
|
+
"required": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
152
|
+
"covers": false,
|
|
153
|
+
"note": "a malformed api: resource never covers a request, even itself"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"entry": "api:api.dfos.com",
|
|
157
|
+
"required": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
158
|
+
"covers": false,
|
|
159
|
+
"note": "a malformed required resource is never covered"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"entry": "chain:*",
|
|
163
|
+
"required": "api:api.dfos.com",
|
|
164
|
+
"covers": false,
|
|
165
|
+
"note": "coverage never crosses resource types"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"entry": "api:api.dfos.com",
|
|
169
|
+
"required": "chain:9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
170
|
+
"covers": false,
|
|
171
|
+
"note": "coverage never crosses resource types"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"attenuation": [
|
|
175
|
+
{
|
|
176
|
+
"note": "bare host narrows to one space",
|
|
177
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:posts,write:comments" }],
|
|
178
|
+
"child": [
|
|
179
|
+
{
|
|
180
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
181
|
+
"action": "read:posts"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"valid": true
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"note": "bare host narrows to two spaces",
|
|
188
|
+
"parent": [
|
|
189
|
+
{ "resource": "api:api.dfos.com", "action": "read:posts,write:upvotes,write:comments" }
|
|
190
|
+
],
|
|
191
|
+
"child": [
|
|
192
|
+
{
|
|
193
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
194
|
+
"action": "read:posts,write:upvotes"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"resource": "api:api.dfos.com/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
198
|
+
"action": "read:posts"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"valid": true
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"note": "bare host to bare host, fewer actions",
|
|
205
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:profile,read:email" }],
|
|
206
|
+
"child": [{ "resource": "api:api.dfos.com", "action": "read:profile" }],
|
|
207
|
+
"valid": true
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"note": "child widening to the bare host is refused",
|
|
211
|
+
"parent": [
|
|
212
|
+
{
|
|
213
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
214
|
+
"action": "read:posts"
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
"child": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
218
|
+
"valid": false
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"note": "sibling space is refused",
|
|
222
|
+
"parent": [
|
|
223
|
+
{
|
|
224
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
225
|
+
"action": "read:posts"
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"child": [
|
|
229
|
+
{
|
|
230
|
+
"resource": "api:api.dfos.com/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
231
|
+
"action": "read:posts"
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"valid": false
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"note": "child to the same child is exact",
|
|
238
|
+
"parent": [
|
|
239
|
+
{
|
|
240
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
241
|
+
"action": "read:posts,write:comments"
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"child": [
|
|
245
|
+
{
|
|
246
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
247
|
+
"action": "write:comments"
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"valid": true
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"note": "resource narrows but the action widens",
|
|
254
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
255
|
+
"child": [
|
|
256
|
+
{
|
|
257
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
258
|
+
"action": "read:posts,write:posts"
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"valid": false
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"note": "another host is refused",
|
|
265
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
266
|
+
"child": [
|
|
267
|
+
{
|
|
268
|
+
"resource": "api:api.example.org/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
269
|
+
"action": "read:posts"
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
"valid": false
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"note": "an account-level token narrows onto a child; the entry is inert, not refused",
|
|
276
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:profile,read:posts" }],
|
|
277
|
+
"child": [
|
|
278
|
+
{
|
|
279
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
280
|
+
"action": "read:profile"
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"valid": true
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"note": "a malformed api: id is carried only by byte-identical parent",
|
|
287
|
+
"parent": [
|
|
288
|
+
{
|
|
289
|
+
"resource": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
290
|
+
"action": "read:posts"
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
"child": [
|
|
294
|
+
{
|
|
295
|
+
"resource": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
296
|
+
"action": "read:posts"
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
"valid": true
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"note": "a bare host does not carry a malformed api: id",
|
|
303
|
+
"parent": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
304
|
+
"child": [
|
|
305
|
+
{
|
|
306
|
+
"resource": "api:api.dfos.com/topics/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
307
|
+
"action": "read:posts"
|
|
308
|
+
}
|
|
309
|
+
],
|
|
310
|
+
"valid": false
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"note": "mailbox: stays exact — the hierarchy is api:-only",
|
|
314
|
+
"parent": [{ "resource": "mailbox:9ctvrdn9vedda7efetrhcdakfh4cr2k", "action": "deposit" }],
|
|
315
|
+
"child": [
|
|
316
|
+
{
|
|
317
|
+
"resource": "mailbox:9ctvrdn9vedda7efetrhcdakfh4cr2k/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
318
|
+
"action": "deposit"
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
"valid": false
|
|
322
|
+
}
|
|
323
|
+
],
|
|
324
|
+
"matches": [
|
|
325
|
+
{
|
|
326
|
+
"note": "bare host entry satisfies a space-addressed demand",
|
|
327
|
+
"att": [{ "resource": "api:api.dfos.com", "action": "read:posts,write:comments" }],
|
|
328
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
329
|
+
"action": "write:comments",
|
|
330
|
+
"covers": true
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"note": "child entry satisfies its own space",
|
|
334
|
+
"att": [
|
|
335
|
+
{
|
|
336
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
337
|
+
"action": "read:posts"
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
341
|
+
"action": "read:posts",
|
|
342
|
+
"covers": true
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"note": "child entry does not satisfy the bare host",
|
|
346
|
+
"att": [
|
|
347
|
+
{
|
|
348
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
349
|
+
"action": "read:profile"
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
"resource": "api:api.dfos.com",
|
|
353
|
+
"action": "read:profile",
|
|
354
|
+
"covers": false
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"note": "child entry does not satisfy a sibling space",
|
|
358
|
+
"att": [
|
|
359
|
+
{
|
|
360
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
361
|
+
"action": "read:posts"
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
"resource": "api:api.dfos.com/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
365
|
+
"action": "read:posts",
|
|
366
|
+
"covers": false
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"note": "the action must be in the covering entry's set",
|
|
370
|
+
"att": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
371
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
372
|
+
"action": "write:comments",
|
|
373
|
+
"covers": false
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"note": "any one entry may cover; the scan does not stop at the first api: entry",
|
|
377
|
+
"att": [
|
|
378
|
+
{
|
|
379
|
+
"resource": "api:api.dfos.com/spaces/cv7n8vkvr64cctf3294h9k4eanhff8z",
|
|
380
|
+
"action": "read:posts"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
384
|
+
"action": "read:posts,write:upvotes"
|
|
385
|
+
}
|
|
386
|
+
],
|
|
387
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
388
|
+
"action": "write:upvotes",
|
|
389
|
+
"covers": true
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"note": "host mismatch",
|
|
393
|
+
"att": [{ "resource": "api:api.dfos.com", "action": "read:posts" }],
|
|
394
|
+
"resource": "api:api.example.org/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
395
|
+
"action": "read:posts",
|
|
396
|
+
"covers": false
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"note": "existing bare-host behavior is unchanged: exact host, exact action",
|
|
400
|
+
"att": [{ "resource": "api:api.dfos.com", "action": "read:profile,read:email" }],
|
|
401
|
+
"resource": "api:api.dfos.com",
|
|
402
|
+
"action": "read:email",
|
|
403
|
+
"covers": true
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"note": "read:* is a literal token and matches no real route",
|
|
407
|
+
"att": [{ "resource": "api:api.dfos.com", "action": "read:*" }],
|
|
408
|
+
"resource": "api:api.dfos.com/spaces/9ctvrdn9vedda7efetrhcdakfh4cr2k",
|
|
409
|
+
"action": "read:posts",
|
|
410
|
+
"covers": false
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
}
|