dsh-daily-brief 2.0.2 → 2.0.3
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/lib/host/routes.js +44 -12
- package/package.json +1 -1
package/lib/host/routes.js
CHANGED
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Host HTTP surface used by the client panel (relative `api/daily-brief/*`).
|
|
3
|
+
*
|
|
4
|
+
* The plugin owns this route, so it also owns the request policy. On hosts that
|
|
5
|
+
* expose DSH's Connection service every call passes the same Host/Origin fence
|
|
6
|
+
* and browser-session check as the built-in `/api` bridge
|
|
7
|
+
* (`connection.requestRejection`). That covers both the Web GUI and the Desktop
|
|
8
|
+
* renderer: the `dsh-app` scheme forwards an authenticated, same-origin request
|
|
9
|
+
* for the panel, so no plugin-specific header is needed.
|
|
10
|
+
*
|
|
11
|
+
* Hosts without that service keep the historical local-only gate, now strict:
|
|
12
|
+
* the request must come from loopback *and* carry `x-daily-brief-client: 1`.
|
|
4
13
|
*/
|
|
5
14
|
const PREFIX = '/api/daily-brief';
|
|
15
|
+
const CLIENT_HEADER = 'x-daily-brief-client';
|
|
6
16
|
|
|
7
17
|
function isLoopback(req) {
|
|
8
18
|
const address = req.socket?.remoteAddress || '';
|
|
9
19
|
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
|
|
10
20
|
}
|
|
11
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Decide whether one request may reach the panel API.
|
|
24
|
+
* @returns `undefined` when admitted, otherwise the status and error to answer with.
|
|
25
|
+
*/
|
|
26
|
+
function admissionFailure(ctx, req) {
|
|
27
|
+
let connection;
|
|
28
|
+
try {
|
|
29
|
+
connection = typeof ctx.get === 'function' ? ctx.get('connection') : ctx.connection;
|
|
30
|
+
} catch {
|
|
31
|
+
connection = undefined;
|
|
32
|
+
}
|
|
33
|
+
if (connection && typeof connection.requestRejection === 'function') {
|
|
34
|
+
try {
|
|
35
|
+
const status = connection.requestRejection(req);
|
|
36
|
+
if (!status) return undefined;
|
|
37
|
+
return {
|
|
38
|
+
status,
|
|
39
|
+
error: status === 401 ? '需要 DSH 网页会话认证' : '请求来源不受信任',
|
|
40
|
+
};
|
|
41
|
+
} catch {
|
|
42
|
+
return { status: 503, error: '暂时无法校验请求来源' };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!isLoopback(req)) return { status: 403, error: '仅限本机访问' };
|
|
46
|
+
if (req.headers[CLIENT_HEADER] !== '1') return { status: 403, error: '客户端标识无效' };
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
12
50
|
function sendJson(res, status, body) {
|
|
13
51
|
const payload = JSON.stringify(body ?? null);
|
|
14
52
|
res.statusCode = status;
|
|
@@ -55,12 +93,13 @@ export function registerRoutes(ctx, service) {
|
|
|
55
93
|
const sub = url.pathname.slice(PREFIX.length) || '/';
|
|
56
94
|
const method = req.method || 'GET';
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
96
|
+
const failure = admissionFailure(ctx, req);
|
|
97
|
+
if (failure) {
|
|
98
|
+
sendJson(res, failure.status, { error: failure.error });
|
|
60
99
|
return;
|
|
61
100
|
}
|
|
62
101
|
|
|
63
|
-
//
|
|
102
|
+
// `/artifact` has no panel client of its own; admission above is the guard.
|
|
64
103
|
if (sub === '/artifact' && method === 'GET') {
|
|
65
104
|
const id = url.searchParams.get('id') || 'latest';
|
|
66
105
|
const briefing = await service.getBriefing(id);
|
|
@@ -88,13 +127,6 @@ export function registerRoutes(ctx, service) {
|
|
|
88
127
|
return;
|
|
89
128
|
}
|
|
90
129
|
|
|
91
|
-
// The Desktop renderer reaches these routes through the dsh-app scheme handler,
|
|
92
|
-
// which cannot attach custom headers on preflighted reads; loopback is the guard.
|
|
93
|
-
if (req.headers['x-daily-brief-client'] !== undefined && req.headers['x-daily-brief-client'] !== '1') {
|
|
94
|
-
sendJson(res, 403, { error: '客户端标识无效' });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
130
|
try {
|
|
99
131
|
if (sub === '/state' && method === 'GET') {
|
|
100
132
|
sendJson(res, 200, await service.getState());
|