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,163 @@
1
+ /**
2
+ * Auth onRequest hooks: `requireAuthHeaders` (401 if Authorization /
3
+ * Capability-Invocation are missing), `requireAuthHeadersOrPublicRead` (same,
4
+ * but safe methods pass through unauthenticated), and `parseAuthHeaders`
5
+ * (parses the auth headers into `request.zcap`). Every route group in
6
+ * routes.js installs `requireAuthHeadersOrPublicRead` then `parseAuthHeaders`.
7
+ */
8
+ import type { FastifyReply, FastifyRequest } from 'fastify'
9
+ import {
10
+ AuthHeaderParseError,
11
+ MissingAuthError,
12
+ MissingKeyIdError
13
+ } from './errors.js'
14
+ import { parseSignatureHeader } from '@interop/http-signature-header'
15
+ import { isValidController } from './lib/validateDid.js'
16
+ import type { IDID, ParsedZcap } from './types.js'
17
+
18
+ /**
19
+ * Adds a request.zcap property, which contains the three parsed auth-related
20
+ * request headers (`Authorization:`, `Capability-Invocation:`, and `Digest:`)
21
+ *
22
+ * Example Authorization header:
23
+ * Signature keyId="did:key:z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD#z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD",
24
+ * headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",
25
+ * signature="6GoRQ+rW69wBhNyERkafAXEZXZArezHvGRNUWC0HNI4Ss1xAiiMHdayS5aA2R6hLuYRNw6h9J9eCmQVMuHE1Bw==",
26
+ * created="1758150502",expires="1758151102"
27
+ *
28
+ * Example Capability-Invocation header:
29
+ * zcap id="urn:zcap:root:http%3A%2F%2Flocalhost%3A42957%2Fspaces%2F",action="POST"
30
+ *
31
+ * Example Digest header:
32
+ * mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA
33
+ *
34
+ * Example request.zcap object value from above headers:
35
+ * {
36
+ * keyId: 'did:key:z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD#z6Mkud27oH7SyTr495b67UgZ6tFmA72egaxyte23ygpUfEvD',
37
+ * headers: '(key-id) (created) (expires) (request-target) host capability-invocation content-type digest',
38
+ * signature: '0SXR+S2EiipdjzS9ahNyM9Zdfe2r3fZHJFTaYwK6HGq4dbWkJkiZkvg8DcvXUGxolF9AVc6LEtmbOfAGDAAVBg==',
39
+ * created: '1758151067',
40
+ * expires: '1758151667',
41
+ * invocation: 'zcap id="urn:zcap:root:http%3A%2F%2Flocalhost%3A40767%2Fspaces%2F",action="POST"',
42
+ * digest: 'mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA'
43
+ * }
44
+ *
45
+ * @param request {import('fastify').FastifyRequest}
46
+ * @param reply {import('fastify').FastifyReply}
47
+ * @returns {Promise<void>}
48
+ */
49
+ export async function parseAuthHeaders(
50
+ request: FastifyRequest,
51
+ _reply: FastifyReply
52
+ ): Promise<void> {
53
+ const { headers } = request
54
+
55
+ // A provisioning-authorized request (e.g. a valid onboarding token) carries a
56
+ // Bearer token, not an HTTP Signature -- there is no zcap to parse.
57
+ if (request.provisioningAuthorized) {
58
+ return
59
+ }
60
+
61
+ // No Authorization header presented (e.g. an anonymous read that a fallback
62
+ // policy may authorize). Leave `request.zcap` unset; the handler decides.
63
+ if (!headers.authorization) {
64
+ return
65
+ }
66
+
67
+ let keyId: string | undefined
68
+ try {
69
+ // { keyId, headers, signature, created, expires }
70
+ const { params } = parseSignatureHeader(headers.authorization ?? '')
71
+ keyId = params.keyId
72
+ request.zcap = {
73
+ ...params,
74
+ invocation: headers['capability-invocation'],
75
+ digest: headers.digest
76
+ } as ParsedZcap
77
+ } catch (err) {
78
+ throw new AuthHeaderParseError({ cause: err as Error })
79
+ }
80
+ // Ensure keyId was parsed from the Authorization header
81
+ if (!keyId) {
82
+ throw new MissingKeyIdError()
83
+ }
84
+ }
85
+
86
+ /**
87
+ * The DID of the party that signed this request's capability invocation, taken
88
+ * from the parsed `keyId` with its verification-method fragment stripped
89
+ * (`did:key:z6Mk...#z6Mk...` to `did:key:z6Mk...`). Under a delegated
90
+ * capability this is the delegatee -- whoever actually invoked -- not the Space
91
+ * controller.
92
+ *
93
+ * Resolves to `undefined` when the request carries no parsed invocation (an
94
+ * anonymous read of a public Resource, or a provisioning-token request), or
95
+ * when the stripped `keyId` is not a syntactically valid `did:key`. Callers
96
+ * recording it as server-managed provenance therefore treat it as optional,
97
+ * and never persist a value that failed to narrow to a DID.
98
+ *
99
+ * @param request {import('fastify').FastifyRequest}
100
+ * @returns {IDID | undefined}
101
+ */
102
+ export function invokerDid(request: FastifyRequest): IDID | undefined {
103
+ const did = request.zcap?.keyId.split('#')[0]
104
+ return isValidController(did) ? did : undefined
105
+ }
106
+
107
+ /**
108
+ * onRequest hook that enforces presence of the auth-related headers. Throws
109
+ * MissingAuthError (401) when `Authorization` or `Capability-Invocation` is
110
+ * absent. For route groups where every operation, reads included, is
111
+ * privileged: the WebKMS `/kms` group uses it (every webkms route is
112
+ * zcap-invoked; the protocol has no public reads). No WAS group qualifies --
113
+ * even the SpacesRepository group lets anonymous List Spaces reads through,
114
+ * answered with the spec's empty-items 200.
115
+ * @param request {import('fastify').FastifyRequest}
116
+ * @param reply {import('fastify').FastifyReply}
117
+ * @returns {Promise<void>}
118
+ */
119
+ export async function requireAuthHeaders(
120
+ request: FastifyRequest,
121
+ _reply: FastifyReply
122
+ ): Promise<void> {
123
+ // A provisioning-authorized request (onboarding token) carries no capability
124
+ // invocation and a Bearer -- not Signature -- Authorization header.
125
+ if (request.provisioningAuthorized) {
126
+ return
127
+ }
128
+ const { headers } = request
129
+ if (!(headers.authorization && headers['capability-invocation'])) {
130
+ throw new MissingAuthError()
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Like `requireAuthHeaders`, but lets safe (read) methods through without auth
136
+ * so the handler can decide what an anonymous read sees: a fallback
137
+ * access-control policy (e.g. a world-readable Resource), or for List Spaces
138
+ * the empty-items 200. The handler still denies anonymous reads that no policy
139
+ * authorizes. Unsafe methods (writes) still require auth. Used by every route
140
+ * group.
141
+ * @param request {import('fastify').FastifyRequest}
142
+ * @param reply {import('fastify').FastifyReply}
143
+ * @returns {Promise<void>}
144
+ */
145
+ export async function requireAuthHeadersOrPublicRead(
146
+ request: FastifyRequest,
147
+ _reply: FastifyReply
148
+ ): Promise<void> {
149
+ // A provisioning-authorized request (onboarding token) carries no capability
150
+ // invocation and a Bearer -- not Signature -- Authorization header.
151
+ if (request.provisioningAuthorized) {
152
+ return
153
+ }
154
+ const { headers, method } = request
155
+ if (headers.authorization && headers['capability-invocation']) {
156
+ return
157
+ }
158
+ // Safe methods may proceed unauthenticated; authorize() decides via policy.
159
+ if (method === 'GET' || method === 'HEAD') {
160
+ return
161
+ }
162
+ throw new MissingAuthError()
163
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Per-request authorization decision. Replaces the inline `handleZcapVerify`
3
+ * call in read handlers with a two-step decision: first try the capability
4
+ * invocation (if auth headers are present); if that does not grant access, fall
5
+ * back to the target's effective access-control policy (see policy.ts). Throws
6
+ * the same errors as `handleZcapVerify` when neither path authorizes the
7
+ * request. The decision is permissive: a policy can only broaden access, never
8
+ * deny a valid capability holder.
9
+ */
10
+ import type { FastifyRequest } from 'fastify'
11
+ import { handleZcapVerify } from './zcap.js'
12
+ import {
13
+ resolveEffectivePolicy,
14
+ policyGrants,
15
+ type AccessAction
16
+ } from './policy.js'
17
+ import { UnauthorizedError } from './errors.js'
18
+ import type { IDID } from './types.js'
19
+
20
+ /**
21
+ * Authorizes a request against a target, by capability invocation first and the
22
+ * effective access-control policy second.
23
+ *
24
+ * @param options {object}
25
+ * @param options.request {FastifyRequest} the request (supplies url, method,
26
+ * headers, logger, and `request.server` for serverUrl + storage)
27
+ * @param options.allowedTarget {string} the capability's expected
28
+ * invocationTarget (full URL, including host and port)
29
+ * @param options.spaceId {string}
30
+ * @param [options.collectionId] {string}
31
+ * @param [options.resourceId] {string}
32
+ * @param options.spaceController {IDID} the did:key that controls the Space
33
+ * @param [options.requestName] {string} human-readable request name, used in
34
+ * error titles
35
+ * @param [options.allowTargetQuery] {boolean} tolerate query parameters that
36
+ * extend `allowedTarget` on the capability-invocation path (see `verifyZcap`)
37
+ * @param [options.attenuatedRootTarget] {string} ancestor target (the Space
38
+ * URL) whose root capability is also accepted as the root of a delegated
39
+ * chain attenuating down to the request URL (see `verifyZcap`)
40
+ * @returns {Promise<void>} resolves when authorized; throws otherwise
41
+ */
42
+ export async function authorize({
43
+ request,
44
+ allowedTarget,
45
+ spaceId,
46
+ collectionId,
47
+ resourceId,
48
+ spaceController,
49
+ requestName = '',
50
+ allowTargetQuery = false,
51
+ attenuatedRootTarget
52
+ }: {
53
+ request: FastifyRequest
54
+ allowedTarget: string
55
+ spaceId: string
56
+ collectionId?: string
57
+ resourceId?: string
58
+ spaceController: IDID
59
+ requestName?: string
60
+ allowTargetQuery?: boolean
61
+ attenuatedRootTarget?: string
62
+ }): Promise<void> {
63
+ const { url, method, headers } = request
64
+ const { serverUrl, storage } = request.server
65
+ const action: AccessAction =
66
+ method === 'GET' || method === 'HEAD' ? 'read' : 'write'
67
+
68
+ // 1. If the caller presented a capability invocation, try it first. Success
69
+ // authorizes the request; failure -- a revoked capability included -- falls
70
+ // through to the policy check (we hold the error to re-throw if the policy
71
+ // does not grant access either). Revoking a capability therefore withdraws
72
+ // only what the capability granted: access a policy already grants everyone
73
+ // (a public-readable Space) survives, which is the permissive-policy model.
74
+ let zcapError: Error | undefined
75
+ if (headers.authorization && headers['capability-invocation']) {
76
+ try {
77
+ await handleZcapVerify({
78
+ url,
79
+ allowedTarget,
80
+ allowedAction: method,
81
+ method,
82
+ headers,
83
+ serverUrl,
84
+ spaceController,
85
+ requestName,
86
+ logger: request.log,
87
+ allowTargetQuery,
88
+ attenuatedRootTarget,
89
+ revocation: { storage, scope: { spaceId } }
90
+ })
91
+ return
92
+ } catch (err) {
93
+ zcapError = err as Error
94
+ }
95
+ }
96
+
97
+ // 2. Fall back to the target's effective access-control policy.
98
+ const policy = await resolveEffectivePolicy({
99
+ storage,
100
+ spaceId,
101
+ collectionId,
102
+ resourceId
103
+ })
104
+ if (policyGrants({ policy, action, logger: request.log })) {
105
+ // Log granted public-access decisions so they are auditable (an anonymous
106
+ // or otherwise-unauthorized read allowed purely by a policy).
107
+ request.log.info(
108
+ { spaceId, collectionId, resourceId, action, policyType: policy?.type },
109
+ 'Access granted by access-control policy.'
110
+ )
111
+ return
112
+ }
113
+
114
+ // Neither a capability nor a policy authorizes this request. Re-throw the
115
+ // capability error when there was one, else a generic 404 (no leak).
116
+ throw zcapError ?? new UnauthorizedError({ requestName })
117
+ }