@serfab/cadre-host 0.12.0 → 0.13.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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The whole shape check for `POST /grants` — one call that either yields the
3
+ * typed {@link DonationProvisionRequest} the `DonationService` is handed, or the
4
+ * message the route answers 400 with.
5
+ *
6
+ * **Why a validator rather than a few more `if`s in the route.** Every field in
7
+ * this body is forwarded into a child process the requester cannot see:
8
+ * `partyId`, `bootstrapNodes` and `profile` are written into the child's
9
+ * `cadre.json` (`orchestrator/host-process-orchestrator.ts`) and `ownerKeys`
10
+ * become its cold-start seed-trust anchors. A field the route forgets to check is
11
+ * not a 400 — it is a donated node that dies at boot, or comes up subtly wrong,
12
+ * with a 201 already sent. Routing the body through one function that *constructs*
13
+ * the request object means a field can only reach the spawn path by having been
14
+ * through here; adding a field to `DonationProvisionRequest` without validating it
15
+ * stops compiling rather than silently shipping.
16
+ *
17
+ * **Division of labour with `DonationService.provision`.** The Ed25519 rule for
18
+ * `ownerKeys` stays in the service, where it is applied by cadre-core's own
19
+ * `requireEd25519PublicKeyB64` — the service is also reached by the respawn path,
20
+ * which replays a persisted record rather than an HTTP body, so the crypto rule
21
+ * has to live there to cover both. What this validator owns for `ownerKeys` is the
22
+ * *container* shape (present, non-empty, all strings), which the service's rule
23
+ * assumes: a number reaching it surfaces as an internal `.trim is not a function`
24
+ * rather than as a message naming what is wrong with the request.
25
+ *
26
+ * The multiaddr rule has no such home — it is duplicated in cadre-provider — so it
27
+ * lives in `bootstrap-node-validation.ts` next to the comment saying so.
28
+ */
29
+ import { validateBootstrapNodes } from './bootstrap-node-validation.js';
30
+ /** `partyId` becomes `controlNetwork.partyId` in the child's config. */
31
+ function validatePartyId(value) {
32
+ if (typeof value !== 'string' || value.trim().length === 0) {
33
+ return { error: 'partyId is required' };
34
+ }
35
+ return { partyId: value.trim() };
36
+ }
37
+ /**
38
+ * Container shape only — see the module comment: the per-key Ed25519 rule is
39
+ * `DonationService.provision`'s, so the respawn path gets it too.
40
+ */
41
+ function validateOwnerKeys(value) {
42
+ if (!Array.isArray(value) || value.length === 0) {
43
+ return { error: 'ownerKeys is required' };
44
+ }
45
+ if (!value.every((key) => typeof key === 'string')) {
46
+ return { error: 'ownerKeys must be an array of strings' };
47
+ }
48
+ return { ownerKeys: value };
49
+ }
50
+ /** Absent means `storage` — see {@link DonationProvisionRequest.profile}. */
51
+ function validateProfile(value) {
52
+ if (value === undefined)
53
+ return {};
54
+ if (value !== 'storage' && value !== 'transaction') {
55
+ return { error: 'profile must be "storage" or "transaction"' };
56
+ }
57
+ return { profile: value };
58
+ }
59
+ /**
60
+ * Validate a `POST /grants` body and build the request `DonationService.provision`
61
+ * is handed.
62
+ *
63
+ * `grantToken` comes from the `Authorization` bearer rather than the body — a
64
+ * requester does not get to name which grant pays for this — so it is a
65
+ * parameter, not a validated field. Whether that grant is live and within quota is
66
+ * `provision`'s call, not this function's.
67
+ *
68
+ * @returns the typed provision request, or the message to answer `invalid_request` with.
69
+ */
70
+ export function validateProvisionRequest(body, grantToken) {
71
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
72
+ return { error: 'request body must be a JSON object' };
73
+ }
74
+ const fields = body;
75
+ const partyId = validatePartyId(fields.partyId);
76
+ if ('error' in partyId)
77
+ return partyId;
78
+ const bootstrap = validateBootstrapNodes(fields.bootstrapNodes);
79
+ if ('error' in bootstrap)
80
+ return bootstrap;
81
+ const ownerKeys = validateOwnerKeys(fields.ownerKeys);
82
+ if ('error' in ownerKeys)
83
+ return ownerKeys;
84
+ const profile = validateProfile(fields.profile);
85
+ if ('error' in profile)
86
+ return profile;
87
+ return {
88
+ request: {
89
+ grantToken,
90
+ partyId: partyId.partyId,
91
+ bootstrapNodes: bootstrap.nodes,
92
+ ownerKeys: ownerKeys.ownerKeys,
93
+ ...(profile.profile ? { profile: profile.profile } : {}),
94
+ },
95
+ };
96
+ }
97
+ //# sourceMappingURL=provision-request-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provision-request-validation.js","sourceRoot":"","sources":["../../../src/server/routes/provision-request-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAExE,wEAAwE;AACxE,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;IAC5C,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,EAAE,CAAC;QAClE,OAAO,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC;IAC5D,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,6EAA6E;AAC7E,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QACnD,OAAO,EAAE,KAAK,EAAE,4CAA4C,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAa,EACb,UAAkB;IAElB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAE/C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,OAAO,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAEvC,MAAM,SAAS,GAAG,sBAAsB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAChE,IAAI,OAAO,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtD,IAAI,OAAO,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,OAAO,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAEvC,OAAO;QACL,OAAO,EAAE;YACP,UAAU;YACV,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,cAAc,EAAE,SAAS,CAAC,KAAK;YAC/B,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzD;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serfab/cadre-host",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted cadre node manager for basement-PC deployments",
6
6
  "main": "dist/index.js",
@@ -64,9 +64,10 @@
64
64
  "@libp2p/crypto": "^5.1.13",
65
65
  "@libp2p/interface": "^3.1.0",
66
66
  "@libp2p/peer-id": "^6.0.4",
67
- "@serfab/cadre-cli": "^0.12.0",
68
- "@serfab/cadre-core": "^0.12.0",
69
- "@serfab/cadre-provider": "^0.12.0",
67
+ "@multiformats/multiaddr": "^12.5.1",
68
+ "@serfab/cadre-cli": "^0.13.0",
69
+ "@serfab/cadre-core": "^0.13.0",
70
+ "@serfab/cadre-provider": "^0.13.0",
70
71
  "commander": "^14.0.2",
71
72
  "debug": "^4.4.3",
72
73
  "fastify": "^5.7.1",