@camstack/server 1.2.577 → 1.2.579

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.
package/dist/main.js CHANGED
@@ -59,7 +59,6 @@ const ws_1 = require("@trpc/server/adapters/ws");
59
59
  const ws_2 = require("ws");
60
60
  const addon_upload_1 = require("./api/addon-upload");
61
61
  const auth_whoami_1 = require("./api/auth-whoami");
62
- const enroll_routes_js_1 = require("./api/mesh/enroll.routes.js");
63
62
  const backup_download_js_1 = require("./api/backup-download.js");
64
63
  const cap_providers_1 = require("./api/core/cap-providers");
65
64
  const logging_settings_1 = require("./api/core/logging-settings");
@@ -99,6 +98,7 @@ const sso_finish_deps_js_1 = require("./auth/sso-finish-deps.js");
99
98
  const app_confirmations_js_1 = require("./auth/app-confirmations.js");
100
99
  const app_confirm_routes_js_1 = require("./auth/app-confirm-routes.js");
101
100
  const app_passkey_routes_js_1 = require("./auth/app-passkey-routes.js");
101
+ const app_refused_route_js_1 = require("./auth/app-refused-route.js");
102
102
  const passkey_provider_js_1 = require("./auth/passkey-provider.js");
103
103
  const second_factors_js_1 = require("./auth/second-factors.js");
104
104
  const sso_routes_js_1 = require("./auth/sso-routes.js");
@@ -474,7 +474,6 @@ async function bootstrap() {
474
474
  // Mirrors the addon-upload auth chain so the CLI can ping for
475
475
  // token-still-valid without consuming a real cap.
476
476
  await (0, auth_whoami_1.registerAuthWhoamiRoute)(fastify, uploadAuthService, uploadAddonRegistry);
477
- (0, enroll_routes_js_1.registerMeshEnrollRoute)(fastify);
478
477
  console.log('[bootstrap] Auth whoami route registered at GET /api/auth/whoami');
479
478
  }
480
479
  catch (err) {
@@ -981,6 +980,7 @@ async function bootstrap() {
981
980
  limiter: authRouteLimiter,
982
981
  logger: app.get(logging_service_1.LoggingService).createLogger('app-passkey'),
983
982
  });
983
+ (0, app_refused_route_js_1.registerAppRefusedRoute)(fastify);
984
984
  // The last step of both app flows: "Sign in the app as <user>?" - the
985
985
  // handoff code is minted only when the person presses Continue (D858).
986
986
  (0, app_confirm_routes_js_1.registerAppConfirmRoutes)(fastify, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.577",
3
+ "version": "1.2.579",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -38,19 +38,19 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@camstack/addon-admin-ui": "1.2.426",
42
- "@camstack/addon-agent-ui": "1.2.233",
43
- "@camstack/addon-auth": "1.2.145",
41
+ "@camstack/addon-admin-ui": "1.2.427",
42
+ "@camstack/addon-agent-ui": "1.2.234",
43
+ "@camstack/addon-auth": "1.2.146",
44
44
  "@camstack/addon-decoder-nodeav": "1.2.123",
45
45
  "@camstack/addon-notifiers": "1.2.134",
46
46
  "@camstack/addon-pipeline": "1.2.369",
47
- "@camstack/addon-pipeline-orchestrator": "1.2.322",
47
+ "@camstack/addon-pipeline-orchestrator": "1.2.323",
48
48
  "@camstack/addon-post-analysis": "1.2.369",
49
- "@camstack/develop": "0.1.10",
49
+ "@camstack/develop": "0.1.11",
50
50
  "@camstack/sdk": "1.2.131",
51
- "@camstack/system": "1.2.435",
52
- "@camstack/types": "1.2.371",
53
- "@camstack/ui-library": "1.2.289",
51
+ "@camstack/system": "1.2.436",
52
+ "@camstack/types": "1.2.372",
53
+ "@camstack/ui-library": "1.2.290",
54
54
  "@fastify/compress": "^9.0.0",
55
55
  "@fastify/cookie": "^11.0.2",
56
56
  "@fastify/cors": "^11.2.0",
@@ -1,317 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.MeshEnrollError = void 0;
37
- exports.decodeJoinTokenClaims = decodeJoinTokenClaims;
38
- exports.hubHostFromMeshAddress = hubHostFromMeshAddress;
39
- exports.enrollAgentMesh = enrollAgentMesh;
40
- exports.readJsonFile = readJsonFile;
41
- exports.writeJsonFile0600 = writeJsonFile0600;
42
- exports.persistEnrollment = persistEnrollment;
43
- exports.discardPendingJoinToken = discardPendingJoinToken;
44
- exports.enrollAndPersist = enrollAndPersist;
45
- /**
46
- * The agent's side of mesh mTLS enrollment (Release A).
47
- *
48
- * The operator mints a single-use join token on the hub (System → Cluster →
49
- * Add agent) and pastes it into this agent's setup. Redeeming it:
50
- *
51
- * 1. decode the token's claims WITHOUT trusting them (the agent holds no CA
52
- * key yet): `hubUrl`, `hubMeshAddress`, `caFingerprint`, optional `nodeId`;
53
- * 2. a token pinned to another node id is refused before anything is sent;
54
- * 3. generate this node's own key pair; the private key never leaves it;
55
- * 4. `POST <hubUrl>/api/mesh/enroll` with `{ token, csr }`. The hub's HTTPS
56
- * certificate is NOT the mesh CA, so the TLS peer is not what is pinned:
57
- * 5. the answer is trusted only if its CA certificate hashes to the token's
58
- * `caFingerprint` (a MITM with another CA fails here), the token's ES256
59
- * signature verifies against that CA, and the issued leaf is signed by it,
60
- * carries this node id as CN and matches the private key;
61
- * 6. only then are the material and the hub address persisted (`agent.json`,
62
- * 0600) — the dial port is NOT stored: with a certificate the broker dials
63
- * the TLS port, so the address keeps the host only.
64
- */
65
- const node_crypto_1 = require("node:crypto");
66
- const fs = __importStar(require("node:fs"));
67
- const http = __importStar(require("node:http"));
68
- const https = __importStar(require("node:https"));
69
- const system_1 = require("@camstack/system");
70
- const node_1 = require("@camstack/types/node");
71
- class MeshEnrollError extends Error {
72
- constructor(message) {
73
- super(message);
74
- this.name = 'MeshEnrollError';
75
- }
76
- }
77
- exports.MeshEnrollError = MeshEnrollError;
78
- const ENROLL_PATH = '/api/mesh/enroll';
79
- const ENROLL_TIMEOUT_MS = 15_000;
80
- function base64urlJson(segment) {
81
- return JSON.parse(Buffer.from(segment, 'base64url').toString('utf8'));
82
- }
83
- /** Decode the payload of a compact JWS. Untrusted: nothing here is verified. */
84
- function decodeJoinTokenClaims(token) {
85
- const parts = token.trim().split('.');
86
- if (parts.length !== 3 || parts[1] === undefined) {
87
- throw new MeshEnrollError('join token is malformed (expected a compact JWT)');
88
- }
89
- let payload;
90
- try {
91
- payload = base64urlJson(parts[1]);
92
- }
93
- catch {
94
- throw new MeshEnrollError('join token payload is not valid JSON');
95
- }
96
- if (payload === null || typeof payload !== 'object') {
97
- throw new MeshEnrollError('join token payload is not an object');
98
- }
99
- const hubUrl = Reflect.get(payload, 'hubUrl');
100
- const hubMeshAddress = Reflect.get(payload, 'hubMeshAddress');
101
- const caFingerprint = Reflect.get(payload, 'caFingerprint');
102
- const nodeId = Reflect.get(payload, 'nodeId');
103
- const exp = Reflect.get(payload, 'exp');
104
- if (typeof hubUrl !== 'string' || hubUrl.length === 0) {
105
- throw new MeshEnrollError('join token carries no hubUrl');
106
- }
107
- if (typeof hubMeshAddress !== 'string' || hubMeshAddress.length === 0) {
108
- throw new MeshEnrollError('join token carries no hubMeshAddress');
109
- }
110
- if (typeof caFingerprint !== 'string' || caFingerprint.length === 0) {
111
- throw new MeshEnrollError('join token carries no caFingerprint');
112
- }
113
- if (typeof exp !== 'number')
114
- throw new MeshEnrollError('join token carries no expiry');
115
- return {
116
- hubUrl,
117
- hubMeshAddress,
118
- caFingerprint: normalizeFingerprint(caFingerprint),
119
- ...(typeof nodeId === 'string' && nodeId.length > 0 ? { nodeId } : {}),
120
- exp,
121
- };
122
- }
123
- function normalizeFingerprint(fp) {
124
- return fp.replace(/:/g, '').toLowerCase();
125
- }
126
- /** `host:6000` / `[::1]:6000` / `host` → the host, port dropped. */
127
- function hubHostFromMeshAddress(address) {
128
- const trimmed = address.trim();
129
- const bracketed = /^(\[[^\]]+\])(?::\d+)?$/.exec(trimmed);
130
- if (bracketed?.[1] !== undefined)
131
- return bracketed[1];
132
- const hostPort = /^([^:]+):\d+$/.exec(trimmed);
133
- return hostPort?.[1] ?? trimmed;
134
- }
135
- function compactFingerprint(certPem) {
136
- return (0, node_crypto_1.createHash)('sha256').update(new node_crypto_1.X509Certificate(certPem).raw).digest('hex');
137
- }
138
- /** ES256 compact-JWS signature check against the CA certificate's public key. */
139
- function tokenSignatureValid(token, caCertPem) {
140
- const parts = token.trim().split('.');
141
- if (parts.length !== 3)
142
- return false;
143
- const [header, payload, signature] = parts;
144
- if (header === undefined || payload === undefined || signature === undefined)
145
- return false;
146
- try {
147
- return (0, node_crypto_1.verify)('sha256', Buffer.from(`${header}.${payload}`), { key: new node_crypto_1.X509Certificate(caCertPem).publicKey, dsaEncoding: 'ieee-p1363' }, Buffer.from(signature, 'base64url'));
148
- }
149
- catch {
150
- return false;
151
- }
152
- }
153
- /** Node `http(s)` POST. The hub's HTTPS leaf is not the mesh CA, so the CA pin below is the trust. */
154
- const nodeEnrollFetch = (url, init) => new Promise((resolve, reject) => {
155
- const target = new URL(url);
156
- const request = (target.protocol === 'http:' ? http : https).request(target, {
157
- method: init.method,
158
- headers: { ...init.headers, 'content-length': Buffer.byteLength(init.body) },
159
- timeout: ENROLL_TIMEOUT_MS,
160
- ...(target.protocol === 'https:' ? { rejectUnauthorized: false } : {}),
161
- }, (res) => {
162
- const chunks = [];
163
- res.on('data', (chunk) => chunks.push(chunk));
164
- res.on('end', () => {
165
- const raw = Buffer.concat(chunks).toString('utf8');
166
- resolve({
167
- status: res.statusCode ?? 0,
168
- json: () => Promise.resolve(JSON.parse(raw)),
169
- });
170
- });
171
- res.on('error', reject);
172
- });
173
- request.on('timeout', () => request.destroy(new Error(`enroll request timed out`)));
174
- request.on('error', reject);
175
- request.end(init.body);
176
- });
177
- function readAnswer(status, body) {
178
- const record = body !== null && typeof body === 'object' ? body : {};
179
- if (status !== 200 || Reflect.get(record, 'ok') !== true) {
180
- const error = Reflect.get(record, 'error');
181
- throw new MeshEnrollError(`hub refused the enrollment (HTTP ${status}): ${typeof error === 'string' ? error : 'no reason given'}`);
182
- }
183
- const cert = Reflect.get(record, 'cert');
184
- const caCert = Reflect.get(record, 'caCert');
185
- if (typeof cert !== 'string' || typeof caCert !== 'string') {
186
- throw new MeshEnrollError('hub answered without a certificate and CA certificate');
187
- }
188
- return { cert, caCert };
189
- }
190
- /**
191
- * Redeem a join token. Throws {@link MeshEnrollError} with an operator-readable
192
- * reason; nothing is persisted here.
193
- */
194
- async function enrollAgentMesh(input) {
195
- const token = input.token.trim();
196
- const claims = decodeJoinTokenClaims(token);
197
- const nowSec = input.nowSec ?? Math.floor(Date.now() / 1000);
198
- if (claims.exp <= nowSec) {
199
- throw new MeshEnrollError('join token has expired — mint a new one on the hub');
200
- }
201
- if (claims.nodeId !== undefined && claims.nodeId !== input.nodeId) {
202
- throw new MeshEnrollError(`join token is for node "${claims.nodeId}", but this agent is "${input.nodeId}"`);
203
- }
204
- const keys = await (0, system_1.generateMeshKeyPair)();
205
- const csr = await (0, system_1.createMeshCsrPem)(keys, input.nodeId);
206
- const keyPem = await (0, system_1.exportMeshPrivateKeyPem)(keys.privateKey);
207
- const url = `${claims.hubUrl.replace(/\/+$/, '')}${ENROLL_PATH}`;
208
- const fetchFn = input.fetchFn ?? nodeEnrollFetch;
209
- let response;
210
- try {
211
- response = await fetchFn(url, {
212
- method: 'POST',
213
- headers: { 'content-type': 'application/json' },
214
- body: JSON.stringify({ token, csr }),
215
- });
216
- }
217
- catch (err) {
218
- throw new MeshEnrollError(`could not reach the hub at ${claims.hubUrl}: ${err instanceof Error ? err.message : String(err)}`);
219
- }
220
- let body;
221
- try {
222
- body = await response.json();
223
- }
224
- catch {
225
- body = null;
226
- }
227
- const answer = readAnswer(response.status, body);
228
- // Trust nothing the hub said until it proves it holds the CA the token names.
229
- let fingerprint;
230
- try {
231
- fingerprint = compactFingerprint(answer.caCert);
232
- }
233
- catch {
234
- throw new MeshEnrollError('hub returned an unreadable CA certificate');
235
- }
236
- if (fingerprint !== claims.caFingerprint) {
237
- throw new MeshEnrollError('the CA certificate returned by the hub does not match the token fingerprint — ' +
238
- 'enrollment aborted (is something between this agent and the hub?)');
239
- }
240
- if (!tokenSignatureValid(token, answer.caCert)) {
241
- throw new MeshEnrollError('join token signature does not verify against the hub CA');
242
- }
243
- assertLeafIsOurs(answer, keyPem, input.nodeId);
244
- return {
245
- meshTls: { certPem: answer.cert, keyPem, caCertPem: answer.caCert },
246
- hubAddress: hubHostFromMeshAddress(claims.hubMeshAddress),
247
- claims,
248
- };
249
- }
250
- function assertLeafIsOurs(answer, keyPem, nodeId) {
251
- let leaf;
252
- try {
253
- leaf = new node_crypto_1.X509Certificate(answer.cert);
254
- }
255
- catch {
256
- throw new MeshEnrollError('hub returned an unreadable node certificate');
257
- }
258
- const ca = new node_crypto_1.X509Certificate(answer.caCert);
259
- if (!leaf.verify(ca.publicKey)) {
260
- throw new MeshEnrollError('the node certificate is not signed by the hub CA');
261
- }
262
- if (!new RegExp(`(?:^|\\n)CN=${escapeRegExp(nodeId)}(?:\\n|$)`).test(leaf.subject)) {
263
- throw new MeshEnrollError(`the node certificate is not issued for "${nodeId}"`);
264
- }
265
- if (!leaf.checkPrivateKey((0, node_crypto_1.createPrivateKey)(keyPem))) {
266
- throw new MeshEnrollError('the node certificate does not match this agent’s private key');
267
- }
268
- }
269
- function escapeRegExp(value) {
270
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
271
- }
272
- function readJsonFile(file) {
273
- if (!fs.existsSync(file))
274
- return {};
275
- try {
276
- const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
277
- return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
278
- ? Object.fromEntries(Object.entries(parsed))
279
- : {};
280
- }
281
- catch {
282
- return {};
283
- }
284
- }
285
- function withoutJoinToken(record) {
286
- return Object.fromEntries(Object.entries(record).filter(([key]) => key !== 'joinToken'));
287
- }
288
- /** Owner-only on every OS (an owner+SYSTEM ACL on Windows, where 0600 is a no-op). */
289
- function writeJsonFile0600(file, data) {
290
- (0, node_1.writeOwnerOnlyFileSync)(file, JSON.stringify(data, null, 2));
291
- }
292
- /**
293
- * Write the enrollment into `agent.json` (0600): `meshTls` + the host-only
294
- * `hubAddress`; a pending `joinToken` is removed. The cluster secret, if any,
295
- * stays — it is still a valid join proof for one release.
296
- */
297
- function persistEnrollment(configPath, enrolled) {
298
- const next = {
299
- ...withoutJoinToken(readJsonFile(configPath)),
300
- hubAddress: enrolled.hubAddress,
301
- meshTls: enrolled.meshTls,
302
- };
303
- writeJsonFile0600(configPath, next);
304
- }
305
- /** Drop a pending `joinToken` from `agent.json` (consumed — or dead). */
306
- function discardPendingJoinToken(configPath) {
307
- const existing = readJsonFile(configPath);
308
- if (!('joinToken' in existing))
309
- return;
310
- writeJsonFile0600(configPath, withoutJoinToken(existing));
311
- }
312
- /** Redeem `token` and persist the result. Throws {@link MeshEnrollError}. */
313
- async function enrollAndPersist(input) {
314
- const enrolled = await enrollAgentMesh(input);
315
- persistEnrollment(input.configPath, enrolled);
316
- return enrolled;
317
- }
@@ -1,140 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAgentMeshJoin = createAgentMeshJoin;
4
- /**
5
- * The agent's mesh join (D802): authenticate to the hub on every connection,
6
- * before anything else reaches it, and check that the hub is the hub — plus
7
- * the agent's inbound mesh gate so only that proven hub (and this agent's own
8
- * registered children) may call it.
9
- *
10
- * `MeshJoinClient` (in `@camstack/system`) holds the outbound protocol and the
11
- * call gate; `AgentMeshAuthenticator` + `createMeshAccessMiddleware` hold the
12
- * inbound side. This file binds both to the agent's broker lifecycle:
13
- * - the hub connects → a join runs; a settled mutual proof stamps the hub;
14
- * - the hub disconnects → back to `pending` (calls wait for the next join)
15
- * and the hub is forgotten on the inbound gate;
16
- * - any other node disconnects → forgotten on the inbound gate;
17
- * - the hub says "not authenticated" (it restarted and lost us) → join again;
18
- * - the join settles `unavailable` (the hub cannot read its secret yet) →
19
- * retried every {@link REJOIN_INTERVAL_MS} while connected.
20
- */
21
- const system_1 = require("@camstack/system");
22
- const REJOIN_INTERVAL_MS = 15_000;
23
- const HUB_NODE_ID = 'hub';
24
- function nodeIdOf(data) {
25
- if (data === null || typeof data !== 'object')
26
- return undefined;
27
- const node = Reflect.get(data, 'node');
28
- if (node === null || typeof node !== 'object')
29
- return undefined;
30
- const id = Reflect.get(node, 'id');
31
- return typeof id === 'string' ? id : undefined;
32
- }
33
- function createAgentMeshJoin(opts) {
34
- let connected = false;
35
- let rejoinTimer = null;
36
- const clearRejoin = () => {
37
- if (rejoinTimer !== null)
38
- clearTimeout(rejoinTimer);
39
- rejoinTimer = null;
40
- };
41
- const authenticator = new system_1.AgentMeshAuthenticator({
42
- ownNodeId: opts.nodeId,
43
- hubNodeId: HUB_NODE_ID,
44
- ...(opts.isVerifiedPeer !== undefined ? { isVerifiedPeer: opts.isVerifiedPeer } : {}),
45
- ...(opts.isPaired !== undefined ? { isPaired: opts.isPaired } : {}),
46
- hasSecret: () => {
47
- const s = opts.secret();
48
- return s !== null && s.length > 0;
49
- },
50
- onTolerated: (nodeId, action) => {
51
- opts.log?.(`mesh inbound: node "${nodeId}" is calling without having authenticated — ` +
52
- `tolerated while this agent has no cluster secret (first call: ${action})`);
53
- },
54
- // D848: what the hub sent before the join settled is held, then decided — said here.
55
- onHoldReport: (r) => {
56
- const what = `${r.events} hub event(s) and ${r.calls} hub call(s) held ${r.heldForMs} ms before the mesh join settled`;
57
- const overflow = r.overflow > 0 ? `; ${r.overflow} more refused because the hold was full` : '';
58
- opts.log?.(r.outcome === 'delivered'
59
- ? `[mesh-join] ${what}: delivered${overflow}`
60
- : `[mesh-join] ${what}: REFUSED (join ${r.reason})${overflow}`);
61
- },
62
- });
63
- const client = new system_1.MeshJoinClient({
64
- nodeId: opts.nodeId,
65
- secret: opts.secret,
66
- call: opts.call,
67
- hubNodeId: HUB_NODE_ID,
68
- ...(opts.log !== undefined ? { log: opts.log } : {}),
69
- onSettled: (state) => {
70
- authenticator.noteHubJoinState(state);
71
- opts.onState?.(state);
72
- clearRejoin();
73
- if (state === 'unavailable' && connected) {
74
- rejoinTimer = setTimeout(() => void runJoin(), REJOIN_INTERVAL_MS);
75
- rejoinTimer.unref?.();
76
- }
77
- },
78
- });
79
- const runJoin = async () => {
80
- if (!connected)
81
- return;
82
- await client.join();
83
- };
84
- const rejoin = () => {
85
- client.reset();
86
- authenticator.noteHubJoinState('pending');
87
- void runJoin();
88
- };
89
- const joinMiddleware = client.middleware(rejoin);
90
- const eventScope = (0, system_1.createAgentEventScope)({
91
- hubNodeId: HUB_NODE_ID,
92
- ...(opts.log !== undefined ? { log: opts.log } : {}),
93
- });
94
- const inboundMiddleware = (0, system_1.createMeshAccessMiddleware)({
95
- ownNodeId: opts.nodeId,
96
- authenticator,
97
- logger: {
98
- warn: (message, meta) => {
99
- const detail = meta === undefined
100
- ? ''
101
- : ` ${Object.entries(meta)
102
- .map(([k, v]) => `${k}=${String(v)}`)
103
- .join(' ')}`;
104
- opts.log?.(`${message}${detail}`);
105
- },
106
- },
107
- });
108
- return {
109
- client,
110
- authenticator,
111
- middleware: joinMiddleware,
112
- eventScope,
113
- middlewares: [inboundMiddleware, joinMiddleware, eventScope.middleware],
114
- rejoin() {
115
- if (connected)
116
- rejoin();
117
- },
118
- attach(broker) {
119
- broker.localBus.on('$node.connected', (data) => {
120
- if (nodeIdOf(data) !== HUB_NODE_ID)
121
- return;
122
- connected = true;
123
- rejoin();
124
- });
125
- broker.localBus.on('$node.disconnected', (data) => {
126
- const id = nodeIdOf(data);
127
- if (id === undefined)
128
- return;
129
- authenticator.forget(id);
130
- if (id !== HUB_NODE_ID)
131
- return;
132
- connected = false;
133
- clearRejoin();
134
- client.reset();
135
- authenticator.noteHubJoinState('pending');
136
- });
137
- },
138
- stop: clearRejoin,
139
- };
140
- }
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerMeshEnrollRoute = registerMeshEnrollRoute;
4
- const system_1 = require("@camstack/system");
5
- function registerMeshEnrollRoute(fastify) {
6
- fastify.post('/api/mesh/enroll', async (request, reply) => {
7
- const caState = (0, system_1.getProcessMeshCaState)();
8
- if (caState.state !== 'ready') {
9
- return reply.status(503).send({
10
- ok: false,
11
- error: `mesh CA unavailable: ${caState.reason}`,
12
- });
13
- }
14
- const body = request.body ?? {};
15
- const result = await (0, system_1.enrollMeshNode)({ token: body.token ?? '', csr: body.csr ?? '' }, {
16
- ca: caState.ca,
17
- onRecordFailed: (nodeId, error) => fastify.log.warn({ nodeId, error: error instanceof Error ? error.message : String(error) }, 'mesh enroll: the issued leaf could not be recorded'),
18
- onIssued: async (nodeId, certPem) => {
19
- await (0, system_1.getProcessMeshCertificateRegistry)()?.recordIssued(nodeId, certPem, 'enroll');
20
- },
21
- });
22
- if (!result.ok) {
23
- return reply.status(result.status).send({ ok: false, error: result.error });
24
- }
25
- return reply.send({
26
- ok: true,
27
- cert: result.cert,
28
- caCert: result.caCert,
29
- nodeId: result.nodeId,
30
- notAfter: result.notAfter,
31
- serialNumber: result.serialNumber,
32
- });
33
- });
34
- }