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
package/dist/errors.js ADDED
@@ -0,0 +1,960 @@
1
+ import { ProblemTypes } from '@interop/storage-core';
2
+ /**
3
+ * Base class for the server's error hierarchy. Carries the four
4
+ * `application/problem+json` fields (`type` / `title` / `detail` /
5
+ * `statusCode`) plus an optional `problems` array of `{ detail, pointer }`
6
+ * entries; `handleError` serializes these to the wire. Subclasses set their
7
+ * distinguishing values via `super({ ... })`.
8
+ * @param options {object}
9
+ * @param options.type {ProblemType} problem-kind URI (see @interop/storage-core)
10
+ * @param options.title {string} short human-readable summary
11
+ * @param options.detail {string} specific explanation of the occurrence
12
+ * @param options.statusCode {number} HTTP status code
13
+ * @param [options.problems] {Problem[]} per-field error entries
14
+ * @param [options.cause] {Error} the underlying error, when wrapping one
15
+ */
16
+ export class ProblemError extends Error {
17
+ type;
18
+ title;
19
+ detail;
20
+ statusCode;
21
+ problems;
22
+ constructor({ type, title, detail, statusCode, problems, cause }) {
23
+ super(detail, cause ? { cause } : undefined);
24
+ this.type = type;
25
+ this.title = title;
26
+ this.detail = detail;
27
+ this.statusCode = statusCode;
28
+ if (problems) {
29
+ this.problems = problems;
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * 404 — the requested Space does not exist, or the caller is not authorized.
35
+ * @param options {object}
36
+ * @param [options.requestName] {string} request name used in the error title
37
+ */
38
+ export class SpaceNotFoundError extends ProblemError {
39
+ constructor({ requestName } = {}) {
40
+ super({
41
+ type: ProblemTypes.NOT_FOUND,
42
+ title: `Invalid ${requestName || 'Space'} request`,
43
+ detail: 'Space not found or invalid authorization.',
44
+ statusCode: 404
45
+ });
46
+ }
47
+ }
48
+ /**
49
+ * 400 — the provided space id is not URL-safe / otherwise invalid.
50
+ * @param options {object}
51
+ * @param [options.requestName] {string} request name used in the error title
52
+ */
53
+ export class InvalidSpaceIdError extends ProblemError {
54
+ constructor({ requestName } = {}) {
55
+ super({
56
+ type: ProblemTypes.INVALID_ID,
57
+ title: `Invalid ${requestName || 'Space'} request`,
58
+ detail: 'Invalid space id (make sure it is URL-safe).',
59
+ statusCode: 400
60
+ });
61
+ }
62
+ }
63
+ /**
64
+ * 400 — the provided collection id is not URL-safe / otherwise invalid.
65
+ * @param options {object}
66
+ * @param [options.requestName] {string} request name used in the error title
67
+ */
68
+ export class InvalidCollectionIdError extends ProblemError {
69
+ constructor({ requestName } = {}) {
70
+ super({
71
+ type: ProblemTypes.INVALID_ID,
72
+ title: `Invalid ${requestName || 'Collection'} request`,
73
+ detail: 'Invalid collection id (make sure it is URL-safe).',
74
+ statusCode: 400
75
+ });
76
+ }
77
+ }
78
+ /**
79
+ * 400 — the provided resource id is not URL-safe / otherwise invalid.
80
+ * @param options {object}
81
+ * @param [options.requestName] {string} request name used in the error title
82
+ */
83
+ export class InvalidResourceIdError extends ProblemError {
84
+ constructor({ requestName } = {}) {
85
+ super({
86
+ type: ProblemTypes.INVALID_ID,
87
+ title: `Invalid ${requestName || 'Resource'} request`,
88
+ detail: 'Invalid resource id (make sure it is URL-safe).',
89
+ statusCode: 400
90
+ });
91
+ }
92
+ }
93
+ /**
94
+ * 409 — a `POST` create operation supplied an `id` that already exists.
95
+ * Create-or-replace at a client-chosen id is the idempotent `PUT` path, which
96
+ * does not conflict.
97
+ * @param options {object}
98
+ * @param options.kind {string} what was being created ('Space', 'Collection',
99
+ * 'Resource'), used in the error title and detail
100
+ */
101
+ export class IdConflictError extends ProblemError {
102
+ constructor({ kind }) {
103
+ const detail = `Use PUT to create-or-replace a ${kind} at a chosen id.`;
104
+ super({
105
+ type: ProblemTypes.ID_CONFLICT,
106
+ title: `A ${kind} with this id already exists.`,
107
+ detail,
108
+ statusCode: 409,
109
+ problems: [{ detail, pointer: '#/id' }]
110
+ });
111
+ }
112
+ }
113
+ /**
114
+ * 409 — a Resource write carries an `indexed` blinded attribute marked
115
+ * `unique: true` whose (HMAC key id, name, value) triple is already claimed by
116
+ * another live Resource in the same Collection (the EDV unique-attribute
117
+ * invariant, enforced by backends carrying the `blinded-index-query` feature).
118
+ * Reuses the `id-conflict` problem type (like the WebKMS conflicts): a unique
119
+ * blinded attribute is a client-chosen identifier-like claim, and the registry
120
+ * defines no more specific kind. Only ever observable by a caller already
121
+ * authorized to write the target.
122
+ */
123
+ export class UniqueAttributeConflictError extends ProblemError {
124
+ constructor() {
125
+ const detail = 'Could not write document; a unique blinded index attribute is already' +
126
+ ' in use by another Resource in this Collection.';
127
+ super({
128
+ type: ProblemTypes.ID_CONFLICT,
129
+ title: 'A unique blinded index attribute is already in use.',
130
+ detail,
131
+ statusCode: 409,
132
+ problems: [{ detail, pointer: '#/indexed' }]
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * 409 — a client-supplied id collides with a segment from the spec's Reserved
138
+ * Path Segment Registry: the id would shadow the reserved route at that
139
+ * position (e.g. a Collection named `export` would shadow
140
+ * `/space/{id}/export`).
141
+ * @param options {object}
142
+ * @param options.kind {string} which id position collided ('collection',
143
+ * 'resource'), used in the error title and detail
144
+ * @param options.id {string} the offending reserved id
145
+ */
146
+ export class ReservedIdError extends ProblemError {
147
+ constructor({ kind, id }) {
148
+ const detail = `'${id}' is a reserved path segment and cannot be used as a ${kind} id.`;
149
+ super({
150
+ type: ProblemTypes.RESERVED_ID,
151
+ title: `Invalid ${kind} id (from reserved list).`,
152
+ detail,
153
+ statusCode: 409,
154
+ problems: [{ detail, pointer: '#/id' }]
155
+ });
156
+ }
157
+ }
158
+ /**
159
+ * 409 — a backend reference cannot be honored. Three occurrences, distinguished
160
+ * by `detail` / `pointer`: a Collection create/update names a `backend` id not in
161
+ * the Space's backends-available list (the default `#/backend` detail); a
162
+ * registration names a `provider` the server does not permit (the allowlist gate,
163
+ * `#/provider`); or a selected backend is registered but has no live adapter yet
164
+ * (the resolver's data-plane backstop). Spec `unsupported-backend`.
165
+ * @param options {object}
166
+ * @param options.backendId {string} the unrecognized backend (or provider) id
167
+ * @param [options.detail] {string} a specific explanation; defaults to the
168
+ * backends-available message
169
+ * @param [options.pointer] {string} RFC 6901 JSON Pointer to the offending
170
+ * field; defaults to `#/backend`
171
+ */
172
+ export class UnsupportedBackendError extends ProblemError {
173
+ constructor({ backendId, detail, pointer = '#/backend' }) {
174
+ const resolvedDetail = detail ??
175
+ `Backend '${backendId}' is not in this Space's backends-available list.`;
176
+ super({
177
+ type: ProblemTypes.UNSUPPORTED_BACKEND,
178
+ title: "Unsupported backend id, check the space's 'backends available' list.",
179
+ detail: resolvedDetail,
180
+ statusCode: 409,
181
+ problems: [{ detail: resolvedDetail, pointer }]
182
+ });
183
+ }
184
+ }
185
+ /**
186
+ * 409 — a Collection update tried to change or clear an existing client-side
187
+ * `encryption` marker. The marker is set-once (spec): declaring it on a
188
+ * Collection that lacks one is allowed, but changing its scheme (or clearing
189
+ * it) on a populated Collection would corrupt the stored, client-encrypted
190
+ * Resources. Like the other state-conflict 409s (`id-conflict`,
191
+ * `unsupported-backend`), it is only observable by a caller already authorized
192
+ * to update the Collection -- it is checked after capability verification.
193
+ */
194
+ export class EncryptionImmutableError extends ProblemError {
195
+ constructor() {
196
+ const detail = "A Collection's 'encryption' marker is set-once and cannot be changed or cleared.";
197
+ super({
198
+ type: ProblemTypes.ENCRYPTION_IMMUTABLE,
199
+ title: 'Collection encryption marker is immutable.',
200
+ detail,
201
+ statusCode: 409,
202
+ problems: [{ detail, pointer: '#/encryption' }]
203
+ });
204
+ }
205
+ }
206
+ /**
207
+ * 400 — a Collection create/update supplied an `encryption` marker naming a
208
+ * `scheme` this server does not recognize and therefore cannot enforce on write.
209
+ * Taking the spec's fail-closed SHOULD path ("Encryption Scheme Registry"), the
210
+ * reference server rejects such a marker rather than storing it opaquely, so
211
+ * every accepted marker is one whose non-conforming (e.g. plaintext) writes it
212
+ * will reject. A pure body-shape rejection, checked before capability
213
+ * verification like the other `invalid`-shaped 400s.
214
+ * @param options {object}
215
+ * @param options.scheme {string} the unrecognized scheme token
216
+ */
217
+ export class UnsupportedEncryptionSchemeError extends ProblemError {
218
+ constructor({ scheme }) {
219
+ const detail = `This server does not support the '${scheme}' encryption scheme.`;
220
+ super({
221
+ type: ProblemTypes.UNSUPPORTED_ENCRYPTION_SCHEME,
222
+ title: 'Unsupported encryption scheme.',
223
+ detail,
224
+ statusCode: 400,
225
+ problems: [{ detail, pointer: '#/encryption/scheme' }]
226
+ });
227
+ }
228
+ }
229
+ /**
230
+ * 422 — a content write into a Collection whose `encryption` marker declares a
231
+ * recognized scheme did not conform to that scheme's envelope profile: the
232
+ * request `Content-Type` was not the scheme's registered media type, or the body
233
+ * was not a structurally valid envelope (spec "Encryption Scheme Registry"). The
234
+ * server validates structure only and never decrypts. Like the write-time 409s
235
+ * (`id-conflict`, `encryption-immutable`), it is only ever observable by a caller
236
+ * already authorized to write the target -- it is checked after capability
237
+ * verification, so an under-authorized caller receives the privacy-merged
238
+ * `not-found` (404) instead.
239
+ * @param options {object}
240
+ * @param [options.detail] {string} which gate failed (media type vs envelope shape)
241
+ */
242
+ export class EncryptionSchemeMismatchError extends ProblemError {
243
+ constructor({ detail } = {}) {
244
+ const message = detail ??
245
+ "Resource body does not conform to the Collection's declared encryption scheme.";
246
+ super({
247
+ type: ProblemTypes.ENCRYPTION_SCHEME_MISMATCH,
248
+ title: 'Encrypted Collection write does not conform to its scheme.',
249
+ detail: message,
250
+ statusCode: 422,
251
+ problems: [{ detail: message }]
252
+ });
253
+ }
254
+ }
255
+ /**
256
+ * 412 — a conditional write's `If-Match` / `If-None-Match` precondition
257
+ * evaluated false: the Resource's current `ETag` did not match, or a
258
+ * create-if-absent (`If-None-Match: *`) target already exists. Header-driven
259
+ * and deliberately distinct from the 409 conflict kinds (`id-conflict`,
260
+ * `reserved-id`, `unsupported-backend`). A `412` is only ever observable by a
261
+ * caller already authorized to write the target -- the request layer checks
262
+ * authorization before the backend evaluates the precondition.
263
+ * @param options {object}
264
+ * @param [options.detail] {string} a specific explanation of the mismatch
265
+ * @param [options.requestName] {string} request name used in the error title
266
+ */
267
+ export class PreconditionFailedError extends ProblemError {
268
+ constructor({ detail, requestName } = {}) {
269
+ const message = detail ?? 'A conditional request precondition was not met.';
270
+ super({
271
+ type: ProblemTypes.PRECONDITION_FAILED,
272
+ title: requestName
273
+ ? `Precondition Failed (${requestName})`
274
+ : 'Precondition Failed',
275
+ detail: message,
276
+ statusCode: 412,
277
+ problems: [{ detail: message }]
278
+ });
279
+ }
280
+ }
281
+ /**
282
+ * 400 — a pagination `cursor` query parameter is malformed or can no longer be
283
+ * honored (not valid base64url, not JSON, or missing its keyset position; spec
284
+ * `invalid-cursor`). Like `precondition-failed`, it is only ever observable by a
285
+ * caller already authorized to list the target -- the request layer checks
286
+ * authorization before the backend validates the cursor, so an under-authorized
287
+ * caller receives the privacy-merged `not-found` (404) instead.
288
+ * @param options {object}
289
+ * @param [options.detail] {string} a specific explanation of the failure
290
+ * @param [options.cause] {Error} the underlying parse error, when wrapping one
291
+ */
292
+ export class InvalidCursorError extends ProblemError {
293
+ constructor({ detail, cause } = {}) {
294
+ const message = detail ??
295
+ 'The pagination cursor is malformed or can no longer be honored.';
296
+ super({
297
+ type: ProblemTypes.INVALID_CURSOR,
298
+ title: 'Invalid pagination cursor',
299
+ detail: message,
300
+ statusCode: 400,
301
+ problems: [{ detail: message }],
302
+ cause
303
+ });
304
+ }
305
+ }
306
+ /**
307
+ * 400 — the Collection Description request body is missing or invalid.
308
+ */
309
+ export class InvalidCollectionError extends ProblemError {
310
+ constructor() {
311
+ super({
312
+ type: ProblemTypes.INVALID_REQUEST_BODY,
313
+ title: 'Invalid Collection Description body',
314
+ detail: 'Collection Description body is missing or invalid.',
315
+ statusCode: 400
316
+ });
317
+ }
318
+ }
319
+ /**
320
+ * 404 — the requested Collection does not exist, or the caller is not
321
+ * authorized.
322
+ * @param options {object}
323
+ * @param [options.requestName] {string} request name used in the error title
324
+ */
325
+ export class CollectionNotFoundError extends ProblemError {
326
+ constructor({ requestName } = {}) {
327
+ super({
328
+ type: ProblemTypes.NOT_FOUND,
329
+ title: `Invalid ${requestName || 'Collection'} request`,
330
+ detail: 'Collection not found or invalid authorization.',
331
+ statusCode: 404
332
+ });
333
+ }
334
+ }
335
+ /**
336
+ * 404 — the requested Resource does not exist, or the caller is not authorized.
337
+ * @param options {object}
338
+ * @param [options.requestName] {string} request name used in the error title
339
+ */
340
+ export class ResourceNotFoundError extends ProblemError {
341
+ constructor({ requestName } = {}) {
342
+ super({
343
+ type: ProblemTypes.NOT_FOUND,
344
+ title: `Invalid ${requestName || 'Resource'} request`,
345
+ detail: 'Resource not found or invalid authorization.',
346
+ statusCode: 404
347
+ });
348
+ }
349
+ }
350
+ /**
351
+ * 404 — no access-control policy document is set at the requested level
352
+ * (reported as not-found, consistent with the privacy-merged kind).
353
+ * @param options {object}
354
+ * @param [options.requestName] {string} request name used in the error title
355
+ */
356
+ export class PolicyNotFoundError extends ProblemError {
357
+ constructor({ requestName } = {}) {
358
+ super({
359
+ type: ProblemTypes.NOT_FOUND,
360
+ title: `Invalid ${requestName || 'Policy'} request`,
361
+ detail: 'Policy not found or invalid authorization.',
362
+ statusCode: 404
363
+ });
364
+ }
365
+ }
366
+ /**
367
+ * 400 — the access-control policy document is missing or malformed (it must be
368
+ * a JSON object carrying a non-empty string `type`).
369
+ * @param options {object}
370
+ * @param [options.requestName] {string} request name used in the error title
371
+ */
372
+ export class InvalidPolicyError extends ProblemError {
373
+ constructor({ requestName } = {}) {
374
+ super({
375
+ type: ProblemTypes.INVALID_REQUEST_BODY,
376
+ title: `Invalid ${requestName || 'Policy'} body`,
377
+ detail: 'Policy document must be a JSON object with a non-empty string "type".',
378
+ statusCode: 400
379
+ });
380
+ }
381
+ }
382
+ /**
383
+ * 404 — capability invocation did not verify (reported as not-found so as not
384
+ * to leak resource existence).
385
+ * @param options {object}
386
+ * @param options.requestName {string} request name used in the error title
387
+ */
388
+ export class UnauthorizedError extends ProblemError {
389
+ constructor({ requestName }) {
390
+ super({
391
+ type: ProblemTypes.NOT_FOUND,
392
+ title: `Invalid ${requestName} request.`,
393
+ detail: 'URL not found or invalid authorization.',
394
+ statusCode: 404
395
+ });
396
+ }
397
+ }
398
+ /**
399
+ * 401 — required `Authorization` / `Capability-Invocation` headers are missing.
400
+ */
401
+ export class MissingAuthError extends ProblemError {
402
+ constructor() {
403
+ super({
404
+ type: ProblemTypes.MISSING_AUTHORIZATION,
405
+ title: 'Invalid request',
406
+ detail: 'Authorization and Capability-Invocation headers are required.',
407
+ statusCode: 401
408
+ });
409
+ }
410
+ }
411
+ /**
412
+ * 403 — provisioning (creating Spaces or keystores) is refused by the server's
413
+ * configured provisioning policy (`authorizeProvisioning` returned `deny`).
414
+ * There is no dedicated `forbidden` problem type in `@interop/storage-core`, so
415
+ * this deliberately reuses `MISSING_AUTHORIZATION`.
416
+ */
417
+ export class ProvisioningNotAuthorizedError extends ProblemError {
418
+ constructor() {
419
+ super({
420
+ type: ProblemTypes.MISSING_AUTHORIZATION,
421
+ title: 'Provisioning not authorized',
422
+ detail: 'Creating Spaces and keystores is not authorized on this server.',
423
+ statusCode: 403
424
+ });
425
+ }
426
+ }
427
+ /**
428
+ * 401 — the server requires an onboarding token to provision, but the request
429
+ * carried no `Authorization: Bearer <token>` header.
430
+ */
431
+ export class MissingOnboardingTokenError extends ProblemError {
432
+ constructor() {
433
+ super({
434
+ type: ProblemTypes.MISSING_AUTHORIZATION,
435
+ title: 'Invalid request',
436
+ detail: 'An onboarding token (Authorization: Bearer) is required to create ' +
437
+ 'Spaces and keystores on this server.',
438
+ statusCode: 401
439
+ });
440
+ }
441
+ }
442
+ /**
443
+ * 403 — the onboarding token presented in the `Authorization: Bearer` header
444
+ * did not match the configured token.
445
+ */
446
+ export class InvalidOnboardingTokenError extends ProblemError {
447
+ constructor() {
448
+ super({
449
+ type: ProblemTypes.INVALID_AUTHORIZATION_HEADER,
450
+ title: 'Invalid request',
451
+ detail: 'The provided onboarding token is not valid.',
452
+ statusCode: 403
453
+ });
454
+ }
455
+ }
456
+ /**
457
+ * 400 — the `Authorization` header did not include a `keyId` parameter.
458
+ */
459
+ export class MissingKeyIdError extends ProblemError {
460
+ constructor() {
461
+ super({
462
+ type: ProblemTypes.INVALID_AUTHORIZATION_HEADER,
463
+ title: 'Invalid Authorization header',
464
+ detail: 'Authorization header is missing the keyId parameter.',
465
+ statusCode: 400
466
+ });
467
+ }
468
+ }
469
+ /**
470
+ * 400 — failed to parse the `Authorization`, `Capability-Invocation`, or
471
+ * `Digest` headers.
472
+ * @param options {object}
473
+ * @param options.cause {Error} the underlying parse error
474
+ */
475
+ export class AuthHeaderParseError extends ProblemError {
476
+ constructor({ cause }) {
477
+ super({
478
+ type: ProblemTypes.INVALID_AUTHORIZATION_HEADER,
479
+ title: 'Invalid authorization headers',
480
+ detail: 'Error parsing Authorization, Capability-Invocation, or Digest headers.',
481
+ statusCode: 400,
482
+ cause
483
+ });
484
+ }
485
+ }
486
+ /**
487
+ * 400 — the `Digest` header is missing, malformed, not covered by the request
488
+ * signature, or does not match the received body (spec "Request Body
489
+ * Integrity"). All of these are reported as `invalid-authorization-header`.
490
+ * @param options {object}
491
+ * @param options.detail {string} which of the digest conditions failed
492
+ * @param [options.cause] {Error} the underlying error, when wrapping one
493
+ */
494
+ export class InvalidDigestError extends ProblemError {
495
+ constructor({ detail, cause }) {
496
+ super({
497
+ type: ProblemTypes.INVALID_AUTHORIZATION_HEADER,
498
+ title: 'Invalid Digest header',
499
+ detail,
500
+ statusCode: 400,
501
+ cause
502
+ });
503
+ }
504
+ }
505
+ /**
506
+ * 400 — an error was thrown while verifying the authorization headers.
507
+ * @param options {object}
508
+ * @param options.requestName {string} request name used in the error title
509
+ * @param options.cause {Error} the underlying verification error
510
+ */
511
+ export class AuthVerificationError extends ProblemError {
512
+ constructor({ requestName, cause }) {
513
+ super({
514
+ type: ProblemTypes.INVALID_AUTHORIZATION_HEADER,
515
+ title: `Invalid ${requestName} request.`,
516
+ detail: 'Error verifying authorization headers.',
517
+ statusCode: 400,
518
+ cause
519
+ });
520
+ }
521
+ }
522
+ /**
523
+ * 400 — the `controller` field is present but is not a syntactically valid
524
+ * `did:key` DID (the only DID method this server accepts). Caught at the request
525
+ * layer so a malformed controller is rejected on the way in rather than failing
526
+ * later, at capability-verification time.
527
+ * @param options {object}
528
+ * @param [options.requestName] {string} request name used in the error title
529
+ */
530
+ export class InvalidControllerError extends ProblemError {
531
+ constructor({ requestName } = {}) {
532
+ const detail = 'The "controller" property must be a valid did:key DID.';
533
+ super({
534
+ type: ProblemTypes.INVALID_REQUEST_BODY,
535
+ title: `Invalid ${requestName || 'request'} body`,
536
+ detail,
537
+ statusCode: 400,
538
+ problems: [{ detail, pointer: '#/controller' }]
539
+ });
540
+ }
541
+ }
542
+ /**
543
+ * 400 — the capability invocation on a Create Space request (via POST or
544
+ * create-via-PUT) is not *authorized by* the `controller` in the request body:
545
+ * it is neither signed directly by that DID nor accompanied by a delegation
546
+ * chain rooted in it.
547
+ * @param options {object}
548
+ * @param options.zcapSigningDid {string} DID that signed the invocation
549
+ * @param options.controller {string} controller DID supplied in the body
550
+ * @param [options.cause] {Error} the underlying chain-verification failure,
551
+ * for a delegated invocation rejected at verification time
552
+ */
553
+ export class SpaceControllerMismatchError extends ProblemError {
554
+ constructor({ zcapSigningDid, controller, cause }) {
555
+ const detail = `The invocation must be authorized by the 'controller' DID in the` +
556
+ ` request body ("${controller}"): signed by it, or via a delegation` +
557
+ ` chain rooted in it (invocation signed by "${zcapSigningDid}").`;
558
+ super({
559
+ type: ProblemTypes.CONTROLLER_MISMATCH,
560
+ title: 'Invalid Create Space request',
561
+ detail,
562
+ statusCode: 400,
563
+ problems: [{ detail, pointer: '#/controller' }],
564
+ cause
565
+ });
566
+ }
567
+ }
568
+ /**
569
+ * 500 — an underlying storage operation failed. The client-visible `title` /
570
+ * `detail` are deliberately generic: the underlying `cause` can carry
571
+ * server-internal specifics (filesystem paths, errnos, SQL) and is logged
572
+ * server-side by `handleError`, never serialized to the wire.
573
+ * @param options {object}
574
+ * @param options.cause {Error} the underlying storage error
575
+ * @param [options.requestName] {string} request name used in the error title
576
+ */
577
+ export class StorageError extends ProblemError {
578
+ constructor({ cause, requestName }) {
579
+ super({
580
+ type: ProblemTypes.STORAGE_ERROR,
581
+ title: requestName ? `Storage Error (${requestName})` : 'Storage Error',
582
+ detail: 'An internal storage error occurred.',
583
+ statusCode: 500,
584
+ cause
585
+ });
586
+ }
587
+ }
588
+ /**
589
+ * 500 — an at-rest WebKMS key record could not be decrypted (its KEK is not
590
+ * registered, or the ciphertext failed to authenticate). A server-side
591
+ * misconfiguration of `KMS_RECORD_KEK`, never a client error; `handleError`
592
+ * logs the underlying `cause`.
593
+ * @param options {object}
594
+ * @param options.cause {Error} the underlying cipher error
595
+ * @param [options.requestName] {string} request name used in the error title
596
+ */
597
+ export class KmsRecordCipherError extends ProblemError {
598
+ constructor({ cause, requestName }) {
599
+ super({
600
+ type: ProblemTypes.INTERNAL_ERROR,
601
+ title: requestName
602
+ ? `KMS Record Cipher Error (${requestName})`
603
+ : 'KMS Record Cipher Error',
604
+ detail: 'A stored key record could not be decrypted. This is a server ' +
605
+ 'configuration error (KMS_RECORD_KEK).',
606
+ statusCode: 500,
607
+ cause
608
+ });
609
+ }
610
+ }
611
+ /**
612
+ * Rethrows a structured failure from a storage call unchanged (any
613
+ * `ProblemError`, e.g. a 507 quota-exceeded raised by the backend), preserving
614
+ * its status code; wraps anything else -- a genuinely unexpected fault -- as a
615
+ * 500 `StorageError`. For use in request handlers' catch blocks around storage
616
+ * operations.
617
+ * @param options {object}
618
+ * @param options.err {unknown} the caught error
619
+ * @param [options.requestName] {string} request name used in the error title
620
+ * @returns {never}
621
+ */
622
+ export function rethrowOrWrapStorageError({ err, requestName }) {
623
+ if (err instanceof ProblemError) {
624
+ throw err;
625
+ }
626
+ throw new StorageError({ cause: err, requestName });
627
+ }
628
+ /**
629
+ * 507 — a write was rejected because the target backend's per-Space storage
630
+ * quota is exhausted (spec "Quotas"). Distinct from `payload-too-large` (413),
631
+ * which is a per-request upload-size limit rather than a cumulative one.
632
+ * @param options {object}
633
+ * @param options.spaceId {string} the Space whose quota is exhausted
634
+ * @param options.capacityBytes {number} the configured per-Space limit
635
+ * @param [options.requestName] {string} request name used in the error title
636
+ */
637
+ export class QuotaExceededError extends ProblemError {
638
+ constructor({ spaceId, capacityBytes, requestName }) {
639
+ super({
640
+ type: ProblemTypes.QUOTA_EXCEEDED,
641
+ title: `Insufficient Storage${requestName ? ` (${requestName})` : ''}`,
642
+ detail: `Space '${spaceId}' storage quota of ${capacityBytes} bytes is exhausted.`,
643
+ statusCode: 507
644
+ });
645
+ }
646
+ }
647
+ /**
648
+ * 507 — a create was rejected because a default-on count quota is exhausted:
649
+ * too many Spaces for one controller, too many Collections in a Space, or too
650
+ * many live Resources in a Space. Distinct from `QuotaExceededError` (also 507),
651
+ * which caps cumulative bytes rather than item counts; both reuse the spec's
652
+ * `quota-exceeded` problem type (there is no count-specific slug). Only the
653
+ * create path trips this -- overwriting an existing item never does.
654
+ * @param options {object}
655
+ * @param options.scope {string} short phrase naming the exhausted count (e.g.
656
+ * `'Spaces per controller'`, `'Collections per Space'`,
657
+ * `'Resources per Space'`), used in the detail message
658
+ * @param options.limit {number} the configured maximum count
659
+ */
660
+ export class CountQuotaExceededError extends ProblemError {
661
+ constructor({ scope, limit }) {
662
+ super({
663
+ type: ProblemTypes.QUOTA_EXCEEDED,
664
+ title: 'Insufficient Storage',
665
+ detail: `Count quota exceeded: ${scope} limit is ${limit}.`,
666
+ statusCode: 507
667
+ });
668
+ }
669
+ }
670
+ /**
671
+ * 413 — an upload exceeds the target backend's per-request `maxUploadBytes`
672
+ * constraint. Distinct from `quota-exceeded` (507): this limit is per-request,
673
+ * not cumulative, so smaller uploads may still succeed.
674
+ * @param options {object}
675
+ * @param options.maxUploadBytes {number} the backend's per-upload limit
676
+ * @param options.backendId {string} the backend enforcing the limit
677
+ * @param [options.uploadBytes] {number} the upload's size, when known up
678
+ * front (a streamed upload without a Content-Length only reveals the
679
+ * overflow, not the total)
680
+ */
681
+ export class PayloadTooLargeError extends ProblemError {
682
+ constructor({ maxUploadBytes, backendId, uploadBytes }) {
683
+ const detail = uploadBytes === undefined
684
+ ? `Upload exceeds 'maxUploadBytes' of ${maxUploadBytes} for backend '${backendId}'.`
685
+ : `Upload size ${uploadBytes} exceeds 'maxUploadBytes' of ${maxUploadBytes} for backend '${backendId}'.`;
686
+ super({
687
+ type: ProblemTypes.PAYLOAD_TOO_LARGE,
688
+ title: "Upload exceeds the backend's maximum upload size.",
689
+ detail,
690
+ statusCode: 413
691
+ });
692
+ }
693
+ }
694
+ /**
695
+ * 400 — a required field of the request body is missing or invalid.
696
+ * @param options {object}
697
+ * @param [options.requestName] {string} request name used in the error title
698
+ * @param [options.detail] {string} specific detail describing the problem
699
+ * @param [options.pointer] {string} RFC 6901 JSON Pointer (`#/field` form)
700
+ * to the offending body field
701
+ */
702
+ export class InvalidRequestBodyError extends ProblemError {
703
+ constructor({ requestName, detail, pointer } = {}) {
704
+ const resolvedDetail = detail || 'Request body is missing one or more required fields.';
705
+ super({
706
+ type: ProblemTypes.INVALID_REQUEST_BODY,
707
+ title: `Invalid ${requestName || 'request'} body`,
708
+ detail: resolvedDetail,
709
+ statusCode: 400,
710
+ ...(pointer ? { problems: [{ detail: resolvedDetail, pointer }] } : {})
711
+ });
712
+ }
713
+ }
714
+ /**
715
+ * 400 — the request is missing a required `Content-Type` header.
716
+ * @param options {object}
717
+ * @param [options.requestName] {string} request name used in the error title
718
+ */
719
+ export class MissingContentTypeError extends ProblemError {
720
+ constructor({ requestName } = {}) {
721
+ super({
722
+ type: ProblemTypes.MISSING_CONTENT_TYPE,
723
+ title: `Invalid ${requestName || 'request'}`,
724
+ detail: 'A Content-Type header is required for this request.',
725
+ statusCode: 400
726
+ });
727
+ }
728
+ }
729
+ /**
730
+ * 501 — the server does not implement this OPTIONAL operation (e.g. updating
731
+ * Resource Metadata, which is not yet supported).
732
+ * @param options {object}
733
+ * @param [options.requestName] {string} request name used in the error title
734
+ */
735
+ export class UnsupportedOperationError extends ProblemError {
736
+ constructor({ requestName } = {}) {
737
+ super({
738
+ type: ProblemTypes.UNSUPPORTED_OPERATION,
739
+ title: `Unsupported ${requestName || 'operation'}`,
740
+ detail: 'This server does not implement this optional operation.',
741
+ statusCode: 501
742
+ });
743
+ }
744
+ }
745
+ /**
746
+ * 400 — the uploaded archive is not a valid WAS space export.
747
+ * @param options {object}
748
+ * @param [options.message] {string} detail message describing the problem
749
+ * @param [options.cause] {Error} the underlying error, when wrapping one
750
+ */
751
+ export class InvalidImportError extends ProblemError {
752
+ constructor({ message, cause } = {}) {
753
+ super({
754
+ type: ProblemTypes.INVALID_IMPORT,
755
+ title: 'Invalid space import',
756
+ detail: message || 'The uploaded archive is not a valid WAS space export.',
757
+ statusCode: 400,
758
+ cause
759
+ });
760
+ }
761
+ }
762
+ // WebKMS facet errors (`/kms`). The webkms
763
+ // protocol is status-code driven -- `@interop/webkms-client` distinguishes
764
+ // only 404 and 409 -- so these reuse the nearest WAS problem-kind URIs rather
765
+ // than minting a parallel registry for a non-WAS route family.
766
+ /**
767
+ * 404 — the requested keystore does not exist, or the caller is not
768
+ * authorized (the WAS existence-masking convention, kept for consistency
769
+ * within this server).
770
+ * @param options {object}
771
+ * @param [options.requestName] {string} request name used in the error title
772
+ */
773
+ export class KeystoreNotFoundError extends ProblemError {
774
+ constructor({ requestName } = {}) {
775
+ super({
776
+ type: ProblemTypes.NOT_FOUND,
777
+ title: `Invalid ${requestName || 'Keystore'} request`,
778
+ detail: 'Keystore not found or invalid authorization.',
779
+ statusCode: 404
780
+ });
781
+ }
782
+ }
783
+ /**
784
+ * 400 — the capability invocation on a Create Keystore request is not
785
+ * *authorized by* the `controller` in the request body: it is neither signed
786
+ * directly by that DID nor accompanied by a delegation chain rooted in it.
787
+ * The keystore-creation bootstrap rule, mirroring Create Space.
788
+ * @param options {object}
789
+ * @param options.zcapSigningDid {string} DID that signed the invocation
790
+ * @param options.controller {string} controller DID supplied in the body
791
+ * @param [options.cause] {Error} the underlying chain-verification failure,
792
+ * for a delegated invocation rejected at verification time
793
+ */
794
+ export class KeystoreControllerMismatchError extends ProblemError {
795
+ constructor({ zcapSigningDid, controller, cause }) {
796
+ const detail = `The invocation must be authorized by the 'controller' DID in the` +
797
+ ` request body ("${controller}"): signed by it, or via a delegation` +
798
+ ` chain rooted in it (invocation signed by "${zcapSigningDid}").`;
799
+ super({
800
+ type: ProblemTypes.CONTROLLER_MISMATCH,
801
+ title: 'Invalid Create Keystore request',
802
+ detail,
803
+ statusCode: 400,
804
+ problems: [{ detail, pointer: '#/controller' }],
805
+ cause
806
+ });
807
+ }
808
+ }
809
+ /**
810
+ * 409 — a keystore config update could not be applied: the supplied `sequence`
811
+ * is not exactly the stored sequence + 1, or the immutable `kmsModule` does
812
+ * not match. One merged conflict kind; the client distinguishes only the 409
813
+ * status.
814
+ * `ID_CONFLICT` is the nearest WAS problem kind (a 409 state conflict on an
815
+ * identified record) -- the webkms protocol has no type registry of its own.
816
+ */
817
+ export class KeystoreStateConflictError extends ProblemError {
818
+ constructor() {
819
+ const detail = 'Could not update keystore configuration: sequence must be exactly the' +
820
+ ' stored sequence + 1 and "kmsModule" cannot change.';
821
+ super({
822
+ type: ProblemTypes.ID_CONFLICT,
823
+ title: 'Keystore configuration state conflict.',
824
+ detail,
825
+ statusCode: 409,
826
+ problems: [{ detail, pointer: '#/sequence' }]
827
+ });
828
+ }
829
+ }
830
+ /**
831
+ * 404 — the requested KMS-held key does not exist, or the caller is not
832
+ * authorized (the same existence-masking as `KeystoreNotFoundError`).
833
+ * @param options {object}
834
+ * @param [options.requestName] {string} request name used in the error title
835
+ */
836
+ export class KeyNotFoundError extends ProblemError {
837
+ constructor({ requestName } = {}) {
838
+ super({
839
+ type: ProblemTypes.NOT_FOUND,
840
+ title: `Invalid ${requestName || 'Key'} request`,
841
+ detail: 'Key not found or invalid authorization.',
842
+ statusCode: 404
843
+ });
844
+ }
845
+ }
846
+ /**
847
+ * 409 — a key record already exists at `(keystoreId, localId)`. Key local ids
848
+ * are server-generated 128-bit random values, so this is effectively
849
+ * unreachable through the API; the storage layer still enforces insert-once
850
+ * semantics (a unique-index insert), and
851
+ * `@interop/webkms-client` maps a 409 on generate to its `DuplicateError`.
852
+ */
853
+ export class KeyIdConflictError extends ProblemError {
854
+ constructor() {
855
+ const detail = 'Duplicate key identifier.';
856
+ super({
857
+ type: ProblemTypes.ID_CONFLICT,
858
+ title: 'A key with this id already exists.',
859
+ detail,
860
+ statusCode: 409,
861
+ problems: [{ detail, pointer: '#/id' }]
862
+ });
863
+ }
864
+ }
865
+ /**
866
+ * 400 — a well-formed key operation names an operation this KMS does not serve
867
+ * for the key's type (e.g. `VerifyOperation` on an asymmetric key -- custody
868
+ * is the criterion: asymmetric verify needs only the public key and is
869
+ * client-local), or an operation type it does not recognize at all. A clean
870
+ * 400.
871
+ * @param options {object}
872
+ * @param options.operationType {string} the operation envelope's `type`
873
+ * @param [options.keyType] {string} the target key's type, when known
874
+ */
875
+ export class UnsupportedKeyOperationError extends ProblemError {
876
+ constructor({ operationType, keyType }) {
877
+ const detail = keyType
878
+ ? `Unsupported operation "${operationType}" for key type "${keyType}".`
879
+ : `Unknown operation type "${operationType}".`;
880
+ super({
881
+ type: ProblemTypes.INVALID_REQUEST_BODY,
882
+ title: 'Unsupported key operation.',
883
+ detail,
884
+ statusCode: 400,
885
+ problems: [{ detail, pointer: '#/type' }]
886
+ });
887
+ }
888
+ }
889
+ /**
890
+ * 400 — a submitted zcap revocation cannot be accepted: the body is not a
891
+ * revocable delegated capability (root zcaps cannot be revoked), its id does
892
+ * not match the revocation URL, its chain does not root in the keystore being
893
+ * posted to, or its delegation chain fails verification (a chain containing
894
+ * an already-revoked capability included -- resubmitting a stored revocation
895
+ * therefore lands here, per ezcap-express's `authorizeZcapRevocation`; the
896
+ * 409 duplicate is reserved for a write race at the store).
897
+ * @param options {object}
898
+ * @param options.detail {string} which of the revocation conditions failed
899
+ * @param [options.cause] {Error} the underlying chain-verification failure
900
+ */
901
+ export class InvalidRevocationError extends ProblemError {
902
+ constructor({ detail, cause }) {
903
+ super({
904
+ type: ProblemTypes.INVALID_REQUEST_BODY,
905
+ title: 'Invalid Revoke Capability request',
906
+ detail,
907
+ statusCode: 400,
908
+ cause
909
+ });
910
+ }
911
+ }
912
+ /**
913
+ * 409 — a revocation record already exists at `(delegator, capabilityId)`
914
+ * (`@interop/webkms-client` maps a 409 to its `DuplicateError`). Reached only
915
+ * on a concurrent-submission race:
916
+ * a sequential resubmission fails the chain verification first (the chain now
917
+ * contains a revoked capability) and is the 400 `InvalidRevocationError`.
918
+ */
919
+ export class DuplicateRevocationError extends ProblemError {
920
+ constructor() {
921
+ const detail = 'Duplicate revocation.';
922
+ super({
923
+ type: ProblemTypes.ID_CONFLICT,
924
+ title: 'This capability is already revoked.',
925
+ detail,
926
+ statusCode: 409,
927
+ problems: [{ detail, pointer: '#/id' }]
928
+ });
929
+ }
930
+ }
931
+ /**
932
+ * Fastify error handler installed by each route group. Serializes the error to
933
+ * an `application/problem+json` response using its `type` / `title` / `detail`
934
+ * (or `problems`), defaulting to a 500 internal error when no statusCode is
935
+ * present. The spec requires `type` and `title`, so both always fall back to a
936
+ * sensible value.
937
+ * @param error {Error & { statusCode?: number, type?: string, title?: string, detail?: string, problems?: Problem[] }}
938
+ * @param request {import('fastify').FastifyRequest}
939
+ * @param reply {import('fastify').FastifyReply}
940
+ * @returns {Promise<FastifyReply>}
941
+ */
942
+ export async function handleError(error, request, reply) {
943
+ const statusCode = error.statusCode || 500;
944
+ // Log server-side faults (5xx, e.g. a StorageError and its underlying
945
+ // `cause`) here through the request logger -- rather than in the error
946
+ // constructors -- so logging lives in one place. Client errors (4xx) are
947
+ // expected and not logged.
948
+ if (statusCode >= 500) {
949
+ request.log.error({ err: error }, error.title || 'Request error');
950
+ }
951
+ return reply
952
+ .status(statusCode)
953
+ .type('application/problem+json')
954
+ .send({
955
+ type: error.type || ProblemTypes.INTERNAL_ERROR,
956
+ title: error.title || 'Request error',
957
+ errors: error.problems ?? [{ detail: error.detail }]
958
+ });
959
+ }
960
+ //# sourceMappingURL=errors.js.map