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,78 @@
1
+ /**
2
+ * Access-control policy evaluation: resolves the effective policy for a target
3
+ * (most-specific level wins) and decides whether a policy grants an action.
4
+ * Policies are the fallback authorization path -- consulted when a capability
5
+ * invocation is absent or does not grant access (see authorize.ts). v1
6
+ * recognizes only `{ "type": "PublicCanRead" }`; any other `type` grants nothing
7
+ * (fail-closed). Also builds the RFC9264 linkset that advertises a Space's or
8
+ * Collection's policy resource for discovery.
9
+ */
10
+ import type { FastifyBaseLogger } from 'fastify';
11
+ import type { PolicyDocument, StorageBackend } from './types.js';
12
+ /** The kind of access a request needs; derived from the HTTP method. */
13
+ export type AccessAction = 'read' | 'write';
14
+ /**
15
+ * Resolves the policy that governs a target, honoring the spec's
16
+ * most-specific-wins inheritance: a Resource policy overrides a Collection
17
+ * policy, which overrides a Space policy. The first level that has a policy
18
+ * document is the effective policy; if none do, resolves undefined.
19
+ *
20
+ * @param options {object}
21
+ * @param options.storage {StorageBackend} the request's storage backend
22
+ * @param options.spaceId {string}
23
+ * @param [options.collectionId] {string}
24
+ * @param [options.resourceId] {string}
25
+ * @returns {Promise<PolicyDocument | undefined>}
26
+ */
27
+ export declare function resolveEffectivePolicy({ storage, spaceId, collectionId, resourceId }: {
28
+ storage: StorageBackend;
29
+ spaceId: string;
30
+ collectionId?: string;
31
+ resourceId?: string;
32
+ }): Promise<PolicyDocument | undefined>;
33
+ /**
34
+ * Decides whether a policy document grants the requested action. Dispatches on
35
+ * the policy `type`; an absent policy or an unrecognized `type` grants nothing
36
+ * (fail-closed).
37
+ *
38
+ * @param options {object}
39
+ * @param [options.policy] {PolicyDocument} the effective policy, if any
40
+ * @param options.action {AccessAction}
41
+ * @param [options.logger] {FastifyBaseLogger} logger for the fail-closed warn
42
+ * on an unrecognized policy type
43
+ * @returns {boolean}
44
+ */
45
+ export declare function policyGrants({ policy, action, logger }: {
46
+ policy?: PolicyDocument;
47
+ action: AccessAction;
48
+ logger?: FastifyBaseLogger;
49
+ }): boolean;
50
+ /**
51
+ * Builds the RFC9264 linkset for a Space or Collection, advertising its
52
+ * auxiliary resources for discovery. Collection vs Space is selected by whether
53
+ * `collectionId` is present.
54
+ *
55
+ * Relations:
56
+ * - `policy` (`POLICY_LINK_RELATION`) -- the access-control policy resource,
57
+ * advertised only when a policy document is set at that exact level.
58
+ * - On a Collection (`collectionId` present): `backend` (`BACKEND_LINK_RELATION`)
59
+ * and `quota` (`QUOTA_LINK_RELATION`), advertised unconditionally (every
60
+ * Collection has a selected backend and a quota report endpoint).
61
+ * - On a Space (`collectionId` absent): `backends-available`
62
+ * (`BACKENDS_AVAILABLE_LINK_RELATION`) and `quotas` (`QUOTAS_LINK_RELATION`),
63
+ * advertised unconditionally (both endpoints always exist).
64
+ *
65
+ * @param options {object}
66
+ * @param options.storage {StorageBackend} the request's storage backend
67
+ * @param options.spaceId {string}
68
+ * @param [options.collectionId] {string}
69
+ * @returns {Promise<object>} a `{ linkset: [...] }` object
70
+ */
71
+ export declare function buildLinkset({ storage, spaceId, collectionId }: {
72
+ storage: StorageBackend;
73
+ spaceId: string;
74
+ collectionId?: string;
75
+ }): Promise<{
76
+ linkset: Array<Record<string, unknown>>;
77
+ }>;
78
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAkBhE,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACX,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAkBtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,MAAM,EACN,MAAM,EACP,EAAE;IACD,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B,GAAG,OAAO,CAiBV;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAAC,EACjC,OAAO,EACP,OAAO,EACP,YAAY,EACb,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,CAAC,CAiCvD"}
package/dist/policy.js ADDED
@@ -0,0 +1,115 @@
1
+ import { POLICY_LINK_RELATION, BACKEND_LINK_RELATION, QUOTA_LINK_RELATION, BACKENDS_AVAILABLE_LINK_RELATION, QUOTAS_LINK_RELATION } from './config.default.js';
2
+ import { collectionPath, spacePath, policyPath, backendPath, quotaPath, backendsPath, quotasPath } from './lib/paths.js';
3
+ /**
4
+ * Resolves the policy that governs a target, honoring the spec's
5
+ * most-specific-wins inheritance: a Resource policy overrides a Collection
6
+ * policy, which overrides a Space policy. The first level that has a policy
7
+ * document is the effective policy; if none do, resolves undefined.
8
+ *
9
+ * @param options {object}
10
+ * @param options.storage {StorageBackend} the request's storage backend
11
+ * @param options.spaceId {string}
12
+ * @param [options.collectionId] {string}
13
+ * @param [options.resourceId] {string}
14
+ * @returns {Promise<PolicyDocument | undefined>}
15
+ */
16
+ export async function resolveEffectivePolicy({ storage, spaceId, collectionId, resourceId }) {
17
+ if (collectionId !== undefined && resourceId !== undefined) {
18
+ const resourcePolicy = await storage.getPolicy({
19
+ spaceId,
20
+ collectionId,
21
+ resourceId
22
+ });
23
+ if (resourcePolicy) {
24
+ return resourcePolicy;
25
+ }
26
+ }
27
+ if (collectionId !== undefined) {
28
+ const collectionPolicy = await storage.getPolicy({ spaceId, collectionId });
29
+ if (collectionPolicy) {
30
+ return collectionPolicy;
31
+ }
32
+ }
33
+ return await storage.getPolicy({ spaceId });
34
+ }
35
+ /**
36
+ * Decides whether a policy document grants the requested action. Dispatches on
37
+ * the policy `type`; an absent policy or an unrecognized `type` grants nothing
38
+ * (fail-closed).
39
+ *
40
+ * @param options {object}
41
+ * @param [options.policy] {PolicyDocument} the effective policy, if any
42
+ * @param options.action {AccessAction}
43
+ * @param [options.logger] {FastifyBaseLogger} logger for the fail-closed warn
44
+ * on an unrecognized policy type
45
+ * @returns {boolean}
46
+ */
47
+ export function policyGrants({ policy, action, logger }) {
48
+ if (!policy) {
49
+ return false;
50
+ }
51
+ switch (policy.type) {
52
+ case 'PublicCanRead':
53
+ return action === 'read';
54
+ default:
55
+ // Unknown policy type: grant nothing, fall through to zcap-only decision.
56
+ // Warn so a misconfigured / unsupported policy is diagnosable (the
57
+ // fail-closed behavior itself is correct; this is purely a signal).
58
+ logger?.warn({ policyType: policy.type }, 'Unrecognized access-control policy type; granting nothing (fail-closed).');
59
+ return false;
60
+ }
61
+ }
62
+ /**
63
+ * Builds the RFC9264 linkset for a Space or Collection, advertising its
64
+ * auxiliary resources for discovery. Collection vs Space is selected by whether
65
+ * `collectionId` is present.
66
+ *
67
+ * Relations:
68
+ * - `policy` (`POLICY_LINK_RELATION`) -- the access-control policy resource,
69
+ * advertised only when a policy document is set at that exact level.
70
+ * - On a Collection (`collectionId` present): `backend` (`BACKEND_LINK_RELATION`)
71
+ * and `quota` (`QUOTA_LINK_RELATION`), advertised unconditionally (every
72
+ * Collection has a selected backend and a quota report endpoint).
73
+ * - On a Space (`collectionId` absent): `backends-available`
74
+ * (`BACKENDS_AVAILABLE_LINK_RELATION`) and `quotas` (`QUOTAS_LINK_RELATION`),
75
+ * advertised unconditionally (both endpoints always exist).
76
+ *
77
+ * @param options {object}
78
+ * @param options.storage {StorageBackend} the request's storage backend
79
+ * @param options.spaceId {string}
80
+ * @param [options.collectionId] {string}
81
+ * @returns {Promise<object>} a `{ linkset: [...] }` object
82
+ */
83
+ export async function buildLinkset({ storage, spaceId, collectionId }) {
84
+ const anchor = collectionId !== undefined
85
+ ? collectionPath({ spaceId, collectionId })
86
+ : spacePath({ spaceId });
87
+ const policyHref = policyPath({ spaceId, collectionId });
88
+ const policy = collectionId !== undefined
89
+ ? await storage.getPolicy({ spaceId, collectionId })
90
+ : await storage.getPolicy({ spaceId });
91
+ const entry = { anchor };
92
+ if (policy) {
93
+ entry[POLICY_LINK_RELATION] = [
94
+ { href: policyHref, type: 'application/json' }
95
+ ];
96
+ }
97
+ if (collectionId !== undefined) {
98
+ entry[BACKEND_LINK_RELATION] = [
99
+ { href: backendPath({ spaceId, collectionId }), type: 'application/json' }
100
+ ];
101
+ entry[QUOTA_LINK_RELATION] = [
102
+ { href: quotaPath({ spaceId, collectionId }), type: 'application/json' }
103
+ ];
104
+ }
105
+ else {
106
+ entry[BACKENDS_AVAILABLE_LINK_RELATION] = [
107
+ { href: backendsPath({ spaceId }), type: 'application/json' }
108
+ ];
109
+ entry[QUOTAS_LINK_RELATION] = [
110
+ { href: quotasPath({ spaceId }), type: 'application/json' }
111
+ ];
112
+ }
113
+ return { linkset: [entry] };
114
+ }
115
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Provisioning gate for the two open provisioning endpoints (`POST /spaces/`
3
+ * and `POST /kms/keystores`). By default provisioning is allowed -- anyone may
4
+ * create a Space or keystore by proving control of the controller DID named in
5
+ * the request body (the teaching-server behavior). A deployment that wants to
6
+ * gate provisioning supplies either an `onboardingToken` (a shared secret,
7
+ * checked here by the stock `onboardingTokenAuthorizer`) or a custom
8
+ * `authorizeProvisioning` callback; both flow through the same seam.
9
+ */
10
+ import type { FastifyReply, FastifyRequest } from 'fastify';
11
+ import type { AuthorizeProvisioning } from './types.js';
12
+ /**
13
+ * onRequest hook: the provisioning gate. Runs first in the hook chain of the
14
+ * SpacesRepository and `/kms` route groups. For a `POST` to one of the two
15
+ * provisioning endpoints it consults the configured `authorizeProvisioning`
16
+ * callback: `verify` proceeds to normal zcap verification, `grant` marks the
17
+ * request as provisioning-authorized (skipping zcap verification downstream),
18
+ * and `deny` refuses with a 403. Every other request (and every deployment with
19
+ * no callback configured) passes straight through, so the default zcap path is
20
+ * unchanged.
21
+ * @param request {import('fastify').FastifyRequest}
22
+ * @param reply {import('fastify').FastifyReply}
23
+ * @returns {Promise<void>}
24
+ */
25
+ export declare function provisioningGate(request: FastifyRequest, _reply: FastifyReply): Promise<void>;
26
+ /**
27
+ * Builds the stock onboarding-token provisioning authorizer: a shared-secret
28
+ * gate that requires an `Authorization: Bearer <token>` header matching the
29
+ * configured token. A request signed with a zcap `Authorization: Signature ...`
30
+ * also lands here -- with a token configured, provisioning requires the token,
31
+ * full stop. Throws `MissingOnboardingTokenError` (401) when no Bearer token is
32
+ * present and `InvalidOnboardingTokenError` (403) when it does not match; a
33
+ * match returns `grant`.
34
+ * @param onboardingToken {string} the configured shared-secret token
35
+ * @returns {AuthorizeProvisioning}
36
+ */
37
+ export declare function onboardingTokenAuthorizer(onboardingToken: string): AuthorizeProvisioning;
38
+ //# sourceMappingURL=provisioning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../src/provisioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAE,qBAAqB,EAAwB,MAAM,YAAY,CAAA;AAc7E;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,MAAM,GACtB,qBAAqB,CAmBvB"}
@@ -0,0 +1,72 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import { InvalidOnboardingTokenError, MissingOnboardingTokenError, ProvisioningNotAuthorizedError } from './errors.js';
3
+ /**
4
+ * The exact route URLs the provisioning gate acts on. `/spaces` (no trailing
5
+ * slash) is included so a token-authorized request reaches the canonical-slash
6
+ * 308 redirect instead of failing the auth-header check first.
7
+ */
8
+ const PROVISIONING_ROUTES = new Set(['/spaces', '/spaces/', '/kms/keystores']);
9
+ /**
10
+ * onRequest hook: the provisioning gate. Runs first in the hook chain of the
11
+ * SpacesRepository and `/kms` route groups. For a `POST` to one of the two
12
+ * provisioning endpoints it consults the configured `authorizeProvisioning`
13
+ * callback: `verify` proceeds to normal zcap verification, `grant` marks the
14
+ * request as provisioning-authorized (skipping zcap verification downstream),
15
+ * and `deny` refuses with a 403. Every other request (and every deployment with
16
+ * no callback configured) passes straight through, so the default zcap path is
17
+ * unchanged.
18
+ * @param request {import('fastify').FastifyRequest}
19
+ * @param reply {import('fastify').FastifyReply}
20
+ * @returns {Promise<void>}
21
+ */
22
+ export async function provisioningGate(request, _reply) {
23
+ // Only the two provisioning POST endpoints are gated; everything else passes.
24
+ if (request.method !== 'POST' ||
25
+ !PROVISIONING_ROUTES.has(request.routeOptions.url ?? '')) {
26
+ return;
27
+ }
28
+ const authorize = request.server.authorizeProvisioning;
29
+ // No policy configured: default allow -- the zcap path is unchanged.
30
+ if (!authorize) {
31
+ return;
32
+ }
33
+ const decision = await authorize({ request });
34
+ if (decision === 'grant') {
35
+ request.provisioningAuthorized = true;
36
+ return;
37
+ }
38
+ if (decision === 'deny') {
39
+ throw new ProvisioningNotAuthorizedError();
40
+ }
41
+ // 'verify': fall through to the normal zcap capability-invocation path.
42
+ }
43
+ /**
44
+ * Builds the stock onboarding-token provisioning authorizer: a shared-secret
45
+ * gate that requires an `Authorization: Bearer <token>` header matching the
46
+ * configured token. A request signed with a zcap `Authorization: Signature ...`
47
+ * also lands here -- with a token configured, provisioning requires the token,
48
+ * full stop. Throws `MissingOnboardingTokenError` (401) when no Bearer token is
49
+ * present and `InvalidOnboardingTokenError` (403) when it does not match; a
50
+ * match returns `grant`.
51
+ * @param onboardingToken {string} the configured shared-secret token
52
+ * @returns {AuthorizeProvisioning}
53
+ */
54
+ export function onboardingTokenAuthorizer(onboardingToken) {
55
+ return async function authorizeWithOnboardingToken({ request }) {
56
+ const header = request.headers.authorization;
57
+ const match = header?.match(/^Bearer\s+(.+)$/i);
58
+ if (!match) {
59
+ throw new MissingOnboardingTokenError();
60
+ }
61
+ const presented = match[1];
62
+ // Timing-safe comparison over fixed-length SHA-256 digests, so mismatched
63
+ // token lengths do not leak through `timingSafeEqual`'s length check.
64
+ const presentedDigest = createHash('sha256').update(presented).digest();
65
+ const expectedDigest = createHash('sha256').update(onboardingToken).digest();
66
+ if (!timingSafeEqual(presentedDigest, expectedDigest)) {
67
+ throw new InvalidOnboardingTokenError();
68
+ }
69
+ return 'grant';
70
+ };
71
+ }
72
+ //# sourceMappingURL=provisioning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../src/provisioning.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzD,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,8BAA8B,EAC/B,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAuB,EACvB,MAAoB;IAEpB,8EAA8E;IAC9E,IACE,OAAO,CAAC,MAAM,KAAK,MAAM;QACzB,CAAC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,CAAC,EACxD,CAAC;QACD,OAAM;IACR,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAA;IACtD,qEAAqE;IACrE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;IAC7C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAA;QACrC,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,8BAA8B,EAAE,CAAA;IAC5C,CAAC;IACD,wEAAwE;AAC1E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CACvC,eAAuB;IAEvB,OAAO,KAAK,UAAU,4BAA4B,CAAC,EACjD,OAAO,EACR;QACC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAA;QAC5C,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,2BAA2B,EAAE,CAAA;QACzC,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;QACpC,0EAA0E;QAC1E,sEAAsE;QACtE,MAAM,eAAe,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAA;QACvE,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,MAAM,EAAE,CAAA;QAC5E,IAAI,CAAC,eAAe,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,2BAA2B,EAAE,CAAA;QACzC,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Request handlers for registering an `external` backend against a Space (spec
3
+ * "Backends"): register (`POST`), replace (`PUT`), and deregister (`DELETE`) a
4
+ * backend record. Authorization is the Space controller's key (capability-only,
5
+ * no policy fallback), exactly like Create Collection / Delete Space.
6
+ *
7
+ * The write body is secret-bearing (`BackendConnectionInput`), but every
8
+ * response returns the **sanitized** `BackendDescriptor` (via
9
+ * `sanitizeBackendRecord`), so a client response can never carry the secret. A
10
+ * registered backend is inert this increment: it is listed but not yet
11
+ * selectable as a Collection's `backend` (the live provider adapter is future
12
+ * work).
13
+ */
14
+ import type { FastifyReply, FastifyRequest } from 'fastify';
15
+ export declare class BackendRequest {
16
+ /**
17
+ * POST /space/:spaceId/backends
18
+ * Registers a new `external` backend. Rejects the reserved `default` id and a
19
+ * duplicate id (checked after capability verification so an unauthorized
20
+ * caller cannot probe ids). Responds 201 with the sanitized descriptor.
21
+ * @param request {import('fastify').FastifyRequest}
22
+ * @param reply {import('fastify').FastifyReply}
23
+ * @returns {Promise<FastifyReply>}
24
+ */
25
+ static post(request: FastifyRequest<{
26
+ Params: {
27
+ spaceId: string;
28
+ };
29
+ Body: unknown;
30
+ }>, reply: FastifyReply): Promise<FastifyReply>;
31
+ /**
32
+ * PUT /space/:spaceId/backends/:backendId
33
+ * Upsert (the re-consent path): create-or-replace a backend record at a chosen
34
+ * id. The body `id` (when present) must match the path id, and `default` is
35
+ * rejected. Responds 201 + Location + sanitized body on create, 204 on update.
36
+ * @param request {import('fastify').FastifyRequest}
37
+ * @param reply {import('fastify').FastifyReply}
38
+ * @returns {Promise<FastifyReply>}
39
+ */
40
+ static put(request: FastifyRequest<{
41
+ Params: {
42
+ spaceId: string;
43
+ backendId: string;
44
+ };
45
+ Body: unknown;
46
+ }>, reply: FastifyReply): Promise<FastifyReply>;
47
+ /**
48
+ * DELETE /space/:spaceId/backends/:backendId
49
+ * Deregisters a backend. Idempotent (204 even if the record was absent).
50
+ * @param request {import('fastify').FastifyRequest}
51
+ * @param reply {import('fastify').FastifyReply}
52
+ * @returns {Promise<FastifyReply>}
53
+ */
54
+ static delete(request: FastifyRequest<{
55
+ Params: {
56
+ spaceId: string;
57
+ backendId: string;
58
+ };
59
+ }>, reply: FastifyReply): Promise<FastifyReply>;
60
+ }
61
+ //# sourceMappingURL=BackendRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackendRequest.d.ts","sourceRoot":"","sources":["../../src/requests/BackendRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAe3D,qBAAa,cAAc;IACzB;;;;;;;;OAQG;WACU,IAAI,CACf,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;QAC3B,IAAI,EAAE,OAAO,CAAA;KACd,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAqDxB;;;;;;;;OAQG;WACU,GAAG,CACd,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;QAC9C,IAAI,EAAE,OAAO,CAAA;KACd,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAkExB;;;;;;OAMG;WACU,MAAM,CACjB,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KAC/C,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;CA4BzB"}
@@ -0,0 +1,152 @@
1
+ import { fetchSpaceAndVerify } from './spaceContext.js';
2
+ import { assertValidIds } from '../lib/validateId.js';
3
+ import { DEFAULT_BACKEND_ID, assertProviderAllowed, assertValidBackendId, buildBackendRecord, parseBackendRegistration, sanitizeBackendRecord } from '../lib/backends.js';
4
+ import { invalidateResolvedBackend } from '../lib/backendRegistry.js';
5
+ import { backendsPath, registeredBackendPath } from '../lib/paths.js';
6
+ import { InvalidRequestBodyError, IdConflictError } from '../errors.js';
7
+ export class BackendRequest {
8
+ /**
9
+ * POST /space/:spaceId/backends
10
+ * Registers a new `external` backend. Rejects the reserved `default` id and a
11
+ * duplicate id (checked after capability verification so an unauthorized
12
+ * caller cannot probe ids). Responds 201 with the sanitized descriptor.
13
+ * @param request {import('fastify').FastifyRequest}
14
+ * @param reply {import('fastify').FastifyReply}
15
+ * @returns {Promise<FastifyReply>}
16
+ */
17
+ static async post(request, reply) {
18
+ const { params: { spaceId }, body } = request;
19
+ const { serverUrl, storage } = request.server;
20
+ const requestName = 'Register Backend';
21
+ // Reject path-traversal / non-URL-safe ids before any storage access.
22
+ assertValidIds({ spaceId }, { requestName });
23
+ const registration = parseBackendRegistration(body, { requestName });
24
+ // Fail fast if the server's registration allowlist excludes this provider
25
+ // (permissive when no allowlist is configured).
26
+ assertProviderAllowed({
27
+ provider: registration.provider,
28
+ enabledProviders: request.server.enabledBackendProviders
29
+ });
30
+ assertValidBackendId(registration.id, { requestName });
31
+ // Verify (capability-only): registering a backend requires a valid
32
+ // capability invocation by the Space controller; no policy fallback.
33
+ await fetchSpaceAndVerify({
34
+ request,
35
+ spaceId,
36
+ targetPath: backendsPath({ spaceId }),
37
+ requestName
38
+ });
39
+ // After verification (so an unauthorized caller cannot probe ids): the
40
+ // `default` id is reserved for the server backend, and POST must not replace
41
+ // an existing record (create-or-replace by id is PUT's job).
42
+ if (registration.id === DEFAULT_BACKEND_ID) {
43
+ throw new InvalidRequestBodyError({
44
+ requestName,
45
+ detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
46
+ pointer: '#/id'
47
+ });
48
+ }
49
+ if (await storage.getBackend({ spaceId, backendId: registration.id })) {
50
+ throw new IdConflictError({ kind: 'Backend' });
51
+ }
52
+ const record = buildBackendRecord(registration);
53
+ await storage.writeBackend({ spaceId, backendId: record.id, record });
54
+ const createdUrl = new URL(registeredBackendPath({ spaceId, backendId: record.id }), serverUrl).toString();
55
+ reply.header('Location', createdUrl);
56
+ return reply.status(201).send(sanitizeBackendRecord(record));
57
+ }
58
+ /**
59
+ * PUT /space/:spaceId/backends/:backendId
60
+ * Upsert (the re-consent path): create-or-replace a backend record at a chosen
61
+ * id. The body `id` (when present) must match the path id, and `default` is
62
+ * rejected. Responds 201 + Location + sanitized body on create, 204 on update.
63
+ * @param request {import('fastify').FastifyRequest}
64
+ * @param reply {import('fastify').FastifyReply}
65
+ * @returns {Promise<FastifyReply>}
66
+ */
67
+ static async put(request, reply) {
68
+ const { params: { spaceId, backendId }, body } = request;
69
+ const { serverUrl, storage } = request.server;
70
+ const requestName = 'Register Backend';
71
+ // Reject path-traversal / non-URL-safe ids before any storage access.
72
+ assertValidIds({ spaceId }, { requestName });
73
+ assertValidBackendId(backendId, { requestName });
74
+ const registration = parseBackendRegistration(body, { requestName });
75
+ // Fail fast if the server's registration allowlist excludes this provider
76
+ // (permissive when no allowlist is configured).
77
+ assertProviderAllowed({
78
+ provider: registration.provider,
79
+ enabledProviders: request.server.enabledBackendProviders
80
+ });
81
+ // The backend `id` is immutable: a body `id`, when present, must match the
82
+ // URL id; and `default` is the reserved server backend id.
83
+ if (registration.id !== backendId) {
84
+ throw new InvalidRequestBodyError({
85
+ requestName,
86
+ detail: `Backend "id" (${registration.id}) does not match the URL backend id (${backendId}).`,
87
+ pointer: '#/id'
88
+ });
89
+ }
90
+ if (backendId === DEFAULT_BACKEND_ID) {
91
+ throw new InvalidRequestBodyError({
92
+ requestName,
93
+ detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
94
+ pointer: '#/id'
95
+ });
96
+ }
97
+ // Verify (capability-only): no policy fallback for a write.
98
+ await fetchSpaceAndVerify({
99
+ request,
100
+ spaceId,
101
+ targetPath: registeredBackendPath({ spaceId, backendId }),
102
+ requestName
103
+ });
104
+ const existing = await storage.getBackend({ spaceId, backendId });
105
+ const record = buildBackendRecord(registration);
106
+ await storage.writeBackend({ spaceId, backendId, record });
107
+ // Bust any memoized adapter so the next resolve rebuilds it from the new
108
+ // connection material.
109
+ invalidateResolvedBackend({
110
+ providers: request.server.backendProviders,
111
+ spaceId,
112
+ backendId
113
+ });
114
+ if (existing) {
115
+ return reply.status(204).send();
116
+ }
117
+ const createdUrl = new URL(registeredBackendPath({ spaceId, backendId }), serverUrl).toString();
118
+ reply.header('Location', createdUrl);
119
+ return reply.status(201).send(sanitizeBackendRecord(record));
120
+ }
121
+ /**
122
+ * DELETE /space/:spaceId/backends/:backendId
123
+ * Deregisters a backend. Idempotent (204 even if the record was absent).
124
+ * @param request {import('fastify').FastifyRequest}
125
+ * @param reply {import('fastify').FastifyReply}
126
+ * @returns {Promise<FastifyReply>}
127
+ */
128
+ static async delete(request, reply) {
129
+ const { params: { spaceId, backendId } } = request;
130
+ const { storage } = request.server;
131
+ const requestName = 'Deregister Backend';
132
+ // Reject path-traversal / non-URL-safe ids before any storage access.
133
+ assertValidIds({ spaceId }, { requestName });
134
+ assertValidBackendId(backendId, { requestName });
135
+ // Verify (capability-only): no policy fallback for a write.
136
+ await fetchSpaceAndVerify({
137
+ request,
138
+ spaceId,
139
+ targetPath: registeredBackendPath({ spaceId, backendId }),
140
+ requestName
141
+ });
142
+ await storage.deleteBackend({ spaceId, backendId });
143
+ // Bust any memoized adapter for the now-removed record.
144
+ invalidateResolvedBackend({
145
+ providers: request.server.backendProviders,
146
+ spaceId,
147
+ backendId
148
+ });
149
+ return reply.status(204).send();
150
+ }
151
+ }
152
+ //# sourceMappingURL=BackendRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackendRequest.js","sourceRoot":"","sources":["../../src/requests/BackendRequest.ts"],"names":[],"mappings":"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"}