@serfab/cadre-host 0.7.1 → 0.8.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.
- package/README.md +296 -101
- package/dist/auth/trust-circle.d.ts +44 -6
- package/dist/auth/trust-circle.js +115 -58
- package/dist/auth/trust-circle.js.map +1 -1
- package/dist/auth/types.d.ts +4 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/authority/authority-node-client.d.ts +77 -0
- package/dist/authority/authority-node-client.js +143 -0
- package/dist/authority/authority-node-client.js.map +1 -0
- package/dist/authority/index.d.ts +11 -0
- package/dist/authority/index.js +11 -0
- package/dist/authority/index.js.map +1 -0
- package/dist/bin/host.js +210 -97
- package/dist/bin/host.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/installer/browser.js +16 -17
- package/dist/installer/browser.js.map +1 -1
- package/dist/installer/config.d.ts +25 -0
- package/dist/installer/config.js +1 -1
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +5 -2
- package/dist/installer/index.js +14 -10
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/service-host/launchd.js +1 -1
- package/dist/installer/service-host/launchd.js.map +1 -1
- package/dist/installer/service-host/systemd.js +1 -1
- package/dist/installer/service-host/systemd.js.map +1 -1
- package/dist/nat/index.d.ts +1 -1
- package/dist/nat/nat-service.d.ts +45 -3
- package/dist/nat/nat-service.js +116 -3
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/secrets/index.d.ts +7 -0
- package/dist/nat/secrets/index.js +28 -2
- package/dist/nat/secrets/index.js.map +1 -1
- package/dist/nat/types.d.ts +3 -1
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +60 -1
- package/dist/orchestrator/host-process-orchestrator.js +246 -39
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/orchestrator/index.d.ts +3 -2
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +6 -5
- package/dist/orchestrator/state-store.js +10 -2
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +45 -10
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/push/index.d.ts +67 -0
- package/dist/push/index.js +101 -0
- package/dist/push/index.js.map +1 -0
- package/dist/server/error-handler.d.ts +2 -0
- package/dist/server/error-handler.js +4 -0
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +4 -3
- package/dist/server/routes/nodes.js +38 -24
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/static.d.ts +12 -0
- package/dist/server/static.js +24 -7
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-BlMXQPSw.js +14 -0
- package/dist/ui/assets/{index-C9uChk2P.js.map → index-BlMXQPSw.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/update/apply.d.ts +23 -1
- package/dist/update/apply.js +46 -4
- package/dist/update/apply.js.map +1 -1
- package/dist/update/index.d.ts +14 -3
- package/dist/update/index.js +35 -2
- package/dist/update/index.js.map +1 -1
- package/dist/update/manifest.d.ts +28 -9
- package/dist/update/manifest.js +56 -29
- package/dist/update/manifest.js.map +1 -1
- package/dist/update/release-key.d.ts +18 -7
- package/dist/update/release-key.js +28 -15
- package/dist/update/release-key.js.map +1 -1
- package/dist/update/sign.d.ts +46 -0
- package/dist/update/sign.js +53 -0
- package/dist/update/sign.js.map +1 -0
- package/package.json +9 -4
- package/service/README.md +95 -86
- package/src/__tests__/cli-invite.smoke.test.ts +6 -1
- package/src/__tests__/cli-nat.smoke.test.ts +6 -1
- package/src/__tests__/cli.smoke.test.ts +6 -1
- package/src/__tests__/host-authority.smoke.test.ts +123 -0
- package/src/__tests__/orchestrator-authority.test.ts +198 -0
- package/src/__tests__/orchestrator-push.test.ts +145 -0
- package/src/auth/__tests__/trust-circle-integration.test.ts +40 -21
- package/src/auth/__tests__/trust-circle.test.ts +90 -23
- package/src/auth/trust-circle.ts +363 -304
- package/src/auth/types.ts +4 -1
- package/src/authority/__tests__/authority-node-client.test.ts +166 -0
- package/src/authority/authority-node-client.ts +199 -0
- package/src/authority/index.ts +12 -0
- package/src/bin/host.ts +1038 -905
- package/src/index.ts +34 -3
- package/src/installer/__tests__/installer.smoke.test.ts +64 -0
- package/src/installer/browser.ts +40 -42
- package/src/installer/config.ts +27 -1
- package/src/installer/index.ts +18 -12
- package/src/installer/service-host/launchd.ts +1 -1
- package/src/installer/service-host/systemd.ts +1 -1
- package/src/nat/__tests__/ddns-updater.test.ts +1 -1
- package/src/nat/__tests__/nat-service.test.ts +192 -0
- package/src/nat/__tests__/port-mapper.test.ts +1 -1
- package/src/nat/__tests__/secrets.test.ts +23 -1
- package/src/nat/index.ts +1 -1
- package/src/nat/nat-service.ts +131 -5
- package/src/nat/secrets/index.ts +32 -2
- package/src/nat/types.ts +3 -1
- package/src/orchestrator/host-process-orchestrator.ts +294 -54
- package/src/orchestrator/index.ts +9 -2
- package/src/orchestrator/state-store.ts +21 -3
- package/src/orchestrator/types.ts +48 -2
- package/src/push/__tests__/push.test.ts +90 -0
- package/src/push/index.ts +146 -0
- package/src/server/__tests__/error-handler.test.ts +4 -4
- package/src/server/__tests__/nodes-route.test.ts +90 -10
- package/src/server/__tests__/publishers.test.ts +1 -1
- package/src/server/__tests__/static.test.ts +145 -0
- package/src/server/__tests__/status-route.test.ts +2 -2
- package/src/server/error-handler.ts +4 -0
- package/src/server/routes/nodes.ts +48 -26
- package/src/server/static.ts +24 -8
- package/src/update/__tests__/apply.test.ts +58 -1
- package/src/update/__tests__/manifest.test.ts +41 -6
- package/src/update/__tests__/release-key.test.ts +29 -0
- package/src/update/__tests__/sign.test.ts +78 -0
- package/src/update/__tests__/update-service.integration.test.ts +44 -9
- package/src/update/apply.ts +46 -4
- package/src/update/index.ts +41 -3
- package/src/update/manifest.ts +214 -172
- package/src/update/release-key.ts +30 -16
- package/src/update/sign.ts +74 -0
- package/dist/ui/assets/index-C9uChk2P.js +0 -14
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import debug from 'debug';
|
|
3
|
+
import { AuthorityNodeUnavailableError } from '../authority/authority-node-client.js';
|
|
3
4
|
import { TrustCircleError } from './types.js';
|
|
4
5
|
const log = debug('cadre:host:trust-circle');
|
|
5
6
|
/** Default invite lifetime: 24 hours. */
|
|
@@ -10,11 +11,21 @@ const TOKEN_BYTES = 32;
|
|
|
10
11
|
* TrustCircleService — orchestrates invite issuance, redemption, and
|
|
11
12
|
* membership management.
|
|
12
13
|
*
|
|
13
|
-
* Membership is canonical in the cadre control DB (`CadrePeer` table)
|
|
14
|
-
*
|
|
14
|
+
* Membership is canonical in the cadre control DB (`CadrePeer` table),
|
|
15
|
+
* reached over the authority node's admin channel; labels and pending tokens
|
|
16
|
+
* live in the host-local `TrustCircleStore`. When the node is unreachable,
|
|
17
|
+
* authority operations surface `node_unavailable` (→ 503) and `list()`
|
|
18
|
+
* degrades to the local labels file.
|
|
15
19
|
*/
|
|
16
20
|
export class TrustCircleService {
|
|
17
21
|
constructor(opts) {
|
|
22
|
+
/**
|
|
23
|
+
* In-flight redemption claims (by token). Serialises concurrent redeems
|
|
24
|
+
* for the same token without burning the durable pending row before
|
|
25
|
+
* `acceptPhone` succeeds. Intra-process only — the orchestrator enforces
|
|
26
|
+
* single-process ownership of a given rootDir, so no file lock is needed.
|
|
27
|
+
*/
|
|
28
|
+
this.inFlightRedemptions = new Set();
|
|
18
29
|
this.cadreNode = opts.cadreNode;
|
|
19
30
|
this.store = opts.store;
|
|
20
31
|
this.now = opts.now ?? (() => new Date());
|
|
@@ -32,7 +43,9 @@ export class TrustCircleService {
|
|
|
32
43
|
const token = generateToken();
|
|
33
44
|
const created = this.now();
|
|
34
45
|
const expiresAt = new Date(created.getTime() + ttlMs);
|
|
35
|
-
const { invite, encodedInvite } = await this.cadreNode
|
|
46
|
+
const { invite, encodedInvite } = await this.cadreNode
|
|
47
|
+
.createInvite(token, ttlMs)
|
|
48
|
+
.catch((err) => this.toDomainError(err));
|
|
36
49
|
const pending = {
|
|
37
50
|
token,
|
|
38
51
|
label,
|
|
@@ -48,52 +61,79 @@ export class TrustCircleService {
|
|
|
48
61
|
* - cadre-core authorizes the peer (inserts CadrePeer row).
|
|
49
62
|
* - The pending row is consumed (one-time enforcement).
|
|
50
63
|
* - A labelled member row is written locally.
|
|
64
|
+
*
|
|
65
|
+
* Ordering: the synchronous *claim* against `inFlightRedemptions` serialises
|
|
66
|
+
* concurrent redeems for the same token (second concurrent caller rejects
|
|
67
|
+
* synchronously before its first await). The durable `removePending` only
|
|
68
|
+
* runs *after* `acceptPhone` succeeds — so a transient `node_unavailable`
|
|
69
|
+
* leaves the token re-redeemable rather than permanently burning it.
|
|
70
|
+
*
|
|
71
|
+
* Crash-safety tradeoff: if the host crashes between `acceptPhone` success
|
|
72
|
+
* and the durable `removePending`, a retry will dial in fine but the
|
|
73
|
+
* authority node's `acceptPhone` will reject the second `CadrePeer` insert
|
|
74
|
+
* with a PK constraint error (cadre-core does not upsert). That surfaces to
|
|
75
|
+
* the redeemer as a non-`node_unavailable` failure; the admin can revoke
|
|
76
|
+
* the lingering pending row. This is a narrower window than the prior
|
|
77
|
+
* behaviour, where *any* transient node outage burned the token.
|
|
51
78
|
*/
|
|
52
79
|
async redeemInvite(opts) {
|
|
53
80
|
const { token, peerId } = opts;
|
|
54
81
|
if (!token || !peerId) {
|
|
55
82
|
throw new TrustCircleError('invalid_token', 'token and peerId are required');
|
|
56
83
|
}
|
|
57
|
-
// Atomically claim the pending row before doing async work. This closes
|
|
58
|
-
// the race between two concurrent redemption requests for the same token —
|
|
59
|
-
// only the call that wins the synchronous removePending() proceeds; the
|
|
60
|
-
// other gets `already_redeemed`. The remove is durable (write-then-rename),
|
|
61
|
-
// so a crash after this point loses the token, which is the safe failure
|
|
62
|
-
// mode for a one-time credential.
|
|
63
84
|
const pending = this.store.getPending(token);
|
|
64
|
-
if (!pending
|
|
65
|
-
// Either never issued, or another concurrent caller just consumed it.
|
|
85
|
+
if (!pending) {
|
|
66
86
|
throw new TrustCircleError('already_redeemed', 'Invite not found or already redeemed');
|
|
67
87
|
}
|
|
68
88
|
if (pending.expiresAt) {
|
|
69
89
|
const expiresAt = new Date(pending.expiresAt);
|
|
70
90
|
if (Number.isFinite(expiresAt.getTime()) && this.now() > expiresAt) {
|
|
71
|
-
//
|
|
91
|
+
// Expiry is permanent — reap the pending row durably.
|
|
92
|
+
this.store.removePending(token);
|
|
72
93
|
throw new TrustCircleError('expired', 'Invite has expired');
|
|
73
94
|
}
|
|
74
95
|
}
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
// Synchronous in-memory claim. Closes the race between two concurrent
|
|
97
|
+
// redemption requests for the same token — the loser sees the token in
|
|
98
|
+
// the set and rejects synchronously before its first `await`.
|
|
99
|
+
if (this.inFlightRedemptions.has(token)) {
|
|
100
|
+
throw new TrustCircleError('already_redeemed', 'Invite not found or already redeemed');
|
|
101
|
+
}
|
|
102
|
+
this.inFlightRedemptions.add(token);
|
|
103
|
+
try {
|
|
104
|
+
// Reconstruct the CadreInvite that was originally issued. cadre-core's
|
|
105
|
+
// acceptPhone validates `issuedInvite.token === options.token` (already
|
|
106
|
+
// matches by construction) and re-checks expiration.
|
|
107
|
+
const reconstructed = {
|
|
108
|
+
partyId: '',
|
|
109
|
+
authorityAddrs: [],
|
|
110
|
+
token: pending.token,
|
|
111
|
+
createdAt: new Date(pending.createdAt).getTime(),
|
|
112
|
+
...(pending.expiresAt ? { expiresAt: new Date(pending.expiresAt).getTime() } : {}),
|
|
113
|
+
};
|
|
114
|
+
await this.cadreNode
|
|
115
|
+
.acceptPhone({ phonePeerId: peerId, token }, reconstructed)
|
|
116
|
+
.catch((err) => this.toDomainError(err));
|
|
117
|
+
// acceptPhone succeeded → durably consume the pending row. If we crash
|
|
118
|
+
// between here and the next line, the token stays pending (re-redeem
|
|
119
|
+
// would fail at acceptPhone with a duplicate-CadrePeer error, not
|
|
120
|
+
// node_unavailable, and the admin can revoke).
|
|
121
|
+
this.store.removePending(token);
|
|
122
|
+
// If the member write fails the peer is still authorized in CadrePeer —
|
|
123
|
+
// the next call to list() will pick it up unlabeled, which is the
|
|
124
|
+
// documented graceful degradation.
|
|
125
|
+
const member = {
|
|
126
|
+
peerId,
|
|
127
|
+
label: pending.label,
|
|
128
|
+
addedAt: this.now().toISOString(),
|
|
129
|
+
};
|
|
130
|
+
this.store.addMember(member);
|
|
131
|
+
log('redeemed invite for "%s" (peerId=%s, token=%s)', pending.label, peerId, token);
|
|
132
|
+
return { peerId, label: pending.label };
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
this.inFlightRedemptions.delete(token);
|
|
136
|
+
}
|
|
97
137
|
}
|
|
98
138
|
/** Revoke a pending invite by token. No-op if not found. */
|
|
99
139
|
async revokePending(token) {
|
|
@@ -113,7 +153,7 @@ export class TrustCircleService {
|
|
|
113
153
|
throw new TrustCircleError('not_found', `Unknown member: ${peerId}`);
|
|
114
154
|
}
|
|
115
155
|
if (inControl) {
|
|
116
|
-
await this.cadreNode.removePeer(peerId);
|
|
156
|
+
await this.cadreNode.removePeer(peerId).catch((err) => this.toDomainError(err));
|
|
117
157
|
}
|
|
118
158
|
if (inLocal) {
|
|
119
159
|
this.store.removeMember(peerId);
|
|
@@ -122,19 +162,18 @@ export class TrustCircleService {
|
|
|
122
162
|
}
|
|
123
163
|
/** Whether the peer is present in the cadre's CadrePeer table. */
|
|
124
164
|
async isMember(peerId) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
return false;
|
|
165
|
+
try {
|
|
166
|
+
return await this.cadreNode.isMember(peerId);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
this.toDomainError(err);
|
|
170
|
+
}
|
|
134
171
|
}
|
|
135
172
|
/**
|
|
136
|
-
* UI snapshot. Joins the canonical CadrePeer list
|
|
137
|
-
* prunes any orphan labels (peer is no longer
|
|
173
|
+
* UI snapshot. Joins the canonical CadrePeer list (fetched over the admin
|
|
174
|
+
* channel) with local labels; prunes any orphan labels (peer is no longer
|
|
175
|
+
* authorised). When the authority node is unreachable, degrades to the
|
|
176
|
+
* local labels file so listing keeps working while the node is down.
|
|
138
177
|
*/
|
|
139
178
|
async list() {
|
|
140
179
|
const labels = new Map();
|
|
@@ -142,12 +181,21 @@ export class TrustCircleService {
|
|
|
142
181
|
labels.set(m.peerId, { label: m.label, addedAt: m.addedAt, ...(m.self ? { self: true } : {}) });
|
|
143
182
|
}
|
|
144
183
|
const members = [];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
// Consult the control DB over the admin channel. A node-unavailable error
|
|
185
|
+
// is non-fatal — fall back to the local labels rather than 503-ing a
|
|
186
|
+
// read-only listing.
|
|
187
|
+
let controlMembers;
|
|
188
|
+
try {
|
|
189
|
+
controlMembers = await this.cadreNode.listMembers();
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
if (!(err instanceof AuthorityNodeUnavailableError))
|
|
193
|
+
throw err;
|
|
194
|
+
controlMembers = null;
|
|
195
|
+
}
|
|
196
|
+
if (controlMembers) {
|
|
197
|
+
const seenPeerIds = new Set();
|
|
198
|
+
for (const { peerId } of controlMembers) {
|
|
151
199
|
seenPeerIds.add(peerId);
|
|
152
200
|
const label = labels.get(peerId);
|
|
153
201
|
members.push({
|
|
@@ -157,11 +205,9 @@ export class TrustCircleService {
|
|
|
157
205
|
...(label?.self ? { self: true } : {}),
|
|
158
206
|
});
|
|
159
207
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// we'd wipe labels for a transient connectivity issue.
|
|
164
|
-
if (db) {
|
|
208
|
+
// Prune labels for peers that no longer exist in CadrePeer. Only when we
|
|
209
|
+
// successfully consulted the node — otherwise a transient outage would
|
|
210
|
+
// wipe labels.
|
|
165
211
|
for (const peerId of labels.keys()) {
|
|
166
212
|
if (!seenPeerIds.has(peerId)) {
|
|
167
213
|
this.store.removeMember(peerId);
|
|
@@ -169,7 +215,7 @@ export class TrustCircleService {
|
|
|
169
215
|
}
|
|
170
216
|
}
|
|
171
217
|
else {
|
|
172
|
-
//
|
|
218
|
+
// Node down: return labels as-is (degradation; no pruning).
|
|
173
219
|
for (const [peerId, row] of labels) {
|
|
174
220
|
members.push({ peerId, ...row });
|
|
175
221
|
}
|
|
@@ -180,6 +226,17 @@ export class TrustCircleService {
|
|
|
180
226
|
pending.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
181
227
|
return { members, pending };
|
|
182
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Translate an admin-channel transport failure into a `node_unavailable`
|
|
231
|
+
* TrustCircleError (→ 503). Re-throws anything else unchanged. Returns
|
|
232
|
+
* `never` so callers can `.catch(err => this.toDomainError(err))`.
|
|
233
|
+
*/
|
|
234
|
+
toDomainError(err) {
|
|
235
|
+
if (err instanceof AuthorityNodeUnavailableError) {
|
|
236
|
+
throw new TrustCircleError('node_unavailable', `Authority node unavailable: ${err.message}`);
|
|
237
|
+
}
|
|
238
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
239
|
+
}
|
|
183
240
|
}
|
|
184
241
|
/**
|
|
185
242
|
* Wrap a TrustCircleService into the typed handler shape consumed by
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trust-circle.js","sourceRoot":"","sources":["../../src/auth/trust-circle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"trust-circle.js","sourceRoot":"","sources":["../../src/auth/trust-circle.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface TrustCircleSnapshot {
|
|
|
57
57
|
* - `not_found` → 404 (unknown token / unknown peer)
|
|
58
58
|
* - `expired` → 410 (invite past expiresAt)
|
|
59
59
|
* - `already_redeemed` → 409 (token was used and is gone)
|
|
60
|
+
* - `node_unavailable` → 503 (authority node admin channel down/not ready)
|
|
60
61
|
* - default → 500
|
|
61
62
|
*/
|
|
62
63
|
export interface TrustCircleHandlers {
|
|
@@ -73,7 +74,9 @@ export interface TrustCircleHandlers {
|
|
|
73
74
|
listTrustCircle(): Promise<TrustCircleSnapshot>;
|
|
74
75
|
}
|
|
75
76
|
/** Error codes thrown by TrustCircleService. */
|
|
76
|
-
export type TrustCircleErrorCode = 'not_found' | 'expired' | 'already_redeemed' | 'invalid_label' | 'invalid_token' | 'storage_error'
|
|
77
|
+
export type TrustCircleErrorCode = 'not_found' | 'expired' | 'already_redeemed' | 'invalid_label' | 'invalid_token' | 'storage_error'
|
|
78
|
+
/** The authority node's admin channel is unreachable / not ready. */
|
|
79
|
+
| 'node_unavailable';
|
|
77
80
|
/** Typed error carrying a stable `code` for HTTP mapping. */
|
|
78
81
|
export declare class TrustCircleError extends Error {
|
|
79
82
|
readonly code: TrustCircleErrorCode;
|
package/dist/auth/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA2EH,6DAA6D;AAC7D,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAGzC,YAAY,IAA0B,EAAE,OAAe;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the authority node's loopback admin channel (the 6.6
|
|
3
|
+
* `cadre-node-admin-channel` contract).
|
|
4
|
+
*
|
|
5
|
+
* cadre-host's manager process holds no in-process `CadreNode`; it spawns the
|
|
6
|
+
* admin's authority node as a managed child (see `HostProcessOrchestrator`)
|
|
7
|
+
* and delegates all authority/membership/identity operations to it over this
|
|
8
|
+
* client. The client implements **both** trimmed `CadreNodeLike` interfaces —
|
|
9
|
+
* the trust-circle one (`auth/trust-circle.ts`) and the NAT one
|
|
10
|
+
* (`nat/nat-service.ts`) — plus `pushInviteAddresses`.
|
|
11
|
+
*
|
|
12
|
+
* Transport: `Authorization: Bearer <token>` against
|
|
13
|
+
* `http://127.0.0.1:<adminPort>`. Every response uses the cadre-provider
|
|
14
|
+
* envelope `{ ok: true, data }` / `{ ok: false, error: { code, message } }`.
|
|
15
|
+
* A refused connection, a transport error, or any non-2xx response surfaces
|
|
16
|
+
* as {@link AuthorityNodeUnavailableError}; the trust-circle / NAT services
|
|
17
|
+
* translate that into a `node_unavailable` domain error so the management API
|
|
18
|
+
* returns a clear 503 rather than a raw 500.
|
|
19
|
+
*/
|
|
20
|
+
import type { CadreInvite } from '@serfab/cadre-core';
|
|
21
|
+
import type { AuthorityAdminEndpoint } from '../orchestrator/index.js';
|
|
22
|
+
import type { CadreNodeLike as TrustCircleCadreNodeLike } from '../auth/trust-circle.js';
|
|
23
|
+
import type { CadreNodeLike as NatCadreNodeLike } from '../nat/nat-service.js';
|
|
24
|
+
/**
|
|
25
|
+
* Raised when the authority node's admin channel can't be reached or returns
|
|
26
|
+
* a non-success response. Carries the node's stable error `code` when one was
|
|
27
|
+
* present in the envelope (e.g. `not_ready`, `not_authorized`).
|
|
28
|
+
*/
|
|
29
|
+
export declare class AuthorityNodeUnavailableError extends Error {
|
|
30
|
+
/** The admin-channel error code, when the node answered with an envelope. */
|
|
31
|
+
readonly nodeCode?: string;
|
|
32
|
+
constructor(message: string, nodeCode?: string);
|
|
33
|
+
}
|
|
34
|
+
/** Endpoint provided directly or via a getter (so it tracks re-spawns). */
|
|
35
|
+
type EndpointSource = AuthorityAdminEndpoint | (() => AuthorityAdminEndpoint | undefined) | undefined;
|
|
36
|
+
export interface AuthorityNodeClientOptions {
|
|
37
|
+
/** Fetch override for tests. Defaults to the global `fetch`. */
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
export declare class AuthorityNodeClient implements TrustCircleCadreNodeLike, NatCadreNodeLike {
|
|
41
|
+
private readonly endpointSource;
|
|
42
|
+
private readonly fetchImpl;
|
|
43
|
+
/**
|
|
44
|
+
* @param endpoint Either a fixed `{ baseUrl, token }` or a getter returning
|
|
45
|
+
* the current endpoint (pass `orchestrator.getAuthorityAdminEndpoint`
|
|
46
|
+
* bound, so a restart's new bearer token is picked up automatically).
|
|
47
|
+
*/
|
|
48
|
+
constructor(endpoint: EndpointSource, opts?: AuthorityNodeClientOptions);
|
|
49
|
+
createInvite(token?: string, expiresInMs?: number): Promise<{
|
|
50
|
+
invite: CadreInvite;
|
|
51
|
+
encodedInvite: string;
|
|
52
|
+
}>;
|
|
53
|
+
acceptPhone(options: {
|
|
54
|
+
phonePeerId: string;
|
|
55
|
+
token?: string;
|
|
56
|
+
}, issuedInvite?: CadreInvite): Promise<void>;
|
|
57
|
+
removePeer(peerId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Encode an invite for out-of-band delivery. The admin contract has no
|
|
60
|
+
* encode route (the mint route already returns `encodedInvite`), so this
|
|
61
|
+
* mirrors cadre-core's encoding (`base64url(JSON.stringify(invite))`).
|
|
62
|
+
*/
|
|
63
|
+
encodeInvite(invite: CadreInvite): string;
|
|
64
|
+
listMembers(): Promise<Array<{
|
|
65
|
+
peerId: string;
|
|
66
|
+
multiaddr: string | null;
|
|
67
|
+
}>>;
|
|
68
|
+
isMember(peerId: string): Promise<boolean>;
|
|
69
|
+
getPeerId(): Promise<string>;
|
|
70
|
+
getMultiaddrs(): Promise<string[]>;
|
|
71
|
+
/** Push the NAT-resolved addresses the node should embed in future invites. */
|
|
72
|
+
pushInviteAddresses(addresses: string[]): Promise<void>;
|
|
73
|
+
private resolveEndpoint;
|
|
74
|
+
private request;
|
|
75
|
+
private parseEnvelope;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the authority node's loopback admin channel (the 6.6
|
|
3
|
+
* `cadre-node-admin-channel` contract).
|
|
4
|
+
*
|
|
5
|
+
* cadre-host's manager process holds no in-process `CadreNode`; it spawns the
|
|
6
|
+
* admin's authority node as a managed child (see `HostProcessOrchestrator`)
|
|
7
|
+
* and delegates all authority/membership/identity operations to it over this
|
|
8
|
+
* client. The client implements **both** trimmed `CadreNodeLike` interfaces —
|
|
9
|
+
* the trust-circle one (`auth/trust-circle.ts`) and the NAT one
|
|
10
|
+
* (`nat/nat-service.ts`) — plus `pushInviteAddresses`.
|
|
11
|
+
*
|
|
12
|
+
* Transport: `Authorization: Bearer <token>` against
|
|
13
|
+
* `http://127.0.0.1:<adminPort>`. Every response uses the cadre-provider
|
|
14
|
+
* envelope `{ ok: true, data }` / `{ ok: false, error: { code, message } }`.
|
|
15
|
+
* A refused connection, a transport error, or any non-2xx response surfaces
|
|
16
|
+
* as {@link AuthorityNodeUnavailableError}; the trust-circle / NAT services
|
|
17
|
+
* translate that into a `node_unavailable` domain error so the management API
|
|
18
|
+
* returns a clear 503 rather than a raw 500.
|
|
19
|
+
*/
|
|
20
|
+
import debug from 'debug';
|
|
21
|
+
const log = debug('cadre:host:authority-client');
|
|
22
|
+
/**
|
|
23
|
+
* Raised when the authority node's admin channel can't be reached or returns
|
|
24
|
+
* a non-success response. Carries the node's stable error `code` when one was
|
|
25
|
+
* present in the envelope (e.g. `not_ready`, `not_authorized`).
|
|
26
|
+
*/
|
|
27
|
+
export class AuthorityNodeUnavailableError extends Error {
|
|
28
|
+
constructor(message, nodeCode) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'AuthorityNodeUnavailableError';
|
|
31
|
+
if (nodeCode !== undefined)
|
|
32
|
+
this.nodeCode = nodeCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class AuthorityNodeClient {
|
|
36
|
+
/**
|
|
37
|
+
* @param endpoint Either a fixed `{ baseUrl, token }` or a getter returning
|
|
38
|
+
* the current endpoint (pass `orchestrator.getAuthorityAdminEndpoint`
|
|
39
|
+
* bound, so a restart's new bearer token is picked up automatically).
|
|
40
|
+
*/
|
|
41
|
+
constructor(endpoint, opts = {}) {
|
|
42
|
+
this.endpointSource = endpoint;
|
|
43
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
44
|
+
}
|
|
45
|
+
// --- trust-circle CadreNodeLike ---
|
|
46
|
+
async createInvite(token, expiresInMs) {
|
|
47
|
+
const body = {};
|
|
48
|
+
if (token !== undefined)
|
|
49
|
+
body.token = token;
|
|
50
|
+
if (expiresInMs !== undefined)
|
|
51
|
+
body.expiresInMs = expiresInMs;
|
|
52
|
+
return await this.request('POST', '/admin/invites', body);
|
|
53
|
+
}
|
|
54
|
+
async acceptPhone(options, issuedInvite) {
|
|
55
|
+
const body = { phonePeerId: options.phonePeerId };
|
|
56
|
+
if (options.token !== undefined)
|
|
57
|
+
body.token = options.token;
|
|
58
|
+
if (issuedInvite !== undefined)
|
|
59
|
+
body.issuedInvite = issuedInvite;
|
|
60
|
+
await this.request('POST', '/admin/accept-phone', body);
|
|
61
|
+
}
|
|
62
|
+
async removePeer(peerId) {
|
|
63
|
+
await this.request('DELETE', `/admin/members/${encodeURIComponent(peerId)}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Encode an invite for out-of-band delivery. The admin contract has no
|
|
67
|
+
* encode route (the mint route already returns `encodedInvite`), so this
|
|
68
|
+
* mirrors cadre-core's encoding (`base64url(JSON.stringify(invite))`).
|
|
69
|
+
*/
|
|
70
|
+
encodeInvite(invite) {
|
|
71
|
+
return Buffer.from(JSON.stringify(invite), 'utf8').toString('base64url');
|
|
72
|
+
}
|
|
73
|
+
async listMembers() {
|
|
74
|
+
const data = await this.request('GET', '/admin/members');
|
|
75
|
+
return data.members;
|
|
76
|
+
}
|
|
77
|
+
async isMember(peerId) {
|
|
78
|
+
const data = await this.request('GET', `/admin/members/${encodeURIComponent(peerId)}`);
|
|
79
|
+
return data.member;
|
|
80
|
+
}
|
|
81
|
+
// --- NAT CadreNodeLike ---
|
|
82
|
+
async getPeerId() {
|
|
83
|
+
const data = await this.request('GET', '/admin/identity');
|
|
84
|
+
return data.peerId ?? '';
|
|
85
|
+
}
|
|
86
|
+
async getMultiaddrs() {
|
|
87
|
+
const data = await this.request('GET', '/admin/multiaddrs');
|
|
88
|
+
return data.multiaddrs;
|
|
89
|
+
}
|
|
90
|
+
// --- push-model invite addresses ---
|
|
91
|
+
/** Push the NAT-resolved addresses the node should embed in future invites. */
|
|
92
|
+
async pushInviteAddresses(addresses) {
|
|
93
|
+
await this.request('PUT', '/admin/invite-addresses', { addresses });
|
|
94
|
+
}
|
|
95
|
+
// --- internals ---
|
|
96
|
+
resolveEndpoint() {
|
|
97
|
+
const ep = typeof this.endpointSource === 'function' ? this.endpointSource() : this.endpointSource;
|
|
98
|
+
if (!ep) {
|
|
99
|
+
throw new AuthorityNodeUnavailableError('Authority node admin endpoint is not available');
|
|
100
|
+
}
|
|
101
|
+
return ep;
|
|
102
|
+
}
|
|
103
|
+
async request(method, path, body) {
|
|
104
|
+
const ep = this.resolveEndpoint();
|
|
105
|
+
const headers = { authorization: `Bearer ${ep.token}` };
|
|
106
|
+
const init = { method, headers };
|
|
107
|
+
if (body !== undefined) {
|
|
108
|
+
headers['content-type'] = 'application/json';
|
|
109
|
+
init.body = JSON.stringify(body);
|
|
110
|
+
}
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await this.fetchImpl(`${ep.baseUrl}${path}`, init);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
log('admin %s %s transport error: %s', method, path, message);
|
|
118
|
+
throw new AuthorityNodeUnavailableError(`Authority node unreachable: ${message}`);
|
|
119
|
+
}
|
|
120
|
+
const envelope = await this.parseEnvelope(res);
|
|
121
|
+
if (!res.ok || !envelope.ok) {
|
|
122
|
+
const code = envelope.error?.code;
|
|
123
|
+
const message = envelope.error?.message ?? `admin ${method} ${path} → HTTP ${res.status}`;
|
|
124
|
+
log('admin %s %s failed: [%s] %s', method, path, code ?? res.status, message);
|
|
125
|
+
throw new AuthorityNodeUnavailableError(message, code);
|
|
126
|
+
}
|
|
127
|
+
return envelope.data;
|
|
128
|
+
}
|
|
129
|
+
async parseEnvelope(res) {
|
|
130
|
+
try {
|
|
131
|
+
const parsed = await res.json();
|
|
132
|
+
if (parsed && typeof parsed === 'object') {
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Non-JSON body — fall through to an empty envelope; status drives the
|
|
138
|
+
// ok/non-ok decision in the caller.
|
|
139
|
+
}
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=authority-node-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authority-node-client.js","sourceRoot":"","sources":["../../src/authority/authority-node-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authority-node delegation surface for cadre-host.
|
|
3
|
+
*
|
|
4
|
+
* The manager process spawns the admin's authority cadre node (via
|
|
5
|
+
* `HostProcessOrchestrator`) and delegates authority/membership/identity
|
|
6
|
+
* operations to it over the loopback admin channel using
|
|
7
|
+
* {@link AuthorityNodeClient}. See `docs/cadre-host.md` § Control-plane
|
|
8
|
+
* separation.
|
|
9
|
+
*/
|
|
10
|
+
export { AuthorityNodeClient, AuthorityNodeUnavailableError } from './authority-node-client.js';
|
|
11
|
+
export type { AuthorityNodeClientOptions } from './authority-node-client.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authority-node delegation surface for cadre-host.
|
|
3
|
+
*
|
|
4
|
+
* The manager process spawns the admin's authority cadre node (via
|
|
5
|
+
* `HostProcessOrchestrator`) and delegates authority/membership/identity
|
|
6
|
+
* operations to it over the loopback admin channel using
|
|
7
|
+
* {@link AuthorityNodeClient}. See `docs/cadre-host.md` § Control-plane
|
|
8
|
+
* separation.
|
|
9
|
+
*/
|
|
10
|
+
export { AuthorityNodeClient, AuthorityNodeUnavailableError } from './authority-node-client.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/authority/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC"}
|