dsh-todo-time-log 0.5.2 → 0.6.0
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 +10 -2
- package/lib/index.js +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| 中文显示名 | 待办时间日志 |
|
|
10
10
|
| 短别名 | `todo-time-log`、`ttl`(文档与工具前缀用;dsh 的 Entry / Slot / Tool 标识统一用包名) |
|
|
11
11
|
| 数据文件 | `<DSH_PROFILE_DIR 或 ~/.dsh>/todo-time-log/todos.json`(可用环境变量或行配置覆盖) |
|
|
12
|
-
| HTTP 接口 | `POST /api/dsh-todo-time-log
|
|
12
|
+
| HTTP 接口 | `POST /api/dsh-todo-time-log/*`(**经宿主认证**:每条请求先过 `connection.requestRejection()`,无登录 cookie 返回 401、跨站/不可信来源返回 403) |
|
|
13
13
|
| 智能体工具 | `todo_time_log`(action = list / get / create / complete / uncomplete / update / delete / health) |
|
|
14
14
|
| UI 位置 | 中央面板 `main`(key = `dsh-todo-time-log`)+ 侧边栏图标 `sidebar.panellist`(id 同名,点击打开面板) |
|
|
15
15
|
| 视图 | 四视图:**表格 / 看板 / 甘特图 / 日历**(甘特条 = 创建 → 完成,未完成延伸到「现在」;四视图内均可完成/编辑/删除) |
|
|
@@ -188,7 +188,15 @@ export function apply(ctx, config = {}) {
|
|
|
188
188
|
|
|
189
189
|
mountOn(ctx, ['webServer'], (scope) => { // 路由:供 UI 调用
|
|
190
190
|
scope.effect(() => scope.webServer.register({
|
|
191
|
-
kind: 'prefix', path: ROUTE_PREFIX,
|
|
191
|
+
kind: 'prefix', path: ROUTE_PREFIX,
|
|
192
|
+
handler: createHttpHandler(store, {
|
|
193
|
+
// 宿主认证:connection 缺失或抛错一律拒绝,不退回到 Host/Origin 自检
|
|
194
|
+
authorize: (req) => {
|
|
195
|
+
const connection = ctx.get('connection');
|
|
196
|
+
if (!connection) return 403;
|
|
197
|
+
return connection.requestRejection(req) ?? undefined;
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
192
200
|
}));
|
|
193
201
|
});
|
|
194
202
|
|
package/lib/index.js
CHANGED
|
@@ -219,6 +219,9 @@ import { createFileIndex } from './files.js';
|
|
|
219
219
|
* @property {() => string} [getWorkspaceRoot]
|
|
220
220
|
* @property {string} [workspaceRoot]
|
|
221
221
|
* @property {() => Array<{ path: string, title: string, source: string }>} [listWorkspaces]
|
|
222
|
+
* @property {(req: import('node:http').IncomingMessage) => number | undefined | null} [authorize]
|
|
223
|
+
* Host admission check; `apply()` wires `connection.requestRejection()`. When
|
|
224
|
+
* omitted (direct handler tests) every request is admitted.
|
|
222
225
|
*/
|
|
223
226
|
|
|
224
227
|
export const name = 'dsh-todo-time-log';
|
|
@@ -1113,6 +1116,15 @@ export function createHttpHandler(store, options = {}) {
|
|
|
1113
1116
|
const listWorkspaces = typeof options.listWorkspaces === 'function'
|
|
1114
1117
|
? options.listWorkspaces
|
|
1115
1118
|
: () => [{ path: getWorkspaceRoot(), title: '', source: 'default' }];
|
|
1119
|
+
/**
|
|
1120
|
+
* Host admission check, supplied by `apply()` from the composition's
|
|
1121
|
+
* `connection` service. It returns the HTTP status that rejects the request
|
|
1122
|
+
* (`401`/`403`) or a falsy value when the caller is an authenticated Peer.
|
|
1123
|
+
* Omitting it (tests that drive the handler directly) admits every request.
|
|
1124
|
+
*
|
|
1125
|
+
* @type {((req: import('node:http').IncomingMessage) => number | undefined | null) | null}
|
|
1126
|
+
*/
|
|
1127
|
+
const authorize = typeof options.authorize === 'function' ? options.authorize : null;
|
|
1116
1128
|
const resolveRequestRoot = (requested) => {
|
|
1117
1129
|
const candidates = listWorkspaces();
|
|
1118
1130
|
if (typeof requested !== 'string' || requested.trim() === '') {
|
|
@@ -1132,6 +1144,27 @@ export function createHttpHandler(store, options = {}) {
|
|
|
1132
1144
|
return { root: match.path, candidates, error: null };
|
|
1133
1145
|
};
|
|
1134
1146
|
return async function handler(req, res) {
|
|
1147
|
+
// Authenticate before touching any data: the routes below read and write
|
|
1148
|
+
// the todo file and enumerate the workspace and live sessions, so they must
|
|
1149
|
+
// stay behind the host's Host/Origin fence and login-token cookie.
|
|
1150
|
+
if (authorize) {
|
|
1151
|
+
let rejection;
|
|
1152
|
+
try {
|
|
1153
|
+
rejection = authorize(req);
|
|
1154
|
+
} catch {
|
|
1155
|
+
rejection = 403;
|
|
1156
|
+
}
|
|
1157
|
+
if (rejection) {
|
|
1158
|
+
sendJson(res, rejection === 401 ? 401 : 403, {
|
|
1159
|
+
ok: false,
|
|
1160
|
+
error: {
|
|
1161
|
+
code: rejection === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
1162
|
+
message: '请求未经 DSH 宿主认证(connection.requestRejection)',
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1135
1168
|
let url;
|
|
1136
1169
|
try {
|
|
1137
1170
|
url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
@@ -1394,6 +1427,15 @@ export function apply(ctx, config = {}) {
|
|
|
1394
1427
|
fileIndex,
|
|
1395
1428
|
getWorkspaceRoot,
|
|
1396
1429
|
listWorkspaces: () => listWorkspaceCandidates(ctx, config),
|
|
1430
|
+
// Host admission: the composition's `connection` service owns the
|
|
1431
|
+
// Host/Origin fence and browser authentication. A missing or failing
|
|
1432
|
+
// service rejects the request instead of falling back to a weaker check.
|
|
1433
|
+
authorize: (req) => {
|
|
1434
|
+
const connection = ctx.get('connection');
|
|
1435
|
+
if (!connection || typeof connection.requestRejection !== 'function') return 403;
|
|
1436
|
+
const rejection = connection.requestRejection(req);
|
|
1437
|
+
return rejection === undefined || rejection === null ? undefined : rejection;
|
|
1438
|
+
},
|
|
1397
1439
|
}),
|
|
1398
1440
|
}));
|
|
1399
1441
|
logger.info(`routes mounted at ${ROUTE_PREFIX}`);
|