@nubisco/openbridge 0.39.0 → 0.39.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/dist/__tests__/auth-gate.test.d.ts +2 -0
- package/dist/__tests__/auth-gate.test.d.ts.map +1 -0
- package/dist/__tests__/auth-gate.test.js +141 -0
- package/dist/__tests__/auth-gate.test.js.map +1 -0
- package/dist/auth.d.ts +27 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +71 -11
- package/dist/auth.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -11
- package/dist/server.js.map +1 -1
- package/package.json +10 -10
- package/ui-dist/assets/index-D9v2kI8s.js +109 -0
- package/ui-dist/index.html +1 -1
- package/ui-dist/assets/index-BkNkuHBq.js +0 -108
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-gate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { createHmac } from 'crypto';
|
|
3
|
+
import { request } from 'http';
|
|
4
|
+
import Fastify from 'fastify';
|
|
5
|
+
import websocket from '@fastify/websocket';
|
|
6
|
+
import { normalizeRequestPath, registerAuthRoutes, requiresSession } from '../auth.js';
|
|
7
|
+
const SECRET = Buffer.from('auth-gate-test-secret-0123456789', 'utf8');
|
|
8
|
+
function sessionCookie() {
|
|
9
|
+
const data = Buffer.from(JSON.stringify({ userId: 'u1', email: 'u1@example.test', exp: Math.floor(Date.now() / 1000) + 600 }), 'utf8').toString('base64url');
|
|
10
|
+
const sig = createHmac('sha256', SECRET).update(data).digest('base64url');
|
|
11
|
+
return `openbridge_session=${data}.${sig}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Every spelling here reached a protected handler, or would have if the
|
|
15
|
+
* router learned to fold it, while the gate compared the raw URL against
|
|
16
|
+
* "/api/" and "/ws/".
|
|
17
|
+
*/
|
|
18
|
+
const BYPASS_ATTEMPTS = [
|
|
19
|
+
'/%61pi/plugins',
|
|
20
|
+
'/%61%70%69/plugins',
|
|
21
|
+
'/%2561pi/plugins',
|
|
22
|
+
'/%41PI/plugins',
|
|
23
|
+
'/API/plugins',
|
|
24
|
+
'/Api/Plugins',
|
|
25
|
+
'/api%2fplugins',
|
|
26
|
+
'/api%2Fplugins',
|
|
27
|
+
'/%2fapi/plugins',
|
|
28
|
+
'//api/plugins',
|
|
29
|
+
'/api//plugins',
|
|
30
|
+
'/api/plugins.',
|
|
31
|
+
'/api./plugins',
|
|
32
|
+
'/api/./plugins',
|
|
33
|
+
'/%2e/api/plugins',
|
|
34
|
+
'/assets/../api/plugins',
|
|
35
|
+
'/assets/..%2fapi/plugins',
|
|
36
|
+
'/assets/%2e%2e/api/plugins',
|
|
37
|
+
'/static/..%5capi/plugins',
|
|
38
|
+
'/api/plugins?x=1',
|
|
39
|
+
];
|
|
40
|
+
describe('normalizeRequestPath', () => {
|
|
41
|
+
it('folds every spelling of a protected path to the same form', () => {
|
|
42
|
+
for (const url of BYPASS_ATTEMPTS) {
|
|
43
|
+
expect(normalizeRequestPath(url), url).toBe('/api/plugins');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it('leaves dashboard routes alone', () => {
|
|
47
|
+
expect(normalizeRequestPath('/devices/abc')).toBe('/devices/abc');
|
|
48
|
+
expect(normalizeRequestPath('/')).toBe('/');
|
|
49
|
+
});
|
|
50
|
+
it('refuses a path that cannot be decoded', () => {
|
|
51
|
+
expect(normalizeRequestPath('/%E0%A4%A')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('requiresSession', () => {
|
|
55
|
+
it('gates by the matched route even when the raw URL looks unrelated', () => {
|
|
56
|
+
expect(requiresSession('/api/plugins', '/%61pi/plugins')).toBe(true);
|
|
57
|
+
expect(requiresSession('/ws/shell', '/%77s/shell')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it('gates unmatched spellings of protected paths', () => {
|
|
60
|
+
for (const url of BYPASS_ATTEMPTS)
|
|
61
|
+
expect(requiresSession(undefined, url), url).toBe(true);
|
|
62
|
+
expect(requiresSession(undefined, '/WS/logs')).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('keeps health, auth and the dashboard public', () => {
|
|
65
|
+
expect(requiresSession('/api/health', '/api/health')).toBe(false);
|
|
66
|
+
expect(requiresSession('/auth/me', '/auth/me')).toBe(false);
|
|
67
|
+
expect(requiresSession('/auth/platform/config', '/auth/platform/config')).toBe(false);
|
|
68
|
+
expect(requiresSession(undefined, '/')).toBe(false);
|
|
69
|
+
expect(requiresSession(undefined, '/devices/abc')).toBe(false);
|
|
70
|
+
expect(requiresSession(undefined, '/apiary')).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('platform auth gate on a running server', () => {
|
|
74
|
+
let app;
|
|
75
|
+
let port = 0;
|
|
76
|
+
beforeAll(async () => {
|
|
77
|
+
app = Fastify({ logger: false });
|
|
78
|
+
await app.register(websocket);
|
|
79
|
+
const config = { enabled: true, issuer: 'https://issuer.invalid', appId: null, secret: SECRET };
|
|
80
|
+
await registerAuthRoutes(app, config);
|
|
81
|
+
app.get('/api/health', async () => ({ status: 'ok' }));
|
|
82
|
+
app.get('/api/plugins', async () => ({ plugins: ['secret'] }));
|
|
83
|
+
app.get('/ws/shell', { websocket: true }, (ws) => ws.send('shell-open'));
|
|
84
|
+
// Mirrors the dashboard fallback in server.ts.
|
|
85
|
+
app.setNotFoundHandler(async (_req, reply) => reply.type('text/html').send('<html></html>'));
|
|
86
|
+
await app.listen({ port: 0, host: '127.0.0.1' });
|
|
87
|
+
port = app.server.address().port;
|
|
88
|
+
});
|
|
89
|
+
afterAll(async () => {
|
|
90
|
+
await app.close();
|
|
91
|
+
});
|
|
92
|
+
it.each(BYPASS_ATTEMPTS)('refuses %s without a session', async (url) => {
|
|
93
|
+
const res = await app.inject({ method: 'GET', url });
|
|
94
|
+
expect(res.statusCode).toBe(401);
|
|
95
|
+
expect(res.json()).toEqual({ error: 'unauthenticated' });
|
|
96
|
+
});
|
|
97
|
+
it('serves protected routes and health as before', async () => {
|
|
98
|
+
expect((await app.inject({ method: 'GET', url: '/api/health' })).statusCode).toBe(200);
|
|
99
|
+
expect((await app.inject({ method: 'GET', url: '/api/plugins' })).statusCode).toBe(401);
|
|
100
|
+
const authed = await app.inject({ method: 'GET', url: '/%61pi/plugins', headers: { cookie: sessionCookie() } });
|
|
101
|
+
expect(authed.statusCode).toBe(200);
|
|
102
|
+
expect(authed.json()).toEqual({ plugins: ['secret'] });
|
|
103
|
+
expect((await app.inject({ method: 'GET', url: '/devices/abc' })).statusCode).toBe(200);
|
|
104
|
+
});
|
|
105
|
+
/** Send a WebSocket upgrade and report the status the server answered with. */
|
|
106
|
+
function upgrade(path, cookie) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
// A fresh connection per attempt: the server closes one it refused an upgrade on.
|
|
109
|
+
const req = request({
|
|
110
|
+
agent: false,
|
|
111
|
+
host: '127.0.0.1',
|
|
112
|
+
port,
|
|
113
|
+
path,
|
|
114
|
+
headers: {
|
|
115
|
+
Connection: 'Upgrade',
|
|
116
|
+
Upgrade: 'websocket',
|
|
117
|
+
'Sec-WebSocket-Version': '13',
|
|
118
|
+
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
|
|
119
|
+
...(cookie ? { Cookie: cookie } : {}),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
req.on('upgrade', (res, socket) => {
|
|
123
|
+
socket.destroy();
|
|
124
|
+
resolve(res.statusCode ?? 0);
|
|
125
|
+
});
|
|
126
|
+
req.on('response', (res) => {
|
|
127
|
+
res.resume();
|
|
128
|
+
resolve(res.statusCode ?? 0);
|
|
129
|
+
});
|
|
130
|
+
req.on('error', reject);
|
|
131
|
+
req.end();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
it.each(['/ws/shell', '/%77s/shell', '/%77%73/shell', '/WS/shell', '/ws//shell', '/assets/..%2fws/shell'])('refuses a WebSocket upgrade to %s without a session', async (path) => {
|
|
135
|
+
expect(await upgrade(path)).toBe(401);
|
|
136
|
+
});
|
|
137
|
+
it('upgrades a WebSocket with a session', async () => {
|
|
138
|
+
expect(await upgrade('/ws/shell', sessionCookie())).toBe(101);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=auth-gate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-gate.test.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* - Verifies platform-issued JWTs via JWKS (no extra deps).
|
|
8
8
|
* - Issues a short-lived, HMAC-signed local session cookie so subsequent
|
|
9
9
|
* /api/* requests don't have to re-validate against the platform.
|
|
10
|
-
* - Adds a Fastify
|
|
11
|
-
* /api/* or /ws/*
|
|
12
|
-
* keeps working)
|
|
10
|
+
* - Adds a Fastify onRequest hook that requires a valid session for any
|
|
11
|
+
* /api/* or /ws/* route, except /api/health (so Docker healthcheck
|
|
12
|
+
* keeps working). /auth/* (the auth endpoints themselves) stays public.
|
|
13
|
+
* The check uses the route the router matched and a normalized path,
|
|
14
|
+
* never the raw URL, see requiresSession().
|
|
13
15
|
*/
|
|
14
16
|
import type { FastifyInstance } from 'fastify';
|
|
15
17
|
export interface TAuthConfig {
|
|
@@ -36,4 +38,26 @@ export interface TAuthConfig {
|
|
|
36
38
|
export declare function acceptPictureClaim(issuer: string | null, picture: unknown): string | undefined;
|
|
37
39
|
export declare function loadAuthConfig(): TAuthConfig;
|
|
38
40
|
export declare function registerAuthRoutes(app: FastifyInstance, config: TAuthConfig): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Reduce a request path to the form the gate compares against, so that no
|
|
43
|
+
* spelling of a protected path can slip past it: percent-encoding (applied any
|
|
44
|
+
* number of times, including encoded slashes and dots), backslashes, repeated
|
|
45
|
+
* slashes, dot segments, trailing dots on a segment and letter case all fold
|
|
46
|
+
* away. Returns null when the path cannot be decoded.
|
|
47
|
+
*/
|
|
48
|
+
export declare function normalizeRequestPath(rawUrl: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a request must carry a valid session.
|
|
51
|
+
*
|
|
52
|
+
* The decision rests on what the router matched, never on the raw URL alone.
|
|
53
|
+
* The router decodes the path before matching, so a check on the raw string
|
|
54
|
+
* let `/%61pi/plugins` reach `/api/plugins` (and `/%77s/shell` open a shell)
|
|
55
|
+
* without a session. The matched route pattern is authoritative for anything
|
|
56
|
+
* that reaches a handler. The normalized path is checked as well, so an
|
|
57
|
+
* unmatched spelling of a protected path is refused rather than falling
|
|
58
|
+
* through, and a future router option (case-insensitive matching, trailing
|
|
59
|
+
* slash folding) cannot reopen the hole. A path that cannot be decoded is
|
|
60
|
+
* refused.
|
|
61
|
+
*/
|
|
62
|
+
export declare function requiresSession(routeUrl: string | undefined, rawUrl: string): boolean;
|
|
39
63
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAA;AAS9D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAc9F;AA2BD,wBAAgB,cAAc,IAAI,WAAW,CAS5C;AAkCD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFjG;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAKrF"}
|
package/dist/auth.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* - Verifies platform-issued JWTs via JWKS (no extra deps).
|
|
8
8
|
* - Issues a short-lived, HMAC-signed local session cookie so subsequent
|
|
9
9
|
* /api/* requests don't have to re-validate against the platform.
|
|
10
|
-
* - Adds a Fastify
|
|
11
|
-
* /api/* or /ws/*
|
|
12
|
-
* keeps working)
|
|
10
|
+
* - Adds a Fastify onRequest hook that requires a valid session for any
|
|
11
|
+
* /api/* or /ws/* route, except /api/health (so Docker healthcheck
|
|
12
|
+
* keeps working). /auth/* (the auth endpoints themselves) stays public.
|
|
13
|
+
* The check uses the route the router matched and a normalized path,
|
|
14
|
+
* never the raw URL, see requiresSession().
|
|
13
15
|
*/
|
|
14
16
|
import { createHmac, randomBytes } from 'crypto';
|
|
15
17
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
@@ -192,19 +194,77 @@ export async function registerAuthRoutes(app, config) {
|
|
|
192
194
|
// Gate /api/* and /ws/* when auth is enabled. Health and auth routes
|
|
193
195
|
// are public so the Docker healthcheck and login flow keep working.
|
|
194
196
|
app.addHook('onRequest', async (req, reply) => {
|
|
195
|
-
|
|
196
|
-
const gated = url.startsWith('/api/') || url.startsWith('/ws/');
|
|
197
|
-
if (!gated)
|
|
198
|
-
return;
|
|
199
|
-
if (url === '/api/health')
|
|
200
|
-
return;
|
|
201
|
-
if (url.startsWith('/auth/'))
|
|
197
|
+
if (!requiresSession(req.routeOptions?.url, req.url))
|
|
202
198
|
return;
|
|
203
199
|
const cookie = getCookie(req, COOKIE_NAME);
|
|
204
200
|
const session = cookie ? verifySession(config.secret, cookie) : null;
|
|
205
201
|
if (!session) {
|
|
206
|
-
reply.code(401).send({ error: 'unauthenticated' });
|
|
202
|
+
return reply.code(401).send({ error: 'unauthenticated' });
|
|
207
203
|
}
|
|
208
204
|
});
|
|
209
205
|
}
|
|
206
|
+
const PUBLIC_PATHS = new Set(['/api/health']);
|
|
207
|
+
function isGatedPath(path) {
|
|
208
|
+
if (PUBLIC_PATHS.has(path))
|
|
209
|
+
return false;
|
|
210
|
+
return path === '/api' || path.startsWith('/api/') || path === '/ws' || path.startsWith('/ws/');
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Reduce a request path to the form the gate compares against, so that no
|
|
214
|
+
* spelling of a protected path can slip past it: percent-encoding (applied any
|
|
215
|
+
* number of times, including encoded slashes and dots), backslashes, repeated
|
|
216
|
+
* slashes, dot segments, trailing dots on a segment and letter case all fold
|
|
217
|
+
* away. Returns null when the path cannot be decoded.
|
|
218
|
+
*/
|
|
219
|
+
export function normalizeRequestPath(rawUrl) {
|
|
220
|
+
let path = rawUrl.split('?')[0]?.split('#')[0] ?? '';
|
|
221
|
+
for (let i = 0; i < 5; i++) {
|
|
222
|
+
let decoded;
|
|
223
|
+
try {
|
|
224
|
+
decoded = decodeURIComponent(path);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
if (decoded === path)
|
|
230
|
+
break;
|
|
231
|
+
path = decoded;
|
|
232
|
+
}
|
|
233
|
+
if (/%[0-9a-f]{2}/i.test(path))
|
|
234
|
+
return null;
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const raw of path.replace(/\\/g, '/').toLowerCase().split('/')) {
|
|
237
|
+
const segment = raw.trim();
|
|
238
|
+
if (segment === '' || segment === '.')
|
|
239
|
+
continue;
|
|
240
|
+
if (segment === '..') {
|
|
241
|
+
out.pop();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// Some layers treat "api." and "api" as the same name, so the gate does too.
|
|
245
|
+
out.push(segment.replace(/[.\s]+$/, '') || segment);
|
|
246
|
+
}
|
|
247
|
+
return '/' + out.join('/');
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Whether a request must carry a valid session.
|
|
251
|
+
*
|
|
252
|
+
* The decision rests on what the router matched, never on the raw URL alone.
|
|
253
|
+
* The router decodes the path before matching, so a check on the raw string
|
|
254
|
+
* let `/%61pi/plugins` reach `/api/plugins` (and `/%77s/shell` open a shell)
|
|
255
|
+
* without a session. The matched route pattern is authoritative for anything
|
|
256
|
+
* that reaches a handler. The normalized path is checked as well, so an
|
|
257
|
+
* unmatched spelling of a protected path is refused rather than falling
|
|
258
|
+
* through, and a future router option (case-insensitive matching, trailing
|
|
259
|
+
* slash folding) cannot reopen the hole. A path that cannot be decoded is
|
|
260
|
+
* refused.
|
|
261
|
+
*/
|
|
262
|
+
export function requiresSession(routeUrl, rawUrl) {
|
|
263
|
+
if (routeUrl !== undefined && isGatedPath(routeUrl))
|
|
264
|
+
return true;
|
|
265
|
+
const path = normalizeRequestPath(rawUrl);
|
|
266
|
+
if (path === null)
|
|
267
|
+
return true;
|
|
268
|
+
return isGatedPath(path);
|
|
269
|
+
}
|
|
210
270
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAa7B,OAAO,KAAK,EAAE,cAAc,EAA0C,MAAM,0BAA0B,CAAA;AAItG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AASjF,OAAO,EAA4B,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAwC7B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAuB,MAAM,mBAAmB,CAAA;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAGL,KAAK,mBAAmB,EAEzB,MAAM,2BAA2B,CAAA;AAElC,MAAM,WAAW,OAAO;IACtB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAWD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,aAAa,GAAG,IAAW,EACnC,OAAO,GAAE,OAAO,GAAG,IAAW,EAC9B,kBAAkB,GAAE,MAAM,EAAO,EACjC,mBAAmB,GAAE,GAAG,CAAC,MAAM,CAAa,EAC5C,QAAQ,GAAE,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAa,EAC3E,kBAAkB,GAAE,GAAG,CAAC,MAAM,CAAa,EAC3C,iBAAiB,GAAE,iBAAiB,GAAG,IAAW,EAClD,mBAAmB,GAAE,mBAAmB,GAAG,IAAW;AACtD;;;;GAIG;AACH,sBAAsB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC,GAAG,IAAW;AAC5E,4EAA4E;AAC5E,mBAAmB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAW;AACxE,6EAA6E;AAC7E,YAAY,GAAE,cAAc,GAAG,IAAW;AAC1C,+EAA+E;AAC/E,YAAY,GAAE,gBAAgB,GAAG,IAAW;AAC5C;;;;;GAKG;AACH,kBAAkB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,IAAW,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAa7B,OAAO,KAAK,EAAE,cAAc,EAA0C,MAAM,0BAA0B,CAAA;AAItG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AASjF,OAAO,EAA4B,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAwC7B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAuB,MAAM,mBAAmB,CAAA;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAGL,KAAK,mBAAmB,EAEzB,MAAM,2BAA2B,CAAA;AAElC,MAAM,WAAW,OAAO;IACtB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAWD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,aAAa,GAAG,IAAW,EACnC,OAAO,GAAE,OAAO,GAAG,IAAW,EAC9B,kBAAkB,GAAE,MAAM,EAAO,EACjC,mBAAmB,GAAE,GAAG,CAAC,MAAM,CAAa,EAC5C,QAAQ,GAAE,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAa,EAC3E,kBAAkB,GAAE,GAAG,CAAC,MAAM,CAAa,EAC3C,iBAAiB,GAAE,iBAAiB,GAAG,IAAW,EAClD,mBAAmB,GAAE,mBAAmB,GAAG,IAAW;AACtD;;;;GAIG;AACH,sBAAsB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC,GAAG,IAAW;AAC5E,4EAA4E;AAC5E,mBAAmB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAW;AACxE,6EAA6E;AAC7E,YAAY,GAAE,cAAc,GAAG,IAAW;AAC1C,+EAA+E;AAC/E,YAAY,GAAE,gBAAgB,GAAG,IAAW;AAC5C;;;;;GAKG;AACH,kBAAkB,GAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,IAAW,sSAszD9F"}
|
package/dist/server.js
CHANGED
|
@@ -62,7 +62,9 @@ observations = null,
|
|
|
62
62
|
*/
|
|
63
63
|
observationContext = null) {
|
|
64
64
|
const app = Fastify({ logger: false });
|
|
65
|
-
|
|
65
|
+
// @fastify/cors 11 narrowed its default to the CORS-safelisted methods. Keep the
|
|
66
|
+
// set it allowed before, so cross-origin clients see the same preflight answers.
|
|
67
|
+
await app.register(cors, { origin: true, methods: 'GET,HEAD,PUT,PATCH,POST,DELETE' });
|
|
66
68
|
await app.register(websocket);
|
|
67
69
|
// Optional: Nubisco Platform auth. No-op when PLATFORM_ENABLED is unset.
|
|
68
70
|
const authConfig = loadAuthConfig();
|
|
@@ -119,8 +121,7 @@ observationContext = null) {
|
|
|
119
121
|
};
|
|
120
122
|
});
|
|
121
123
|
// ─── Update progress WebSocket ────────────────────────────────────────────
|
|
122
|
-
app.get('/ws/updates', { websocket: true }, (
|
|
123
|
-
const ws = connection.socket;
|
|
124
|
+
app.get('/ws/updates', { websocket: true }, (ws) => {
|
|
124
125
|
const listener = (msg) => {
|
|
125
126
|
try {
|
|
126
127
|
ws.send(JSON.stringify(msg));
|
|
@@ -361,8 +362,7 @@ observationContext = null) {
|
|
|
361
362
|
return hapInfo ?? { setupURI: null, pincode: null };
|
|
362
363
|
});
|
|
363
364
|
// ─── Live metrics WebSocket ───────────────────────────────────────────────
|
|
364
|
-
app.get('/ws/metrics', { websocket: true }, (
|
|
365
|
-
const ws = connection.socket;
|
|
365
|
+
app.get('/ws/metrics', { websocket: true }, (ws) => {
|
|
366
366
|
// Send history immediately on connect
|
|
367
367
|
ws.send(JSON.stringify({ type: 'history', data: getHistory() }));
|
|
368
368
|
const unsub = onMetrics((snap) => {
|
|
@@ -1465,8 +1465,7 @@ observationContext = null) {
|
|
|
1465
1465
|
const entries = Logger.getEntries(query.plugin, query.limit ? parseInt(query.limit) : 200);
|
|
1466
1466
|
return { entries };
|
|
1467
1467
|
});
|
|
1468
|
-
app.get('/ws/logs', { websocket: true }, (
|
|
1469
|
-
const ws = connection.socket;
|
|
1468
|
+
app.get('/ws/logs', { websocket: true }, (ws) => {
|
|
1470
1469
|
log.debug('Log WebSocket client connected');
|
|
1471
1470
|
const unsubscribe = Logger.subscribe((entry) => {
|
|
1472
1471
|
if (ws.readyState === ws.OPEN)
|
|
@@ -1688,8 +1687,7 @@ observationContext = null) {
|
|
|
1688
1687
|
catch {
|
|
1689
1688
|
log.info('node-pty is not installed: the interactive shell pane is disabled. Everything else runs normally.');
|
|
1690
1689
|
}
|
|
1691
|
-
app.get('/ws/shell', { websocket: true }, (
|
|
1692
|
-
const ws = connection.socket;
|
|
1690
|
+
app.get('/ws/shell', { websocket: true }, (ws) => {
|
|
1693
1691
|
let pty = null;
|
|
1694
1692
|
if (!shellAvailable) {
|
|
1695
1693
|
ws.send(`\r\n\x1b[33mInteractive shell unavailable: the optional 'node-pty' dependency is not installed.\x1b[0m\r\n` +
|
|
@@ -1744,8 +1742,7 @@ observationContext = null) {
|
|
|
1744
1742
|
});
|
|
1745
1743
|
});
|
|
1746
1744
|
// Terminal WebSocket: streams ANSI-formatted log lines for xterm.js
|
|
1747
|
-
app.get('/ws/terminal', { websocket: true }, (
|
|
1748
|
-
const ws = connection.socket;
|
|
1745
|
+
app.get('/ws/terminal', { websocket: true }, (ws) => {
|
|
1749
1746
|
function ansiLine(entry) {
|
|
1750
1747
|
const ANSI = {
|
|
1751
1748
|
debug: '\x1b[90m',
|