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,9 @@
1
+ <div class="container">
2
+ <h1>W.A.S. Teaching Server</h1>
3
+ <p class="grey-text">Version {{SERVER_VERSION}}</p>
4
+
5
+ <p>
6
+ See: <a href="{{SPEC_URL}}">Wallet Attached Storage</a> specification
7
+ </p>
8
+ </div>
9
+
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>WAS Teaching Server: {{title}}</title>
7
+ <link rel="stylesheet" href="/common/css/materialize.1.0.0.min.css" />
8
+ </head>
9
+ <body>
10
+ {{{body}}}
11
+ </body>
12
+ </html>
package/src/zcap.ts ADDED
@@ -0,0 +1,657 @@
1
+ /**
2
+ * ZCap verification: handleZcapVerify() checks the capability-invocation
3
+ * signature against the Space controller's Ed25519 key, synthesizing the root
4
+ * capability via the document loader. Also home to the zcap *revocation*
5
+ * verification pair, shared by the `/kms` and WAS route families:
6
+ * verifyRevocationChain() validates a to-be-revoked capability's delegation
7
+ * chain, and handleRevocationInvocationVerify() authorizes the submission
8
+ * under the dual-root rule (the scope's root -- a keystore or a Space -- or
9
+ * the revocation URL's own root controlled by any chain participant --
10
+ * ezcap-express's `authorizeZcapRevocation` convention).
11
+ */
12
+ import type { IncomingHttpHeaders } from 'node:http'
13
+ import { securityLoader } from '@interop/security-document-loader'
14
+ import {
15
+ verifyCapabilityInvocation,
16
+ type VerifyCapabilityInvocationResult
17
+ } from '@interop/http-signature-zcap-verify'
18
+ import jsigs from '@interop/jsonld-signatures'
19
+ import {
20
+ CapabilityDelegation,
21
+ type InspectCapabilityChain
22
+ } from '@interop/zcap'
23
+ import { Ed25519VerificationKey } from '@interop/ed25519-verification-key'
24
+ import { Ed25519Signature2020 } from '@interop/ed25519-signature'
25
+ import * as didKey from '@interop/did-method-key'
26
+ import type { IDocumentLoader, IPublicKey } from '@interop/data-integrity-core'
27
+ import {
28
+ AuthVerificationError,
29
+ InvalidRevocationError,
30
+ UnauthorizedError
31
+ } from './errors.js'
32
+ import {
33
+ capabilitySummaries,
34
+ revocationChainInspector
35
+ } from './lib/revocations.js'
36
+ import type {
37
+ CapabilitySummary,
38
+ IDID,
39
+ IVerificationMethod,
40
+ RevocationScope,
41
+ StorageBackend
42
+ } from './types.js'
43
+
44
+ const didKeyDriver = didKey.driver()
45
+ didKeyDriver.use({
46
+ multibaseMultikeyHeader: 'z6Mk',
47
+ fromMultibase: Ed25519VerificationKey.from
48
+ })
49
+
50
+ /**
51
+ * The root capability id convention: `urn:zcap:root:` + the url-encoded
52
+ * invocation target (shared by WAS and webkms).
53
+ * @param target {string} the root invocation target (full URL)
54
+ * @returns {string}
55
+ */
56
+ function rootCapabilityId(target: string): string {
57
+ return `urn:zcap:root:${encodeURIComponent(target)}`
58
+ }
59
+
60
+ /**
61
+ * Builds a document loader whose `urn` protocol handler synthesizes
62
+ * `urn:zcap:root:<target>` capabilities on demand, with the controller chosen
63
+ * per target. The zcap library only dereferences a root capability it already
64
+ * expects (per `expectedRootCapability`), so `controllerFor` sees expected
65
+ * targets only -- it may still throw to refuse one outright.
66
+ *
67
+ * @param options {object}
68
+ * @param options.controllerFor {(target: string) => IDID | string[]} maps a
69
+ * decoded root invocation target to the controller(s) of its synthesized
70
+ * root capability
71
+ * @returns {IDocumentLoader}
72
+ */
73
+ function rootCapabilityLoader({
74
+ controllerFor
75
+ }: {
76
+ controllerFor: (target: string) => IDID | string[]
77
+ }): IDocumentLoader {
78
+ const loader = securityLoader()
79
+ loader.setProtocolHandler({
80
+ protocol: 'urn',
81
+ handler: {
82
+ get: async ({ id, url }: { id: string; url?: string }) => {
83
+ const resolvedUrl = url || id
84
+ const rootZcapTarget = decodeURIComponent(
85
+ resolvedUrl.split('urn:zcap:root:')[1]!
86
+ )
87
+ return {
88
+ '@context': 'https://w3id.org/zcap/v1',
89
+ id: resolvedUrl,
90
+ invocationTarget: rootZcapTarget,
91
+ controller: controllerFor(rootZcapTarget)
92
+ }
93
+ }
94
+ }
95
+ })
96
+ return loader.build()
97
+ }
98
+
99
+ /**
100
+ * Resolves an invocation's keyId to an Ed25519 verifier (the
101
+ * `verifyCapabilityInvocation` HTTP-signature key hook).
102
+ * @param options {object}
103
+ * @param options.keyId {string} the did:key verification method URL
104
+ * @returns {Promise<{ verifier: object, verificationMethod: IVerificationMethod }>}
105
+ */
106
+ async function getVerifier({ keyId }: { keyId: string }) {
107
+ const verificationMethod = await didKeyDriver.get({ url: keyId })
108
+ const key = await Ed25519VerificationKey.from(
109
+ verificationMethod as IPublicKey
110
+ )
111
+ const verifier = key.verifier()
112
+ return {
113
+ verifier,
114
+ verificationMethod: verificationMethod as IVerificationMethod
115
+ }
116
+ }
117
+
118
+ /** Minimal logger surface used during verification (console / request.log). */
119
+ interface ZcapLogger {
120
+ error: (...args: any[]) => void
121
+ }
122
+
123
+ /**
124
+ * Returns true when a `Capability-Invocation` header value is the bare root
125
+ * form (`zcap id="urn:zcap:root:..."` -- the signer invokes the root capability
126
+ * directly), false when it embeds a delegated capability
127
+ * (`zcap capability="<base64url(gzip(json))>"`). The check is safe on the raw
128
+ * header: a `capability=` substring cannot occur inside the root form's
129
+ * url-encoded `id` (where `=` is percent-encoded).
130
+ *
131
+ * @param options {object}
132
+ * @param options.invocation {string} the raw `Capability-Invocation` header
133
+ * @returns {boolean}
134
+ */
135
+ export function isRootInvocation({
136
+ invocation
137
+ }: {
138
+ invocation: string
139
+ }): boolean {
140
+ return !invocation.includes('capability=')
141
+ }
142
+
143
+ /**
144
+ * Verifies the capability-invocation signature on a request against the Space
145
+ * controller's key. Throws AuthVerificationError if verification itself errors,
146
+ * or UnauthorizedError if the capability does not verify.
147
+ *
148
+ * @param options {object}
149
+ * @param options.url {string} request URL (path), resolved against serverUrl
150
+ * @param options.allowedTarget {string} the capability's expected
151
+ * invocationTarget (full URL, including host and port)
152
+ * @param options.allowedAction {string} expected action, e.g. an HTTP verb
153
+ * @param options.method {string} the HTTP method of the request
154
+ * @param options.headers {IncomingHttpHeaders} the request headers (including
155
+ * `authorization`, `capability-invocation`, and `digest`)
156
+ * @param options.serverUrl {string} this server's base URL
157
+ * @param options.spaceController {IDID} the did:key that controls the Space
158
+ * @param [options.requestName] {string} human-readable request name, used in
159
+ * error titles
160
+ * @param [options.logger] {ZcapLogger} logger for verification errors;
161
+ * defaults to `console`
162
+ * @param [options.allowTargetQuery] {boolean} tolerate query parameters that
163
+ * extend `allowedTarget` on the request URL (see `verifyZcap`)
164
+ * @param [options.allowTargetAttenuation] {boolean} accept a request URL
165
+ * that path-extends `allowedTarget` under a capability rooted at
166
+ * `allowedTarget` (see `verifyZcap`)
167
+ * @param [options.attenuatedRootTarget] {string} an ancestor target (e.g.
168
+ * the Space URL) whose root capability is also accepted as the root of a
169
+ * delegated chain that attenuates down to the request URL (see `verifyZcap`)
170
+ * @param options.revocation {object|string} the revocation-store check, run
171
+ * against the dereferenced chain after signature verification. REQUIRED so
172
+ * that skipping revocation is a stated decision, never an omission: pass
173
+ * `{ storage, scope }` -- the scope (keystore or Space) the chain roots
174
+ * in -- or the literal `'no-revocation-scope'` when the verified target has
175
+ * no scope a revocation could be stored under (a create/consent
176
+ * verification for a not-yet-existing resource, or a collection-level root
177
+ * like `/kms/keystores`).
178
+ * @param [options.maxChainLength] {number} max delegation chain length,
179
+ * root included (see `verifyZcap`)
180
+ * @param [options.maxDelegationTtl] {number} max delegated-zcap TTL in
181
+ * milliseconds (see `verifyZcap`)
182
+ * @returns {Promise<VerifyCapabilityInvocationResult>} the successful
183
+ * verification result (callers needing the dereferenced chain, e.g. the
184
+ * per-key `maxCapabilityChainLength` gate, read it from here)
185
+ */
186
+ export async function handleZcapVerify({
187
+ url,
188
+ allowedTarget,
189
+ allowedAction,
190
+ method,
191
+ headers,
192
+ serverUrl,
193
+ spaceController,
194
+ requestName = '',
195
+ logger = console,
196
+ allowTargetQuery = false,
197
+ allowTargetAttenuation = false,
198
+ attenuatedRootTarget,
199
+ revocation,
200
+ maxChainLength,
201
+ maxDelegationTtl
202
+ }: {
203
+ url: string
204
+ allowedTarget: string
205
+ allowedAction: string
206
+ method: string
207
+ headers: IncomingHttpHeaders
208
+ serverUrl: string
209
+ spaceController: IDID
210
+ requestName?: string
211
+ logger?: ZcapLogger
212
+ allowTargetQuery?: boolean
213
+ allowTargetAttenuation?: boolean
214
+ attenuatedRootTarget?: string
215
+ revocation:
216
+ { storage: StorageBackend; scope: RevocationScope } | 'no-revocation-scope'
217
+ maxChainLength?: number
218
+ maxDelegationTtl?: number
219
+ }): Promise<VerifyCapabilityInvocationResult> {
220
+ const inspectCapabilityChain =
221
+ revocation === 'no-revocation-scope'
222
+ ? undefined
223
+ : revocationChainInspector(revocation)
224
+ let zcapVerifyResult: VerifyCapabilityInvocationResult
225
+ try {
226
+ zcapVerifyResult = await verifyZcap({
227
+ url,
228
+ allowedTarget,
229
+ allowedAction,
230
+ method,
231
+ headers,
232
+ serverUrl,
233
+ spaceController,
234
+ allowTargetQuery,
235
+ allowTargetAttenuation,
236
+ attenuatedRootTarget,
237
+ inspectCapabilityChain,
238
+ maxChainLength,
239
+ maxDelegationTtl
240
+ })
241
+ } catch (err) {
242
+ logger.error({ err }, 'ZCAP verification failed')
243
+ throw new AuthVerificationError({ requestName, cause: err as Error })
244
+ }
245
+
246
+ if (!zcapVerifyResult.verified) {
247
+ throw new UnauthorizedError({ requestName })
248
+ }
249
+ return zcapVerifyResult
250
+ }
251
+
252
+ /**
253
+ * Performs the underlying capability-invocation verification: builds a document
254
+ * loader whose `urn` protocol handler synthesizes the root capability on demand
255
+ * (its controller is the Space controller), then calls
256
+ * verifyCapabilityInvocation().
257
+ *
258
+ * @param options {object}
259
+ * @param options.url {string} request URL (path), resolved against serverUrl
260
+ * @param options.allowedTarget {string} expected invocationTarget (full URL)
261
+ * @param options.allowedAction {string} expected action, e.g. an HTTP verb
262
+ * @param options.method {string} the HTTP method of the request
263
+ * @param options.headers {IncomingHttpHeaders} the request headers
264
+ * @param options.serverUrl {string} this server's base URL
265
+ * @param options.spaceController {IDID} the did:key that controls the Space
266
+ * @param [options.allowTargetQuery] {boolean} when set, accept a request URL
267
+ * that adds query parameters to `allowedTarget` (e.g. List Collection's
268
+ * `?limit`/`cursor`) as authorized by a capability for the bare target. The
269
+ * spec requires that pagination parameters select a page within an
270
+ * already-authorized target without changing the target a capability must
271
+ * match. The zcap library otherwise requires the capability's
272
+ * `invocationTarget` to equal the full request URL exactly, so this enables
273
+ * target attenuation (the library treats a `?`-query suffix as a valid RESTful
274
+ * attenuation) and admits both the bare-target root capability (a delegate
275
+ * following `next`) and the query-bearing one (a controller invoking the URL
276
+ * directly). The actual gate -- the bare-target root capability -- is
277
+ * unchanged. (TODO: the `/quotas` endpoint should adopt this too, so its
278
+ * per-Collection breakdown can return to the spec's `?include=collections`
279
+ * opt-in.)
280
+ * @param [options.allowTargetAttenuation] {boolean} when set, accept a
281
+ * request URL that *path*-extends `allowedTarget` (e.g. a WebKMS key
282
+ * operation posted to `<keystoreId>/keys/<keyId>` under a capability rooted
283
+ * at the keystore). The root capability is `allowedTarget`'s alone -- unlike
284
+ * `allowTargetQuery`, the extended URL is never itself an acceptable root --
285
+ * so both a root invocation by the controller and a delegated zcap whose
286
+ * `invocationTarget` narrows down to the request URL verify against the
287
+ * `allowedTarget` root (the webkms authorization model, which roots the
288
+ * invocation target at the keystore id).
289
+ * @param [options.attenuatedRootTarget] {string} when set, an *ancestor*
290
+ * invocation target (the Space URL for the WAS route families) whose root
291
+ * capability is accepted -- in addition to `allowedTarget`'s own -- as the
292
+ * root of the invocation. This is what lets a controller delegate one
293
+ * capability for a whole Space (or a Collection under it, by attenuating
294
+ * the `invocationTarget` down at delegation time) and have the delegate
295
+ * invoke it against any URL underneath: the chain roots at the ancestor's
296
+ * root capability and narrows toward the request URL (RESTful attenuation,
297
+ * the same shape `allowTargetAttenuation` gives the WebKMS keystore).
298
+ * Root invocations of `allowedTarget`'s own root capability verify
299
+ * unchanged, so this only widens what the Space controller can delegate,
300
+ * never who can access.
301
+ * @param [options.inspectCapabilityChain] {InspectCapabilityChain} hook run
302
+ * against the dereferenced chain after signature verification -- the
303
+ * revocation-check extension point (a stored revocation of any capability
304
+ * in the chain fails the verification). Both route families pass one, scoped
305
+ * to the keystore or the Space the request roots in.
306
+ * @param [options.maxChainLength] {number} max delegation chain length,
307
+ * root included (the `/kms` families pass `KMS_MAX_CHAIN_LENGTH`; absent,
308
+ * the zcap library's own default applies)
309
+ * @param [options.maxDelegationTtl] {number} max delegated-zcap TTL in
310
+ * milliseconds, measured `expires` minus the delegation proof's `created`
311
+ * (the `/kms` families pass `KMS_MAX_DELEGATION_TTL`; absent, unbounded)
312
+ * @returns {Promise<VerifyCapabilityInvocationResult>}
313
+ */
314
+ export async function verifyZcap({
315
+ url,
316
+ allowedTarget,
317
+ allowedAction,
318
+ method,
319
+ headers,
320
+ serverUrl,
321
+ spaceController,
322
+ allowTargetQuery = false,
323
+ allowTargetAttenuation = false,
324
+ attenuatedRootTarget,
325
+ inspectCapabilityChain,
326
+ maxChainLength,
327
+ maxDelegationTtl
328
+ }: {
329
+ url: string
330
+ allowedTarget: string
331
+ allowedAction: string
332
+ method: string
333
+ headers: IncomingHttpHeaders
334
+ serverUrl: string
335
+ spaceController: IDID
336
+ allowTargetQuery?: boolean
337
+ allowTargetAttenuation?: boolean
338
+ attenuatedRootTarget?: string
339
+ inspectCapabilityChain?: InspectCapabilityChain
340
+ maxChainLength?: number
341
+ maxDelegationTtl?: number
342
+ }): Promise<VerifyCapabilityInvocationResult> {
343
+ const fullRequestUrl = new URL(url, serverUrl).toString()
344
+ let expected
345
+ if (allowTargetQuery || attenuatedRootTarget) {
346
+ // The acceptable roots: the ancestor's root capability (a delegated chain
347
+ // rooted at e.g. the Space URL, narrowing to the request URL), the
348
+ // `allowedTarget`'s own (a root invocation, or a delegated chain for the
349
+ // exact target -- the pre-existing shapes, unchanged), and, under
350
+ // `allowTargetQuery`, the query-bearing request URL's own (a controller
351
+ // invoking the query URL directly).
352
+ const rootTargets = [
353
+ ...(attenuatedRootTarget ? [attenuatedRootTarget] : []),
354
+ allowedTarget,
355
+ ...(allowTargetQuery ? [fullRequestUrl] : [])
356
+ ]
357
+ expected = {
358
+ expectedAction: allowedAction,
359
+ expectedHost: new URL(serverUrl).host,
360
+ expectedRootCapability: [...new Set(rootTargets.map(rootCapabilityId))],
361
+ // The proof's invocationTarget is the invoked URL: `allowedTarget`
362
+ // itself, or (under `allowTargetQuery`) the query-bearing request URL.
363
+ // The array form is narrowed to `string` by the verify fork's option
364
+ // type, but the underlying `@interop/zcap` CapabilityInvocation
365
+ // accepts `string | string[]` -- hence the cast.
366
+ expectedTarget: [
367
+ ...new Set([allowedTarget, fullRequestUrl])
368
+ ] as unknown as string,
369
+ allowTargetAttenuation: true
370
+ }
371
+ } else if (allowTargetAttenuation) {
372
+ expected = {
373
+ expectedAction: allowedAction,
374
+ expectedHost: new URL(serverUrl).host,
375
+ // The proof's invocationTarget is the invoked URL: `allowedTarget`
376
+ // itself, or a path under it (accepted as a RESTful attenuation).
377
+ // The only acceptable root capability is `allowedTarget`'s. (Same
378
+ // array-form cast as above.)
379
+ expectedRootCapability: rootCapabilityId(allowedTarget),
380
+ expectedTarget: [
381
+ ...new Set([allowedTarget, fullRequestUrl])
382
+ ] as unknown as string,
383
+ allowTargetAttenuation: true
384
+ }
385
+ } else {
386
+ expected = {
387
+ expectedAction: allowedAction,
388
+ expectedHost: new URL(serverUrl).host,
389
+ rootInvocationTarget: allowedTarget,
390
+ expectedRootCapability: rootCapabilityId(allowedTarget),
391
+ expectedTarget: allowedTarget
392
+ }
393
+ }
394
+
395
+ const documentLoader = rootCapabilityLoader({
396
+ controllerFor: () => spaceController
397
+ })
398
+
399
+ // Returns the following object:
400
+ // {
401
+ // capability, capabilityAction, controller,
402
+ // dereferencedChain,
403
+ // invoker: controller,
404
+ // verificationMethod,
405
+ // verified: true
406
+ // }
407
+ return await verifyCapabilityInvocation({
408
+ url: fullRequestUrl,
409
+ method,
410
+ headers: headers as Record<string, string>,
411
+ ...expected,
412
+ documentLoader,
413
+ getVerifier,
414
+ inspectCapabilityChain,
415
+ maxChainLength,
416
+ maxDelegationTtl,
417
+ suite: new Ed25519Signature2020()
418
+ })
419
+ }
420
+
421
+ /**
422
+ * Extracts the controller DIDs of one capability (`controller` may be a
423
+ * single value or an array on a synthesized root).
424
+ * @param capability {object} a capability from a dereferenced chain
425
+ * @returns {string[]}
426
+ */
427
+ function capabilityControllers(capability: {
428
+ controller?: string | string[]
429
+ }): string[] {
430
+ const { controller } = capability
431
+ if (controller === undefined) {
432
+ return []
433
+ }
434
+ return Array.isArray(controller) ? controller : [controller]
435
+ }
436
+
437
+ /**
438
+ * Verifies the delegation chain of a capability submitted for revocation
439
+ * (`CapabilityDelegation` proof purpose over the embedded chain), throwing
440
+ * `InvalidRevocationError` (400) when it does not verify. The chain must root
441
+ * in the revocation's scope: its root capability's invocation target must be
442
+ * `rootTarget` -- the keystore URL, or the Space URL for a WAS-route
443
+ * revocation -- or a path under it (enforced where the root is synthesized,
444
+ * so a chain aimed at another keystore or Space -- or another service --
445
+ * cannot be submitted here, per ezcap-express `authorizeZcapRevocation`).
446
+ * Deliberately structural only -- it does NOT consult the revocation store:
447
+ * this runs before the invocation is authorized, and a store-dependent
448
+ * failure here would disclose revocation state to unauthorized callers
449
+ * (400 already-revoked vs the masked 404). The caller checks the returned
450
+ * `capabilities` against the store after authorization.
451
+ *
452
+ * @param options {object}
453
+ * @param options.capability {object} the delegated capability to be revoked
454
+ * (the request body, verbatim)
455
+ * @param options.rootTarget {string} the scope's full URL -- the keystore or
456
+ * the Space -- which the chain is required to root in
457
+ * @param options.rootController {IDID} the scope's controller (controller of
458
+ * the synthesized root capability)
459
+ * @param [options.maxChainLength] {number} max chain length, root included
460
+ * @param [options.maxDelegationTtl] {number} max delegated-zcap TTL (ms)
461
+ * @returns {Promise<{ delegator: string, chainControllers: string[],
462
+ * capabilities: CapabilitySummary[] }>} the capability's delegator (its
463
+ * delegation proof's controller), every controller in its chain (the
464
+ * parties allowed to submit the revocation), and the chain's
465
+ * `(capabilityId, delegator)` pairs for the caller's post-authorization
466
+ * revocation-store check
467
+ */
468
+ export async function verifyRevocationChain({
469
+ capability,
470
+ rootTarget,
471
+ rootController,
472
+ maxChainLength,
473
+ maxDelegationTtl
474
+ }: {
475
+ capability: Record<string, unknown>
476
+ rootTarget: string
477
+ rootController: IDID
478
+ maxChainLength?: number
479
+ maxDelegationTtl?: number
480
+ }): Promise<{
481
+ delegator: string
482
+ chainControllers: string[]
483
+ capabilities: CapabilitySummary[]
484
+ }> {
485
+ const chainControllers: string[] = []
486
+ let capabilities: CapabilitySummary[] = []
487
+ const documentLoader = rootCapabilityLoader({
488
+ controllerFor: target => {
489
+ if (target !== rootTarget && !target.startsWith(`${rootTarget}/`)) {
490
+ throw new Error(
491
+ `The root capability from the revocation's delegation chain must` +
492
+ ` have an invocation target that starts with "${rootTarget}".`
493
+ )
494
+ }
495
+ return rootController
496
+ }
497
+ })
498
+ const suite = new Ed25519Signature2020()
499
+ const result = (await jsigs.verify(capability, {
500
+ documentLoader,
501
+ suite,
502
+ purpose: new CapabilityDelegation({
503
+ suite,
504
+ expectedRootCapability: rootCapabilityId(rootTarget),
505
+ // Attenuation is always tolerated when judging revocability: a zcap
506
+ // delegated with attenuation rules an invocation endpoint would refuse
507
+ // can still be revoked (ezcap-express `_verifyDelegation`).
508
+ allowTargetAttenuation: true,
509
+ maxChainLength,
510
+ maxDelegationTtl,
511
+ inspectCapabilityChain: async details => {
512
+ // Capture every controller in the dereferenced chain -- these are the
513
+ // parties the dual-root rule lets submit this revocation -- and the
514
+ // chain's lookup pairs for the caller's post-authorization store check.
515
+ for (const chainCapability of details.capabilityChain) {
516
+ chainControllers.push(...capabilityControllers(chainCapability))
517
+ }
518
+ capabilities = capabilitySummaries(details)
519
+ return { valid: true }
520
+ }
521
+ })
522
+ })) as {
523
+ verified: boolean
524
+ error?: Error
525
+ results?: Array<{
526
+ purposeResult?: { delegator?: { id?: string } | string }
527
+ }>
528
+ }
529
+ if (!result.verified) {
530
+ throw new InvalidRevocationError({
531
+ detail: 'The provided capability delegation is invalid.',
532
+ cause: result.error
533
+ })
534
+ }
535
+ const rawDelegator = result.results?.[0]?.purposeResult?.delegator
536
+ const delegator =
537
+ typeof rawDelegator === 'string' ? rawDelegator : rawDelegator?.id
538
+ if (!delegator) {
539
+ throw new InvalidRevocationError({
540
+ detail: 'The capability delegation has no identifiable delegator.'
541
+ })
542
+ }
543
+ return { delegator, chainControllers, capabilities }
544
+ }
545
+
546
+ /**
547
+ * Verifies the capability invocation on a revocation submission under the
548
+ * dual-root rule: the invocation may root in the scope -- the keystore or the
549
+ * Space -- (whose controller may revoke anything delegated from it, delegates
550
+ * of a revocation capability included, via target attenuation), or in the
551
+ * revocation URL itself, whose
552
+ * synthesized root capability is controlled by *every controller in the
553
+ * to-be-revoked capability's chain* -- so a delegee can revoke its own zcap
554
+ * without holding a separate capability (ezcap-express
555
+ * `authorizeZcapRevocation`). Throws like `handleZcapVerify`:
556
+ * `AuthVerificationError` (400) when verification errors, the 404-masked
557
+ * `UnauthorizedError` when the invocation does not verify.
558
+ *
559
+ * @param options {object}
560
+ * @param options.url {string} request URL (path), resolved against serverUrl
561
+ * @param options.method {string} the HTTP method of the request
562
+ * @param options.headers {IncomingHttpHeaders} the request headers
563
+ * @param options.serverUrl {string} this server's base URL
564
+ * @param options.rootTarget {string} the scope's full URL (the keystore or
565
+ * the Space)
566
+ * @param options.rootController {IDID} the scope's controller
567
+ * @param options.chainControllers {string[]} every controller in the
568
+ * to-be-revoked capability's (already verified) chain
569
+ * @param options.expectedAction {string} the action the invocation must
570
+ * carry: the webkms `write` on `/kms`, the HTTP verb (`POST`) on the WAS
571
+ * route families, whose capabilities are scoped by HTTP method
572
+ * @param [options.inspectCapabilityChain] {InspectCapabilityChain} the
573
+ * revocation-store hook, run against the *invoking* chain
574
+ * @param [options.maxChainLength] {number} max chain length, root included
575
+ * @param [options.maxDelegationTtl] {number} max delegated-zcap TTL (ms)
576
+ * @param [options.requestName] {string} request name used in error titles
577
+ * @param [options.logger] {ZcapLogger} logger for verification errors
578
+ * @returns {Promise<void>}
579
+ */
580
+ export async function handleRevocationInvocationVerify({
581
+ url,
582
+ method,
583
+ headers,
584
+ serverUrl,
585
+ rootTarget,
586
+ rootController,
587
+ chainControllers,
588
+ expectedAction,
589
+ inspectCapabilityChain,
590
+ maxChainLength,
591
+ maxDelegationTtl,
592
+ requestName = '',
593
+ logger = console
594
+ }: {
595
+ url: string
596
+ method: string
597
+ headers: IncomingHttpHeaders
598
+ serverUrl: string
599
+ rootTarget: string
600
+ rootController: IDID
601
+ chainControllers: string[]
602
+ expectedAction: string
603
+ inspectCapabilityChain?: InspectCapabilityChain
604
+ maxChainLength?: number
605
+ maxDelegationTtl?: number
606
+ requestName?: string
607
+ logger?: ZcapLogger
608
+ }): Promise<void> {
609
+ const fullRequestUrl = new URL(url, serverUrl).toString()
610
+ const documentLoader = rootCapabilityLoader({
611
+ controllerFor: target => {
612
+ if (target === rootTarget) {
613
+ return rootController
614
+ }
615
+ if (target === fullRequestUrl) {
616
+ return chainControllers
617
+ }
618
+ throw new Error(
619
+ `Unexpected root capability target "${target}" on a revocation.`
620
+ )
621
+ }
622
+ })
623
+
624
+ let zcapVerifyResult: VerifyCapabilityInvocationResult
625
+ try {
626
+ zcapVerifyResult = await verifyCapabilityInvocation({
627
+ url: fullRequestUrl,
628
+ method,
629
+ headers: headers as Record<string, string>,
630
+ expectedAction,
631
+ expectedHost: new URL(serverUrl).host,
632
+ expectedRootCapability: [
633
+ rootCapabilityId(rootTarget),
634
+ rootCapabilityId(fullRequestUrl)
635
+ ],
636
+ // The invoked target is the revocation URL, a path under the scope's
637
+ // root; accept either as a delegated zcap's (attenuated) target. The
638
+ // array form is narrowed to `string` by the verify fork's option type
639
+ // (see the same cast in `verifyZcap`'s attenuation branch).
640
+ expectedTarget: [rootTarget, fullRequestUrl] as unknown as string,
641
+ allowTargetAttenuation: true,
642
+ documentLoader,
643
+ getVerifier,
644
+ inspectCapabilityChain,
645
+ maxChainLength,
646
+ maxDelegationTtl,
647
+ suite: new Ed25519Signature2020()
648
+ })
649
+ } catch (err) {
650
+ logger.error({ err }, 'ZCAP revocation invocation verification failed')
651
+ throw new AuthVerificationError({ requestName, cause: err as Error })
652
+ }
653
+
654
+ if (!zcapVerifyResult.verified) {
655
+ throw new UnauthorizedError({ requestName })
656
+ }
657
+ }