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 @@
1
+ {"version":3,"file":"edvEnvelope.js","sourceRoot":"","sources":["../../src/lib/edvEnvelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,KAAK,CAAA;IACd,CAAC;IACD,MAAM,SAAS,GAAG,KAAgC,CAAA;IAClD,IACE,SAAS,CAAC,MAAM,KAAK,SAAS;QAC9B,CAAC,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;YACnC,SAAS,CAAC,MAAM,KAAK,IAAI;YACzB,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAClC,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IACE,SAAS,CAAC,aAAa,KAAK,SAAS;QACrC,OAAO,SAAS,CAAC,aAAa,KAAK,QAAQ,EAC3C,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,KAAK,CAAA;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAA+B,CAAA;IAEhD,6CAA6C;IAC7C,IACE,OAAO,QAAQ,CAAC,UAAU,KAAK,QAAQ;QACvC,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAChC,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,2BAA2B;IAC3B,KAAK,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,CAAU,EAAE,CAAC;QACvE,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrE,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrC,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QACD,YAAY,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAA;IAC/C,CAAC;IAED,qEAAqE;IACrE,6EAA6E;IAC7E,oDAAoD;IACpD,OAAO,CACL,YAAY;QACZ,OAAO,QAAQ,CAAC,aAAa,KAAK,QAAQ;QAC1C,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,CACvC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,kBAAkB,CAAE,IAA0B,CAAC,GAAG,CAAC,CAAA;AAC5D,CAAC"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Collection client-side encryption marker helpers (spec "Encrypted
3
+ * Collections"). A Collection MAY carry a non-secret `encryption` marker
4
+ * declaring that its Resources are client-encrypted and naming the scheme; any
5
+ * authorized reader discovers it by reading the Collection Description and then
6
+ * decrypts with its own keys. The server never decrypts: it validates only the
7
+ * marker's *shape* and enforces *set-once* immutability, storing the value
8
+ * opaquely. This mirrors the backend-selection helpers in lib/backends.ts
9
+ * (validate on write / preserve on read), kept separate because encryption is a
10
+ * per-Collection client concern, not a backend capability.
11
+ */
12
+ import type { CollectionEncryption } from '../types.js';
13
+ /**
14
+ * The encryption schemes this server recognizes and can enforce on write (spec
15
+ * "Encryption Scheme Registry"). Each entry pins the scheme token to its
16
+ * required stored-representation media type and the structural validator for its
17
+ * envelope profile. v1 has exactly one entry: `edv` (EDV-over-WAS), an EDV
18
+ * **Encrypted Document** (a JSON object whose `jwe` member is a JWE in JSON
19
+ * serialization) carried as `application/json` -- matching what the EDV codec
20
+ * actually stores and how a native EDV server serves an Encrypted Document.
21
+ * "Marked with a recognized scheme" structurally implies "non-conforming writes
22
+ * rejected here" -- the fail-closed guarantee: a plaintext object under
23
+ * `application/json` passes the media-type gate but fails the structural
24
+ * `jwe` gate, so it is still rejected. Extending the registry (a new scheme, or
25
+ * a new media type for an existing one) is the only place a scheme becomes
26
+ * acceptable.
27
+ */
28
+ export declare const SUPPORTED_ENCRYPTION_SCHEMES: Record<string, {
29
+ mediaType: string;
30
+ validateEnvelope: (body: unknown) => boolean;
31
+ }>;
32
+ /**
33
+ * Validates a client-supplied Collection `encryption` marker and returns the
34
+ * normalized value to persist, or `undefined` when absent (plaintext). Two
35
+ * gates: (1) shape -- a present value must be an object with a non-empty string
36
+ * `scheme`, else `invalid-request-body` (400, pointer `#/encryption`); (2)
37
+ * fail-closed scheme gate -- the `scheme` MUST name one this server recognizes
38
+ * and can enforce on write (`SUPPORTED_ENCRYPTION_SCHEMES`), else
39
+ * `unsupported-encryption-scheme` (400, pointer `#/encryption/scheme`). Taking
40
+ * the spec's SHOULD path, the reference server refuses to store a marker it
41
+ * cannot back with write-time validation, rather than storing an unknown scheme
42
+ * opaquely. Unknown **extra fields** on an otherwise-recognized marker are still
43
+ * preserved (the whole object is returned, not reduced to `{ scheme }`) so
44
+ * future public-reference fields a newer client adds (e.g. recipient key
45
+ * references) survive an older server unchanged.
46
+ *
47
+ * @param options {object}
48
+ * @param [options.encryption] {unknown} the request body's `encryption` value
49
+ * @param [options.requestName] {string} request name for the 400 error title
50
+ * @returns {CollectionEncryption | undefined} the marker to store, or undefined
51
+ */
52
+ export declare function assertSupportedEncryption({ encryption, requestName }: {
53
+ encryption?: unknown;
54
+ requestName?: string;
55
+ }): CollectionEncryption | undefined;
56
+ /**
57
+ * Enforces the epoch-safety rails on an UPDATE, when the existing marker already
58
+ * carries `epochs` (spec "Encrypted Collections"; the `key-epochs` feature).
59
+ * Call only when an `incoming` marker was supplied and shape-validated. Rules
60
+ * (both `invalid-request-body`, 400):
61
+ * - **append-only**: every existing epoch id must still be present in
62
+ * `incoming.epochs` -- dropping an epoch would strand every Resource stamped
63
+ * with it (the removed reader could still hold that epoch's key, but no
64
+ * remaining reader could find it).
65
+ * - **`currentEpoch` never moves backwards**: the incoming `currentEpoch` must
66
+ * equal the existing one OR name an epoch id that was NOT in the existing
67
+ * `epochs` list (a freshly appended epoch). This is the array-order-independent
68
+ * formulation of "monotonic".
69
+ *
70
+ * Recipients WITHIN an existing epoch MAY change (adding a recipient wraps the
71
+ * epoch key to it; escrow adds entries to old epochs), so that is not
72
+ * restricted. A first declaration of `epochs` on a marker that had none is
73
+ * likewise unrestricted (there is nothing to append to yet).
74
+ *
75
+ * @param options {object}
76
+ * @param [options.existing] {CollectionEncryption} the persisted marker
77
+ * @param options.incoming {CollectionEncryption} the validated request marker
78
+ * @returns {void}
79
+ */
80
+ export declare function assertEncryptionEpochsTransition({ existing, incoming }: {
81
+ existing?: CollectionEncryption;
82
+ incoming: CollectionEncryption;
83
+ }): void;
84
+ /**
85
+ * Enforces the full `encryption`-marker transition rails against a persisted
86
+ * marker in one call: set-once immutability
87
+ * ({@link assertEncryptionTransition}) plus the key-epoch rails
88
+ * ({@link assertEncryptionEpochsTransition}). Unlike those two -- which require
89
+ * a supplied `incoming` -- this also accepts an absent one: a write whose
90
+ * description would CLEAR an existing marker is rejected with
91
+ * `encryption-immutable` (409), on the same terms as changing it. The request
92
+ * layer uses this twice per Update Collection: once against its own
93
+ * (pre-lock) read for a clean early rejection, and again as the
94
+ * `writeCollection` `assertTransition` callback, re-evaluated inside the
95
+ * backend's lock/transaction against the freshly re-read description -- so a
96
+ * concurrent marker write cannot be silently clobbered and the epoch
97
+ * append-only rail holds unconditionally, not just under `If-Match`.
98
+ *
99
+ * @param options {object}
100
+ * @param [options.existing] {CollectionEncryption} the persisted marker
101
+ * @param [options.incoming] {CollectionEncryption} the marker about to be
102
+ * persisted (absent when the write would drop the marker entirely)
103
+ * @returns {void}
104
+ */
105
+ export declare function assertEncryptionMarkerTransition({ existing, incoming }: {
106
+ existing?: CollectionEncryption;
107
+ incoming?: CollectionEncryption;
108
+ }): void;
109
+ /**
110
+ * Enforces set-once immutability of a Collection's `encryption` marker on
111
+ * update. Call only when an `incoming` marker was supplied (and shape-validated)
112
+ * by the request. Declaring a marker on a Collection that lacks one is allowed
113
+ * (`absent -> present`: late declaration / migration of a pre-marker
114
+ * Collection); re-sending the same `scheme` is a no-op. Changing the `scheme` of
115
+ * an existing marker is rejected with `encryption-immutable` (409) -- it would
116
+ * corrupt the stored, client-encrypted Resources. (Clearing is not expressible:
117
+ * an absent body `encryption` leaves the existing marker untouched, and an
118
+ * explicit non-object is already a 400 in `assertSupportedEncryption`.) The
119
+ * `scheme` is the immutable identity; comparison deliberately ignores any
120
+ * future public-reference fields, whose evolution (e.g. adding a recipient) is a
121
+ * separate, allowed operation.
122
+ *
123
+ * @param options {object}
124
+ * @param [options.existing] {CollectionEncryption} the persisted marker
125
+ * @param options.incoming {CollectionEncryption} the validated request marker
126
+ * @returns {void}
127
+ */
128
+ export declare function assertEncryptionTransition({ existing, incoming }: {
129
+ existing?: CollectionEncryption;
130
+ incoming: CollectionEncryption;
131
+ }): void;
132
+ /**
133
+ * Fail-closed structural validation of a Resource **content** write into an
134
+ * encrypted Collection (spec "Encryption Scheme Registry"). When the target
135
+ * Collection declares a recognized `encryption` scheme, a write MUST be a
136
+ * conforming envelope of that scheme -- two gates: (1) the request `Content-Type`
137
+ * MUST be the scheme's registered media type (so a binary/`octet-stream`,
138
+ * `multipart`, or plain `application/json` upload is rejected outright), and (2)
139
+ * the parsed body MUST satisfy the scheme's structural envelope profile. A
140
+ * failure of either is `encryption-scheme-mismatch` (422). No-op when the
141
+ * Collection has no marker (plaintext) or -- defensively -- an unrecognized
142
+ * scheme (which `assertSupportedEncryption` prevents from ever being stored), so
143
+ * plaintext Collections and API documents are unaffected. The server validates
144
+ * structure only; it never decrypts. Call this **after** capability verification
145
+ * and the 404-if-missing check, and before resolving the body stream, so a wrong
146
+ * content type is rejected without consuming the upload and a 422 is observable
147
+ * only to a caller already authorized to write the target.
148
+ *
149
+ * @param options {object}
150
+ * @param options.collectionDescription {{ encryption?: CollectionEncryption }}
151
+ * the target Collection's stored description
152
+ * @param [options.contentType] {string} the request `Content-Type` header
153
+ * @param options.body {unknown} the parsed request body (an object for the
154
+ * `application/<suffix>+json` media types the scheme registry uses)
155
+ * @returns {void}
156
+ */
157
+ export declare function assertEncryptedWriteConforms({ collectionDescription, contentType, body }: {
158
+ collectionDescription: {
159
+ encryption?: CollectionEncryption;
160
+ };
161
+ contentType?: string;
162
+ body: unknown;
163
+ }): void;
164
+ /**
165
+ * Fail-closed structural validation of a Resource **metadata** write (`PUT
166
+ * /meta`) into an encrypted Collection (spec "Encrypted Collections"). When the
167
+ * target Collection declares a recognized `encryption` scheme, the user-writable
168
+ * `custom` object MUST be a conforming envelope of that scheme -- the same
169
+ * structural profile used for content -- so a plaintext `{ name, tags }` cannot
170
+ * be stored server-visibly. Unlike {@link assertEncryptedWriteConforms} there is
171
+ * **no media-type gate**: the metadata document itself stays `application/json`
172
+ * (its server-managed top-level fields are plaintext); only the `custom`
173
+ * sub-value is the envelope. A non-conforming `custom` is
174
+ * `encryption-scheme-mismatch` (422). No-op when the Collection has no marker
175
+ * (plaintext) or -- defensively -- an unrecognized scheme. The server validates
176
+ * structure only; it never decrypts. Call this **after** capability verification
177
+ * and the 404-if-missing check, before the write, so a 422 is observable only to
178
+ * a caller already authorized to write the target.
179
+ *
180
+ * @param options {object}
181
+ * @param options.collectionDescription {{ encryption?: CollectionEncryption }}
182
+ * the target Collection's stored description
183
+ * @param options.custom {unknown} the request body's `custom` value
184
+ * @returns {void}
185
+ */
186
+ export declare function assertEncryptedMetaConforms({ collectionDescription, custom }: {
187
+ collectionDescription: {
188
+ encryption?: CollectionEncryption;
189
+ };
190
+ custom: unknown;
191
+ }): void;
192
+ //# sourceMappingURL=encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,aAAa,CAAA;AASpB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAC/C,MAAM,EACN;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;CAAE,CAMpE,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACV,WAAW,EACZ,EAAE;IACD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,oBAAoB,GAAG,SAAS,CAmCnC;AAiID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACR,QAAQ,EACT,EAAE;IACD,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B,QAAQ,EAAE,oBAAoB,CAAA;CAC/B,GAAG,IAAI,CA6BP;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACR,QAAQ,EACT,EAAE;IACD,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B,QAAQ,CAAC,EAAE,oBAAoB,CAAA;CAChC,GAAG,IAAI,CAWP;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,QAAQ,EACT,EAAE;IACD,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B,QAAQ,EAAE,oBAAoB,CAAA;CAC/B,GAAG,IAAI,CAIP;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,qBAAqB,EACrB,WAAW,EACX,IAAI,EACL,EAAE;IACD,qBAAqB,EAAE;QAAE,UAAU,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,OAAO,CAAA;CACd,GAAG,IAAI,CAwBP;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,2BAA2B,CAAC,EAC1C,qBAAqB,EACrB,MAAM,EACP,EAAE;IACD,qBAAqB,EAAE;QAAE,UAAU,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IAC5D,MAAM,EAAE,OAAO,CAAA;CAChB,GAAG,IAAI,CAcP"}
@@ -0,0 +1,383 @@
1
+ import { InvalidRequestBodyError, EncryptionImmutableError, UnsupportedEncryptionSchemeError, EncryptionSchemeMismatchError } from '../errors.js';
2
+ import { isValidEdvDocument } from './edvEnvelope.js';
3
+ /**
4
+ * The encryption schemes this server recognizes and can enforce on write (spec
5
+ * "Encryption Scheme Registry"). Each entry pins the scheme token to its
6
+ * required stored-representation media type and the structural validator for its
7
+ * envelope profile. v1 has exactly one entry: `edv` (EDV-over-WAS), an EDV
8
+ * **Encrypted Document** (a JSON object whose `jwe` member is a JWE in JSON
9
+ * serialization) carried as `application/json` -- matching what the EDV codec
10
+ * actually stores and how a native EDV server serves an Encrypted Document.
11
+ * "Marked with a recognized scheme" structurally implies "non-conforming writes
12
+ * rejected here" -- the fail-closed guarantee: a plaintext object under
13
+ * `application/json` passes the media-type gate but fails the structural
14
+ * `jwe` gate, so it is still rejected. Extending the registry (a new scheme, or
15
+ * a new media type for an existing one) is the only place a scheme becomes
16
+ * acceptable.
17
+ */
18
+ export const SUPPORTED_ENCRYPTION_SCHEMES = {
19
+ edv: {
20
+ mediaType: 'application/json',
21
+ validateEnvelope: isValidEdvDocument
22
+ }
23
+ };
24
+ /**
25
+ * Validates a client-supplied Collection `encryption` marker and returns the
26
+ * normalized value to persist, or `undefined` when absent (plaintext). Two
27
+ * gates: (1) shape -- a present value must be an object with a non-empty string
28
+ * `scheme`, else `invalid-request-body` (400, pointer `#/encryption`); (2)
29
+ * fail-closed scheme gate -- the `scheme` MUST name one this server recognizes
30
+ * and can enforce on write (`SUPPORTED_ENCRYPTION_SCHEMES`), else
31
+ * `unsupported-encryption-scheme` (400, pointer `#/encryption/scheme`). Taking
32
+ * the spec's SHOULD path, the reference server refuses to store a marker it
33
+ * cannot back with write-time validation, rather than storing an unknown scheme
34
+ * opaquely. Unknown **extra fields** on an otherwise-recognized marker are still
35
+ * preserved (the whole object is returned, not reduced to `{ scheme }`) so
36
+ * future public-reference fields a newer client adds (e.g. recipient key
37
+ * references) survive an older server unchanged.
38
+ *
39
+ * @param options {object}
40
+ * @param [options.encryption] {unknown} the request body's `encryption` value
41
+ * @param [options.requestName] {string} request name for the 400 error title
42
+ * @returns {CollectionEncryption | undefined} the marker to store, or undefined
43
+ */
44
+ export function assertSupportedEncryption({ encryption, requestName }) {
45
+ if (encryption === undefined) {
46
+ return undefined;
47
+ }
48
+ const scheme = encryption?.scheme;
49
+ if (typeof encryption !== 'object' ||
50
+ encryption === null ||
51
+ typeof scheme !== 'string' ||
52
+ scheme.length === 0) {
53
+ throw new InvalidRequestBodyError({
54
+ requestName,
55
+ detail: 'Collection "encryption" must be an object with a non-empty string "scheme".',
56
+ pointer: '#/encryption'
57
+ });
58
+ }
59
+ // Fail closed: only a recognized scheme (one the server validates on write) is
60
+ // accepted; an unknown scheme is rejected rather than stored opaquely.
61
+ if (!Object.hasOwn(SUPPORTED_ENCRYPTION_SCHEMES, scheme)) {
62
+ throw new UnsupportedEncryptionSchemeError({ scheme });
63
+ }
64
+ // Validate the OPTIONAL key-epoch fields (`epochs` / `currentEpoch`) when
65
+ // present: shape-only safety rails against client bugs (a dropped epoch, a
66
+ // dangling `currentEpoch`), never crypto verification -- the server holds no
67
+ // key. Absent fields are a plain single-key-set marker and pass unchanged.
68
+ assertValidEncryptionEpochs({
69
+ marker: encryption,
70
+ requestName
71
+ });
72
+ // Preserve the whole marker (only `scheme` is typed today; keep any extra
73
+ // forward-compat fields on a recognized scheme).
74
+ return encryption;
75
+ }
76
+ /**
77
+ * Validates the OPTIONAL key-epoch public-reference fields of a Collection
78
+ * `encryption` marker (spec "Encrypted Collections"; the `key-epochs` feature).
79
+ * Shape-only integrity checks that catch client bugs -- the server never
80
+ * interprets key material, so these are safety rails, not cryptographic
81
+ * verification. Rejects with `invalid-request-body` (400) and a precise
82
+ * `pointer`. Rules:
83
+ * - `epochs` and `currentEpoch` are all-or-nothing: either both absent (a plain
84
+ * single-key-set marker) or both present.
85
+ * - `epochs`: a non-empty array; each entry an object with a non-empty string
86
+ * `id` (ids unique across the array) and a non-empty `recipients` array.
87
+ * - each `recipients` entry carries the JWE recipients-entry members the
88
+ * marker requires: a `header` object with non-empty string `kid` and `alg`,
89
+ * plus a string `encrypted_key` (the wrapped epoch key).
90
+ * - `currentEpoch`: a non-empty string naming an `id` that exists in `epochs`.
91
+ *
92
+ * @param options {object}
93
+ * @param options.marker {CollectionEncryption} the shape-validated marker
94
+ * @param [options.requestName] {string} request name for the 400 error title
95
+ * @returns {void}
96
+ */
97
+ function assertValidEncryptionEpochs({ marker, requestName }) {
98
+ const { epochs, currentEpoch } = marker;
99
+ // All-or-nothing: `epochs` and `currentEpoch` appear together or not at all.
100
+ if ((epochs === undefined) !== (currentEpoch === undefined)) {
101
+ throw new InvalidRequestBodyError({
102
+ requestName,
103
+ detail: 'Collection "encryption.epochs" and "encryption.currentEpoch" must both be present or both absent.',
104
+ pointer: '#/encryption/epochs'
105
+ });
106
+ }
107
+ if (epochs === undefined) {
108
+ return;
109
+ }
110
+ if (!Array.isArray(epochs) || epochs.length === 0) {
111
+ throw new InvalidRequestBodyError({
112
+ requestName,
113
+ detail: 'Collection "encryption.epochs" must be a non-empty array.',
114
+ pointer: '#/encryption/epochs'
115
+ });
116
+ }
117
+ const ids = new Set();
118
+ epochs.forEach((epoch, epochIndex) => {
119
+ const pointer = `#/encryption/epochs/${epochIndex}`;
120
+ if (typeof epoch !== 'object' || epoch === null || Array.isArray(epoch)) {
121
+ throw new InvalidRequestBodyError({
122
+ requestName,
123
+ detail: 'Each "encryption.epochs" entry must be an object.',
124
+ pointer
125
+ });
126
+ }
127
+ const { id, recipients } = epoch;
128
+ if (typeof id !== 'string' || id.length === 0) {
129
+ throw new InvalidRequestBodyError({
130
+ requestName,
131
+ detail: 'Each "encryption.epochs" entry must have a non-empty string "id".',
132
+ pointer: `${pointer}/id`
133
+ });
134
+ }
135
+ if (ids.has(id)) {
136
+ throw new InvalidRequestBodyError({
137
+ requestName,
138
+ detail: `Duplicate "encryption.epochs" id "${id}".`,
139
+ pointer: `${pointer}/id`
140
+ });
141
+ }
142
+ ids.add(id);
143
+ if (!Array.isArray(recipients) || recipients.length === 0) {
144
+ throw new InvalidRequestBodyError({
145
+ requestName,
146
+ detail: 'Each "encryption.epochs" entry must have a non-empty "recipients" array.',
147
+ pointer: `${pointer}/recipients`
148
+ });
149
+ }
150
+ recipients.forEach((recipient, recipientIndex) => {
151
+ const rPointer = `${pointer}/recipients/${recipientIndex}`;
152
+ // The JWE recipients-entry members the wrapped-epoch-key marker needs:
153
+ // `header.kid` / `header.alg` and the wrapped key `encrypted_key`. The
154
+ // member checks (optional chaining included) already reject every
155
+ // non-object or malformed entry, so no generic JWE-entry shape test runs
156
+ // first.
157
+ const header = recipient?.header;
158
+ if (typeof header?.kid !== 'string' ||
159
+ header.kid.length === 0 ||
160
+ typeof header.alg !== 'string' ||
161
+ header.alg.length === 0 ||
162
+ typeof recipient.encrypted_key !==
163
+ 'string') {
164
+ throw new InvalidRequestBodyError({
165
+ requestName,
166
+ detail: 'Each "recipients" entry must have a "header" object with non-empty string "kid" and "alg", plus a string "encrypted_key".',
167
+ pointer: rPointer
168
+ });
169
+ }
170
+ });
171
+ });
172
+ if (typeof currentEpoch !== 'string' || currentEpoch.length === 0) {
173
+ throw new InvalidRequestBodyError({
174
+ requestName,
175
+ detail: 'Collection "encryption.currentEpoch" must be a non-empty string.',
176
+ pointer: '#/encryption/currentEpoch'
177
+ });
178
+ }
179
+ if (!ids.has(currentEpoch)) {
180
+ throw new InvalidRequestBodyError({
181
+ requestName,
182
+ detail: `Collection "encryption.currentEpoch" ("${currentEpoch}") does not name an epoch in "epochs".`,
183
+ pointer: '#/encryption/currentEpoch'
184
+ });
185
+ }
186
+ }
187
+ /**
188
+ * Enforces the epoch-safety rails on an UPDATE, when the existing marker already
189
+ * carries `epochs` (spec "Encrypted Collections"; the `key-epochs` feature).
190
+ * Call only when an `incoming` marker was supplied and shape-validated. Rules
191
+ * (both `invalid-request-body`, 400):
192
+ * - **append-only**: every existing epoch id must still be present in
193
+ * `incoming.epochs` -- dropping an epoch would strand every Resource stamped
194
+ * with it (the removed reader could still hold that epoch's key, but no
195
+ * remaining reader could find it).
196
+ * - **`currentEpoch` never moves backwards**: the incoming `currentEpoch` must
197
+ * equal the existing one OR name an epoch id that was NOT in the existing
198
+ * `epochs` list (a freshly appended epoch). This is the array-order-independent
199
+ * formulation of "monotonic".
200
+ *
201
+ * Recipients WITHIN an existing epoch MAY change (adding a recipient wraps the
202
+ * epoch key to it; escrow adds entries to old epochs), so that is not
203
+ * restricted. A first declaration of `epochs` on a marker that had none is
204
+ * likewise unrestricted (there is nothing to append to yet).
205
+ *
206
+ * @param options {object}
207
+ * @param [options.existing] {CollectionEncryption} the persisted marker
208
+ * @param options.incoming {CollectionEncryption} the validated request marker
209
+ * @returns {void}
210
+ */
211
+ export function assertEncryptionEpochsTransition({ existing, incoming }) {
212
+ const existingEpochs = existing?.epochs;
213
+ if (existingEpochs === undefined || existingEpochs.length === 0) {
214
+ // No prior epochs: a first epoch declaration has nothing to append to.
215
+ return;
216
+ }
217
+ const incomingIds = new Set((incoming.epochs ?? []).map(epoch => epoch.id));
218
+ // Append-only: no existing epoch id may vanish.
219
+ for (const epoch of existingEpochs) {
220
+ if (!incomingIds.has(epoch.id)) {
221
+ throw new InvalidRequestBodyError({
222
+ detail: `Collection "encryption.epochs" is append-only: epoch "${epoch.id}" may not be removed.`,
223
+ pointer: '#/encryption/epochs'
224
+ });
225
+ }
226
+ }
227
+ // `currentEpoch` never moves backwards: keep it, or repoint it to a
228
+ // newly-appended epoch id (one that did not exist before).
229
+ const existingIds = new Set(existingEpochs.map(epoch => epoch.id));
230
+ const { currentEpoch } = incoming;
231
+ if (currentEpoch !== existing?.currentEpoch &&
232
+ existingIds.has(currentEpoch)) {
233
+ throw new InvalidRequestBodyError({
234
+ detail: `Collection "encryption.currentEpoch" may not move back to the existing epoch "${currentEpoch}"; repoint it only to a newly appended epoch.`,
235
+ pointer: '#/encryption/currentEpoch'
236
+ });
237
+ }
238
+ }
239
+ /**
240
+ * Enforces the full `encryption`-marker transition rails against a persisted
241
+ * marker in one call: set-once immutability
242
+ * ({@link assertEncryptionTransition}) plus the key-epoch rails
243
+ * ({@link assertEncryptionEpochsTransition}). Unlike those two -- which require
244
+ * a supplied `incoming` -- this also accepts an absent one: a write whose
245
+ * description would CLEAR an existing marker is rejected with
246
+ * `encryption-immutable` (409), on the same terms as changing it. The request
247
+ * layer uses this twice per Update Collection: once against its own
248
+ * (pre-lock) read for a clean early rejection, and again as the
249
+ * `writeCollection` `assertTransition` callback, re-evaluated inside the
250
+ * backend's lock/transaction against the freshly re-read description -- so a
251
+ * concurrent marker write cannot be silently clobbered and the epoch
252
+ * append-only rail holds unconditionally, not just under `If-Match`.
253
+ *
254
+ * @param options {object}
255
+ * @param [options.existing] {CollectionEncryption} the persisted marker
256
+ * @param [options.incoming] {CollectionEncryption} the marker about to be
257
+ * persisted (absent when the write would drop the marker entirely)
258
+ * @returns {void}
259
+ */
260
+ export function assertEncryptionMarkerTransition({ existing, incoming }) {
261
+ if (existing === undefined) {
262
+ // Nothing persisted yet: a first declaration (or a plaintext Collection
263
+ // staying plaintext) has no rails to check.
264
+ return;
265
+ }
266
+ if (incoming === undefined) {
267
+ throw new EncryptionImmutableError();
268
+ }
269
+ assertEncryptionTransition({ existing, incoming });
270
+ assertEncryptionEpochsTransition({ existing, incoming });
271
+ }
272
+ /**
273
+ * Enforces set-once immutability of a Collection's `encryption` marker on
274
+ * update. Call only when an `incoming` marker was supplied (and shape-validated)
275
+ * by the request. Declaring a marker on a Collection that lacks one is allowed
276
+ * (`absent -> present`: late declaration / migration of a pre-marker
277
+ * Collection); re-sending the same `scheme` is a no-op. Changing the `scheme` of
278
+ * an existing marker is rejected with `encryption-immutable` (409) -- it would
279
+ * corrupt the stored, client-encrypted Resources. (Clearing is not expressible:
280
+ * an absent body `encryption` leaves the existing marker untouched, and an
281
+ * explicit non-object is already a 400 in `assertSupportedEncryption`.) The
282
+ * `scheme` is the immutable identity; comparison deliberately ignores any
283
+ * future public-reference fields, whose evolution (e.g. adding a recipient) is a
284
+ * separate, allowed operation.
285
+ *
286
+ * @param options {object}
287
+ * @param [options.existing] {CollectionEncryption} the persisted marker
288
+ * @param options.incoming {CollectionEncryption} the validated request marker
289
+ * @returns {void}
290
+ */
291
+ export function assertEncryptionTransition({ existing, incoming }) {
292
+ if (existing !== undefined && existing.scheme !== incoming.scheme) {
293
+ throw new EncryptionImmutableError();
294
+ }
295
+ }
296
+ /**
297
+ * Fail-closed structural validation of a Resource **content** write into an
298
+ * encrypted Collection (spec "Encryption Scheme Registry"). When the target
299
+ * Collection declares a recognized `encryption` scheme, a write MUST be a
300
+ * conforming envelope of that scheme -- two gates: (1) the request `Content-Type`
301
+ * MUST be the scheme's registered media type (so a binary/`octet-stream`,
302
+ * `multipart`, or plain `application/json` upload is rejected outright), and (2)
303
+ * the parsed body MUST satisfy the scheme's structural envelope profile. A
304
+ * failure of either is `encryption-scheme-mismatch` (422). No-op when the
305
+ * Collection has no marker (plaintext) or -- defensively -- an unrecognized
306
+ * scheme (which `assertSupportedEncryption` prevents from ever being stored), so
307
+ * plaintext Collections and API documents are unaffected. The server validates
308
+ * structure only; it never decrypts. Call this **after** capability verification
309
+ * and the 404-if-missing check, and before resolving the body stream, so a wrong
310
+ * content type is rejected without consuming the upload and a 422 is observable
311
+ * only to a caller already authorized to write the target.
312
+ *
313
+ * @param options {object}
314
+ * @param options.collectionDescription {{ encryption?: CollectionEncryption }}
315
+ * the target Collection's stored description
316
+ * @param [options.contentType] {string} the request `Content-Type` header
317
+ * @param options.body {unknown} the parsed request body (an object for the
318
+ * `application/<suffix>+json` media types the scheme registry uses)
319
+ * @returns {void}
320
+ */
321
+ export function assertEncryptedWriteConforms({ collectionDescription, contentType, body }) {
322
+ const scheme = collectionDescription.encryption?.scheme;
323
+ if (scheme === undefined) {
324
+ return;
325
+ }
326
+ const profile = SUPPORTED_ENCRYPTION_SCHEMES[scheme];
327
+ if (!profile) {
328
+ return;
329
+ }
330
+ // Gate 1: the stored representation's media type. Compare the bare media type
331
+ // (parameters like `; charset=utf-8` stripped), case-insensitively.
332
+ const mediaType = (contentType ?? '').split(';')[0].trim().toLowerCase();
333
+ if (mediaType !== profile.mediaType) {
334
+ throw new EncryptionSchemeMismatchError({
335
+ detail: `A write into a Collection encrypted with the '${scheme}' scheme must use Content-Type '${profile.mediaType}'.`
336
+ });
337
+ }
338
+ // Gate 2: the envelope's structural profile (server validates shape, not
339
+ // contents -- it never decrypts).
340
+ if (!profile.validateEnvelope(body)) {
341
+ throw new EncryptionSchemeMismatchError({
342
+ detail: `Resource body is not a structurally valid '${scheme}' encryption envelope.`
343
+ });
344
+ }
345
+ }
346
+ /**
347
+ * Fail-closed structural validation of a Resource **metadata** write (`PUT
348
+ * /meta`) into an encrypted Collection (spec "Encrypted Collections"). When the
349
+ * target Collection declares a recognized `encryption` scheme, the user-writable
350
+ * `custom` object MUST be a conforming envelope of that scheme -- the same
351
+ * structural profile used for content -- so a plaintext `{ name, tags }` cannot
352
+ * be stored server-visibly. Unlike {@link assertEncryptedWriteConforms} there is
353
+ * **no media-type gate**: the metadata document itself stays `application/json`
354
+ * (its server-managed top-level fields are plaintext); only the `custom`
355
+ * sub-value is the envelope. A non-conforming `custom` is
356
+ * `encryption-scheme-mismatch` (422). No-op when the Collection has no marker
357
+ * (plaintext) or -- defensively -- an unrecognized scheme. The server validates
358
+ * structure only; it never decrypts. Call this **after** capability verification
359
+ * and the 404-if-missing check, before the write, so a 422 is observable only to
360
+ * a caller already authorized to write the target.
361
+ *
362
+ * @param options {object}
363
+ * @param options.collectionDescription {{ encryption?: CollectionEncryption }}
364
+ * the target Collection's stored description
365
+ * @param options.custom {unknown} the request body's `custom` value
366
+ * @returns {void}
367
+ */
368
+ export function assertEncryptedMetaConforms({ collectionDescription, custom }) {
369
+ const scheme = collectionDescription.encryption?.scheme;
370
+ if (scheme === undefined) {
371
+ return;
372
+ }
373
+ const profile = SUPPORTED_ENCRYPTION_SCHEMES[scheme];
374
+ if (!profile) {
375
+ return;
376
+ }
377
+ if (!profile.validateEnvelope(custom)) {
378
+ throw new EncryptionSchemeMismatchError({
379
+ detail: `Resource metadata "custom" is not a structurally valid '${scheme}' encryption envelope.`
380
+ });
381
+ }
382
+ }
383
+ //# sourceMappingURL=encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/lib/encryption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Helpers for the HTTP `ETag` strong validator that backs conditional writes
3
+ * (the `conditional-writes` feature). A Resource's monotonic `version` is
4
+ * formatted as a quoted strong validator on the wire, and incoming `If-Match` /
5
+ * `If-None-Match` request headers are normalized into write preconditions.
6
+ */
7
+ /**
8
+ * Formats a Resource's monotonic `version` as a quoted strong `ETag` validator
9
+ * (e.g. version 3 to `"3"`). The quotes are part of the on-the-wire value, and
10
+ * `If-Match` comparison is exact-string (strong) comparison.
11
+ * @param version {number}
12
+ * @returns {string}
13
+ */
14
+ export declare function formatEtag(version: number): string;
15
+ /**
16
+ * Normalizes the `If-Match` / `If-None-Match` request headers into the write
17
+ * preconditions the storage layer evaluates. Only `If-None-Match: *`
18
+ * (create-if-absent) is supported; an `If-Match` value is passed through as the
19
+ * quoted ETag to match. A header that is absent (or, for an array-valued header,
20
+ * not a single string) contributes no precondition.
21
+ * @param headers {object}
22
+ * @param [headers.if-match] {string | string[]}
23
+ * @param [headers.if-none-match] {string | string[]}
24
+ * @returns {{ ifMatch?: string, ifNoneMatch?: boolean }}
25
+ */
26
+ export declare function parseWritePreconditions(headers: {
27
+ 'if-match'?: string | string[];
28
+ 'if-none-match'?: string | string[];
29
+ }): {
30
+ ifMatch?: string;
31
+ ifNoneMatch?: boolean;
32
+ };
33
+ //# sourceMappingURL=etag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"etag.d.ts","sourceRoot":"","sources":["../../src/lib/etag.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC9B,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CACpC,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAO9C"}