was-teaching-server 0.9.1
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/CHANGELOG.md +1432 -0
- package/LICENSE +661 -0
- package/README.md +305 -0
- package/common/css/materialize.1.0.0.min.css +13 -0
- package/dist/auth-header-hooks.d.ts +84 -0
- package/dist/auth-header-hooks.d.ts.map +1 -0
- package/dist/auth-header-hooks.js +136 -0
- package/dist/auth-header-hooks.js.map +1 -0
- package/dist/authorize.d.ts +45 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +76 -0
- package/dist/authorize.js.map +1 -0
- package/dist/backends/filesystem.d.ts +1179 -0
- package/dist/backends/filesystem.d.ts.map +1 -0
- package/dist/backends/filesystem.js +2727 -0
- package/dist/backends/filesystem.js.map +1 -0
- package/dist/backends/postgres.d.ts +833 -0
- package/dist/backends/postgres.d.ts.map +1 -0
- package/dist/backends/postgres.js +2164 -0
- package/dist/backends/postgres.js.map +1 -0
- package/dist/backends/postgresSchema.d.ts +36 -0
- package/dist/backends/postgresSchema.d.ts.map +1 -0
- package/dist/backends/postgresSchema.js +203 -0
- package/dist/backends/postgresSchema.js.map +1 -0
- package/dist/config.default.d.ts +360 -0
- package/dist/config.default.d.ts.map +1 -0
- package/dist/config.default.js +493 -0
- package/dist/config.default.js.map +1 -0
- package/dist/corsProxy.d.ts +45 -0
- package/dist/corsProxy.d.ts.map +1 -0
- package/dist/corsProxy.js +278 -0
- package/dist/corsProxy.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +174 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +654 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +960 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/atomicFile.d.ts +61 -0
- package/dist/lib/atomicFile.d.ts.map +1 -0
- package/dist/lib/atomicFile.js +150 -0
- package/dist/lib/atomicFile.js.map +1 -0
- package/dist/lib/backendRegistry.d.ts +59 -0
- package/dist/lib/backendRegistry.d.ts.map +1 -0
- package/dist/lib/backendRegistry.js +92 -0
- package/dist/lib/backendRegistry.js.map +1 -0
- package/dist/lib/backendUsage.d.ts +28 -0
- package/dist/lib/backendUsage.d.ts.map +1 -0
- package/dist/lib/backendUsage.js +55 -0
- package/dist/lib/backendUsage.js.map +1 -0
- package/dist/lib/backends.d.ts +142 -0
- package/dist/lib/backends.d.ts.map +1 -0
- package/dist/lib/backends.js +272 -0
- package/dist/lib/backends.js.map +1 -0
- package/dist/lib/blindedIndex.d.ts +156 -0
- package/dist/lib/blindedIndex.d.ts.map +1 -0
- package/dist/lib/blindedIndex.js +338 -0
- package/dist/lib/blindedIndex.js.map +1 -0
- package/dist/lib/collectionDescription.d.ts +33 -0
- package/dist/lib/collectionDescription.d.ts.map +1 -0
- package/dist/lib/collectionDescription.js +21 -0
- package/dist/lib/collectionDescription.js.map +1 -0
- package/dist/lib/cursor.d.ts +18 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +50 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/edvEnvelope.d.ts +50 -0
- package/dist/lib/edvEnvelope.d.ts.map +1 -0
- package/dist/lib/edvEnvelope.js +106 -0
- package/dist/lib/edvEnvelope.js.map +1 -0
- package/dist/lib/encryption.d.ts +192 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +383 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/etag.d.ts +33 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +36 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/exportManifest.d.ts +29 -0
- package/dist/lib/exportManifest.d.ts.map +1 -0
- package/dist/lib/exportManifest.js +77 -0
- package/dist/lib/exportManifest.js.map +1 -0
- package/dist/lib/importTar.d.ts +81 -0
- package/dist/lib/importTar.d.ts.map +1 -0
- package/dist/lib/importTar.js +301 -0
- package/dist/lib/importTar.js.map +1 -0
- package/dist/lib/isJson.d.ts +16 -0
- package/dist/lib/isJson.d.ts.map +1 -0
- package/dist/lib/isJson.js +17 -0
- package/dist/lib/isJson.js.map +1 -0
- package/dist/lib/keyEpoch.d.ts +38 -0
- package/dist/lib/keyEpoch.d.ts.map +1 -0
- package/dist/lib/keyEpoch.js +75 -0
- package/dist/lib/keyEpoch.js.map +1 -0
- package/dist/lib/keyedMutex.d.ts +28 -0
- package/dist/lib/keyedMutex.d.ts.map +1 -0
- package/dist/lib/keyedMutex.js +45 -0
- package/dist/lib/keyedMutex.js.map +1 -0
- package/dist/lib/kmsModule.d.ts +62 -0
- package/dist/lib/kmsModule.d.ts.map +1 -0
- package/dist/lib/kmsModule.js +327 -0
- package/dist/lib/kmsModule.js.map +1 -0
- package/dist/lib/kmsRecordCipher.d.ts +82 -0
- package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
- package/dist/lib/kmsRecordCipher.js +226 -0
- package/dist/lib/kmsRecordCipher.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/paths.d.ts +273 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +270 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preconditions.d.ts +65 -0
- package/dist/lib/preconditions.d.ts.map +1 -0
- package/dist/lib/preconditions.js +112 -0
- package/dist/lib/preconditions.js.map +1 -0
- package/dist/lib/resourceFileName.d.ts +41 -0
- package/dist/lib/resourceFileName.d.ts.map +1 -0
- package/dist/lib/resourceFileName.js +58 -0
- package/dist/lib/resourceFileName.js.map +1 -0
- package/dist/lib/revocations.d.ts +49 -0
- package/dist/lib/revocations.d.ts.map +1 -0
- package/dist/lib/revocations.js +86 -0
- package/dist/lib/revocations.js.map +1 -0
- package/dist/lib/validateDid.d.ts +19 -0
- package/dist/lib/validateDid.d.ts.map +1 -0
- package/dist/lib/validateDid.js +35 -0
- package/dist/lib/validateDid.js.map +1 -0
- package/dist/lib/validateId.d.ts +66 -0
- package/dist/lib/validateId.d.ts.map +1 -0
- package/dist/lib/validateId.js +126 -0
- package/dist/lib/validateId.js.map +1 -0
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +78 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/provisioning.d.ts +38 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/requests/BackendRequest.d.ts +61 -0
- package/dist/requests/BackendRequest.d.ts.map +1 -0
- package/dist/requests/BackendRequest.js +152 -0
- package/dist/requests/BackendRequest.js.map +1 -0
- package/dist/requests/CollectionRequest.d.ts +234 -0
- package/dist/requests/CollectionRequest.d.ts.map +1 -0
- package/dist/requests/CollectionRequest.js +705 -0
- package/dist/requests/CollectionRequest.js.map +1 -0
- package/dist/requests/KeyRequest.d.ts +115 -0
- package/dist/requests/KeyRequest.d.ts.map +1 -0
- package/dist/requests/KeyRequest.js +468 -0
- package/dist/requests/KeyRequest.js.map +1 -0
- package/dist/requests/KeystoreRequest.d.ts +99 -0
- package/dist/requests/KeystoreRequest.d.ts.map +1 -0
- package/dist/requests/KeystoreRequest.js +247 -0
- package/dist/requests/KeystoreRequest.js.map +1 -0
- package/dist/requests/PolicyRequest.d.ts +53 -0
- package/dist/requests/PolicyRequest.d.ts.map +1 -0
- package/dist/requests/PolicyRequest.js +124 -0
- package/dist/requests/PolicyRequest.js.map +1 -0
- package/dist/requests/ResourceRequest.d.ts +134 -0
- package/dist/requests/ResourceRequest.d.ts.map +1 -0
- package/dist/requests/ResourceRequest.js +548 -0
- package/dist/requests/ResourceRequest.js.map +1 -0
- package/dist/requests/RevocationRequest.d.ts +67 -0
- package/dist/requests/RevocationRequest.d.ts.map +1 -0
- package/dist/requests/RevocationRequest.js +187 -0
- package/dist/requests/RevocationRequest.js.map +1 -0
- package/dist/requests/SpaceRequest.d.ts +204 -0
- package/dist/requests/SpaceRequest.d.ts.map +1 -0
- package/dist/requests/SpaceRequest.js +520 -0
- package/dist/requests/SpaceRequest.js.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.js +159 -0
- package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
- package/dist/requests/collectionContext.d.ts +21 -0
- package/dist/requests/collectionContext.d.ts.map +1 -0
- package/dist/requests/collectionContext.js +30 -0
- package/dist/requests/collectionContext.js.map +1 -0
- package/dist/requests/controllerConsent.d.ts +58 -0
- package/dist/requests/controllerConsent.d.ts.map +1 -0
- package/dist/requests/controllerConsent.js +73 -0
- package/dist/requests/controllerConsent.js.map +1 -0
- package/dist/requests/keystoreContext.d.ts +68 -0
- package/dist/requests/keystoreContext.d.ts.map +1 -0
- package/dist/requests/keystoreContext.js +87 -0
- package/dist/requests/keystoreContext.js.map +1 -0
- package/dist/requests/resourceInput.d.ts +19 -0
- package/dist/requests/resourceInput.d.ts.map +1 -0
- package/dist/requests/resourceInput.js +102 -0
- package/dist/requests/resourceInput.js.map +1 -0
- package/dist/requests/spaceContext.d.ts +127 -0
- package/dist/requests/spaceContext.d.ts.map +1 -0
- package/dist/requests/spaceContext.js +201 -0
- package/dist/requests/spaceContext.js.map +1 -0
- package/dist/routes.d.ts +55 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +306 -0
- package/dist/routes.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/start.d.ts +7 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +61 -0
- package/dist/start.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +47 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +915 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/home.hbs +9 -0
- package/dist/views/templates/main.hbs +12 -0
- package/dist/zcap.d.ts +267 -0
- package/dist/zcap.d.ts.map +1 -0
- package/dist/zcap.js +478 -0
- package/dist/zcap.js.map +1 -0
- package/package.json +109 -0
- package/src/auth-header-hooks.ts +163 -0
- package/src/authorize.ts +117 -0
- package/src/backends/filesystem.ts +3626 -0
- package/src/backends/postgres.ts +3073 -0
- package/src/backends/postgresSchema.ts +231 -0
- package/src/config.default.ts +657 -0
- package/src/corsProxy.ts +326 -0
- package/src/declarations.d.ts +28 -0
- package/src/digest.ts +223 -0
- package/src/errors.ts +1122 -0
- package/src/index.ts +21 -0
- package/src/lib/atomicFile.ts +176 -0
- package/src/lib/backendRegistry.ts +139 -0
- package/src/lib/backendUsage.ts +79 -0
- package/src/lib/backends.ts +351 -0
- package/src/lib/blindedIndex.ts +466 -0
- package/src/lib/collectionDescription.ts +43 -0
- package/src/lib/cursor.ts +52 -0
- package/src/lib/edvEnvelope.ts +120 -0
- package/src/lib/encryption.ts +472 -0
- package/src/lib/etag.ts +40 -0
- package/src/lib/exportManifest.ts +115 -0
- package/src/lib/importTar.ts +381 -0
- package/src/lib/isJson.ts +18 -0
- package/src/lib/keyEpoch.ts +89 -0
- package/src/lib/keyedMutex.ts +45 -0
- package/src/lib/kmsModule.ts +451 -0
- package/src/lib/kmsRecordCipher.ts +292 -0
- package/src/lib/pagination.ts +22 -0
- package/src/lib/paths.ts +382 -0
- package/src/lib/preconditions.ts +145 -0
- package/src/lib/resourceFileName.ts +69 -0
- package/src/lib/revocations.ts +116 -0
- package/src/lib/validateDid.ts +41 -0
- package/src/lib/validateId.ts +148 -0
- package/src/plugin.ts +288 -0
- package/src/policy.ts +176 -0
- package/src/provisioning.ts +98 -0
- package/src/requests/BackendRequest.ts +219 -0
- package/src/requests/CollectionRequest.ts +899 -0
- package/src/requests/KeyRequest.ts +617 -0
- package/src/requests/KeystoreRequest.ts +325 -0
- package/src/requests/PolicyRequest.ts +169 -0
- package/src/requests/ResourceRequest.ts +649 -0
- package/src/requests/RevocationRequest.ts +267 -0
- package/src/requests/SpaceRequest.ts +686 -0
- package/src/requests/SpacesRepositoryRequest.ts +194 -0
- package/src/requests/collectionContext.ts +41 -0
- package/src/requests/controllerConsent.ts +108 -0
- package/src/requests/keystoreContext.ts +124 -0
- package/src/requests/resourceInput.ts +118 -0
- package/src/requests/spaceContext.ts +311 -0
- package/src/routes.ts +420 -0
- package/src/server.ts +69 -0
- package/src/start.ts +71 -0
- package/src/storage.ts +60 -0
- package/src/types.ts +1016 -0
- package/src/views/home.hbs +9 -0
- package/src/views/templates/main.hbs +12 -0
- package/src/zcap.ts +657 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zcap revocation chain-inspection hook. Wired into every keystore-rooted
|
|
3
|
+
* and Space-rooted
|
|
4
|
+
* verification as the zcap library's `inspectCapabilityChain` extension
|
|
5
|
+
* point, it fails a verification whose dereferenced chain contains any
|
|
6
|
+
* capability with a stored revocation. Both route families share this hook;
|
|
7
|
+
* they differ only in the scope the storage lookup is keyed on (a keystore or
|
|
8
|
+
* a Space). Also home to the revocation-record file-name codec shared by the
|
|
9
|
+
* filesystem store and both backends' Space exports.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
/**
|
|
13
|
+
* The file name a revocation record is stored (and archived) under:
|
|
14
|
+
* the `(delegator, capabilityId)` unique key folded into a SHA-256 digest,
|
|
15
|
+
* plus `.json`. Both parts are arbitrary-length URIs, so hashing (rather than
|
|
16
|
+
* encoding) keeps the name fixed-width and filesystem-safe. Shared by the
|
|
17
|
+
* filesystem store and both backends' `exportSpace`, so the same record
|
|
18
|
+
* always lands under the same name.
|
|
19
|
+
* @param options {object}
|
|
20
|
+
* @param options.delegator {string} the revoked capability's delegator
|
|
21
|
+
* @param options.capabilityId {string} the revoked capability's id
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function revocationFileName({ delegator, capabilityId }) {
|
|
25
|
+
const digest = createHash('sha256')
|
|
26
|
+
.update(`${delegator}\n${capabilityId}`)
|
|
27
|
+
.digest('hex');
|
|
28
|
+
return `${digest}.json`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the `(capabilityId, delegator)` lookup pairs from a verified,
|
|
32
|
+
* dereferenced capability chain. The root capability is skipped -- root zcaps
|
|
33
|
+
* cannot be revoked -- and so is any link whose verify result carries no
|
|
34
|
+
* delegator (nothing to key a revocation record on).
|
|
35
|
+
*
|
|
36
|
+
* @param options {object}
|
|
37
|
+
* @param options.capabilityChain {object[]} the dereferenced chain (root to
|
|
38
|
+
* tail)
|
|
39
|
+
* @param options.capabilityChainMeta {object[]} the per-capability verify
|
|
40
|
+
* results (the root's entry has a `null` `verifyResult`)
|
|
41
|
+
* @returns {CapabilitySummary[]}
|
|
42
|
+
*/
|
|
43
|
+
export function capabilitySummaries({ capabilityChain, capabilityChainMeta }) {
|
|
44
|
+
const summaries = [];
|
|
45
|
+
for (const [index, capability] of capabilityChain.entries()) {
|
|
46
|
+
// skip the root zcap; it cannot be revoked
|
|
47
|
+
if (index === 0) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const verifyResult = capabilityChainMeta[index]?.verifyResult;
|
|
51
|
+
const delegator = verifyResult?.results?.[0]?.purposeResult?.delegator?.id;
|
|
52
|
+
if (delegator) {
|
|
53
|
+
summaries.push({ capabilityId: capability.id, delegator });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return summaries;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds the `inspectCapabilityChain` hook for one scope: valid when no
|
|
60
|
+
* delegated capability in the chain has a stored revocation under that
|
|
61
|
+
* keystore or Space (a chain of just the root has nothing to check, so a bare
|
|
62
|
+
* root invocation never reaches the store).
|
|
63
|
+
*
|
|
64
|
+
* @param options {object}
|
|
65
|
+
* @param options.storage {StorageBackend} supplies the revocation store
|
|
66
|
+
* @param options.scope {RevocationScope} the keystore or Space the lookup is
|
|
67
|
+
* keyed on
|
|
68
|
+
* @returns {InspectCapabilityChain}
|
|
69
|
+
*/
|
|
70
|
+
export function revocationChainInspector({ storage, scope }) {
|
|
71
|
+
return async (details) => {
|
|
72
|
+
const capabilities = capabilitySummaries(details);
|
|
73
|
+
if (capabilities.length === 0) {
|
|
74
|
+
return { valid: true };
|
|
75
|
+
}
|
|
76
|
+
const revoked = await storage.isRevoked({ scope, capabilities });
|
|
77
|
+
if (revoked) {
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
error: new Error('One or more capabilities in the chain have been revoked.')
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { valid: true };
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=revocations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revocations.js","sourceRoot":"","sources":["../../src/lib/revocations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAWxC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,SAAS,EACT,YAAY,EAIb;IACC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;SAChC,MAAM,CAAC,GAAG,SAAS,KAAK,YAAY,EAAE,CAAC;SACvC,MAAM,CAAC,KAAK,CAAC,CAAA;IAChB,OAAO,GAAG,MAAM,OAAO,CAAA;AACzB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,eAAe,EACf,mBAAmB,EACI;IACvB,MAAM,SAAS,GAAwB,EAAE,CAAA;IACzC,KAAK,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5D,2CAA2C;QAC3C,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,SAAQ;QACV,CAAC;QACD,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,EAAE,YAEzC,CAAA;QACR,MAAM,SAAS,GAAG,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,CAAA;QAC1E,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CAAC,EACvC,OAAO,EACP,KAAK,EAIN;IACC,OAAO,KAAK,EAAC,OAAO,EAAC,EAAE;QACrB,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;QACjD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QACxB,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;QAChE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,IAAI,KAAK,CACd,0DAA0D,CAC3D;aACF,CAAA;QACH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACxB,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IDID } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns true when `value` is a syntactically valid Ed25519 `did:key` DID.
|
|
4
|
+
* @param value {unknown}
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export declare function isValidController(value: unknown): value is IDID;
|
|
8
|
+
/**
|
|
9
|
+
* Asserts that `controller` is a valid Ed25519 `did:key`, throwing
|
|
10
|
+
* InvalidControllerError (400) otherwise.
|
|
11
|
+
* @param controller {unknown} the `controller` value from the request body
|
|
12
|
+
* @param options {object}
|
|
13
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertValidController(controller: unknown, { requestName }?: {
|
|
17
|
+
requestName?: string;
|
|
18
|
+
}): void;
|
|
19
|
+
//# sourceMappingURL=validateDid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateDid.d.ts","sourceRoot":"","sources":["../../src/lib/validateDid.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAMvC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,OAAO,EACnB,EAAE,WAAW,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,IAAI,CAIN"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation for the `controller` DID supplied in Space Description request
|
|
3
|
+
* bodies. The server's only DID method is `did:key` with Ed25519 keys, whose
|
|
4
|
+
* multibase encoding always begins `z6Mk` (the `0xed01` Ed25519-pub multicodec
|
|
5
|
+
* prefix) followed by base58btc characters. This asserts that shape at the
|
|
6
|
+
* request layer so a malformed or non-`did:key` controller is rejected on the
|
|
7
|
+
* way in, rather than being stored and only failing later at
|
|
8
|
+
* capability-verification time.
|
|
9
|
+
*/
|
|
10
|
+
import { InvalidControllerError } from '../errors.js';
|
|
11
|
+
// An Ed25519 `did:key` is `did:key:` + `z6Mk` + a base58btc payload. The
|
|
12
|
+
// base58btc (Bitcoin) alphabet omits `0`, `O`, `I`, and `l`.
|
|
13
|
+
const DID_KEY_ED25519_PATTERN = /^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]+$/;
|
|
14
|
+
/**
|
|
15
|
+
* Returns true when `value` is a syntactically valid Ed25519 `did:key` DID.
|
|
16
|
+
* @param value {unknown}
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function isValidController(value) {
|
|
20
|
+
return typeof value === 'string' && DID_KEY_ED25519_PATTERN.test(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Asserts that `controller` is a valid Ed25519 `did:key`, throwing
|
|
24
|
+
* InvalidControllerError (400) otherwise.
|
|
25
|
+
* @param controller {unknown} the `controller` value from the request body
|
|
26
|
+
* @param options {object}
|
|
27
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
export function assertValidController(controller, { requestName } = {}) {
|
|
31
|
+
if (!isValidController(controller)) {
|
|
32
|
+
throw new InvalidControllerError({ requestName });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=validateDid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateDid.js","sourceRoot":"","sources":["../../src/lib/validateDid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAGrD,yEAAyE;AACzE,6DAA6D;AAC7D,MAAM,uBAAuB,GAAG,qCAAqC,CAAA;AAErE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAmB,EACnB,EAAE,WAAW,KAA+B,EAAE;IAE9C,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,sBAAsB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAA;IACnD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Which kind of id is being validated (selects the thrown error class). */
|
|
2
|
+
export type IdKind = 'space' | 'collection' | 'resource';
|
|
3
|
+
/**
|
|
4
|
+
* Whether `id` is a single, URL-safe path segment: non-empty, not `.` / `..`,
|
|
5
|
+
* free of path separators, and made only of the RFC 3986 "unreserved" charset
|
|
6
|
+
* ({@link ID_PATTERN}). The shared safety predicate behind `assertValidId` (and
|
|
7
|
+
* `assertValidBackendId` in `lib/backends.ts`); does **not** check the reserved
|
|
8
|
+
* path-segment registry, which is id-kind specific.
|
|
9
|
+
* @param id {string}
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export declare function isUrlSafeSegment(id: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Reserved path segments from the spec's Reserved Path Segment Registry (plus
|
|
15
|
+
* the server's own non-spec `import` endpoint). A client-chosen Collection or
|
|
16
|
+
* Resource id matching one of these would shadow the reserved route at that
|
|
17
|
+
* position (e.g. a Collection named `export` would shadow
|
|
18
|
+
* `/space/{id}/export`), so the spec requires rejecting it with 409
|
|
19
|
+
* `reserved-id`. Space ids have no reserved siblings (`/space/{id}` has no
|
|
20
|
+
* static neighbors), so no set exists for the `space` kind.
|
|
21
|
+
*
|
|
22
|
+
* Exported as the server's authoritative per-kind sets so a client can mirror
|
|
23
|
+
* them rather than hand-maintaining a copy (client #13). NOTE the one known
|
|
24
|
+
* divergence from the pure spec registry: `import` is this server's non-spec
|
|
25
|
+
* tar-import endpoint, so a client mirroring the *spec* registry should omit
|
|
26
|
+
* it. Kept byte-identical to `@interop/storage-core`'s exported registry
|
|
27
|
+
* (locked by a drift-guard test); the local definition keeps the id-safety
|
|
28
|
+
* logic self-contained.
|
|
29
|
+
*/
|
|
30
|
+
export declare const RESERVED_COLLECTION_IDS: Set<string>;
|
|
31
|
+
export declare const RESERVED_RESOURCE_IDS: Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Asserts that an id is a single, URL-safe path segment that does not collide
|
|
34
|
+
* with a reserved path segment -- throwing the typed 400 `invalid-id` error
|
|
35
|
+
* matching `kind` for an unsafe id, or the 409 `reserved-id` error for a
|
|
36
|
+
* reserved-segment collision.
|
|
37
|
+
* @param id {string} the id parsed from a URL param, body, or tar entry
|
|
38
|
+
* @param options {object}
|
|
39
|
+
* @param options.kind {IdKind} which id is being validated
|
|
40
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
41
|
+
* @returns {void}
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertValidId(id: string, { kind, requestName }: {
|
|
44
|
+
kind: IdKind;
|
|
45
|
+
requestName?: string;
|
|
46
|
+
}): void;
|
|
47
|
+
/**
|
|
48
|
+
* Convenience wrapper that validates whichever of `spaceId` / `collectionId` /
|
|
49
|
+
* `resourceId` are present on a request's params object. Call at the top of a
|
|
50
|
+
* handler, before any storage access.
|
|
51
|
+
* @param ids {object}
|
|
52
|
+
* @param [ids.spaceId] {string}
|
|
53
|
+
* @param [ids.collectionId] {string}
|
|
54
|
+
* @param [ids.resourceId] {string}
|
|
55
|
+
* @param options {object}
|
|
56
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertValidIds(ids: {
|
|
60
|
+
spaceId?: string;
|
|
61
|
+
collectionId?: string;
|
|
62
|
+
resourceId?: string;
|
|
63
|
+
}, { requestName }?: {
|
|
64
|
+
requestName?: string;
|
|
65
|
+
}): void;
|
|
66
|
+
//# sourceMappingURL=validateId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateId.d.ts","sourceRoot":"","sources":["../../src/lib/validateId.ts"],"names":[],"mappings":"AAiBA,4EAA4E;AAC5E,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,CAAA;AAUxD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAUpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,aASlC,CAAA;AACF,eAAO,MAAM,qBAAqB,aAMhC,CAAA;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EACV,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D,IAAI,CAsBN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EACrE,EAAE,WAAW,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,IAAI,CAUN"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Id sanitization for path-traversal defense. `spaceId` / `collectionId` /
|
|
3
|
+
* `resourceId` values arrive from URL params, request bodies, and tar-entry
|
|
4
|
+
* names, then flow into filesystem paths (and glob patterns). This validator
|
|
5
|
+
* rejects anything that is not a single, URL-safe path segment -- empty, `.`,
|
|
6
|
+
* `..`, a value containing `/` or `\`, or any character outside the RFC 3986
|
|
7
|
+
* "unreserved" set -- so a malicious id can never escape its parent directory.
|
|
8
|
+
* It also rejects Collection / Resource ids that collide with the spec's
|
|
9
|
+
* Reserved Path Segment Registry (`reserved-id`, 409).
|
|
10
|
+
*/
|
|
11
|
+
import { InvalidSpaceIdError, InvalidCollectionIdError, InvalidResourceIdError, ReservedIdError } from '../errors.js';
|
|
12
|
+
/**
|
|
13
|
+
* URL-safe id charset: the RFC 3986 "unreserved" characters
|
|
14
|
+
* (ALPHA / DIGIT / `-` / `.` / `_` / `~`). This excludes path separators
|
|
15
|
+
* (`/`, `\`) and every glob metacharacter, so a validated id is safe to use
|
|
16
|
+
* both as a single path segment and inside a glob pattern.
|
|
17
|
+
*/
|
|
18
|
+
const ID_PATTERN = /^[A-Za-z0-9._~-]+$/;
|
|
19
|
+
/**
|
|
20
|
+
* Whether `id` is a single, URL-safe path segment: non-empty, not `.` / `..`,
|
|
21
|
+
* free of path separators, and made only of the RFC 3986 "unreserved" charset
|
|
22
|
+
* ({@link ID_PATTERN}). The shared safety predicate behind `assertValidId` (and
|
|
23
|
+
* `assertValidBackendId` in `lib/backends.ts`); does **not** check the reserved
|
|
24
|
+
* path-segment registry, which is id-kind specific.
|
|
25
|
+
* @param id {string}
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
export function isUrlSafeSegment(id) {
|
|
29
|
+
return (typeof id === 'string' &&
|
|
30
|
+
id.length > 0 &&
|
|
31
|
+
id !== '.' &&
|
|
32
|
+
id !== '..' &&
|
|
33
|
+
!id.includes('/') &&
|
|
34
|
+
!id.includes('\\') &&
|
|
35
|
+
ID_PATTERN.test(id));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reserved path segments from the spec's Reserved Path Segment Registry (plus
|
|
39
|
+
* the server's own non-spec `import` endpoint). A client-chosen Collection or
|
|
40
|
+
* Resource id matching one of these would shadow the reserved route at that
|
|
41
|
+
* position (e.g. a Collection named `export` would shadow
|
|
42
|
+
* `/space/{id}/export`), so the spec requires rejecting it with 409
|
|
43
|
+
* `reserved-id`. Space ids have no reserved siblings (`/space/{id}` has no
|
|
44
|
+
* static neighbors), so no set exists for the `space` kind.
|
|
45
|
+
*
|
|
46
|
+
* Exported as the server's authoritative per-kind sets so a client can mirror
|
|
47
|
+
* them rather than hand-maintaining a copy (client #13). NOTE the one known
|
|
48
|
+
* divergence from the pure spec registry: `import` is this server's non-spec
|
|
49
|
+
* tar-import endpoint, so a client mirroring the *spec* registry should omit
|
|
50
|
+
* it. Kept byte-identical to `@interop/storage-core`'s exported registry
|
|
51
|
+
* (locked by a drift-guard test); the local definition keeps the id-safety
|
|
52
|
+
* logic self-contained.
|
|
53
|
+
*/
|
|
54
|
+
export const RESERVED_COLLECTION_IDS = new Set([
|
|
55
|
+
'backends',
|
|
56
|
+
'collections',
|
|
57
|
+
'export',
|
|
58
|
+
'import', // non-spec: this server's tar-import endpoint
|
|
59
|
+
'linkset',
|
|
60
|
+
'policy',
|
|
61
|
+
'query',
|
|
62
|
+
'quotas'
|
|
63
|
+
]);
|
|
64
|
+
export const RESERVED_RESOURCE_IDS = new Set([
|
|
65
|
+
'backend',
|
|
66
|
+
'linkset',
|
|
67
|
+
'policy',
|
|
68
|
+
'query',
|
|
69
|
+
'quota'
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Asserts that an id is a single, URL-safe path segment that does not collide
|
|
73
|
+
* with a reserved path segment -- throwing the typed 400 `invalid-id` error
|
|
74
|
+
* matching `kind` for an unsafe id, or the 409 `reserved-id` error for a
|
|
75
|
+
* reserved-segment collision.
|
|
76
|
+
* @param id {string} the id parsed from a URL param, body, or tar entry
|
|
77
|
+
* @param options {object}
|
|
78
|
+
* @param options.kind {IdKind} which id is being validated
|
|
79
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
export function assertValidId(id, { kind, requestName }) {
|
|
83
|
+
if (!isUrlSafeSegment(id)) {
|
|
84
|
+
switch (kind) {
|
|
85
|
+
case 'collection':
|
|
86
|
+
throw new InvalidCollectionIdError({ requestName });
|
|
87
|
+
case 'resource':
|
|
88
|
+
throw new InvalidResourceIdError({ requestName });
|
|
89
|
+
default:
|
|
90
|
+
throw new InvalidSpaceIdError({ requestName });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Collections and Resources may not take a reserved path-segment name.
|
|
94
|
+
const reserved = kind === 'collection'
|
|
95
|
+
? RESERVED_COLLECTION_IDS
|
|
96
|
+
: kind === 'resource'
|
|
97
|
+
? RESERVED_RESOURCE_IDS
|
|
98
|
+
: undefined;
|
|
99
|
+
if (reserved?.has(id)) {
|
|
100
|
+
throw new ReservedIdError({ kind, id });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convenience wrapper that validates whichever of `spaceId` / `collectionId` /
|
|
105
|
+
* `resourceId` are present on a request's params object. Call at the top of a
|
|
106
|
+
* handler, before any storage access.
|
|
107
|
+
* @param ids {object}
|
|
108
|
+
* @param [ids.spaceId] {string}
|
|
109
|
+
* @param [ids.collectionId] {string}
|
|
110
|
+
* @param [ids.resourceId] {string}
|
|
111
|
+
* @param options {object}
|
|
112
|
+
* @param [options.requestName] {string} request name used in the error title
|
|
113
|
+
* @returns {void}
|
|
114
|
+
*/
|
|
115
|
+
export function assertValidIds(ids, { requestName } = {}) {
|
|
116
|
+
if (ids.spaceId !== undefined) {
|
|
117
|
+
assertValidId(ids.spaceId, { kind: 'space', requestName });
|
|
118
|
+
}
|
|
119
|
+
if (ids.collectionId !== undefined) {
|
|
120
|
+
assertValidId(ids.collectionId, { kind: 'collection', requestName });
|
|
121
|
+
}
|
|
122
|
+
if (ids.resourceId !== undefined) {
|
|
123
|
+
assertValidId(ids.resourceId, { kind: 'resource', requestName });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=validateId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateId.js","sourceRoot":"","sources":["../../src/lib/validateId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAA;AAKrB;;;;;GAKG;AACH,MAAM,UAAU,GAAG,oBAAoB,CAAA;AAEvC;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO,CACL,OAAO,EAAE,KAAK,QAAQ;QACtB,EAAE,CAAC,MAAM,GAAG,CAAC;QACb,EAAE,KAAK,GAAG;QACV,EAAE,KAAK,IAAI;QACX,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;QACjB,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CACpB,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IAC7C,UAAU;IACV,aAAa;IACb,QAAQ;IACR,QAAQ,EAAE,8CAA8C;IACxD,SAAS;IACT,QAAQ;IACR,OAAO;IACP,QAAQ;CACT,CAAC,CAAA;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IAC3C,SAAS;IACT,SAAS;IACT,QAAQ;IACR,OAAO;IACP,OAAO;CACR,CAAC,CAAA;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,EAAU,EACV,EAAE,IAAI,EAAE,WAAW,EAA0C;IAE7D,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;QAC1B,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,YAAY;gBACf,MAAM,IAAI,wBAAwB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAA;YACrD,KAAK,UAAU;gBACb,MAAM,IAAI,sBAAsB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAA;YACnD;gBACE,MAAM,IAAI,mBAAmB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,MAAM,QAAQ,GACZ,IAAI,KAAK,YAAY;QACnB,CAAC,CAAC,uBAAuB;QACzB,CAAC,CAAC,IAAI,KAAK,UAAU;YACnB,CAAC,CAAC,qBAAqB;YACvB,CAAC,CAAC,SAAS,CAAA;IACjB,IAAI,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACzC,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAqE,EACrE,EAAE,WAAW,KAA+B,EAAE;IAE9C,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9B,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACnC,aAAa,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAA;IACtE,CAAC;IACD,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjC,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAA;IAClE,CAAC;AACH,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WAS protocol surface as a registerable Fastify plugin (`fastifyWas`):
|
|
3
|
+
* the storage/config decorations, CORS, multipart and content-type parsers,
|
|
4
|
+
* and the route groups (the four WAS groups plus the WebKMS `/kms` facet) --
|
|
5
|
+
* everything from routes.ts down, including the auth/digest hook chains and
|
|
6
|
+
* the error handler those groups install.
|
|
7
|
+
*
|
|
8
|
+
* The community `createApp()` (server.ts) registers this plugin with defaults;
|
|
9
|
+
* a hardened downstream composition registers the same plugin (with its own
|
|
10
|
+
* backend and policy plugins around it) and inherits the identical wire
|
|
11
|
+
* behavior. Wrapped with `fastify-plugin`, so the decorations and parsers land
|
|
12
|
+
* on the root instance -- while each route group still creates its own
|
|
13
|
+
* encapsulated context for its hooks.
|
|
14
|
+
*/
|
|
15
|
+
import type { FastifyInstance } from 'fastify';
|
|
16
|
+
import type { StorageBackend, BackendProviderRegistry, KmsRecordKekRegistry, AuthorizeProvisioning } from './types.js';
|
|
17
|
+
export interface FastifyWasOptions {
|
|
18
|
+
/**
|
|
19
|
+
* This server's base URL; used to build and match ZCap invocationTarget URLs
|
|
20
|
+
* (host and port must match exactly). When provided, it must be an absolute
|
|
21
|
+
* `http:`/`https:` URL with no path, query, or fragment (validated at
|
|
22
|
+
* registration -- sub-path deployment is not supported).
|
|
23
|
+
*/
|
|
24
|
+
serverUrl?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Persistence backend to use; defaults to a filesystem backend rooted at the
|
|
27
|
+
* project `data/` directory. Tests inject their own (e.g. a
|
|
28
|
+
* FileSystemBackend over a temp dir).
|
|
29
|
+
*/
|
|
30
|
+
backend?: StorageBackend;
|
|
31
|
+
/**
|
|
32
|
+
* Per-Space storage limit in bytes (spec "Quotas"); applied only to the
|
|
33
|
+
* default backend (an injected `backend` carries its own `capacityBytes`).
|
|
34
|
+
* `undefined` means unlimited; `Infinity` (an explicit `unlimited`) is
|
|
35
|
+
* normalized by the backend to the same no-limit behavior.
|
|
36
|
+
*/
|
|
37
|
+
storageLimitPerSpace?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Per-upload size cap in bytes (spec "Quotas", `maxUploadBytes`); applied
|
|
40
|
+
* only to the default backend (an injected `backend` carries its own).
|
|
41
|
+
* `undefined` applies the backend's default-on cap
|
|
42
|
+
* ({@link DEFAULT_MAX_UPLOAD_BYTES}); `Infinity` (an explicit `unlimited`)
|
|
43
|
+
* disables the cap.
|
|
44
|
+
*/
|
|
45
|
+
maxUploadBytes?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Max Spaces a single controller may create (spec "Quotas", a default-on
|
|
48
|
+
* count quota); applied only to the default backend (an injected `backend`
|
|
49
|
+
* carries its own). `undefined` applies the backend's default
|
|
50
|
+
* ({@link DEFAULT_MAX_SPACES_PER_CONTROLLER}); `Infinity` (an explicit
|
|
51
|
+
* `unlimited`) disables the cap.
|
|
52
|
+
*/
|
|
53
|
+
maxSpacesPerController?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Max Collections a single Space may hold (spec "Quotas", a default-on count
|
|
56
|
+
* quota); applied only to the default backend (an injected `backend` carries
|
|
57
|
+
* its own). `undefined` applies the backend's default
|
|
58
|
+
* ({@link DEFAULT_MAX_COLLECTIONS_PER_SPACE}); `Infinity` (an explicit
|
|
59
|
+
* `unlimited`) disables the cap.
|
|
60
|
+
*/
|
|
61
|
+
maxCollectionsPerSpace?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Max live Resources a single Space may hold across all its Collections (spec
|
|
64
|
+
* "Quotas", a default-on count quota); applied only to the default backend
|
|
65
|
+
* (an injected `backend` carries its own). `undefined` applies the backend's
|
|
66
|
+
* default ({@link DEFAULT_MAX_RESOURCES_PER_SPACE}); `Infinity` (an explicit
|
|
67
|
+
* `unlimited`) disables the cap.
|
|
68
|
+
*/
|
|
69
|
+
maxResourcesPerSpace?: number;
|
|
70
|
+
/**
|
|
71
|
+
* The provider-adapter registry the resolver uses to build a Collection's
|
|
72
|
+
* selected external backend; defaults to an empty map (no external backend
|
|
73
|
+
* is operable).
|
|
74
|
+
*/
|
|
75
|
+
providers?: BackendProviderRegistry;
|
|
76
|
+
/**
|
|
77
|
+
* The registration allowlist of backend `provider` names; `undefined` means
|
|
78
|
+
* permissive.
|
|
79
|
+
*/
|
|
80
|
+
enabledBackendProviders?: string[];
|
|
81
|
+
/**
|
|
82
|
+
* The at-rest WebKMS key-record encryption registry (config `KMS_RECORD_KEK` /
|
|
83
|
+
* `KMS_RECORD_KEKS` / `KMS_RECORD_CURRENT_KEK`); `undefined` (or
|
|
84
|
+
* `currentKekId: null`) disables encryption -- key records are written
|
|
85
|
+
* plaintext (the teaching default).
|
|
86
|
+
*/
|
|
87
|
+
kmsRecordKek?: KmsRecordKekRegistry;
|
|
88
|
+
/**
|
|
89
|
+
* Custom provisioning gate for `POST /spaces/` and `POST /kms/keystores`;
|
|
90
|
+
* receives `{ request }` and returns `'verify'` (normal zcap path), `'grant'`
|
|
91
|
+
* (authorized by the callback -- skip zcap verification), or `'deny'` (403).
|
|
92
|
+
* `undefined` means allow (the teaching default). Mutually exclusive with
|
|
93
|
+
* `onboardingToken`.
|
|
94
|
+
*/
|
|
95
|
+
authorizeProvisioning?: AuthorizeProvisioning;
|
|
96
|
+
/**
|
|
97
|
+
* Shared-secret gate for `POST /spaces/` and `POST /kms/keystores` (config
|
|
98
|
+
* `WAS_ONBOARDING_TOKEN`); when set, those two endpoints require an
|
|
99
|
+
* `Authorization: Bearer <token>` header, which then substitutes for zcap
|
|
100
|
+
* verification on that request. `undefined` means disabled (the teaching
|
|
101
|
+
* default). Mutually exclusive with `authorizeProvisioning`.
|
|
102
|
+
*/
|
|
103
|
+
onboardingToken?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Decorates the instance with the WAS storage/config surface, registers the
|
|
107
|
+
* protocol-level plugins and content-type parsers, and mounts the route groups.
|
|
108
|
+
* @param fastify {import('fastify').FastifyInstance}
|
|
109
|
+
* @param options {FastifyWasOptions}
|
|
110
|
+
* @returns {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
declare function wasPlugin(fastify: FastifyInstance, options: FastifyWasOptions): Promise<void>;
|
|
113
|
+
export declare const fastifyWas: typeof wasPlugin;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAe9C,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAA;IACnC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAA;IAClC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAA;IACnC;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;GAMG;AACH,iBAAe,SAAS,CACtB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAmJf;AAED,eAAO,MAAM,UAAU,kBAGrB,CAAA"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import Multipart from '@fastify/multipart';
|
|
4
|
+
import { initCollectionRoutes, initKmsRoutes, initResourceRoutes, initSpaceRoutes, initSpacesRepositoryRoutes } from './routes.js';
|
|
5
|
+
import { assertValidServerUrl } from './config.default.js';
|
|
6
|
+
import { defaultBackend } from './storage.js';
|
|
7
|
+
import { onboardingTokenAuthorizer } from './provisioning.js';
|
|
8
|
+
/**
|
|
9
|
+
* Decorates the instance with the WAS storage/config surface, registers the
|
|
10
|
+
* protocol-level plugins and content-type parsers, and mounts the route groups.
|
|
11
|
+
* @param fastify {import('fastify').FastifyInstance}
|
|
12
|
+
* @param options {FastifyWasOptions}
|
|
13
|
+
* @returns {Promise<void>}
|
|
14
|
+
*/
|
|
15
|
+
async function wasPlugin(fastify, options) {
|
|
16
|
+
const { serverUrl, backend, storageLimitPerSpace, maxUploadBytes, maxSpacesPerController, maxCollectionsPerSpace, maxResourcesPerSpace, providers, enabledBackendProviders, kmsRecordKek, authorizeProvisioning, onboardingToken } = options;
|
|
17
|
+
// Fail fast on a malformed base URL: a serverUrl carrying a path, query, or
|
|
18
|
+
// fragment silently breaks every ZCap invocationTarget match and Location
|
|
19
|
+
// header (URL-joins drop the base path), so it is rejected at registration.
|
|
20
|
+
if (serverUrl !== undefined) {
|
|
21
|
+
assertValidServerUrl(serverUrl);
|
|
22
|
+
}
|
|
23
|
+
// The two provisioning gates are alternative ways to configure the same seam.
|
|
24
|
+
if (authorizeProvisioning && onboardingToken) {
|
|
25
|
+
throw new Error('authorizeProvisioning and onboardingToken are mutually exclusive.');
|
|
26
|
+
}
|
|
27
|
+
fastify.decorate('serverUrl', serverUrl);
|
|
28
|
+
// Route the backend's diagnostics through the Fastify pino logger (the backend
|
|
29
|
+
// defaults to a silent logger until wired here).
|
|
30
|
+
const storage = backend ??
|
|
31
|
+
defaultBackend({
|
|
32
|
+
capacityBytes: storageLimitPerSpace,
|
|
33
|
+
maxUploadBytes,
|
|
34
|
+
maxSpacesPerController,
|
|
35
|
+
maxCollectionsPerSpace,
|
|
36
|
+
maxResourcesPerSpace
|
|
37
|
+
});
|
|
38
|
+
storage.logger = fastify.log;
|
|
39
|
+
fastify.decorate('storage', storage);
|
|
40
|
+
// Backend lifecycle: run the optional startup hook (e.g. Postgres connect +
|
|
41
|
+
// migrations) during registration, before the server starts listening, and
|
|
42
|
+
// wire the optional shutdown hook (pool drain) to Fastify's close.
|
|
43
|
+
if (storage.init) {
|
|
44
|
+
await storage.init();
|
|
45
|
+
}
|
|
46
|
+
if (storage.close) {
|
|
47
|
+
fastify.addHook('onClose', async () => {
|
|
48
|
+
await storage.close();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// The provider-adapter registry the resolver (lib/backendRegistry.ts) consults
|
|
52
|
+
// to build a Collection's selected external backend. Injected (rather than a
|
|
53
|
+
// module-global mutable registry) so parallel test suites stay isolated -- the
|
|
54
|
+
// same rationale as the injected `storage`. Empty in production this stage.
|
|
55
|
+
fastify.decorate('backendProviders', providers ?? new Map());
|
|
56
|
+
// The optional registration allowlist (config `WAS_ENABLED_BACKENDS`);
|
|
57
|
+
// `undefined` = permissive (any provider may be registered).
|
|
58
|
+
fastify.decorate('enabledBackendProviders', enabledBackendProviders);
|
|
59
|
+
// The at-rest key-record encryption registry (config `KMS_RECORD_KEK`);
|
|
60
|
+
// `undefined` = disabled (records written plaintext). Read at the KMS
|
|
61
|
+
// orchestration seam (KeyRequest), never inside a backend.
|
|
62
|
+
fastify.decorate('kmsRecordKek', kmsRecordKek);
|
|
63
|
+
// The provisioning gate for `POST /spaces/` and `POST /kms/keystores`: a
|
|
64
|
+
// custom callback, or the stock onboarding-token check when a token is set,
|
|
65
|
+
// or `undefined` = allow (the teaching default). Read by the `provisioningGate`
|
|
66
|
+
// onRequest hook installed by those two route groups.
|
|
67
|
+
fastify.decorate('authorizeProvisioning', authorizeProvisioning ??
|
|
68
|
+
(onboardingToken ? onboardingTokenAuthorizer(onboardingToken) : undefined));
|
|
69
|
+
// Disable CORS
|
|
70
|
+
fastify.register(cors, {
|
|
71
|
+
origin: '*',
|
|
72
|
+
methods: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
|
|
73
|
+
});
|
|
74
|
+
// Multipart file uploading. The cap is `files: 2`, not `1`: a write MUST carry
|
|
75
|
+
// exactly one file part, and `resolveResourceInput` enforces that by iterating
|
|
76
|
+
// the parts and rejecting a second one with `invalid-request-body` (400). With
|
|
77
|
+
// a `files: 1` limit busboy would instead silently drop the second part and
|
|
78
|
+
// raise its own `FST_FILES_LIMIT` (413), so the second part must be allowed
|
|
79
|
+
// through to the iterator to be caught and rejected with the correct error.
|
|
80
|
+
//
|
|
81
|
+
// `fileSize` bounds the in-memory buffer of the single permitted part to the
|
|
82
|
+
// backend's per-upload cap (`throwFileSizeLimit` makes `toBuffer()` throw at
|
|
83
|
+
// the boundary, which the request layer maps to `payload-too-large` (413)) --
|
|
84
|
+
// so an oversize multipart upload is rejected before it is fully buffered.
|
|
85
|
+
// The cap is default-on (the backend applies `DEFAULT_MAX_UPLOAD_BYTES` when
|
|
86
|
+
// none is configured), so `storage.maxUploadBytes` is `undefined` here only
|
|
87
|
+
// when the operator explicitly opted out (`MAX_UPLOAD_BYTES=unlimited`); the
|
|
88
|
+
// conditional spread then leaves multipart uncapped. Large binaries should
|
|
89
|
+
// use the streaming raw-body path, not multipart.
|
|
90
|
+
fastify.register(Multipart, {
|
|
91
|
+
throwFileSizeLimit: true,
|
|
92
|
+
limits: {
|
|
93
|
+
files: 2,
|
|
94
|
+
...(storage.maxUploadBytes !== undefined && {
|
|
95
|
+
fileSize: storage.maxUploadBytes
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// Parse `application/<suffix>+json` bodies (e.g. `application/jose+json` for
|
|
100
|
+
// EDV-over-WAS encrypted documents, `application/ld+json`, etc.) as JSON, the
|
|
101
|
+
// same as plain `application/json`. Fastify's built-in JSON parser only
|
|
102
|
+
// matches `application/json` exactly, so structured-suffix JSON media types
|
|
103
|
+
// would otherwise be rejected with a 415. The regex deliberately requires a
|
|
104
|
+
// non-`+` suffix before `+json`, so it never shadows the built-in parser for
|
|
105
|
+
// plain `application/json`. Registered on the root instance so every route
|
|
106
|
+
// group inherits it; `isJson()` already treats `+json` as JSON downstream
|
|
107
|
+
// (digest capture, resource-input resolution).
|
|
108
|
+
fastify.addContentTypeParser(/^application\/[^+]+\+json/, { parseAs: 'string' }, fastify.getDefaultJsonParser('error', 'error'));
|
|
109
|
+
// Catch-all parser for arbitrary binary representations. The spec ("Content
|
|
110
|
+
// Types and Representations") lets a Resource be any media type, so a raw
|
|
111
|
+
// (non-multipart) blob PUT/POST -- `application/octet-stream`,
|
|
112
|
+
// `application/jsonl`, images, etc. -- must reach the handler as a byte
|
|
113
|
+
// stream. Fastify only ships parsers for `application/json` and `text/plain`
|
|
114
|
+
// and would otherwise reject every other media type with a 415 before the
|
|
115
|
+
// route runs. This bare pass-through leaves `request.body` as the raw stream,
|
|
116
|
+
// which `resolveResourceInput` normalizes to a `kind: 'binary'` input that the
|
|
117
|
+
// backend streams straight to storage. More specific parsers still win over
|
|
118
|
+
// this fallback: the built-in JSON/text parsers, the `+json` regex above,
|
|
119
|
+
// `@fastify/multipart`'s `multipart/*`, and the `application/x-tar` import
|
|
120
|
+
// parser are all matched ahead of it.
|
|
121
|
+
fastify.addContentTypeParser('*', function (_request, payload, done) {
|
|
122
|
+
done(null, payload);
|
|
123
|
+
});
|
|
124
|
+
fastify.register(initSpacesRepositoryRoutes);
|
|
125
|
+
fastify.register(initSpaceRoutes);
|
|
126
|
+
fastify.register(initCollectionRoutes);
|
|
127
|
+
fastify.register(initResourceRoutes);
|
|
128
|
+
fastify.register(initKmsRoutes);
|
|
129
|
+
}
|
|
130
|
+
export const fastifyWas = fp(wasPlugin, {
|
|
131
|
+
fastify: '5.x',
|
|
132
|
+
name: 'fastify-was'
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|