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.
Files changed (295) hide show
  1. package/CHANGELOG.md +1432 -0
  2. package/LICENSE +661 -0
  3. package/README.md +305 -0
  4. package/common/css/materialize.1.0.0.min.css +13 -0
  5. package/dist/auth-header-hooks.d.ts +84 -0
  6. package/dist/auth-header-hooks.d.ts.map +1 -0
  7. package/dist/auth-header-hooks.js +136 -0
  8. package/dist/auth-header-hooks.js.map +1 -0
  9. package/dist/authorize.d.ts +45 -0
  10. package/dist/authorize.d.ts.map +1 -0
  11. package/dist/authorize.js +76 -0
  12. package/dist/authorize.js.map +1 -0
  13. package/dist/backends/filesystem.d.ts +1179 -0
  14. package/dist/backends/filesystem.d.ts.map +1 -0
  15. package/dist/backends/filesystem.js +2727 -0
  16. package/dist/backends/filesystem.js.map +1 -0
  17. package/dist/backends/postgres.d.ts +833 -0
  18. package/dist/backends/postgres.d.ts.map +1 -0
  19. package/dist/backends/postgres.js +2164 -0
  20. package/dist/backends/postgres.js.map +1 -0
  21. package/dist/backends/postgresSchema.d.ts +36 -0
  22. package/dist/backends/postgresSchema.d.ts.map +1 -0
  23. package/dist/backends/postgresSchema.js +203 -0
  24. package/dist/backends/postgresSchema.js.map +1 -0
  25. package/dist/config.default.d.ts +360 -0
  26. package/dist/config.default.d.ts.map +1 -0
  27. package/dist/config.default.js +493 -0
  28. package/dist/config.default.js.map +1 -0
  29. package/dist/corsProxy.d.ts +45 -0
  30. package/dist/corsProxy.d.ts.map +1 -0
  31. package/dist/corsProxy.js +278 -0
  32. package/dist/corsProxy.js.map +1 -0
  33. package/dist/digest.d.ts +45 -0
  34. package/dist/digest.d.ts.map +1 -0
  35. package/dist/digest.js +174 -0
  36. package/dist/digest.js.map +1 -0
  37. package/dist/errors.d.ts +654 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +960 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/index.d.ts +22 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +21 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lib/atomicFile.d.ts +61 -0
  46. package/dist/lib/atomicFile.d.ts.map +1 -0
  47. package/dist/lib/atomicFile.js +150 -0
  48. package/dist/lib/atomicFile.js.map +1 -0
  49. package/dist/lib/backendRegistry.d.ts +59 -0
  50. package/dist/lib/backendRegistry.d.ts.map +1 -0
  51. package/dist/lib/backendRegistry.js +92 -0
  52. package/dist/lib/backendRegistry.js.map +1 -0
  53. package/dist/lib/backendUsage.d.ts +28 -0
  54. package/dist/lib/backendUsage.d.ts.map +1 -0
  55. package/dist/lib/backendUsage.js +55 -0
  56. package/dist/lib/backendUsage.js.map +1 -0
  57. package/dist/lib/backends.d.ts +142 -0
  58. package/dist/lib/backends.d.ts.map +1 -0
  59. package/dist/lib/backends.js +272 -0
  60. package/dist/lib/backends.js.map +1 -0
  61. package/dist/lib/blindedIndex.d.ts +156 -0
  62. package/dist/lib/blindedIndex.d.ts.map +1 -0
  63. package/dist/lib/blindedIndex.js +338 -0
  64. package/dist/lib/blindedIndex.js.map +1 -0
  65. package/dist/lib/collectionDescription.d.ts +33 -0
  66. package/dist/lib/collectionDescription.d.ts.map +1 -0
  67. package/dist/lib/collectionDescription.js +21 -0
  68. package/dist/lib/collectionDescription.js.map +1 -0
  69. package/dist/lib/cursor.d.ts +18 -0
  70. package/dist/lib/cursor.d.ts.map +1 -0
  71. package/dist/lib/cursor.js +50 -0
  72. package/dist/lib/cursor.js.map +1 -0
  73. package/dist/lib/edvEnvelope.d.ts +50 -0
  74. package/dist/lib/edvEnvelope.d.ts.map +1 -0
  75. package/dist/lib/edvEnvelope.js +106 -0
  76. package/dist/lib/edvEnvelope.js.map +1 -0
  77. package/dist/lib/encryption.d.ts +192 -0
  78. package/dist/lib/encryption.d.ts.map +1 -0
  79. package/dist/lib/encryption.js +383 -0
  80. package/dist/lib/encryption.js.map +1 -0
  81. package/dist/lib/etag.d.ts +33 -0
  82. package/dist/lib/etag.d.ts.map +1 -0
  83. package/dist/lib/etag.js +36 -0
  84. package/dist/lib/etag.js.map +1 -0
  85. package/dist/lib/exportManifest.d.ts +29 -0
  86. package/dist/lib/exportManifest.d.ts.map +1 -0
  87. package/dist/lib/exportManifest.js +77 -0
  88. package/dist/lib/exportManifest.js.map +1 -0
  89. package/dist/lib/importTar.d.ts +81 -0
  90. package/dist/lib/importTar.d.ts.map +1 -0
  91. package/dist/lib/importTar.js +301 -0
  92. package/dist/lib/importTar.js.map +1 -0
  93. package/dist/lib/isJson.d.ts +16 -0
  94. package/dist/lib/isJson.d.ts.map +1 -0
  95. package/dist/lib/isJson.js +17 -0
  96. package/dist/lib/isJson.js.map +1 -0
  97. package/dist/lib/keyEpoch.d.ts +38 -0
  98. package/dist/lib/keyEpoch.d.ts.map +1 -0
  99. package/dist/lib/keyEpoch.js +75 -0
  100. package/dist/lib/keyEpoch.js.map +1 -0
  101. package/dist/lib/keyedMutex.d.ts +28 -0
  102. package/dist/lib/keyedMutex.d.ts.map +1 -0
  103. package/dist/lib/keyedMutex.js +45 -0
  104. package/dist/lib/keyedMutex.js.map +1 -0
  105. package/dist/lib/kmsModule.d.ts +62 -0
  106. package/dist/lib/kmsModule.d.ts.map +1 -0
  107. package/dist/lib/kmsModule.js +327 -0
  108. package/dist/lib/kmsModule.js.map +1 -0
  109. package/dist/lib/kmsRecordCipher.d.ts +82 -0
  110. package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
  111. package/dist/lib/kmsRecordCipher.js +226 -0
  112. package/dist/lib/kmsRecordCipher.js.map +1 -0
  113. package/dist/lib/pagination.d.ts +19 -0
  114. package/dist/lib/pagination.d.ts.map +1 -0
  115. package/dist/lib/pagination.js +21 -0
  116. package/dist/lib/pagination.js.map +1 -0
  117. package/dist/lib/paths.d.ts +273 -0
  118. package/dist/lib/paths.d.ts.map +1 -0
  119. package/dist/lib/paths.js +270 -0
  120. package/dist/lib/paths.js.map +1 -0
  121. package/dist/lib/preconditions.d.ts +65 -0
  122. package/dist/lib/preconditions.d.ts.map +1 -0
  123. package/dist/lib/preconditions.js +112 -0
  124. package/dist/lib/preconditions.js.map +1 -0
  125. package/dist/lib/resourceFileName.d.ts +41 -0
  126. package/dist/lib/resourceFileName.d.ts.map +1 -0
  127. package/dist/lib/resourceFileName.js +58 -0
  128. package/dist/lib/resourceFileName.js.map +1 -0
  129. package/dist/lib/revocations.d.ts +49 -0
  130. package/dist/lib/revocations.d.ts.map +1 -0
  131. package/dist/lib/revocations.js +86 -0
  132. package/dist/lib/revocations.js.map +1 -0
  133. package/dist/lib/validateDid.d.ts +19 -0
  134. package/dist/lib/validateDid.d.ts.map +1 -0
  135. package/dist/lib/validateDid.js +35 -0
  136. package/dist/lib/validateDid.js.map +1 -0
  137. package/dist/lib/validateId.d.ts +66 -0
  138. package/dist/lib/validateId.d.ts.map +1 -0
  139. package/dist/lib/validateId.js +126 -0
  140. package/dist/lib/validateId.js.map +1 -0
  141. package/dist/plugin.d.ts +115 -0
  142. package/dist/plugin.d.ts.map +1 -0
  143. package/dist/plugin.js +134 -0
  144. package/dist/plugin.js.map +1 -0
  145. package/dist/policy.d.ts +78 -0
  146. package/dist/policy.d.ts.map +1 -0
  147. package/dist/policy.js +115 -0
  148. package/dist/policy.js.map +1 -0
  149. package/dist/provisioning.d.ts +38 -0
  150. package/dist/provisioning.d.ts.map +1 -0
  151. package/dist/provisioning.js +72 -0
  152. package/dist/provisioning.js.map +1 -0
  153. package/dist/requests/BackendRequest.d.ts +61 -0
  154. package/dist/requests/BackendRequest.d.ts.map +1 -0
  155. package/dist/requests/BackendRequest.js +152 -0
  156. package/dist/requests/BackendRequest.js.map +1 -0
  157. package/dist/requests/CollectionRequest.d.ts +234 -0
  158. package/dist/requests/CollectionRequest.d.ts.map +1 -0
  159. package/dist/requests/CollectionRequest.js +705 -0
  160. package/dist/requests/CollectionRequest.js.map +1 -0
  161. package/dist/requests/KeyRequest.d.ts +115 -0
  162. package/dist/requests/KeyRequest.d.ts.map +1 -0
  163. package/dist/requests/KeyRequest.js +468 -0
  164. package/dist/requests/KeyRequest.js.map +1 -0
  165. package/dist/requests/KeystoreRequest.d.ts +99 -0
  166. package/dist/requests/KeystoreRequest.d.ts.map +1 -0
  167. package/dist/requests/KeystoreRequest.js +247 -0
  168. package/dist/requests/KeystoreRequest.js.map +1 -0
  169. package/dist/requests/PolicyRequest.d.ts +53 -0
  170. package/dist/requests/PolicyRequest.d.ts.map +1 -0
  171. package/dist/requests/PolicyRequest.js +124 -0
  172. package/dist/requests/PolicyRequest.js.map +1 -0
  173. package/dist/requests/ResourceRequest.d.ts +134 -0
  174. package/dist/requests/ResourceRequest.d.ts.map +1 -0
  175. package/dist/requests/ResourceRequest.js +548 -0
  176. package/dist/requests/ResourceRequest.js.map +1 -0
  177. package/dist/requests/RevocationRequest.d.ts +67 -0
  178. package/dist/requests/RevocationRequest.d.ts.map +1 -0
  179. package/dist/requests/RevocationRequest.js +187 -0
  180. package/dist/requests/RevocationRequest.js.map +1 -0
  181. package/dist/requests/SpaceRequest.d.ts +204 -0
  182. package/dist/requests/SpaceRequest.d.ts.map +1 -0
  183. package/dist/requests/SpaceRequest.js +520 -0
  184. package/dist/requests/SpaceRequest.js.map +1 -0
  185. package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
  186. package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
  187. package/dist/requests/SpacesRepositoryRequest.js +159 -0
  188. package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
  189. package/dist/requests/collectionContext.d.ts +21 -0
  190. package/dist/requests/collectionContext.d.ts.map +1 -0
  191. package/dist/requests/collectionContext.js +30 -0
  192. package/dist/requests/collectionContext.js.map +1 -0
  193. package/dist/requests/controllerConsent.d.ts +58 -0
  194. package/dist/requests/controllerConsent.d.ts.map +1 -0
  195. package/dist/requests/controllerConsent.js +73 -0
  196. package/dist/requests/controllerConsent.js.map +1 -0
  197. package/dist/requests/keystoreContext.d.ts +68 -0
  198. package/dist/requests/keystoreContext.d.ts.map +1 -0
  199. package/dist/requests/keystoreContext.js +87 -0
  200. package/dist/requests/keystoreContext.js.map +1 -0
  201. package/dist/requests/resourceInput.d.ts +19 -0
  202. package/dist/requests/resourceInput.d.ts.map +1 -0
  203. package/dist/requests/resourceInput.js +102 -0
  204. package/dist/requests/resourceInput.js.map +1 -0
  205. package/dist/requests/spaceContext.d.ts +127 -0
  206. package/dist/requests/spaceContext.d.ts.map +1 -0
  207. package/dist/requests/spaceContext.js +201 -0
  208. package/dist/requests/spaceContext.js.map +1 -0
  209. package/dist/routes.d.ts +55 -0
  210. package/dist/routes.d.ts.map +1 -0
  211. package/dist/routes.js +306 -0
  212. package/dist/routes.js.map +1 -0
  213. package/dist/server.d.ts +20 -0
  214. package/dist/server.d.ts.map +1 -0
  215. package/dist/server.js +60 -0
  216. package/dist/server.js.map +1 -0
  217. package/dist/start.d.ts +7 -0
  218. package/dist/start.d.ts.map +1 -0
  219. package/dist/start.js +61 -0
  220. package/dist/start.js.map +1 -0
  221. package/dist/storage.d.ts +29 -0
  222. package/dist/storage.d.ts.map +1 -0
  223. package/dist/storage.js +47 -0
  224. package/dist/storage.js.map +1 -0
  225. package/dist/types.d.ts +915 -0
  226. package/dist/types.d.ts.map +1 -0
  227. package/dist/types.js +2 -0
  228. package/dist/types.js.map +1 -0
  229. package/dist/views/home.hbs +9 -0
  230. package/dist/views/templates/main.hbs +12 -0
  231. package/dist/zcap.d.ts +267 -0
  232. package/dist/zcap.d.ts.map +1 -0
  233. package/dist/zcap.js +478 -0
  234. package/dist/zcap.js.map +1 -0
  235. package/package.json +109 -0
  236. package/src/auth-header-hooks.ts +163 -0
  237. package/src/authorize.ts +117 -0
  238. package/src/backends/filesystem.ts +3626 -0
  239. package/src/backends/postgres.ts +3073 -0
  240. package/src/backends/postgresSchema.ts +231 -0
  241. package/src/config.default.ts +657 -0
  242. package/src/corsProxy.ts +326 -0
  243. package/src/declarations.d.ts +28 -0
  244. package/src/digest.ts +223 -0
  245. package/src/errors.ts +1122 -0
  246. package/src/index.ts +21 -0
  247. package/src/lib/atomicFile.ts +176 -0
  248. package/src/lib/backendRegistry.ts +139 -0
  249. package/src/lib/backendUsage.ts +79 -0
  250. package/src/lib/backends.ts +351 -0
  251. package/src/lib/blindedIndex.ts +466 -0
  252. package/src/lib/collectionDescription.ts +43 -0
  253. package/src/lib/cursor.ts +52 -0
  254. package/src/lib/edvEnvelope.ts +120 -0
  255. package/src/lib/encryption.ts +472 -0
  256. package/src/lib/etag.ts +40 -0
  257. package/src/lib/exportManifest.ts +115 -0
  258. package/src/lib/importTar.ts +381 -0
  259. package/src/lib/isJson.ts +18 -0
  260. package/src/lib/keyEpoch.ts +89 -0
  261. package/src/lib/keyedMutex.ts +45 -0
  262. package/src/lib/kmsModule.ts +451 -0
  263. package/src/lib/kmsRecordCipher.ts +292 -0
  264. package/src/lib/pagination.ts +22 -0
  265. package/src/lib/paths.ts +382 -0
  266. package/src/lib/preconditions.ts +145 -0
  267. package/src/lib/resourceFileName.ts +69 -0
  268. package/src/lib/revocations.ts +116 -0
  269. package/src/lib/validateDid.ts +41 -0
  270. package/src/lib/validateId.ts +148 -0
  271. package/src/plugin.ts +288 -0
  272. package/src/policy.ts +176 -0
  273. package/src/provisioning.ts +98 -0
  274. package/src/requests/BackendRequest.ts +219 -0
  275. package/src/requests/CollectionRequest.ts +899 -0
  276. package/src/requests/KeyRequest.ts +617 -0
  277. package/src/requests/KeystoreRequest.ts +325 -0
  278. package/src/requests/PolicyRequest.ts +169 -0
  279. package/src/requests/ResourceRequest.ts +649 -0
  280. package/src/requests/RevocationRequest.ts +267 -0
  281. package/src/requests/SpaceRequest.ts +686 -0
  282. package/src/requests/SpacesRepositoryRequest.ts +194 -0
  283. package/src/requests/collectionContext.ts +41 -0
  284. package/src/requests/controllerConsent.ts +108 -0
  285. package/src/requests/keystoreContext.ts +124 -0
  286. package/src/requests/resourceInput.ts +118 -0
  287. package/src/requests/spaceContext.ts +311 -0
  288. package/src/routes.ts +420 -0
  289. package/src/server.ts +69 -0
  290. package/src/start.ts +71 -0
  291. package/src/storage.ts +60 -0
  292. package/src/types.ts +1016 -0
  293. package/src/views/home.hbs +9 -0
  294. package/src/views/templates/main.hbs +12 -0
  295. 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"}
@@ -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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"}