@serfab/cadre-host 1.13.0 → 1.15.0

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 (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,115 +0,0 @@
1
- /**
2
- * Shape validation for the requester-supplied `bootstrapNodes` of `POST /grants`
3
- * — the requester's control-network addresses the donated node is started with
4
- * (written into the child's `cadre.json` as `controlNetwork.bootstrapNodes` by
5
- * `orchestrator/host-process-orchestrator.ts`) and hands to `@libp2p/bootstrap`.
6
- *
7
- * Its own module rather than part of `grants.ts`: the per-entry rule is a duplicate
8
- * of cadre-provider's copy (`packages/cadre-provider/src/server/bootstrap-node-validation.ts`)
9
- * and must stay identical to it, so it is worth reading, testing and changing on
10
- * its own, without a routing file around it — the same arrangement cadre-provider
11
- * uses for its restatement of cadre-core's owner-key rule.
12
- *
13
- * ## An empty list is accepted — where this copy differs, on purpose
14
- *
15
- * The field may be absent or `[]`; cadre-provider's copy still requires at least one
16
- * address. A lent node's requester is typically a phone, and a phone has no address
17
- * to give — its node listens on nothing and can only dial out. Such a requester sends
18
- * no addresses and dials the node itself, at the WebSocket address
19
- * `GET /grants/:id/peer` reports. The node starts with no bootstrap peers and waits,
20
- * and admits that first inbound connection because it holds no authorized members
21
- * yet (`admitInboundControlConnection` / `authorizeInboundControlStream` in
22
- * `cadre-core/src/cadre-node.ts`). A provider container has no such requester yet, so
23
- * there an empty list is still the node-comes-up-alone failure described below
24
- * (`backlog/feat-provider-drone-reachable-by-phone`).
25
- *
26
- * The difference is at the list level only: every entry that IS given must pass the
27
- * rule below, which is identical in both packages.
28
- *
29
- * ## The rule, and why each clause is there
30
- *
31
- * An entry is usable exactly when it is a string, parses as a multiaddr, carries
32
- * a `/p2p/<peerId>` component whose peer id decodes, and says where that peer is.
33
- * Each clause corresponds to a distinct downstream failure the caller would
34
- * otherwise never be told about — the value is forwarded verbatim into the spawned
35
- * child, so every one of these surfaces where the caller cannot see it:
36
- *
37
- * - **Not parsable as a multiaddr → the child dies at boot.** `@libp2p/bootstrap`'s
38
- * constructor maps `multiaddr()` over the whole list *before* filtering anything,
39
- * and `multiaddr()` throws on a string that does not start with `/` (and on a
40
- * non-string outright). libp2p construction throws and the node never starts.
41
- * - **No `/p2p/<peerId>` → the node starts and never joins.** The quiet one.
42
- * `/ip4/127.0.0.1/tcp/4001` parses fine, then `@libp2p/bootstrap` filters it out
43
- * ("invalid bootstrap multiaddr without peer id", logged inside the child) and
44
- * the node comes up with *zero* bootstrap peers: healthy-looking, permanently
45
- * alone, never reaching the party it was created for. cadre-core's
46
- * `getBootstrapPeerIds` (`packages/cadre-core/src/cadre-node.ts`) skips such an
47
- * entry silently for the same reason.
48
- * - **Malformed peer id → the child dies at boot.** `multiaddr()` does *not*
49
- * validate the `/p2p/` value — parsing `/ip4/1.2.3.4/tcp/1/p2p/12D3KooReq` yields
50
- * a `p2p` component whose value is the junk string unchanged — but
51
- * `@libp2p/bootstrap` then calls `peerIdFromString` on it, unguarded, and it
52
- * throws `Incorrect length`. A truncated copy-paste of a 52-character peer id is
53
- * the likeliest human typo in this field.
54
- * - **No location at all → the node starts and never joins.** `/p2p/<peerId>` (and
55
- * `/p2p/<relay>/p2p-circuit/p2p/<target>`) parses and names a decodable peer, and
56
- * `@libp2p/bootstrap` keeps it — but it says nothing about *where* the peer is, so
57
- * libp2p finds no transport for it and the dial fails with no valid addresses.
58
- * The node comes up with a bootstrap peer it can never reach, which looks the same
59
- * from outside as having none. Only the total absence of a location is rejected:
60
- * which transports the child can actually dial is the embedder's choice
61
- * (`NetworkConfig.transports`), invisible from here, so a partial address like
62
- * `/ip4/1.2.3.4/p2p/<peerId>` is deliberately left to the child.
63
- *
64
- * The peer id is read the way `@libp2p/bootstrap` reads it — the `p2p` components
65
- * of `getComponents()` — rather than through the deprecated `getPeerId()`, so the
66
- * two cannot disagree about which part of the address is the peer. Bootstrap uses
67
- * only the *last* `p2p` component; this checks **every** one, so the relay half of
68
- * a `…/p2p/<relay>/p2p-circuit/p2p/<target>` address must decode too. An address
69
- * naming an undecodable relay is not dialable either, and bootstrap's own
70
- * `P2P.matches` filter accepts any address carrying a `p2p` component, so nothing
71
- * this accepts is dropped there.
72
- *
73
- * Requiring a multiaddr also closes a downstream ambiguity on the *other* side of
74
- * this rule: cadre-provider joins its list with `,` into `CADRE_BOOTSTRAP_NODES`
75
- * and cadre-cli's env loader splits it back on `,`, so an entry containing a comma
76
- * would silently become two addresses there. This host writes JSON rather than an
77
- * env var and so is not exposed to that, but the two per-entry rules are kept
78
- * identical — see the note about manual upkeep below.
79
- *
80
- * Reachability is deliberately NOT checked: a well-formed address for a peer that
81
- * happens to be down is indistinguishable here from one that is up, and no
82
- * boundary check can tell them apart. This validates the *shape* the child must be
83
- * able to parse, nothing more.
84
- *
85
- * Keeping the two per-entry rules in step is **manual** — neither package can see
86
- * the other's rule, so no test can compare them. What the tests give instead is a
87
- * tripwire on each side: `../__tests__/bootstrap-node-validation.test.ts` here and
88
- * `packages/cadre-provider/src/server/__tests__/bootstrap-node-validation.test.ts`
89
- * there each pin their own copy to the same per-entry accept/reject table (the
90
- * empty-list row is the one that differs), so changing either rule fails that
91
- * package's own suite — and the comment above the rule you just changed is what
92
- * points at the other copy.
93
- */
94
- /**
95
- * Validate the optional `bootstrapNodes` field of a provision request: absent, or an
96
- * array of dialable control-network addresses (see {@link validateBootstrapNode}).
97
- * Absent and `[]` alike mean the requester dials the node itself — see "An empty list
98
- * is accepted" in the module comment.
99
- *
100
- * Provisioning is the last point at which the requester can still fix a typo, so
101
- * the shape is checked here: without it a bad address is answered 201 and then
102
- * produces a donated node that either dies at boot or comes up permanently alone
103
- * — the requester learning about it, if ever, from donation status rather than
104
- * from a 400 naming the bad address.
105
- *
106
- * Every rejection names the offending entry: with several addresses in one
107
- * request the message is the only thing that says WHICH one.
108
- *
109
- * @returns the trimmed addresses (`[]` when none were given), so the provision request carries exactly what was validated.
110
- */
111
- export declare function validateBootstrapNodes(value: unknown): {
112
- nodes: string[];
113
- } | {
114
- error: string;
115
- };
@@ -1,197 +0,0 @@
1
- /**
2
- * Shape validation for the requester-supplied `bootstrapNodes` of `POST /grants`
3
- * — the requester's control-network addresses the donated node is started with
4
- * (written into the child's `cadre.json` as `controlNetwork.bootstrapNodes` by
5
- * `orchestrator/host-process-orchestrator.ts`) and hands to `@libp2p/bootstrap`.
6
- *
7
- * Its own module rather than part of `grants.ts`: the per-entry rule is a duplicate
8
- * of cadre-provider's copy (`packages/cadre-provider/src/server/bootstrap-node-validation.ts`)
9
- * and must stay identical to it, so it is worth reading, testing and changing on
10
- * its own, without a routing file around it — the same arrangement cadre-provider
11
- * uses for its restatement of cadre-core's owner-key rule.
12
- *
13
- * ## An empty list is accepted — where this copy differs, on purpose
14
- *
15
- * The field may be absent or `[]`; cadre-provider's copy still requires at least one
16
- * address. A lent node's requester is typically a phone, and a phone has no address
17
- * to give — its node listens on nothing and can only dial out. Such a requester sends
18
- * no addresses and dials the node itself, at the WebSocket address
19
- * `GET /grants/:id/peer` reports. The node starts with no bootstrap peers and waits,
20
- * and admits that first inbound connection because it holds no authorized members
21
- * yet (`admitInboundControlConnection` / `authorizeInboundControlStream` in
22
- * `cadre-core/src/cadre-node.ts`). A provider container has no such requester yet, so
23
- * there an empty list is still the node-comes-up-alone failure described below
24
- * (`backlog/feat-provider-drone-reachable-by-phone`).
25
- *
26
- * The difference is at the list level only: every entry that IS given must pass the
27
- * rule below, which is identical in both packages.
28
- *
29
- * ## The rule, and why each clause is there
30
- *
31
- * An entry is usable exactly when it is a string, parses as a multiaddr, carries
32
- * a `/p2p/<peerId>` component whose peer id decodes, and says where that peer is.
33
- * Each clause corresponds to a distinct downstream failure the caller would
34
- * otherwise never be told about — the value is forwarded verbatim into the spawned
35
- * child, so every one of these surfaces where the caller cannot see it:
36
- *
37
- * - **Not parsable as a multiaddr → the child dies at boot.** `@libp2p/bootstrap`'s
38
- * constructor maps `multiaddr()` over the whole list *before* filtering anything,
39
- * and `multiaddr()` throws on a string that does not start with `/` (and on a
40
- * non-string outright). libp2p construction throws and the node never starts.
41
- * - **No `/p2p/<peerId>` → the node starts and never joins.** The quiet one.
42
- * `/ip4/127.0.0.1/tcp/4001` parses fine, then `@libp2p/bootstrap` filters it out
43
- * ("invalid bootstrap multiaddr without peer id", logged inside the child) and
44
- * the node comes up with *zero* bootstrap peers: healthy-looking, permanently
45
- * alone, never reaching the party it was created for. cadre-core's
46
- * `getBootstrapPeerIds` (`packages/cadre-core/src/cadre-node.ts`) skips such an
47
- * entry silently for the same reason.
48
- * - **Malformed peer id → the child dies at boot.** `multiaddr()` does *not*
49
- * validate the `/p2p/` value — parsing `/ip4/1.2.3.4/tcp/1/p2p/12D3KooReq` yields
50
- * a `p2p` component whose value is the junk string unchanged — but
51
- * `@libp2p/bootstrap` then calls `peerIdFromString` on it, unguarded, and it
52
- * throws `Incorrect length`. A truncated copy-paste of a 52-character peer id is
53
- * the likeliest human typo in this field.
54
- * - **No location at all → the node starts and never joins.** `/p2p/<peerId>` (and
55
- * `/p2p/<relay>/p2p-circuit/p2p/<target>`) parses and names a decodable peer, and
56
- * `@libp2p/bootstrap` keeps it — but it says nothing about *where* the peer is, so
57
- * libp2p finds no transport for it and the dial fails with no valid addresses.
58
- * The node comes up with a bootstrap peer it can never reach, which looks the same
59
- * from outside as having none. Only the total absence of a location is rejected:
60
- * which transports the child can actually dial is the embedder's choice
61
- * (`NetworkConfig.transports`), invisible from here, so a partial address like
62
- * `/ip4/1.2.3.4/p2p/<peerId>` is deliberately left to the child.
63
- *
64
- * The peer id is read the way `@libp2p/bootstrap` reads it — the `p2p` components
65
- * of `getComponents()` — rather than through the deprecated `getPeerId()`, so the
66
- * two cannot disagree about which part of the address is the peer. Bootstrap uses
67
- * only the *last* `p2p` component; this checks **every** one, so the relay half of
68
- * a `…/p2p/<relay>/p2p-circuit/p2p/<target>` address must decode too. An address
69
- * naming an undecodable relay is not dialable either, and bootstrap's own
70
- * `P2P.matches` filter accepts any address carrying a `p2p` component, so nothing
71
- * this accepts is dropped there.
72
- *
73
- * Requiring a multiaddr also closes a downstream ambiguity on the *other* side of
74
- * this rule: cadre-provider joins its list with `,` into `CADRE_BOOTSTRAP_NODES`
75
- * and cadre-cli's env loader splits it back on `,`, so an entry containing a comma
76
- * would silently become two addresses there. This host writes JSON rather than an
77
- * env var and so is not exposed to that, but the two per-entry rules are kept
78
- * identical — see the note about manual upkeep below.
79
- *
80
- * Reachability is deliberately NOT checked: a well-formed address for a peer that
81
- * happens to be down is indistinguishable here from one that is up, and no
82
- * boundary check can tell them apart. This validates the *shape* the child must be
83
- * able to parse, nothing more.
84
- *
85
- * Keeping the two per-entry rules in step is **manual** — neither package can see
86
- * the other's rule, so no test can compare them. What the tests give instead is a
87
- * tripwire on each side: `../__tests__/bootstrap-node-validation.test.ts` here and
88
- * `packages/cadre-provider/src/server/__tests__/bootstrap-node-validation.test.ts`
89
- * there each pin their own copy to the same per-entry accept/reject table (the
90
- * empty-list row is the one that differs), so changing either rule fails that
91
- * package's own suite — and the comment above the rule you just changed is what
92
- * points at the other copy.
93
- */
94
- import debug from 'debug';
95
- import { CODE_P2P, CODE_P2P_CIRCUIT, multiaddr } from '@multiformats/multiaddr';
96
- import { peerIdFromString } from '@libp2p/peer-id';
97
- const log = debug('cadre:host:bootstrap-nodes');
98
- /**
99
- * Longest rejected address echoed back in an error message. Generous compared to
100
- * the owner-key limit because a legitimate address is long: a plain
101
- * `/ip4/…/tcp/…/p2p/<52 chars>` runs ~80 characters and a relayed
102
- * `…/p2p-circuit/p2p/<52 chars>` about 150, so a smaller cap would truncate the
103
- * very values an operator needs to read back.
104
- */
105
- const REJECTED_ADDRESS_ECHO_LIMIT = 192;
106
- /**
107
- * Render a rejected address for an error message, capped: `bootstrapNodes`
108
- * arrives from a caller over the network, so an unbounded echo would let one junk
109
- * string become a megabyte of log line.
110
- */
111
- function describeRejectedAddress(value) {
112
- return value.length <= REJECTED_ADDRESS_ECHO_LIMIT
113
- ? value
114
- : `${value.slice(0, REJECTED_ADDRESS_ECHO_LIMIT)}… (${value.length} chars)`;
115
- }
116
- /** The message of a thrown parse/decode failure, whatever the thrower used. */
117
- function reasonOf(error) {
118
- return error instanceof Error ? error.message : String(error);
119
- }
120
- /**
121
- * Apply the rule above to one entry. Returns the trimmed value, so the create
122
- * provision request carries exactly what was validated.
123
- */
124
- function validateBootstrapNode(value) {
125
- const trimmed = value.trim();
126
- if (trimmed.length === 0) {
127
- return { error: 'bootstrapNodes entries must not be empty or whitespace-only' };
128
- }
129
- let address;
130
- try {
131
- address = multiaddr(trimmed);
132
- }
133
- catch (error) {
134
- log('rejecting bootstrapNodes entry: %s', reasonOf(error));
135
- return {
136
- error: `bootstrapNodes entries must be multiaddrs (could not parse "${describeRejectedAddress(trimmed)}": ${reasonOf(error)})`,
137
- };
138
- }
139
- const components = address.getComponents();
140
- const peerIds = components.filter(component => component.code === CODE_P2P);
141
- if (peerIds.length === 0) {
142
- return {
143
- error: `bootstrapNodes entries must include a /p2p/<peerId> component ("${describeRejectedAddress(trimmed)}" names no peer, so the node would drop it and start with no bootstrap peers)`,
144
- };
145
- }
146
- if (!components.some(component => component.code !== CODE_P2P && component.code !== CODE_P2P_CIRCUIT)) {
147
- return {
148
- error: `bootstrapNodes entries must name where to reach the peer ("${describeRejectedAddress(trimmed)}" names only peer ids, so no transport can dial it and the node would start with no reachable bootstrap peers)`,
149
- };
150
- }
151
- for (const { value } of peerIds) {
152
- try {
153
- peerIdFromString(value ?? '');
154
- }
155
- catch (error) {
156
- log('rejecting bootstrapNodes peer id: %s', reasonOf(error));
157
- return {
158
- error: `bootstrapNodes entries must carry a decodable peer id ("${describeRejectedAddress(trimmed)}" names peer "${describeRejectedAddress(value ?? '')}", which does not decode: ${reasonOf(error)})`,
159
- };
160
- }
161
- }
162
- return { node: trimmed };
163
- }
164
- /**
165
- * Validate the optional `bootstrapNodes` field of a provision request: absent, or an
166
- * array of dialable control-network addresses (see {@link validateBootstrapNode}).
167
- * Absent and `[]` alike mean the requester dials the node itself — see "An empty list
168
- * is accepted" in the module comment.
169
- *
170
- * Provisioning is the last point at which the requester can still fix a typo, so
171
- * the shape is checked here: without it a bad address is answered 201 and then
172
- * produces a donated node that either dies at boot or comes up permanently alone
173
- * — the requester learning about it, if ever, from donation status rather than
174
- * from a 400 naming the bad address.
175
- *
176
- * Every rejection names the offending entry: with several addresses in one
177
- * request the message is the only thing that says WHICH one.
178
- *
179
- * @returns the trimmed addresses (`[]` when none were given), so the provision request carries exactly what was validated.
180
- */
181
- export function validateBootstrapNodes(value) {
182
- if (value === undefined) {
183
- return { nodes: [] };
184
- }
185
- if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) {
186
- return { error: 'bootstrapNodes must be an array of strings' };
187
- }
188
- const nodes = [];
189
- for (const entry of value) {
190
- const checked = validateBootstrapNode(entry);
191
- if ('error' in checked)
192
- return checked;
193
- nodes.push(checked.node);
194
- }
195
- return { nodes };
196
- }
197
- //# sourceMappingURL=bootstrap-node-validation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bootstrap-node-validation.js","sourceRoot":"","sources":["../../../src/server/routes/bootstrap-node-validation.ts"],"names":[],"mappings":"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"}
@@ -1,29 +0,0 @@
1
- /**
2
- * Donation grant **admin** HTTP routes — thin adapters around
3
- * `GrantAdminHandlers` (`createGrantAdminHandlers(service, donations)`).
4
- *
5
- * Mount path: `/grants-admin` — the admin surface the `cadre-host grant` CLI
6
- * targets. It is **loopback, no bearer**: same-machine admin, matching
7
- * cadre-host's local-UI "no login" posture (see docs/cadre-host.md §
8
- * Security posture). This is distinct from the grantee-facing `/grants`
9
- * provisioning surface (`routes/grants.ts`), which carries the bearer gate.
10
- *
11
- * Clients: the `cadre-host grant` CLI and the local UI's Grants page. The list
12
- * reports each grant's live-node count and the donations a revoke would end.
13
- *
14
- * Revoking a grant also terminates the nodes donated under it unless the
15
- * caller passes `?keepNodes=true`; `DELETE /grants-admin/donations/:id` ends a
16
- * single donated node. Those are the host's only teardown paths once a grant is
17
- * revoked — the grantee's own `DELETE /grants/:id` is refused from then on.
18
- *
19
- * Each successful mutation publishes a `grants-changed` bus event, so an open
20
- * Grants page refreshes when the CLI acts.
21
- */
22
- import type { FastifyInstance } from 'fastify';
23
- import type { GrantAdminHandlers } from '../../donation/types.js';
24
- import type { EventBus } from '../events/bus.js';
25
- export interface GrantsAdminRoutesOptions {
26
- handlers: GrantAdminHandlers;
27
- events: EventBus;
28
- }
29
- export declare function registerGrantsAdminRoutes(app: FastifyInstance, opts: GrantsAdminRoutesOptions): void;
@@ -1,58 +0,0 @@
1
- /**
2
- * Donation grant **admin** HTTP routes — thin adapters around
3
- * `GrantAdminHandlers` (`createGrantAdminHandlers(service, donations)`).
4
- *
5
- * Mount path: `/grants-admin` — the admin surface the `cadre-host grant` CLI
6
- * targets. It is **loopback, no bearer**: same-machine admin, matching
7
- * cadre-host's local-UI "no login" posture (see docs/cadre-host.md §
8
- * Security posture). This is distinct from the grantee-facing `/grants`
9
- * provisioning surface (`routes/grants.ts`), which carries the bearer gate.
10
- *
11
- * Clients: the `cadre-host grant` CLI and the local UI's Grants page. The list
12
- * reports each grant's live-node count and the donations a revoke would end.
13
- *
14
- * Revoking a grant also terminates the nodes donated under it unless the
15
- * caller passes `?keepNodes=true`; `DELETE /grants-admin/donations/:id` ends a
16
- * single donated node. Those are the host's only teardown paths once a grant is
17
- * revoked — the grantee's own `DELETE /grants/:id` is refused from then on.
18
- *
19
- * Each successful mutation publishes a `grants-changed` bus event, so an open
20
- * Grants page refreshes when the CLI acts.
21
- */
22
- export function registerGrantsAdminRoutes(app, opts) {
23
- const { handlers, events } = opts;
24
- app.get('/grants-admin', async () => {
25
- return handlers.listGrants();
26
- });
27
- app.post('/grants-admin', async (request) => {
28
- const body = (request.body ?? {});
29
- const label = typeof body.label === 'string' ? body.label : '';
30
- const args = { label };
31
- if (typeof body.maxNodes === 'number')
32
- args.maxNodes = body.maxNodes;
33
- if (typeof body.ttlMs === 'number')
34
- args.ttlMs = body.ttlMs;
35
- const result = await handlers.postGrant(args);
36
- events.publish({ type: 'grants-changed', kind: 'issued' });
37
- return result;
38
- });
39
- app.delete('/grants-admin/:token', async (request) => {
40
- const keepNodes = isTrueFlag(request.query.keepNodes);
41
- const { terminated } = await handlers.deleteGrant(request.params.token, { keepNodes });
42
- events.publish({ type: 'grants-changed', kind: 'revoked' });
43
- return { ok: true, terminated };
44
- });
45
- // Two path segments past `/grants-admin`, so it never collides with `/:token`.
46
- const { terminateDonation } = handlers;
47
- if (terminateDonation) {
48
- app.delete('/grants-admin/donations/:id', async (request) => {
49
- await terminateDonation(request.params.id);
50
- events.publish({ type: 'grants-changed', kind: 'terminated' });
51
- return { ok: true };
52
- });
53
- }
54
- }
55
- function isTrueFlag(value) {
56
- return value === 'true' || value === '1';
57
- }
58
- //# sourceMappingURL=grants-admin.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grants-admin.js","sourceRoot":"","sources":["../../../src/server/routes/grants-admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAYH,MAAM,UAAU,yBAAyB,CAAC,GAAoB,EAAE,IAA8B;IAC5F,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAElC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;QAClC,OAAO,QAAQ,CAAC,UAAU,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAA6D,CAAC;QAC9F,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAyD,EAAE,KAAK,EAAE,CAAC;QAC7E,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QACrE,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CACR,sBAAsB,EACtB,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACtD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QACvF,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAClC,CAAC,CACF,CAAC;IAEF,+EAA+E;IAC/E,MAAM,EAAE,iBAAiB,EAAE,GAAG,QAAQ,CAAC;IACvC,IAAI,iBAAiB,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAA6B,6BAA6B,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACtF,MAAM,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;YAC/D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB;IAC3C,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,CAAC;AAC3C,CAAC"}
@@ -1,28 +0,0 @@
1
- /**
2
- * Grantee-facing donation routes — the HTTP surface an external cadre authority
3
- * (a friend's phone) drives to have this host donate a node into *its* cadre.
4
- *
5
- * Mount path: `/grants`. Distinct from the loopback-only `/grants-admin` surface
6
- * (issue/list/revoke, no bearer): this one is **bearer-gated** — every request
7
- * carries `Authorization: Bearer <grant-token>`, and a grantee sees only the
8
- * donations its own grant authorized (the single cross-grantee boundary,
9
- * analogous to cadre-provider's per-customer ownership check).
10
- *
11
- * Reachability is out of scope here: in v1 this mounts on the loopback
12
- * management server, same as the trust-circle / NAT surfaces. Physically exposing
13
- * it to a remote phone is `backlog/feat-cadre-host-wan-grant-reachability`.
14
- *
15
- * Errors: handlers either send a `{ ok:false, error:{code,message} }` envelope
16
- * inline (auth / body validation) or let the `DonationService` throw a
17
- * `DonationError`, which `server/error-handler.ts` maps to the matching HTTP
18
- * status and the same envelope.
19
- */
20
- import type { FastifyInstance } from 'fastify';
21
- import type { DonationService } from '../../donation/donation-service.js';
22
- import type { GrantValidator } from '../../donation/types.js';
23
- export interface GrantsRoutesOptions {
24
- donations: DonationService;
25
- /** Validates the presented bearer for non-provisioning ops (provision revalidates internally). */
26
- grants: GrantValidator;
27
- }
28
- export declare function registerGrantsRoutes(app: FastifyInstance, opts: GrantsRoutesOptions): void;
@@ -1,165 +0,0 @@
1
- /**
2
- * Grantee-facing donation routes — the HTTP surface an external cadre authority
3
- * (a friend's phone) drives to have this host donate a node into *its* cadre.
4
- *
5
- * Mount path: `/grants`. Distinct from the loopback-only `/grants-admin` surface
6
- * (issue/list/revoke, no bearer): this one is **bearer-gated** — every request
7
- * carries `Authorization: Bearer <grant-token>`, and a grantee sees only the
8
- * donations its own grant authorized (the single cross-grantee boundary,
9
- * analogous to cadre-provider's per-customer ownership check).
10
- *
11
- * Reachability is out of scope here: in v1 this mounts on the loopback
12
- * management server, same as the trust-circle / NAT surfaces. Physically exposing
13
- * it to a remote phone is `backlog/feat-cadre-host-wan-grant-reachability`.
14
- *
15
- * Errors: handlers either send a `{ ok:false, error:{code,message} }` envelope
16
- * inline (auth / body validation) or let the `DonationService` throw a
17
- * `DonationError`, which `server/error-handler.ts` maps to the matching HTTP
18
- * status and the same envelope.
19
- */
20
- import { validateProvisionRequest } from './provision-request-validation.js';
21
- /** Uniform error envelope (matches `error-handler.ts` and the provider API). */
22
- function errorResponse(reply, code, message, status) {
23
- return reply.status(status).send({ ok: false, error: { code, message } });
24
- }
25
- /** Pull the `Bearer <token>` value out of the Authorization header, or undefined. */
26
- function parseBearer(header) {
27
- if (!header)
28
- return undefined;
29
- const match = /^Bearer\s+(.+)$/i.exec(header.trim());
30
- return match ? match[1].trim() : undefined;
31
- }
32
- /** Map a grant denial reason to an HTTP status for the bearer gate. */
33
- function denyStatus(reason) {
34
- if (reason === 'expired' || reason === 'revoked')
35
- return 403;
36
- return 401; // unknown_token / missing
37
- }
38
- /**
39
- * Authenticate the grant bearer for a non-provisioning op (identity + not
40
- * expired/revoked; no quota). Returns the token, or null after sending the
41
- * failure response. Provision does its own quota-aware validation inside
42
- * `DonationService.provision`.
43
- */
44
- function authenticate(request, reply, grants) {
45
- const token = parseBearer(request.headers.authorization);
46
- if (!token) {
47
- errorResponse(reply, 'unauthorized', 'Missing bearer grant token', 401);
48
- return null;
49
- }
50
- const validation = grants.validate(token);
51
- if (!validation.ok) {
52
- const status = denyStatus(validation.reason);
53
- // Keep the envelope code aligned with the status (and with the POST path,
54
- // which surfaces an unknown token as `unauthorized`): 401 → unauthorized,
55
- // 403 → forbidden. A 401 labelled `forbidden` is self-contradictory.
56
- const code = status === 403 ? 'forbidden' : 'unauthorized';
57
- errorResponse(reply, code, `Grant is ${validation.reason ?? 'invalid'}`, status);
58
- return null;
59
- }
60
- return token;
61
- }
62
- /**
63
- * Resolve a donation the presented grant owns. Returns the redacted view, or
64
- * null after sending a 404 — an unknown id and one owned by a *different* grant
65
- * are deliberately indistinguishable (a grantee never learns another grantee's
66
- * donations exist).
67
- */
68
- function requireOwned(donations, id, token, reply) {
69
- const donation = donations.get(id);
70
- if (!donation || donation.grantToken !== token) {
71
- errorResponse(reply, 'not_found', `No such donation: ${id}`, 404);
72
- return null;
73
- }
74
- return donation;
75
- }
76
- export function registerGrantsRoutes(app, opts) {
77
- const { donations, grants } = opts;
78
- // POST /grants — provision a donated node. The bearer's identity/expiry/
79
- // revocation AND quota are validated inside provision(), which throws a
80
- // DonationError (unauthorized / forbidden / quota_exceeded) the error handler
81
- // maps. We only parse the bearer and validate the request body here.
82
- app.post('/grants', async (request, reply) => {
83
- const token = parseBearer(request.headers.authorization);
84
- if (!token) {
85
- return errorResponse(reply, 'unauthorized', 'Missing bearer grant token', 401);
86
- }
87
- // One validator owns the whole body — see `provision-request-validation.ts`
88
- // for why no per-field check lives here. Nothing is provisioned on a
89
- // rejection: this runs before the service is touched.
90
- const validated = validateProvisionRequest(request.body ?? {}, token);
91
- if ('error' in validated) {
92
- return errorResponse(reply, 'invalid_request', validated.error, 400);
93
- }
94
- const donation = await donations.provision(validated.request);
95
- return reply.status(201).send({ ok: true, data: { donation } });
96
- });
97
- // GET /grants — list only the donations this grant authorized.
98
- app.get('/grants', async (request, reply) => {
99
- const token = authenticate(request, reply, grants);
100
- if (!token)
101
- return reply;
102
- return reply.send({ ok: true, data: { donations: donations.list(token) } });
103
- });
104
- // GET /grants/:id — one donation (redacted), owned by this grant.
105
- app.get('/grants/:id', async (request, reply) => {
106
- const token = authenticate(request, reply, grants);
107
- if (!token)
108
- return reply;
109
- const donation = requireOwned(donations, request.params.id, token, reply);
110
- if (!donation)
111
- return reply;
112
- return reply.send({ ok: true, data: { donation } });
113
- });
114
- // GET /grants/:id/peer — live { peerId, multiaddrs } for the requester's addDrone.
115
- app.get('/grants/:id/peer', async (request, reply) => {
116
- const token = authenticate(request, reply, grants);
117
- if (!token)
118
- return reply;
119
- if (!requireOwned(donations, request.params.id, token, reply))
120
- return reply;
121
- const peer = await donations.getPeer(request.params.id);
122
- return reply.send({ ok: true, data: peer });
123
- });
124
- // PUT /grants/:id/seed — apply the requester's phone-signed seed.
125
- app.put('/grants/:id/seed', async (request, reply) => {
126
- const token = authenticate(request, reply, grants);
127
- if (!token)
128
- return reply;
129
- if (!requireOwned(donations, request.params.id, token, reply))
130
- return reply;
131
- const body = (request.body ?? {});
132
- if (typeof body.seed !== 'string' || body.seed.length === 0) {
133
- return errorResponse(reply, 'invalid_request', 'seed is required', 400);
134
- }
135
- const result = await donations.applySeed(request.params.id, body.seed);
136
- // NOTE: exhaustive over today's three outcomes, and nothing forces it to stay
137
- // that way — an unhandled fourth would fall out of the handler as `undefined`
138
- // and hang the request rather than fail to compile. If `DonationSeedResult`
139
- // ever grows a variant, add a `default:` that 500s here.
140
- switch (result.outcome) {
141
- case 'rejected':
142
- return errorResponse(reply, 'seed_failed', result.error ?? 'Node rejected the seed', 502);
143
- // The loan ended while the seed was in flight. The ending won, so the
144
- // caller is told the record it seeded is no longer seedable (409) — or is
145
- // gone entirely (404) — rather than a 200 that implies a live node.
146
- case 'abandoned':
147
- return result.status
148
- ? errorResponse(reply, 'invalid_state', `Donation ${request.params.id} ended (${result.status}) while the seed was in flight`, 409)
149
- : errorResponse(reply, 'not_found', `No such donation: ${request.params.id}`, 404);
150
- case 'seeded':
151
- return reply.send({ ok: true, data: { peersAdded: result.peersAdded } });
152
- }
153
- });
154
- // DELETE /grants/:id — terminate the donated node.
155
- app.delete('/grants/:id', async (request, reply) => {
156
- const token = authenticate(request, reply, grants);
157
- if (!token)
158
- return reply;
159
- if (!requireOwned(donations, request.params.id, token, reply))
160
- return reply;
161
- await donations.terminate(request.params.id);
162
- return reply.send({ ok: true });
163
- });
164
- }
165
- //# sourceMappingURL=grants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grants.js","sourceRoot":"","sources":["../../../src/server/routes/grants.ts"],"names":[],"mappings":"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"}