Skip to content

feat(worker): complete Fetch web console support#26

Open
one-ea wants to merge 6 commits into
mainfrom
260701-feat-worker-console-completion
Open

feat(worker): complete Fetch web console support#26
one-ea wants to merge 6 commits into
mainfrom
260701-feat-worker-console-completion

Conversation

@one-ea

@one-ea one-ea commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • route Web Console dashboard, config, operations, metrics, login, logout, and POST actions through the shared Fetch handler
  • add Worker-safe signed cookie sessions for Web Console auth
  • delegate Node Web Console requests to the shared Fetch handler while preserving Telegram webhook handling
  • serve Web Console routes from the Workers runtime and keep Telegram webhook routing isolated
  • add Workers completion plan and update Wrangler compatibility date

Validation

  • npm run verify
  • TypeScript check passed
  • 70 tests passed
  • npm audit found 0 vulnerabilities

Cloudflare token status

  • CLOUDFLARE_TOKEN is not present in this environment
  • wrangler command is not installed in this environment
  • Wiki smoke checklist was updated for deployment-time validation

Wiki

  • pushed wiki documentation update: eb04f8a docs: add Cloudflare Workers smoke checklist

Summary by CodeRabbit

  • 新功能

    • Web Console 现在支持更统一的登录、登出与会话管理体验,并可在 Worker 环境中直接访问相关页面。
    • 增加了更安全的会话保留方式,提升登录状态在不同运行环境中的一致性。
  • Bug 修复

    • 修正了 Worker 路由对 Web Console 与 Telegram webhook 的处理,避免请求互相干扰。
    • 改进了登录失效后的重定向与会话清理行为。
  • 文档

    • 新增了 Web Console 完成计划与验收检查清单。

Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

本次改动为 Web Console 引入基于 HMAC 签名的会话 Cookie 机制,并将 Web Console 请求处理重构为 Fetch API 风格(Request/Response),同时在 Cloudflare Worker 路由中接入 Web Console 处理逻辑并区分 Telegram webhook 路径,附带相应测试与计划文档、配置日期更新。

Changes

Web Console 会话与 Fetch 化路由

Layer / File(s) Summary
签名会话 Cookie 模块
src/runtime/web-console-session.ts
新增 WebConsoleSessionKind 类型及 createSignedSessionCookie/verifySignedSessionCookie/expireSessionCookie 函数,配合内部 HMAC 签名、base64url 编解码、Cookie 解析与常量时间比较工具实现签名会话。
Web Console Fetch 化与鉴权重构
src/runtime/web-console.ts
将路由处理改为基于 Request/Response 的 Fetch 风格,WebConsoleOptions 新增 sessionSecret/sessionMaxAgeSeconds/now,登录/登出根据是否配置签名密钥选择签名 Cookie 或旧随机 token 会话,重构表单读取与鉴权辅助逻辑,移除旧 IncomingMessage 侧实现。
Worker 路由接入 Web Console
src/runtime/worker.ts
新增 isWebConsolePathhandleWorkerWebConsoleRequest,校验 WEB_CONSOLE_SESSION_SECRET,基于 configIssues 计算运行状态并转发 Web Console 请求,Telegram webhook 路径与 Web Console 路径保持独立。
测试覆盖与配置/文档
test/core.test.ts, wrangler.toml, docs/superpowers/plans/2026-07-01-workers-completion.md
新增签名 Cookie 签发/过期校验、Fetch 认证/登录登出、Worker 路由与 webhook 隔离的测试用例;更新 wrangler.toml 兼容日期;新增实施计划文档。

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • one-ea/InboxBridge#25: 同样修改了 Workers 运行时入口 src/runtime/worker.ts 与共享的 Fetch 路由/Telegram webhook 处理逻辑,本次 PR 在此基础上扩展了 Web Console 路由与会话处理。
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了核心改动:在 Worker 中完成基于 Fetch 的 Web Console 支持。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 260701-feat-worker-console-completion

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-07-01-workers-completion.md`:
- Line 321: The plan document’s SessionKind examples and self-review are
inconsistent with the implemented contract, which uses "password" instead of
"admin". Update the SessionKind references in the affected Task examples and the
Self-Review to match the actual types used by web-console-session.ts and the
related test signatures, ensuring all documented session kind values are
"password" | "setup" and no "admin" remains.

In `@src/runtime/web-console-session.ts`:
- Line 22: The session cookie built in web-console-session’s cookie setter is
missing the Secure attribute for production use. Update the Set-Cookie string
construction in the session cookie helper so `inboxbridge_session` is marked
`Secure` by default in HTTPS/Worker environments, while keeping any HTTP/local
override logic at the call site if needed; use the existing cookie assembly in
`web-console-session` as the single place to add this flag.

In `@src/runtime/web-console.ts`:
- Around line 376-397: POST /login 的失败分支没有任何限流,导致无效登录请求可以被无限重试。请在 web-console
的登录处理逻辑中,围绕 loginSessionKind / renderLogin 这条失败路径加入按
IP、用户名或会话维度的失败计数与冷却/封禁机制,并在超过阈值时直接拒绝而不是继续渲染登录页;同时保持成功登录时清理或重置对应计数,确保 public
console 不易被暴力尝试。
- Around line 469-477: `readNodeRequestBody` currently buffers the entire
request before any size check, so large POST bodies can exhaust memory before
`readRequestForm` enforces `maxFormBodyBytes`. Update `readNodeRequestBody` to
track accumulated bytes inside the `for await` loop and throw
`FormBodyTooLargeError` as soon as the limit is exceeded, keeping the check
synchronous with streaming reads; use the existing `readNodeRequestBody` and
`readRequestForm` flow as the integration points.

In `@src/runtime/worker.ts`:
- Around line 67-68: The current WEB_CONSOLE_SESSION_SECRET check in the worker
only verifies presence, so weak values like “test” or “123” can still be
accepted. Update the validation in the worker’s session secret handling to
enforce a minimum strength requirement, such as requiring a 32-byte random
secret (or equivalent entropy) before continuing, and keep returning a 503 from
the same json error path when the secret is missing or too weak. Use the
existing WEB_CONSOLE_SESSION_SECRET gate in src/runtime/worker.ts as the place
to add the stronger validation.
- Around line 98-102: Worker 控制台操作现在用
listConversations/listFailedDeliveries/listAuditLogs/searchMessages
返回空结果、scheduleRetry 也只是 no-op,导致 /operations/deliveries/retry
之类入口会显示成功但没有任何数据库写入。请在 src/runtime/worker.ts 中基于现有的 Worker 操作实现补上 D1 版本回调(尤其是
scheduleRetry 和各个 list* 方法),确保这些方法真实读写数据库;如果暂时无法实现,则在 Worker
的控制台入口里禁用或隐藏这些未实现的操作,避免返回假成功。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9fe7b00f-73ae-4378-b940-df800aaa475f

📥 Commits

Reviewing files that changed from the base of the PR and between d3ecff3 and f46d58f.

📒 Files selected for processing (6)
  • docs/superpowers/plans/2026-07-01-workers-completion.md
  • src/runtime/web-console-session.ts
  • src/runtime/web-console.ts
  • src/runtime/worker.ts
  • test/core.test.ts
  • wrangler.toml

Create `src/runtime/web-console-session.ts`:

```ts
export type SessionKind = "setup" | "admin";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

计划文档中的 SessionKind 取值与实际实现不一致。

本计划文档在 Task 3 的示例代码(SessionKind = "setup" | "admin",第 321 行)以及 Task 1/2/5 的测试片段中反复使用 "admin" 作为会话种类;但根据本次改动引入的实际实现(web-console-session.tstest/core.test.ts 中新增的签名 Cookie 测试均使用 kind: "password"),真实的 SessionKind/WebConsoleSessionKind 取值应为 "password" | "setup"。第 723 行的 Self-Review 甚至断言“Session kind remains "setup" | "admin"”,这与实际交付的代码契约不符,未来若有人依据本计划继续实施后续任务(Task 6/7 或衍生工作),可能被误导。

建议将文档中所有 "admin" 更新为 "password",并修正第 723 行的自查结论。

📝 建议修正
- Type consistency: Session kind remains `"setup" | "admin"`; Fetch handler remains `handleWebConsoleRequest(request, options, sessions?)`; Worker entry remains `handleWorkerFetch(request, env, options?)`.
+ Type consistency: Session kind remains `"setup" | "password"`; Fetch handler remains `handleWebConsoleRequest(request, options, sessions?)`; Worker entry remains `handleWorkerFetch(request, env, options?)`.

Also applies to: 354-354, 723-723

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-07-01-workers-completion.md` at line 321, The
plan document’s SessionKind examples and self-review are inconsistent with the
implemented contract, which uses "password" instead of "admin". Update the
SessionKind references in the affected Task examples and the Self-Review to
match the actual types used by web-console-session.ts and the related test
signatures, ensuring all documented session kind values are "password" | "setup"
and no "admin" remains.

const expiresAt = Math.floor(input.now.getTime() / 1000) + input.maxAgeSeconds;
const payload = base64UrlEncode(new TextEncoder().encode(JSON.stringify({ kind: input.kind, exp: expiresAt })));
const signature = await sign(input.secret, payload);
return `${cookieName}=${payload}.${signature}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${input.maxAgeSeconds}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

为会话 Cookie 增加生产环境 Secure 属性。

inboxbridge_session 是控制台认证凭据;当前 Set-Cookie 未带 Secure,生产/Worker HTTPS 场景下应避免 Cookie 被明文 HTTP 请求携带。若仍需支持本地 HTTP,可在调用层按协议配置,但 Worker/生产默认应启用。

建议修复方向
-  return `${cookieName}=${payload}.${signature}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${input.maxAgeSeconds}`;
+  return `${cookieName}=${payload}.${signature}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${input.maxAgeSeconds}`;
...
-  return `${cookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
+  return `${cookieName}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`;

Also applies to: 44-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/web-console-session.ts` at line 22, The session cookie built in
web-console-session’s cookie setter is missing the Secure attribute for
production use. Update the Set-Cookie string construction in the session cookie
helper so `inboxbridge_session` is marked `Secure` by default in HTTPS/Worker
environments, while keeping any HTTP/local override logic at the call site if
needed; use the existing cookie assembly in `web-console-session` as the single
place to add this flag.

Comment on lines +376 to +397
if (url.pathname === "/login" && request.method === "POST") {
const form = await readRequestForm(request);
const sessionKind = await loginSessionKind(options.settings, form);
if (sessionKind) {
const serverResponse = res.asServerResponse();
if (options.sessionSecret) {
serverResponse.setHeader("set-cookie", await createSignedSessionCookie({
secret: options.sessionSecret,
kind: sessionKind,
now: currentDate(options),
maxAgeSeconds: options.sessionMaxAgeSeconds ?? 60 * 60 * 8,
}));
} else {
const session = randomBytes(24).toString("hex");
sessions.set(session, sessionKind);
serverResponse.setHeader("set-cookie", `${sessionCookie}=${session}; HttpOnly; SameSite=Lax; Path=/`);
}
redirect(serverResponse, "/");
return res.toResponse();
}
await renderLogin(res.asServerResponse(), options.settings, "登录凭据无效。");
return res.toResponse();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

登录失败路径需要限流。

POST /login 对无效凭据直接重新渲染,没有按 IP/账号/会话做失败次数限制;公开部署控制台时会放大密码或 setup token 暴力尝试风险。As per path instructions, src/**/*.ts: “重点审查 ... 限流 ... 和用户数据泄露的问题”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/web-console.ts` around lines 376 - 397, POST /login
的失败分支没有任何限流,导致无效登录请求可以被无限重试。请在 web-console 的登录处理逻辑中,围绕 loginSessionKind /
renderLogin 这条失败路径加入按
IP、用户名或会话维度的失败计数与冷却/封禁机制,并在超过阈值时直接拒绝而不是继续渲染登录页;同时保持成功登录时清理或重置对应计数,确保 public
console 不易被暴力尝试。

Source: Path instructions

Comment on lines +469 to +477
const body = method === "GET" || method === "HEAD" ? undefined : await readNodeRequestBody(req);
return new Request(url, { method, headers, body });
}

async function readNodeRequestBody(req: IncomingMessage): Promise<ArrayBuffer> {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
const body = Buffer.concat(chunks);
return body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

读取 Node 请求体时同步限制大小。

这里先完整 Buffer.concatreadRequestForm 才检查 maxFormBodyBytes;攻击者可向 /login 或认证 POST 发送超大 body,在触发 413 前耗尽内存。请在 for await 循环中累计字节并提前抛出 FormBodyTooLargeError

建议修复
 async function readNodeRequestBody(req: IncomingMessage): Promise<ArrayBuffer> {
   const chunks: Buffer[] = [];
-  for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
+  let size = 0;
+  for await (const chunk of req) {
+    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
+    size += buffer.length;
+    if (size > maxFormBodyBytes) throw new FormBodyTooLargeError("form body too large");
+    chunks.push(buffer);
+  }
   const body = Buffer.concat(chunks);
   return body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const body = method === "GET" || method === "HEAD" ? undefined : await readNodeRequestBody(req);
return new Request(url, { method, headers, body });
}
async function readNodeRequestBody(req: IncomingMessage): Promise<ArrayBuffer> {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
const body = Buffer.concat(chunks);
return body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;
const body = method === "GET" || method === "HEAD" ? undefined : await readNodeRequestBody(req);
return new Request(url, { method, headers, body });
}
async function readNodeRequestBody(req: IncomingMessage): Promise<ArrayBuffer> {
const chunks: Buffer[] = [];
let size = 0;
for await (const chunk of req) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
size += buffer.length;
if (size > maxFormBodyBytes) throw new FormBodyTooLargeError("form body too large");
chunks.push(buffer);
}
const body = Buffer.concat(chunks);
return body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/web-console.ts` around lines 469 - 477, `readNodeRequestBody`
currently buffers the entire request before any size check, so large POST bodies
can exhaust memory before `readRequestForm` enforces `maxFormBodyBytes`. Update
`readNodeRequestBody` to track accumulated bytes inside the `for await` loop and
throw `FormBodyTooLargeError` as soon as the limit is exceeded, keeping the
check synchronous with streaming reads; use the existing `readNodeRequestBody`
and `readRequestForm` flow as the integration points.

Comment thread src/runtime/worker.ts
Comment on lines +67 to +68
const sessionSecret = typeof env.WEB_CONSOLE_SESSION_SECRET === "string" ? env.WEB_CONSOLE_SESSION_SECRET : "";
if (!sessionSecret) return json({ error: "WEB_CONSOLE_SESSION_SECRET is required" }, { status: 503 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

校验 WEB_CONSOLE_SESSION_SECRET 强度。

仅检查存在会接受 "test"/"123" 这类低熵密钥;攻击者一旦猜中 HMAC secret 即可伪造 password 会话 Cookie。建议要求至少 32 字节随机值,并在不满足时返回 503。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/worker.ts` around lines 67 - 68, The current
WEB_CONSOLE_SESSION_SECRET check in the worker only verifies presence, so weak
values like “test” or “123” can still be accepted. Update the validation in the
worker’s session secret handling to enforce a minimum strength requirement, such
as requiring a 32-byte random secret (or equivalent entropy) before continuing,
and keep returning a 503 from the same json error path when the secret is
missing or too weak. Use the existing WEB_CONSOLE_SESSION_SECRET gate in
src/runtime/worker.ts as the place to add the stronger validation.

Comment thread src/runtime/worker.ts
Comment on lines +98 to +102
listConversations: () => ({ items: [], total: 0 }),
listFailedDeliveries: () => ({ items: [], total: 0 }),
scheduleRetry: async () => {},
listAuditLogs: () => ({ items: [], total: 0 }),
searchMessages: () => ({ items: [], total: 0 }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

不要让 Worker 控制台操作返回假成功。

list* 全部返回空数据且 scheduleRetry 是 no-op,但 /operations/deliveries/retry 会重定向到成功状态;管理员会看到“已重试”但数据库没有任何写入。请实现 D1 版本回调,或在 Worker 中禁用/隐藏未实现的操作入口。As per path instructions, src/**/*.ts: “重点审查 ... 数据库写入逻辑”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/worker.ts` around lines 98 - 102, Worker 控制台操作现在用
listConversations/listFailedDeliveries/listAuditLogs/searchMessages
返回空结果、scheduleRetry 也只是 no-op,导致 /operations/deliveries/retry
之类入口会显示成功但没有任何数据库写入。请在 src/runtime/worker.ts 中基于现有的 Worker 操作实现补上 D1 版本回调(尤其是
scheduleRetry 和各个 list* 方法),确保这些方法真实读写数据库;如果暂时无法实现,则在 Worker
的控制台入口里禁用或隐藏这些未实现的操作,避免返回假成功。

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants