@camstack/server 1.2.486 → 1.2.488
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/agent/agent-mesh-join.js +121 -0
- package/dist/agent/agent-service.js +4 -0
- package/dist/agent/main.js +62 -2
- package/dist/api/auth-whoami.js +1 -1
- package/dist/api/core/auth-admin-tokens.procedures.js +138 -0
- package/dist/api/core/auth-limits.js +77 -0
- package/dist/api/core/auth-reauth.procedures.js +108 -0
- package/dist/api/core/auth-sessions.procedures.js +166 -0
- package/dist/api/core/auth.router.js +281 -80
- package/dist/api/health/adaptive-probe.routes.js +1 -1
- package/dist/api/mesh/enroll.routes.js +28 -0
- package/dist/api/trpc/core-cap-bridge.js +88 -5
- package/dist/api/trpc/generated-cap-mounts.js +19 -1
- package/dist/api/trpc/generated-cap-routers.js +512 -205
- package/dist/api/trpc/trpc.context.js +86 -34
- package/dist/api/trpc/trpc.middleware.js +8 -11
- package/dist/api/trpc/trpc.router.js +6 -1
- package/dist/auth/auth-network-policy.js +75 -0
- package/dist/auth/auth-rate-limit.js +25 -20
- package/dist/auth/client-address.js +68 -0
- package/dist/auth/external-link-intents.js +12 -3
- package/dist/auth/hub-sessions.js +74 -0
- package/dist/auth/native-link-completions.js +68 -0
- package/dist/auth/native-return.js +33 -0
- package/dist/auth/session-lifetimes.js +46 -0
- package/dist/auth/session-mint.js +110 -13
- package/dist/auth/sso-finish-deps.js +56 -1
- package/dist/auth/sso-finish.js +132 -17
- package/dist/auth/sso-routes.js +95 -0
- package/dist/auth/used-bridge-ids.js +52 -0
- package/dist/core/addon/addon-registry.service.js +5 -0
- package/dist/core/agent/agent-registry.service.js +23 -0
- package/dist/core/auth/auth.service.js +50 -1
- package/dist/core/auth/handoff-code.service.js +9 -3
- package/dist/core/moleculer/hub-mesh-guard.js +132 -0
- package/dist/core/moleculer/moleculer.service.js +46 -0
- package/dist/main.js +97 -29
- package/package.json +13 -13
|
@@ -53,7 +53,7 @@ function registerAdaptiveProbeRoute(fastify, deps) {
|
|
|
53
53
|
}, deps.verifyToken);
|
|
54
54
|
if (!authorized)
|
|
55
55
|
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
56
|
-
const key = (0, auth_rate_limit_js_1.clientKeyFromRequest)(req);
|
|
56
|
+
const key = (0, auth_rate_limit_js_1.clientKeyFromRequest)(req, deps.network);
|
|
57
57
|
if (key !== null) {
|
|
58
58
|
const verdict = deps.limiter.check(`adaptive-probe:${key}`);
|
|
59
59
|
if (!verdict.allowed) {
|
|
@@ -0,0 +1,28 @@
|
|
|
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 ?? '' }, { ca: caState.ca });
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
return reply.status(result.status).send({ ok: false, error: result.error });
|
|
18
|
+
}
|
|
19
|
+
return reply.send({
|
|
20
|
+
ok: true,
|
|
21
|
+
cert: result.cert,
|
|
22
|
+
caCert: result.caCert,
|
|
23
|
+
nodeId: result.nodeId,
|
|
24
|
+
notAfter: result.notAfter,
|
|
25
|
+
serialNumber: result.serialNumber,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -18,10 +18,15 @@
|
|
|
18
18
|
* routes the call exactly like any addon-provided cap.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.REMOTE_PRIVILEGE_LOG_ONLY = exports.CORE_CAP_REMOTE_FORBIDDEN_TYPE = exports.REMOTE_DENIED_NAMESPACES = void 0;
|
|
22
|
+
exports.setRemotePrivilegeLogSink = setRemotePrivilegeLogSink;
|
|
23
|
+
exports.__resetRemotePrivilegeObservationForTests = __resetRemotePrivilegeObservationForTests;
|
|
24
|
+
exports.observeRemotePrivilege = observeRemotePrivilege;
|
|
21
25
|
exports.isCoreServiceCapName = isCoreServiceCapName;
|
|
22
26
|
exports.describeMeshOrigin = describeMeshOrigin;
|
|
23
27
|
exports.buildCoreCapService = buildCoreCapService;
|
|
24
28
|
const system_1 = require("@camstack/system");
|
|
29
|
+
const moleculer_1 = require("moleculer");
|
|
25
30
|
const http_request_census_singleton_js_1 = require("./http-request-census.singleton.js");
|
|
26
31
|
const server_provided_caps_js_1 = require("./server-provided-caps.js");
|
|
27
32
|
const trpc_context_js_1 = require("./trpc.context.js");
|
|
@@ -37,6 +42,10 @@ const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
|
37
42
|
* - `auth` — issuing service / scoped tokens over the trusted mesh
|
|
38
43
|
* would let any forked addon mint admin credentials. Addons must
|
|
39
44
|
* not perform authentication operations; this stays hub-local.
|
|
45
|
+
* - `repl` — `repl.execute` runs code in the hub process (`vm` is no
|
|
46
|
+
* boundary). The admin UI's REPL reaches it over HTTP tRPC as an admin
|
|
47
|
+
* session; nothing calls it over the mesh, and a mesh caller must never
|
|
48
|
+
* be able to (D802, security plan §1).
|
|
40
49
|
* - `live`, `systemEvents` — listed in `NEVER_BRIDGED_CAPS`
|
|
41
50
|
* (`trpc-links.ts`). They are hub-only push streams, not
|
|
42
51
|
* request/reply, and the worker side fast-fails on them by design.
|
|
@@ -51,9 +60,57 @@ const CORE_NAMESPACES = new Set([
|
|
|
51
60
|
'hwaccel',
|
|
52
61
|
'streamProbe',
|
|
53
62
|
'eventBusProxy',
|
|
54
|
-
'repl',
|
|
55
63
|
'addonSettingsRaw',
|
|
56
64
|
]);
|
|
65
|
+
/**
|
|
66
|
+
* Bridged namespaces another NODE may not call (D802) — only the hub's own
|
|
67
|
+
* runners may. `logs` reads the hub's whole log store, `addonSettingsRaw` every
|
|
68
|
+
* addon's raw config (tokens included). No addon calls either over the mesh
|
|
69
|
+
* (they read their own settings through the settings door), so refusing them
|
|
70
|
+
* to an agent costs nothing and takes them off the LAN.
|
|
71
|
+
*/
|
|
72
|
+
exports.REMOTE_DENIED_NAMESPACES = new Set([
|
|
73
|
+
'logs',
|
|
74
|
+
'addonSettingsRaw',
|
|
75
|
+
]);
|
|
76
|
+
/** Moleculer error `type` of a bridged call another node may not make. */
|
|
77
|
+
exports.CORE_CAP_REMOTE_FORBIDDEN_TYPE = 'CORE_CAP_REMOTE_FORBIDDEN';
|
|
78
|
+
/**
|
|
79
|
+
* When true, a remote node's allowed bridged calls are logged once per
|
|
80
|
+
* `(nodeId, path)` per process — still admin, not refused. Granting least
|
|
81
|
+
* privilege waits on census; this is the log-only week before that.
|
|
82
|
+
*/
|
|
83
|
+
exports.REMOTE_PRIVILEGE_LOG_ONLY = true;
|
|
84
|
+
let remotePrivilegeLogSink = null;
|
|
85
|
+
/** Attach the warn sink used by {@link observeRemotePrivilege}. */
|
|
86
|
+
function setRemotePrivilegeLogSink(sink) {
|
|
87
|
+
remotePrivilegeLogSink = sink;
|
|
88
|
+
}
|
|
89
|
+
const remotePrivilegeObserved = new Set();
|
|
90
|
+
/** Clears dedupe state and the sink — tests only. */
|
|
91
|
+
function __resetRemotePrivilegeObservationForTests() {
|
|
92
|
+
remotePrivilegeObserved.clear();
|
|
93
|
+
remotePrivilegeLogSink = null;
|
|
94
|
+
}
|
|
95
|
+
function remotePrivilegeKey(nodeId, path) {
|
|
96
|
+
return `${nodeId}\0${path}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* One warn per `(nodeId, path)` while {@link REMOTE_PRIVILEGE_LOG_ONLY} is on.
|
|
100
|
+
* Call only after {@link REMOTE_DENIED_NAMESPACES} has passed.
|
|
101
|
+
*/
|
|
102
|
+
function observeRemotePrivilege(nodeId, path) {
|
|
103
|
+
if (!exports.REMOTE_PRIVILEGE_LOG_ONLY)
|
|
104
|
+
return;
|
|
105
|
+
const key = remotePrivilegeKey(nodeId, path);
|
|
106
|
+
if (remotePrivilegeObserved.has(key))
|
|
107
|
+
return;
|
|
108
|
+
remotePrivilegeObserved.add(key);
|
|
109
|
+
const message = `mesh privilege: node:${nodeId} called ${path} (log-only; still allowed)`;
|
|
110
|
+
remotePrivilegeLogSink?.warn(message, {
|
|
111
|
+
meta: { nodeId, path, principal: `node:${nodeId}` },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
57
114
|
/**
|
|
58
115
|
* camelCase → kebab-case. Mirrors `toKebab` in `trpc-links.ts` so the
|
|
59
116
|
* action name matches what `brokerTransportLink` derives from `op.path`.
|
|
@@ -164,9 +221,21 @@ function resolveInvoker(caller, path) {
|
|
|
164
221
|
* appRouter. Query + mutation procedures in {@link CORE_NAMESPACES} are
|
|
165
222
|
* exposed; subscriptions are skipped (the broker transport is
|
|
166
223
|
* request/reply only).
|
|
224
|
+
*
|
|
225
|
+
* Each call runs under a principal naming its origin (D802): `mesh` for the
|
|
226
|
+
* hub's own runners, `node:<nodeId>` for another node.
|
|
167
227
|
*/
|
|
168
|
-
function buildCoreCapService(appRouter, census = (0, http_request_census_singleton_js_1.getHttpRequestCensus)()) {
|
|
169
|
-
const
|
|
228
|
+
function buildCoreCapService(appRouter, census = (0, http_request_census_singleton_js_1.getHttpRequestCensus)(), options = {}) {
|
|
229
|
+
const callerFactory = (0, trpc_middleware_js_1.createCallerFactory)(appRouter);
|
|
230
|
+
const callers = new Map();
|
|
231
|
+
const callerFor = (principal) => {
|
|
232
|
+
let caller = callers.get(principal);
|
|
233
|
+
if (caller === undefined) {
|
|
234
|
+
caller = callerFactory((0, trpc_context_js_1.createMeshTrpcContext)(principal));
|
|
235
|
+
callers.set(principal, caller);
|
|
236
|
+
}
|
|
237
|
+
return caller;
|
|
238
|
+
};
|
|
170
239
|
const discovered = [];
|
|
171
240
|
collectProcedures(appRouter._def.procedures, '', discovered);
|
|
172
241
|
const actions = [];
|
|
@@ -179,17 +248,31 @@ function buildCoreCapService(appRouter, census = (0, http_request_census_singlet
|
|
|
179
248
|
continue;
|
|
180
249
|
if (type === 'subscription')
|
|
181
250
|
continue;
|
|
182
|
-
|
|
183
|
-
if (invoke === null)
|
|
251
|
+
if (resolveInvoker(callerFor('mesh'), path) === null)
|
|
184
252
|
continue;
|
|
185
253
|
const method = path.slice(dot + 1);
|
|
186
254
|
actions.push({
|
|
187
255
|
actionName: `${toKebab(namespace)}.${method}`,
|
|
188
256
|
invoke: (input, origin) => {
|
|
189
257
|
countMeshCall(census, path, origin);
|
|
258
|
+
const remoteNode = remoteOrigin(origin, options.ownNodeId);
|
|
259
|
+
if (remoteNode !== null && exports.REMOTE_DENIED_NAMESPACES.has(namespace)) {
|
|
260
|
+
throw new moleculer_1.Errors.MoleculerClientError(`"${path}" is not callable from another node (caller "${remoteNode}")`, 403, exports.CORE_CAP_REMOTE_FORBIDDEN_TYPE);
|
|
261
|
+
}
|
|
262
|
+
if (remoteNode !== null)
|
|
263
|
+
observeRemotePrivilege(remoteNode, path);
|
|
264
|
+
const invoke = resolveInvoker(callerFor(remoteNode === null ? 'mesh' : `node:${remoteNode}`), path);
|
|
265
|
+
if (invoke === null)
|
|
266
|
+
throw new Error(`$core-caps: "${path}" is not invocable`);
|
|
190
267
|
return invoke(input);
|
|
191
268
|
},
|
|
192
269
|
});
|
|
193
270
|
}
|
|
194
271
|
return (0, system_1.createCoreCapService)({ actions });
|
|
195
272
|
}
|
|
273
|
+
/** The calling node when it is not the hub itself; `null` for the hub's own calls. */
|
|
274
|
+
function remoteOrigin(origin, ownNodeId) {
|
|
275
|
+
if (ownNodeId === undefined || origin.nodeId === null)
|
|
276
|
+
return null;
|
|
277
|
+
return origin.nodeId === ownNodeId ? null : origin.nodeId;
|
|
278
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
|
|
3
3
|
// Re-run: npx tsx scripts/generate-cap-mounts.ts
|
|
4
4
|
//
|
|
5
|
-
// Mounted:
|
|
5
|
+
// Mounted: 166 Skipped (legacy): 3
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.LEGACY_SHAPE_SKIP = void 0;
|
|
8
8
|
exports.mountAllCaps = mountAllCaps;
|
|
@@ -530,6 +530,24 @@ function mountAllCaps(services) {
|
|
|
530
530
|
terminalSession: (0, generated_cap_routers_js_1.createCapRouter_terminalSession)((_ctx) => reg?.getSingleton('terminal-session') ??
|
|
531
531
|
null, remoteCapProxy),
|
|
532
532
|
toast: (0, generated_cap_routers_js_1.createCapRouter_toast)((_ctx) => reg?.getSingleton('toast') ?? null, remoteCapProxy),
|
|
533
|
+
training: (0, generated_cap_routers_js_1.createCapRouter_training)((_ctx) => reg?.getSingleton('training') ?? null, remoteCapProxy),
|
|
534
|
+
trainingProvider: (0, generated_cap_routers_js_1.createCapRouter_trainingProvider)((_ctx, addonId) => {
|
|
535
|
+
if (!reg)
|
|
536
|
+
return null;
|
|
537
|
+
if (addonId !== undefined) {
|
|
538
|
+
return reg.getProviderByAddonId('training-provider', addonId);
|
|
539
|
+
}
|
|
540
|
+
const entries = reg.getCollectionEntries('training-provider');
|
|
541
|
+
if (entries.length === 0)
|
|
542
|
+
return null;
|
|
543
|
+
const providers = entries.map(([, p]) => p);
|
|
544
|
+
const first = providers[0];
|
|
545
|
+
return {
|
|
546
|
+
...first,
|
|
547
|
+
describe: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'describe'),
|
|
548
|
+
listJobs: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listJobs'),
|
|
549
|
+
};
|
|
550
|
+
}, remoteCapProxy),
|
|
533
551
|
turnProvider: (0, generated_cap_routers_js_1.createCapRouter_turnProvider)((_ctx, addonId) => {
|
|
534
552
|
if (!reg)
|
|
535
553
|
return null;
|