@camstack/server 1.1.53 → 1.1.54
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/api/health/health.routes.js +52 -7
- package/dist/main.js +1 -1
- package/package.json +3 -3
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.buildHubHealth = buildHubHealth;
|
|
24
|
+
exports.isAuthorizedHealthRequest = isAuthorizedHealthRequest;
|
|
24
25
|
exports.registerHealthRoutes = registerHealthRoutes;
|
|
26
|
+
const session_cookie_js_1 = require("../../auth/session-cookie.js");
|
|
25
27
|
const AGENT_HEALTH_TIMEOUT_MS = 3_000;
|
|
26
28
|
function nowIso() {
|
|
27
29
|
return new Date().toISOString();
|
|
@@ -68,23 +70,64 @@ async function fetchAgentHealth(deps, nodeId) {
|
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* True when the request carries a valid SESSION-grade credential: a Bearer
|
|
75
|
+
* JWT or the httpOnly session cookie. Health details expose topology
|
|
76
|
+
* (agent ids, versions, pids) — public probes get only `{ok}`.
|
|
77
|
+
* Pure over (headers, verifier) so the spec exercises it directly.
|
|
78
|
+
*/
|
|
79
|
+
function isAuthorizedHealthRequest(headers, verifyToken) {
|
|
80
|
+
const bearer = headers.authorization?.startsWith('Bearer ')
|
|
81
|
+
? headers.authorization.slice('Bearer '.length)
|
|
82
|
+
: null;
|
|
83
|
+
const cookieToken = (0, session_cookie_js_1.readSessionCookieFromHeader)(headers.cookie);
|
|
84
|
+
for (const token of [bearer, cookieToken]) {
|
|
85
|
+
if (!token)
|
|
86
|
+
continue;
|
|
87
|
+
try {
|
|
88
|
+
if ((0, session_cookie_js_1.isSessionGradeJwtPayload)(verifyToken(token)))
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* invalid/expired — try the next credential */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
71
97
|
function registerHealthRoutes(fastify, deps) {
|
|
98
|
+
const authorized = (req) => isAuthorizedHealthRequest({
|
|
99
|
+
...(typeof req.headers.authorization === 'string'
|
|
100
|
+
? { authorization: req.headers.authorization }
|
|
101
|
+
: {}),
|
|
102
|
+
...(typeof req.headers.cookie === 'string' ? { cookie: req.headers.cookie } : {}),
|
|
103
|
+
}, deps.verifyToken);
|
|
104
|
+
// PUBLIC probe: liveness only. Everything beyond `ok` (version, uptime,
|
|
105
|
+
// pid, agent topology) moved to the AUTHENTICATED /health/details — a
|
|
106
|
+
// public endpoint must not enumerate the deployment.
|
|
72
107
|
fastify.get('/health', async (_req, reply) => {
|
|
108
|
+
const ok = deps.isReady();
|
|
109
|
+
if (!ok)
|
|
110
|
+
reply.status(503);
|
|
111
|
+
return { ok };
|
|
112
|
+
});
|
|
113
|
+
// AUTHENTICATED detailed health — the payload /health used to return.
|
|
114
|
+
fastify.get('/health/details', async (req, reply) => {
|
|
115
|
+
if (!authorized(req))
|
|
116
|
+
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
73
117
|
const health = await buildHubHealth(deps);
|
|
74
118
|
if (!health.ok)
|
|
75
119
|
reply.status(503);
|
|
76
120
|
return health;
|
|
77
121
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return { service: 'camstack-hub', nodeId: 'hub', version: deps.hubVersion, name };
|
|
82
|
-
});
|
|
83
|
-
fastify.get('/health/agents', async () => {
|
|
122
|
+
fastify.get('/health/agents', async (req, reply) => {
|
|
123
|
+
if (!authorized(req))
|
|
124
|
+
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
84
125
|
const nodes = deps.agentRegistry.listNodeLiveness();
|
|
85
126
|
return { agents: nodes.filter((n) => !n.isHub && n.isOnline).map((n) => n.id) };
|
|
86
127
|
});
|
|
87
128
|
fastify.get('/health/agents/:nodeId', async (req, reply) => {
|
|
129
|
+
if (!authorized(req))
|
|
130
|
+
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
88
131
|
const { nodeId } = req.params;
|
|
89
132
|
if (!nodeId) {
|
|
90
133
|
return reply.status(400).send({ ok: false, error: 'nodeId required' });
|
|
@@ -95,7 +138,9 @@ function registerHealthRoutes(fastify, deps) {
|
|
|
95
138
|
}
|
|
96
139
|
return result;
|
|
97
140
|
});
|
|
98
|
-
fastify.get('/health/cluster', async () => {
|
|
141
|
+
fastify.get('/health/cluster', async (req, reply) => {
|
|
142
|
+
if (!authorized(req))
|
|
143
|
+
return reply.status(401).send({ ok: false, error: 'unauthorized' });
|
|
99
144
|
const hub = await buildHubHealth(deps);
|
|
100
145
|
// Enumeration only (fan-out-free); the $agent.health fan-out below is intentional.
|
|
101
146
|
const nodes = deps.agentRegistry.listNodeLiveness();
|
package/dist/main.js
CHANGED
|
@@ -320,6 +320,7 @@ async function bootstrap() {
|
|
|
320
320
|
// Read the live flag at request time — flips to true once the tRPC
|
|
321
321
|
// router finishes registering (see `trpcRegistered = true` below).
|
|
322
322
|
isReady: () => trpcRegistered,
|
|
323
|
+
verifyToken: (token) => app.get(auth_service_1.AuthService).verifyToken(token),
|
|
323
324
|
});
|
|
324
325
|
console.log(`[bootstrap] Health routes registered (hub v${hubVersion})`);
|
|
325
326
|
}
|
|
@@ -997,7 +998,6 @@ async function bootstrap() {
|
|
|
997
998
|
url.startsWith('/api/') ||
|
|
998
999
|
url.startsWith('/agent') ||
|
|
999
1000
|
url.startsWith('/health') ||
|
|
1000
|
-
url.startsWith('/discovery') ||
|
|
1001
1001
|
// The viewer SPA owns /viewer/** via its own catch-all (registered
|
|
1002
1002
|
// below). Fall through here so admin-ui never serves it — and so
|
|
1003
1003
|
// /viewer 404s cleanly when the viewer-ui addon isn't registered.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.54",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"test:watch": "vitest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@camstack/addon-admin-ui": "1.1.
|
|
26
|
+
"@camstack/addon-admin-ui": "1.1.46",
|
|
27
27
|
"@camstack/addon-advanced-notifier": "1.1.21",
|
|
28
28
|
"@camstack/addon-auth": "1.1.6",
|
|
29
29
|
"@camstack/addon-decoder-nodeav": "1.1.9",
|
|
30
30
|
"@camstack/addon-notifiers": "1.1.21",
|
|
31
31
|
"@camstack/addon-pipeline": "1.1.51",
|
|
32
|
-
"@camstack/addon-pipeline-orchestrator": "1.1.
|
|
32
|
+
"@camstack/addon-pipeline-orchestrator": "1.1.40",
|
|
33
33
|
"@camstack/addon-post-analysis": "1.1.23",
|
|
34
34
|
"@camstack/sdk": "1.1.22",
|
|
35
35
|
"@camstack/shm-ring": "1.0.21",
|