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,493 @@
1
+ /**
2
+ * Default configuration: spec and FEP documentation URLs referenced by the
3
+ * home page, error responses, and space-export manifests.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { parseKekMultibase } from './lib/kmsRecordCipher.js';
8
+ // package.json sits a level above both src/ (dev, via tsx) and dist/ (prod),
9
+ // so '../package.json' from import.meta.dirname resolves in either layout.
10
+ const packageJsonPath = path.join(import.meta.dirname, '..', 'package.json');
11
+ /** Server version, read from package.json at startup. */
12
+ export const SERVER_VERSION = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version;
13
+ /**
14
+ * Space Description cache (see src/requests/spaceContext.ts). The description is
15
+ * read on every authorized handler, so it is memoized per storage backend.
16
+ * Writes invalidate the entry explicitly; the short TTL is a backstop that also
17
+ * bounds staleness when several server processes share one storage backend (so
18
+ * one process's cache cannot serve another process's write indefinitely).
19
+ */
20
+ export const SPACE_DESCRIPTION_CACHE_TTL = 5_000; // milliseconds
21
+ /** Max number of Space Descriptions held per backend cache (LRU-bounded). */
22
+ export const SPACE_DESCRIPTION_CACHE_MAX = 1_000;
23
+ /**
24
+ * Max number of resolved external-backend adapters held per provider-registry
25
+ * cache (see src/lib/backendRegistry.ts), LRU-bounded. One adapter instance is
26
+ * memoized per selected `{spaceId}/{backendId}` and reused across requests;
27
+ * record changes bust the entry explicitly (no TTL backstop is needed because
28
+ * the provider registry itself is fixed for an instance's lifetime).
29
+ */
30
+ export const RESOLVED_BACKEND_CACHE_MAX = 1_000;
31
+ /**
32
+ * Linkset relation URI for the access-control `policy` auxiliary resource
33
+ * (RFC9264 linkset discovery; see src/policy.ts and the linkset handlers).
34
+ */
35
+ export const POLICY_LINK_RELATION = 'https://wallet.storage/spec#policy';
36
+ /**
37
+ * Linkset relation URI for a Collection's selected `backend` auxiliary resource
38
+ * (RFC9264 linkset discovery; advertised at `/space/{id}/{cid}/backend`).
39
+ */
40
+ export const BACKEND_LINK_RELATION = 'https://wallet.storage/spec#backend';
41
+ /**
42
+ * Linkset relation URI for a Collection's `quota` report auxiliary resource
43
+ * (RFC9264 linkset discovery; advertised at `/space/{id}/{cid}/quota`).
44
+ */
45
+ export const QUOTA_LINK_RELATION = 'https://wallet.storage/spec#quota';
46
+ /**
47
+ * Linkset relation URI for a Space's `backends-available` auxiliary resource
48
+ * (RFC9264 linkset discovery; advertised at `/space/{id}/backends`).
49
+ */
50
+ export const BACKENDS_AVAILABLE_LINK_RELATION = 'https://wallet.storage/spec#backends-available';
51
+ /**
52
+ * Linkset relation URI for a Space's `quotas` report auxiliary resource
53
+ * (RFC9264 linkset discovery; advertised at `/space/{id}/quotas`).
54
+ */
55
+ export const QUOTAS_LINK_RELATION = 'https://wallet.storage/spec#quotas';
56
+ /**
57
+ * Fraction of a backend's configured capacity at or above which its quota
58
+ * report `state` becomes `near-limit` (spec "Quotas"). Below this it is `ok`;
59
+ * at or above full capacity it is `over-quota`. Only applies when a finite
60
+ * capacity is configured (an unlimited backend is always `ok`).
61
+ */
62
+ export const QUOTA_NEAR_LIMIT_FRACTION = 0.9;
63
+ /**
64
+ * Write-path quota usage cache TTL (see
65
+ * `FileSystemBackend._assertSpaceHeadroom`). The quota pre-flight measures a
66
+ * Space's on-disk usage with `du`, which walks the whole Space tree -- too
67
+ * costly to repeat on every resource write. The measured total is cached per
68
+ * Space for this long, with each accepted write's incoming bytes added to the
69
+ * cached figure; deletes invalidate the entry. The TTL bounds the drift of
70
+ * that optimistic accounting (overwrites that replaced rather than added
71
+ * bytes, streamed bodies of undeclared size) -- the quota is a documented
72
+ * soft limit, and the TTL bounds the re-measurement window.
73
+ */
74
+ export const QUOTA_USAGE_CACHE_TTL = 5_000; // milliseconds
75
+ /**
76
+ * The single in-process KMS module this server hard-wires.
77
+ * A keystore created without one gets this alias, and it is immutable thereafter.
78
+ */
79
+ export const DEFAULT_KMS_MODULE = 'local-v1';
80
+ /**
81
+ * Max keystore configs returned by `GET /kms/keystores?controller=...` (the
82
+ * webkms protocol's list cap).
83
+ */
84
+ export const KEYSTORE_LIST_LIMIT = 100;
85
+ /**
86
+ * Max key descriptions returned per page by
87
+ * `GET /kms/keystores/:keystoreId/keys` (the List Keys fork extension). A
88
+ * further page is signalled by the response's `next` cursor URL; realistic
89
+ * keystores hold a handful of keys, so one page is the steady state.
90
+ */
91
+ export const KEY_LIST_LIMIT = 100;
92
+ /**
93
+ * Max capability delegation chain length accepted on `/kms` invocations, the
94
+ * root capability included (webkms-switch's `maxChainLength` default). A
95
+ * per-key `maxCapabilityChainLength` (1-10, set at generate time) may narrow
96
+ * this further for key operations; it can never widen it.
97
+ */
98
+ export const KMS_MAX_CHAIN_LENGTH = 10;
99
+ /**
100
+ * Max time-to-live of a delegated capability accepted on `/kms` invocations,
101
+ * measured `expires` minus the delegation proof's `created`, in milliseconds
102
+ * (90 days -- webkms-switch's `maxDelegationTtl` default). One unified bound
103
+ * for every `/kms` route family:
104
+ * revocation plus the mandatory `expires` is the real control.
105
+ */
106
+ export const KMS_MAX_DELEGATION_TTL = 90 * 24 * 60 * 60 * 1000;
107
+ /** TCP port the server listens on when `PORT` is unset. */
108
+ export const DEFAULT_PORT = 3002;
109
+ /**
110
+ * The default per-upload byte cap applied by BOTH backends when
111
+ * `MAX_UPLOAD_BYTES` is unset (a default-on limit, 64 MiB). Every blob write
112
+ * buffers through process memory on at least one path (the Postgres single
113
+ * `bytea`, multipart's in-memory `toBuffer()`), so "no cap" would be a footgun.
114
+ * Opt out explicitly with `MAX_UPLOAD_BYTES=unlimited`, which the backends
115
+ * accept only where an unbounded upload is actually safe (the filesystem
116
+ * streaming path; the Postgres backend rejects it at construction).
117
+ */
118
+ export const DEFAULT_MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
119
+ /**
120
+ * Default cap on the number of Spaces a single controller may create, applied
121
+ * by BOTH backends when `MAX_SPACES_PER_CONTROLLER` is unset (a default-on
122
+ * count quota). Chosen far above any teaching or conformance workload while
123
+ * still bounding runaway creation by one controller. Opt out with
124
+ * `MAX_SPACES_PER_CONTROLLER=unlimited`.
125
+ */
126
+ export const DEFAULT_MAX_SPACES_PER_CONTROLLER = 100;
127
+ /**
128
+ * Default cap on the number of Collections a single Space may hold, applied by
129
+ * BOTH backends when `MAX_COLLECTIONS_PER_SPACE` is unset (a default-on count
130
+ * quota). Opt out with `MAX_COLLECTIONS_PER_SPACE=unlimited`.
131
+ */
132
+ export const DEFAULT_MAX_COLLECTIONS_PER_SPACE = 100;
133
+ /**
134
+ * Default cap on the number of live Resources a single Space may hold across
135
+ * all its Collections, applied by BOTH backends when `MAX_RESOURCES_PER_SPACE`
136
+ * is unset (a default-on count quota). A tombstone (soft-deleted Resource) does
137
+ * not count against it. Opt out with `MAX_RESOURCES_PER_SPACE=unlimited`.
138
+ */
139
+ export const DEFAULT_MAX_RESOURCES_PER_SPACE = 10_000;
140
+ /**
141
+ * Reads and validates the server's whole env config surface in one place
142
+ * (fail-fast startup): a missing `SERVER_URL` or any malformed value throws
143
+ * with the offending variable named, before the server starts listening --
144
+ * instead of silently breaking ZCap matching at request time.
145
+ * @param [env] {NodeJS.ProcessEnv} defaults to `process.env`
146
+ * @returns {EnvConfig}
147
+ */
148
+ export function loadConfigFromEnv(env = process.env) {
149
+ return {
150
+ serverUrl: parseServerUrl(env.SERVER_URL),
151
+ port: parsePort(env.PORT),
152
+ databaseUrl: parseDatabaseUrl(env.DATABASE_URL),
153
+ storageLimitPerSpace: parseStorageLimit(env.STORAGE_LIMIT_PER_SPACE),
154
+ maxUploadBytes: parseMaxUploadBytes(env.MAX_UPLOAD_BYTES),
155
+ maxSpacesPerController: parseCountLimit(env.MAX_SPACES_PER_CONTROLLER, 'MAX_SPACES_PER_CONTROLLER'),
156
+ maxCollectionsPerSpace: parseCountLimit(env.MAX_COLLECTIONS_PER_SPACE, 'MAX_COLLECTIONS_PER_SPACE'),
157
+ maxResourcesPerSpace: parseCountLimit(env.MAX_RESOURCES_PER_SPACE, 'MAX_RESOURCES_PER_SPACE'),
158
+ enabledBackendProviders: parseEnabledBackends(env.WAS_ENABLED_BACKENDS),
159
+ kmsRecordKek: parseKmsRecordKekRegistry({
160
+ kek: env.KMS_RECORD_KEK,
161
+ keks: env.KMS_RECORD_KEKS,
162
+ currentKek: env.KMS_RECORD_CURRENT_KEK
163
+ }),
164
+ onboardingToken: parseOnboardingToken(env.WAS_ONBOARDING_TOKEN)
165
+ };
166
+ }
167
+ /**
168
+ * Validates a server base URL (the `serverUrl` option / `SERVER_URL` env
169
+ * value): it must be an absolute `http:`/`https:` URL with no path, query, or
170
+ * fragment. ZCap `invocationTarget` URLs and `Location` headers are built by
171
+ * resolving absolute paths against this base (`new URL(path, serverUrl)`),
172
+ * which silently drops any base path -- so a sub-path deployment would break
173
+ * every delegated invocation. Rejected at startup instead (fail-fast).
174
+ * @param serverUrl {string} the candidate base URL
175
+ * @returns {void} throws on an invalid value
176
+ */
177
+ export function assertValidServerUrl(serverUrl) {
178
+ let url;
179
+ try {
180
+ url = new URL(serverUrl);
181
+ }
182
+ catch {
183
+ throw new Error(`serverUrl (env SERVER_URL) must be an absolute URL; got "${serverUrl}".`);
184
+ }
185
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
186
+ throw new Error(`serverUrl (env SERVER_URL) must use http: or https:; got "${serverUrl}".`);
187
+ }
188
+ if (url.pathname !== '/' || url.search !== '' || url.hash !== '') {
189
+ throw new Error(`serverUrl (env SERVER_URL) must not include a path, query, or ` +
190
+ `fragment -- deploying under a sub-path is not supported; ` +
191
+ `got "${serverUrl}".`);
192
+ }
193
+ }
194
+ /**
195
+ * Parses the `SERVER_URL` env value: the server's base URL, used to build and
196
+ * match ZCap `invocationTarget` URLs (host and port must match the client's
197
+ * exactly). Required -- unset would silently break all ZCap matching, so
198
+ * startup fails instead. The value is trimmed but otherwise preserved
199
+ * byte-for-byte (never normalized), since capability targets compare as exact
200
+ * strings. Validated by {@link assertValidServerUrl}.
201
+ * @param raw {string|undefined} the raw env value
202
+ * @returns {string} the trimmed, validated base URL
203
+ */
204
+ export function parseServerUrl(raw) {
205
+ if (raw === undefined || raw.trim() === '') {
206
+ throw new Error(`SERVER_URL is required: the server base URL used to build and match ` +
207
+ `ZCap invocationTarget URLs (e.g. SERVER_URL='http://localhost:3002').`);
208
+ }
209
+ const serverUrl = raw.trim();
210
+ assertValidServerUrl(serverUrl);
211
+ return serverUrl;
212
+ }
213
+ /**
214
+ * Parses the `PORT` env value into the TCP port to listen on. An unset or
215
+ * empty value returns {@link DEFAULT_PORT}.
216
+ * @param raw {string|undefined} the raw env value
217
+ * @returns {number} the port
218
+ */
219
+ export function parsePort(raw) {
220
+ if (raw === undefined || raw.trim() === '') {
221
+ return DEFAULT_PORT;
222
+ }
223
+ const value = Number(raw);
224
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
225
+ throw new Error(`PORT must be an integer between 1 and 65535; got "${raw}".`);
226
+ }
227
+ return value;
228
+ }
229
+ /**
230
+ * Parses the `DATABASE_URL` env value: a Postgres connection string that,
231
+ * when set, selects the PostgreSQL storage backend (unset keeps the default
232
+ * filesystem backend). The string's shape is left to the `pg` driver, which
233
+ * accepts several connection-string forms; an unset or empty value returns
234
+ * `undefined`.
235
+ * @param raw {string|undefined} the raw env value
236
+ * @returns {string|undefined} the trimmed connection string, or `undefined`
237
+ */
238
+ export function parseDatabaseUrl(raw) {
239
+ if (raw === undefined || raw.trim() === '') {
240
+ return undefined;
241
+ }
242
+ return raw.trim();
243
+ }
244
+ /**
245
+ * Parses the at-rest key-record encryption env surface into a
246
+ * {@link KmsRecordKekRegistry} (the optional hardening increment). Each KEK is a
247
+ * single AES-256 key-encryption key in base58btc Multikey form
248
+ * (`secretKeyMultibase`, header `0xa2 0x01`), whose id is derived from its
249
+ * material (`deriveKekId`) and stored per record -- so registering a second KEK
250
+ * for rotation is a config change, not a schema migration. Three env variables
251
+ * feed it:
252
+ *
253
+ * - `KMS_RECORD_KEK` -- a single KEK (the teaching default alias);
254
+ * - `KMS_RECORD_KEKS` -- a comma-separated list of KEKs, each registered by its
255
+ * derived id; the FIRST entry is the current KEK by default, so a rotation is
256
+ * "prepend the new KEK, keep the old one behind it". Surrounding whitespace is
257
+ * trimmed and empty entries ignored; a duplicate entry (same derived id twice)
258
+ * throws;
259
+ * - `KMS_RECORD_CURRENT_KEK` -- optionally overrides which registered KEK wraps
260
+ * NEW records: a `urn:kek:sha256:<hex>` id, a multibase KEK value (its id
261
+ * derived), or the literal `none` (case-insensitive) which sets
262
+ * `currentKekId: null` -- the decrypt-only posture (old records still read, new
263
+ * records are written plaintext). It must name a registered KEK.
264
+ *
265
+ * All three unset/empty returns `undefined`, meaning encryption is disabled --
266
+ * key records are written plaintext (the default, honest about the teaching
267
+ * server's threat model). Setting both `KMS_RECORD_KEK` and `KMS_RECORD_KEKS` is
268
+ * ambiguous and throws; setting `KMS_RECORD_CURRENT_KEK` with no KEK configured
269
+ * throws. A malformed value throws (fails startup), naming the offending
270
+ * variable (and, for a list entry, its 1-based position) but never echoing the
271
+ * secret.
272
+ * @param options {object}
273
+ * @param [options.kek] {string} the `KMS_RECORD_KEK` value
274
+ * @param [options.keks] {string} the `KMS_RECORD_KEKS` value
275
+ * @param [options.currentKek] {string} the `KMS_RECORD_CURRENT_KEK` value
276
+ * @returns {KmsRecordKekRegistry|undefined} the registry, or `undefined` when
277
+ * no KEK is configured
278
+ */
279
+ export function parseKmsRecordKekRegistry({ kek, keks, currentKek }) {
280
+ const kekSet = kek !== undefined && kek.trim() !== '';
281
+ const keksSet = keks !== undefined && keks.trim() !== '';
282
+ const currentSet = currentKek !== undefined && currentKek.trim() !== '';
283
+ if (kekSet && keksSet) {
284
+ throw new Error('Set only one of KMS_RECORD_KEK or KMS_RECORD_KEKS, not both (ambiguous).');
285
+ }
286
+ // Build the ordered registry: each KEK keyed by its derived id, in config
287
+ // order (the first entry is the default current KEK).
288
+ const registry = new Map();
289
+ const order = [];
290
+ if (kekSet) {
291
+ const parsed = parseKekMultibase(kek.trim());
292
+ registry.set(parsed.id, parsed);
293
+ order.push(parsed);
294
+ }
295
+ else if (keksSet) {
296
+ let position = 0;
297
+ for (const entry of keks.split(',')) {
298
+ const trimmed = entry.trim();
299
+ if (trimmed === '') {
300
+ continue; // empty entries are ignored (like WAS_ENABLED_BACKENDS)
301
+ }
302
+ position += 1;
303
+ const parsed = parseKekMultibase(trimmed, `KMS_RECORD_KEKS entry ${position}`);
304
+ if (registry.has(parsed.id)) {
305
+ throw new Error(`KMS_RECORD_KEKS entry ${position} duplicates an earlier KEK ` +
306
+ `(same derived kekId); register each KEK only once.`);
307
+ }
308
+ registry.set(parsed.id, parsed);
309
+ order.push(parsed);
310
+ }
311
+ }
312
+ // No KEK material at all (all unset, or a list of only empty entries): a
313
+ // dangling KMS_RECORD_CURRENT_KEK is an error; otherwise encryption is off.
314
+ if (registry.size === 0) {
315
+ if (currentSet) {
316
+ throw new Error('KMS_RECORD_CURRENT_KEK is set but no KEK is configured; ' +
317
+ 'set KMS_RECORD_KEK or KMS_RECORD_KEKS.');
318
+ }
319
+ return undefined;
320
+ }
321
+ const currentKekId = currentSet
322
+ ? resolveCurrentKekId(currentKek.trim(), registry)
323
+ : order[0].id;
324
+ return { keks: registry, currentKekId };
325
+ }
326
+ /**
327
+ * Resolves a `KMS_RECORD_CURRENT_KEK` value to a registered `kekId` (or `null`
328
+ * for the literal `none`, the decrypt-only posture). The value is either a
329
+ * `urn:kek:sha256:<hex>` id or a multibase KEK whose id is derived; either way
330
+ * it must match a KEK already in `registry`. Throws on an unregistered target,
331
+ * naming the variable but never echoing the secret.
332
+ * @param value {string} the trimmed `KMS_RECORD_CURRENT_KEK` value
333
+ * @param registry {Map<string, RecordKek>} the registered KEKs, by id
334
+ * @returns {string|null} the resolved current `kekId`, or `null` for `none`
335
+ */
336
+ function resolveCurrentKekId(value, registry) {
337
+ if (value.toLowerCase() === 'none') {
338
+ return null; // decrypt-only: keep KEKs for unwrap, write new records plaintext
339
+ }
340
+ const kekId = value.startsWith('urn:kek:sha256:')
341
+ ? value
342
+ : parseKekMultibase(value, 'KMS_RECORD_CURRENT_KEK').id;
343
+ if (!registry.has(kekId)) {
344
+ throw new Error('KMS_RECORD_CURRENT_KEK names a KEK that is not registered ' +
345
+ '(no matching KMS_RECORD_KEK / KMS_RECORD_KEKS entry).');
346
+ }
347
+ return kekId;
348
+ }
349
+ /**
350
+ * Parses the `STORAGE_LIMIT_PER_SPACE` env value into a per-Space capacity in
351
+ * bytes (spec "Quotas"). Accepts a plain non-negative integer number of bytes,
352
+ * or the literal `unlimited` (case-insensitive, trimmed) which returns
353
+ * `Infinity` -- an explicitly acknowledged unlimited quota (no startup
354
+ * warning). An unset or empty value returns `undefined`, meaning no limit is
355
+ * configured (still unlimited, but `start.ts` warns to prompt an explicit
356
+ * choice). A malformed value throws.
357
+ * @param raw {string|undefined} the raw env value
358
+ * @returns {number|undefined} capacity in bytes, `Infinity` for `unlimited`,
359
+ * or `undefined` when unset
360
+ */
361
+ export function parseStorageLimit(raw) {
362
+ if (raw === undefined || raw.trim() === '') {
363
+ return undefined;
364
+ }
365
+ if (raw.trim().toLowerCase() === 'unlimited') {
366
+ return Infinity;
367
+ }
368
+ const value = Number(raw);
369
+ if (!Number.isInteger(value) || value < 0) {
370
+ throw new Error(`STORAGE_LIMIT_PER_SPACE must be a non-negative integer number of ` +
371
+ `bytes, or "unlimited"; got "${raw}".`);
372
+ }
373
+ return value;
374
+ }
375
+ /**
376
+ * Parses the `MAX_UPLOAD_BYTES` env value into a per-upload size cap in bytes
377
+ * (spec "Quotas", the backend's `maxUploadBytes` constraint). Accepts a plain
378
+ * non-negative integer number of bytes, or the literal `unlimited`
379
+ * (case-insensitive, trimmed) which returns `Infinity` -- explicitly no cap.
380
+ * An unset or empty value returns `undefined`, meaning not configured: the
381
+ * backends apply the {@link DEFAULT_MAX_UPLOAD_BYTES} default (a default-on
382
+ * limit). A single upload exceeding the cap is rejected with
383
+ * `payload-too-large` (413), while smaller uploads still succeed. A malformed
384
+ * value throws.
385
+ * @param raw {string|undefined} the raw env value
386
+ * @returns {number|undefined} the per-upload cap in bytes, `Infinity` for
387
+ * `unlimited`, or `undefined` when unset
388
+ */
389
+ export function parseMaxUploadBytes(raw) {
390
+ if (raw === undefined || raw.trim() === '') {
391
+ return undefined;
392
+ }
393
+ if (raw.trim().toLowerCase() === 'unlimited') {
394
+ return Infinity;
395
+ }
396
+ const value = Number(raw);
397
+ if (!Number.isInteger(value) || value < 0) {
398
+ throw new Error(`MAX_UPLOAD_BYTES must be a non-negative integer number of bytes, or ` +
399
+ `"unlimited"; got "${raw}".`);
400
+ }
401
+ return value;
402
+ }
403
+ /**
404
+ * Parses one of the count-quota env values (`MAX_SPACES_PER_CONTROLLER`,
405
+ * `MAX_COLLECTIONS_PER_SPACE`, `MAX_RESOURCES_PER_SPACE`) into a maximum count.
406
+ * Accepts a plain non-negative integer, or the literal `unlimited`
407
+ * (case-insensitive, trimmed) which returns `Infinity` -- explicitly no cap. An
408
+ * unset or empty value returns `undefined`, meaning not configured: the backends
409
+ * apply the matching default-on limit ({@link DEFAULT_MAX_SPACES_PER_CONTROLLER}
410
+ * and friends). A malformed value throws, naming the variable and the
411
+ * `unlimited` escape hatch.
412
+ * @param raw {string|undefined} the raw env value
413
+ * @param name {string} the env variable name, for the error message
414
+ * @returns {number|undefined} the max count, `Infinity` for `unlimited`, or
415
+ * `undefined` when unset
416
+ */
417
+ export function parseCountLimit(raw, name) {
418
+ if (raw === undefined || raw.trim() === '') {
419
+ return undefined;
420
+ }
421
+ if (raw.trim().toLowerCase() === 'unlimited') {
422
+ return Infinity;
423
+ }
424
+ const value = Number(raw);
425
+ if (!Number.isInteger(value) || value < 0) {
426
+ throw new Error(`${name} must be a non-negative integer, or "unlimited"; got "${raw}".`);
427
+ }
428
+ return value;
429
+ }
430
+ /**
431
+ * Normalizes a count-quota constructor option to the internal instance field
432
+ * both backends store: an unset option (`undefined`) applies the default-on
433
+ * `fallback` limit; a non-finite option (`Infinity`, from an explicit
434
+ * `unlimited`) becomes `undefined` (no cap); a finite value passes through.
435
+ * Shared so the two backends cannot drift on the mapping (the same posture as
436
+ * the `maxUploadBytes` normalization each backend already applies), letting each
437
+ * count guard keep its plain `!== undefined` test.
438
+ * @param value {number|undefined} the constructor option
439
+ * @param fallback {number} the default-on limit applied when unset
440
+ * @returns {number|undefined} the internal limit, or `undefined` for no cap
441
+ */
442
+ export function normalizeCountLimit(value, fallback) {
443
+ if (value === undefined) {
444
+ return fallback;
445
+ }
446
+ return Number.isFinite(value) ? value : undefined;
447
+ }
448
+ /**
449
+ * Parses the `WAS_ENABLED_BACKENDS` env value into the server-wide registration
450
+ * allowlist: the backend `provider` names a client may register (spec
451
+ * "Backends"). A comma-separated list (e.g. `gdrive,s3`); surrounding whitespace
452
+ * and empty entries are ignored. An unset or empty value returns `undefined`,
453
+ * meaning no allowlist is configured -- any provider may be registered (the
454
+ * permissive default, preserving prior behavior). When set, a registration whose
455
+ * `provider` is not listed is rejected with `unsupported-backend` (409).
456
+ * @param raw {string|undefined} the raw env value
457
+ * @returns {string[]|undefined} the allowed provider names, or `undefined`
458
+ */
459
+ export function parseEnabledBackends(raw) {
460
+ if (raw === undefined) {
461
+ return undefined;
462
+ }
463
+ const providers = raw
464
+ .split(',')
465
+ .map(entry => entry.trim())
466
+ .filter(entry => entry.length > 0);
467
+ return providers.length > 0 ? providers : undefined;
468
+ }
469
+ /**
470
+ * Parses the `WAS_ONBOARDING_TOKEN` env value into the shared-secret onboarding
471
+ * token gating the two open provisioning endpoints (`POST /spaces/`,
472
+ * `POST /kms/keystores`). An unset or empty/whitespace-only value returns
473
+ * `undefined`, meaning the feature is off -- provisioning is authorized by
474
+ * proving control of the body's controller DID (the teaching default). When
475
+ * set, those two endpoints instead require an `Authorization: Bearer <token>`
476
+ * header matching this value, which then substitutes for zcap verification.
477
+ * @param raw {string|undefined} the raw env value
478
+ * @returns {string|undefined} the trimmed token, or `undefined` when unset
479
+ */
480
+ export function parseOnboardingToken(raw) {
481
+ if (raw === undefined || raw.trim() === '') {
482
+ return undefined;
483
+ }
484
+ return raw.trim();
485
+ }
486
+ export const SPEC_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/';
487
+ export const UBC_MANIFEST_URL = 'https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file';
488
+ export const SPACE_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/#spaces';
489
+ export const COLLECTION_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/#collection-data-model';
490
+ export const RESOURCE_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/#resource-data-model';
491
+ export const POLICY_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/#policy';
492
+ export const META_URL = 'https://digitalcredentials.github.io/wallet-attached-storage-spec/#resource-metadata-data-model';
493
+ //# sourceMappingURL=config.default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.default.js","sourceRoot":"","sources":["../src/config.default.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
2
+ import type { LookupFunction } from 'node:net';
3
+ /**
4
+ * Builds the undici `connect.lookup` function that pins each upstream socket to
5
+ * the exact addresses `checkProxyTarget` already validated, keyed by hostname.
6
+ * The `fetch` still connects using the original hostname (so TLS certificate
7
+ * validation and SNI keep working) but resolves it only via this map, so a
8
+ * rebinding attacker cannot swap in a fresh, private address between validation
9
+ * and connection. A hostname absent from the map is refused (defense in depth:
10
+ * the agent must never resolve an unpinned host).
11
+ * @param pins {Map<string, {address: string, family: number}[]>} validated
12
+ * addresses keyed by lower-cased hostname.
13
+ * @returns {LookupFunction} an undici/`net`-compatible lookup callback.
14
+ */
15
+ export declare function createPinnedLookup(pins: Map<string, {
16
+ address: string;
17
+ family: number;
18
+ }[]>): LookupFunction;
19
+ /**
20
+ * Registers a server-side CORS proxy at `/api/cors`.
21
+ *
22
+ * Example:
23
+ * `/api/cors?url=https%3A%2F%2Fregistry.dcconsortium.org%2F.well-known%2Fopenid-federation`
24
+ *
25
+ * SSRF guard: only `http`/`https` URLs are proxied, and the target host is
26
+ * resolved and rejected when it maps to a private, loopback, or link-local
27
+ * address (e.g. `http://169.254.169.254/...` cloud-metadata, or an internal
28
+ * service). Redirects are followed manually (up to `MAX_REDIRECTS` hops) and
29
+ * every hop is re-validated the same way, so a public host cannot 3xx the
30
+ * proxy into an internal one. The addresses each hop validated are then pinned
31
+ * for the actual upstream connection (via a per-request undici `Agent` whose
32
+ * `connect.lookup` resolves only from that pin map), so a DNS-rebinding
33
+ * attacker cannot pass validation with a public IP and have the subsequent
34
+ * `fetch` re-resolve to a private one (closing the TOCTOU). The relayed
35
+ * response body is capped at `PROXY_MAX_RESPONSE_BYTES`, both from a declared
36
+ * `content-length` and while streaming. This is an unauthenticated open
37
+ * endpoint; adding a lightweight auth gate (a shared secret header, an
38
+ * allowlist of caller origins, or the server's own capability-invocation
39
+ * check) is a reasonable follow-up.
40
+ *
41
+ * @param app - Fastify instance
42
+ * @param _options - Fastify plugin options
43
+ */
44
+ export declare function initCorsProxyRoutes(app: FastifyInstance, _options: FastifyPluginOptions): Promise<void>;
45
+ //# sourceMappingURL=corsProxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corsProxy.d.ts","sourceRoot":"","sources":["../src/corsProxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AA8I9C;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,GACvD,cAAc,CAsBhB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAoHf"}