Skip to content

feat(captcha): switchable multi-provider set + GeeTest v4 + Cap - #30

Merged
wyf9 merged 5 commits into
mainfrom
feat/captcha-enhance-and-2fa
Sep 6, 2026
Merged

feat(captcha): switchable multi-provider set + GeeTest v4 + Cap#30
wyf9 merged 5 commits into
mainfrom
feat/captcha-enhance-and-2fa

Conversation

@w9auto

@w9auto w9auto commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Evolves the captcha system from a single site-wide provider into an ordered, switchable set of providers, and adds GeeTest v4 and Cap as new providers.

  • Switchable setcaptcha_providers[]: element 0 is the default rendered first; the rest are alternates a visitor can switch to. The switch control is manual, revealed on a verification failure, and revealed after captcha_switch_timeout_seconds (default 15s, 0 = failure-only). Switching never happens automatically; the chosen provider is remembered in localStorage.
  • Per-provider credentials — every provider has its own keys and can be configured at once. A submission names its provider; the server verifies against that provider and rejects any provider not in the enabled set.
  • Migration — a read-time migration seeds captcha_providers + per-provider credentials from the legacy captcha_provider + shared key pair, so existing deployments keep working until the settings are next saved.

New providers

  • GeeTest v4 (worker/lib/geetest.ts) — HMAC-signed server validation; geetest_fail_open chooses the posture on a GeeTest outage (default: fail closed).
  • Cap (worker/lib/cap.ts) — embedded via capjs-core (stateless signed-JWT challenges; single-use nonce + redeem token in KV_CACHE; HMAC secret derived from the JWT secret; routes POST /api/auth/cap/{challenge,redeem}) or external Cap Standalone. esbuild / javascript-obfuscator are aliased to a stub in wrangler.jsonc because capjs-core lazily imports them for instrumentation obfuscation levels the embedded path never uses.

The legacy Rust→WASM pow provider is retained (deprecated in favour of Cap).

Changes

  • Backend: verifyCaptchaToken rewrite (provider discriminator + enabled-set check + dispatch), captchaPublic.ts public descriptor, Cap challenge/redeem routes, config defaults + migration, SENSITIVE_CONFIG_KEYS / admin allowlist / redaction / log redaction / secret-migration lists.
  • Frontend: Captcha.tsx multi-provider + switch UI + localStorage; GeeTest v4 + Cap renderers; 5 gated pages updated (login, register, join, verify-choose, 2FA); AdminSettings provider-set editor (default + alternates with reorder + per-provider panels).
  • i18n (en + zh), docs (configuration, architecture, admin + zh mirrors), CONTEXT.md glossary, ADR 0001-switchable-captcha-set.
  • Deps: capjs-core, @cap.js/widget.

Testing

  • bunx tsc -b ✅ · bun run lint ✅ · bun test (84 pass) ✅
  • bun run build ✅ · bun run docs:build
  • wrangler deploy --dry-run bundles cleanly with the capjs-core alias ✅

Notes for reviewers

  • One global enabled set applies to every gated action (login, register, team-join, invite, step-up 2FA); per-action sets were deliberately out of scope.
  • @cap.js/server is intentionally not used (it needs a filesystem and doesn't run on Workers); capjs-core is the Worker-compatible library.

Sourcery 摘要

将验证码保护演进为向后兼容、按顺序排列的多提供商系统,并支持访客切换以及 GeeTest v4 和 Cap。

新功能:

  • 添加可排序、可切换的验证码提供商集合,支持每个提供商单独配置凭据,并将访客选择持久化到浏览器。
  • 添加 GeeTest v4 和 Cap 验证码集成,包括内置的 Worker/KV 支持模式和外部 Cap 模式。
  • 提供管理员界面,用于选择、排序和配置多个验证码提供商。

错误修复:

  • 通过在读取时将旧版单提供商格式迁移到新格式,保留现有验证码配置。
  • 拒绝来自未启用提供商的验证码提交,并通过加密、脱敏和管理员安全措施保护所有新增的验证码密钥。

增强功能:

  • 更新所有受保护的身份验证和验证流程,以使用共享的公开验证码描述信息,并执行面向提供商的验证。
  • 保留旧版工作量证明提供商,同时将 Cap 定位为其现代替代方案。
  • 添加账户偏好设置,用于控制 GPG 登录是否需要后续的 TOTP 验证。

构建:

  • 添加 Cap 小组件和兼容 Worker 的 Cap 依赖,包括针对可选的、仅限 Node.js 的检测依赖的打包器别名。

文档:

  • 使用英文和中文记录可切换的验证码提供商模型、GeeTest 和 Cap 配置、迁移行为、架构及运维术语。

测试:

  • 通过类型检查、代码检查、单元测试、应用构建、文档构建以及干净的 Wrangler 部署试运行,验证更新后的实现。

杂项:

  • 添加每个账户的 GPG 双因素认证偏好所需的数据库迁移以及 API/类型更新。
Original summary in English

Sourcery 摘要

将验证码保护机制演进为向后兼容的有序提供商集合,支持访客切换、GeeTest v4 和 Cap,并提供安全的按提供商配置。

新功能:

  • 添加可排序、可切换的验证码提供商集合,支持按提供商配置凭据、持久化访客偏好,以及管理员选择和排序控制。
  • 添加 GeeTest v4 和 Cap 集成,包括基于 Worker/KV 的内嵌模式和外部 Cap 模式。
  • 添加账户偏好设置,用于控制 GPG 登录是否需要后续的 TOTP 验证。

错误修复:

  • 通过在读取时从旧版单一提供商配置进行迁移,保留现有的验证码部署。
  • 拒绝来自启用集合之外提供商的验证码提交,并将机密信息脱敏、加密和管理员保护扩展到所有新增凭据。

增强功能:

  • 更新所有受验证码保护的身份验证、邀请、验证和升级验证流程,使其使用共享的公共提供商描述信息和特定于提供商的提交数据。
  • 保留旧版工作量证明提供商,同时将 Cap 定位为其现代替代方案。

构建:

  • 添加 Cap 小组件依赖,以及针对可选 Node-only instrumentation 依赖的兼容 Worker 打包别名。

文档:

  • 使用英文和中文记录可切换提供商模型、GeeTest 和 Cap 配置、迁移行为、架构及运维术语。

测试:

  • 通过类型检查、代码检查、单元测试、应用和文档构建,以及干净的 Wrangler 部署试运行验证这些更改。

杂项:

  • 添加按账户设置 GPG 双因素偏好所需的数据库和 API 支持。
Original summary in English

Sourcery 摘要

将验证码保护机制演进为向后兼容的、有序提供商集合,支持访客切换、GeeTest v4 和 Cap,并提供安全的提供商级配置。

新功能:

  • 添加有序且可切换的验证码提供商集合,支持提供商级凭据、访客偏好持久化和管理员配置控制。
  • 添加 GeeTest v4 和 Cap 集成,包括内置的 Worker/KV 支持模式和外部 Cap 模式。
  • 添加账户级偏好设置,用于控制 GPG 登录是否需要后续的 TOTP 验证。

错误修复:

  • 通过在读取时从旧版单一提供商配置迁移,保留现有的验证码部署。
  • 拒绝来自启用集合之外的提供商的验证码提交,并将密钥脱敏、加密和管理员保护扩展到新增凭据。

增强功能:

  • 更新身份验证、邀请、电子邮件验证和增强验证流程,以使用共享的公共验证码描述符和提供商专属的提交方式。
  • 保留现有的工作量证明提供商,同时将 Cap 定位为其现代化的自托管替代方案。

构建:

  • 添加 Cap 小组件和兼容 Worker 的 Cap 依赖,并通过打包器别名处理可选的、仅限 Node 的检测依赖。

文档:

  • 使用英文和中文记录可切换提供商模型、GeeTest 和 Cap 配置、迁移行为、架构及运维术语。

测试:

  • 通过类型检查、代码检查、单元测试、应用程序和文档构建,以及干净的 Wrangler 部署预演来验证这些更改。

杂项:

  • 添加 GPG 双因素身份验证偏好设置所需的数据库、API 和用户界面支持。
Original summary in English

Sourcery 摘要

将验证码保护演进为向后兼容、有序的提供商集合,支持访客切换、GeeTest v4 和 Cap,并提供安全的提供商专属管理功能。

新功能:

  • 添加有序且可切换的验证码提供商集合,支持每个提供商的凭据、访客偏好持久化以及管理员配置控制。
  • 添加 GeeTest v4 和 Cap 集成,包括基于 Worker/KV 的内嵌模式和外部 Cap 模式。
  • 添加账户偏好设置,用于控制 GPG 登录是否需要后续的 TOTP 验证。

错误修复:

  • 通过在读取时从旧版单一提供商配置迁移,保留现有的验证码部署。
  • 拒绝来自已启用集合之外的提供商的验证码提交,并将密钥脱敏和保护扩展到所有新增的提供商凭据。

增强功能:

  • 更新身份验证、邀请、电子邮件验证和升级验证流程,以使用共享的公共验证码描述符和提供商专属提交内容。
  • 保留现有的工作量证明提供商,同时将 Cap 定位为其现代化的自托管替代方案。

构建:

  • 添加 Cap widget 依赖项,以及兼容 Worker 的打包别名,用于可选的、仅支持 Node 的检测依赖项。

文档:

  • 使用英文和中文记录可切换验证码提供商模型、GeeTest 和 Cap 配置、迁移行为、架构及运维术语。

测试:

  • 通过类型检查、代码检查、单元测试、应用程序和文档构建,以及干净的 Wrangler 部署试运行来验证这些更改。

杂项:

  • 添加数据库、API 和用户界面对每个账户 GPG 双因素身份验证偏好的支持。
Original summary in English

Summary by Sourcery

Evolve captcha protection into a backward-compatible, ordered provider set with visitor switching, GeeTest v4 and Cap support, and secure provider-specific administration.

New Features:

  • Add an ordered, switchable captcha provider set with per-provider credentials, visitor preference persistence, and administrator configuration controls.
  • Add GeeTest v4 and Cap integrations, including embedded Worker/KV-backed and external Cap modes.
  • Add an account preference controlling whether GPG login requires a subsequent TOTP challenge.

Bug Fixes:

  • Preserve existing captcha deployments through read-time migration from the legacy single-provider configuration.
  • Reject captcha submissions from providers outside the enabled set and extend secret redaction and protection to all new provider credentials.

Enhancements:

  • Update authentication, invitation, email verification, and step-up verification flows to use shared public captcha descriptors and provider-specific submissions.
  • Retain the existing proof-of-work provider while positioning Cap as its modern self-hosted alternative.

Build:

  • Add Cap widget dependencies and Worker-compatible bundling aliases for optional Node-only instrumentation dependencies.

Documentation:

  • Document the switchable captcha provider model, GeeTest and Cap configuration, migration behavior, architecture, and operational terminology in English and Chinese.

Tests:

  • Validate the changes with type checking, linting, unit tests, application and documentation builds, and a clean Wrangler deployment dry run.

Chores:

  • Add the database, API, and user-interface support for per-account GPG two-factor authentication preferences.

Replace the single site-wide captcha provider with an ordered, switchable set:

- captcha_providers[] — element 0 is the default, the rest are alternates a
  visitor can switch to (manually, on failure, or after a configurable timeout).
  The chosen provider is remembered in localStorage.
- Each provider carries its own credentials; submissions name their provider and
  the server verifies against it, rejecting providers outside the enabled set.
- Read-time migration seeds captcha_providers + per-provider keys from the legacy
  captcha_provider / shared key pair, so existing deployments keep working.

New providers:

- GeeTest v4 (SenseBot) — lib/geetest.ts, HMAC-signed server validation with a
  configurable fail-open/closed posture on GeeTest outage.
- Cap (trycap.dev) — lib/cap.ts, embedded via capjs-core (KV-backed replay guard
  + redeem tokens, secret derived from the JWT secret) or external Cap Standalone.
  Aliases esbuild/javascript-obfuscator to a stub since capjs-core lazily imports
  them for instrumentation levels the embedded path never uses.

Also: AdminSettings provider-set editor (default + alternates with reordering +
per-provider panels), new config keys wired through allowlist/redaction/secret
encryption/log redaction, EN+zh docs, CONTEXT.md glossary and ADR 0001.
@sourcery-ai

sourcery-ai Bot commented Sep 5, 2026

Copy link
Copy Markdown

审查者指南

此 PR 将验证码从单一的旧版提供商迁移为一组按顺序排列、可手动切换的提供商,并支持按提供商进行验证以及向后兼容的读取时迁移;同时在所有受保护流程中加入 GeeTest v4 和嵌入式/外部 Cap 集成,并提供相应的管理员配置、安全处理、UI、类型、测试/构建接线、本地化和文档;此外,还引入了独立的按账户配置的 GPG 登录 TOTP 退出选项。

可切换验证码验证的时序图

sequenceDiagram
    actor Visitor
    participant Captcha as Captcha UI
    participant GatedPage as Gated flow
    participant AuthRoute as Auth route
    participant Middleware as verifyCaptchaToken
    participant Provider as Selected provider verifier

    GatedPage->>Captcha: Render captcha descriptor
    Captcha->>Captcha: Select stored provider or default
    Captcha-->>Visitor: Show selected widget
    alt Verification succeeds
        Visitor->>Captcha: Solve widget
        Captcha->>GatedPage: onVerified(provider, proof)
        GatedPage->>AuthRoute: Submit provider-specific proof
        AuthRoute->>Middleware: verifyCaptchaToken(submission, ip, env)
        Middleware->>Middleware: Check provider is enabled
        Middleware->>Provider: Dispatch provider verification
        Provider-->>Middleware: Verification result
        Middleware-->>AuthRoute: CaptchaResult
        AuthRoute-->>Visitor: Continue gated action
    else Verification fails or timeout expires
        Captcha-->>Visitor: Reveal switch control
        Visitor->>Captcha: Switch provider
        Captcha->>Captcha: storeProvider(provider)
        Captcha-->>Visitor: Render alternate widget
    end
Loading

嵌入式 Cap 挑战兑换的时序图

sequenceDiagram
    actor Visitor
    participant CapWidget as Cap widget
    participant Worker as Prism Worker
    participant CapCore as capjs-core
    participant KV as KV_CACHE
    participant AuthRoute as Gated auth route
    participant Captcha as verifyCaptchaToken

    CapWidget->>Worker: POST /api/auth/cap/challenge
    Worker->>CapCore: issueCapChallenge()
    CapCore-->>Worker: Signed challenge JWT
    Worker-->>CapWidget: Challenge
    Visitor->>CapWidget: Solve proof-of-work
    CapWidget->>Worker: POST /api/auth/cap/redeem
    Worker->>CapCore: redeemCapChallenge()
    CapCore->>KV: consumeNonce
    KV-->>CapCore: Nonce available
    CapCore->>KV: Store redeem token
    CapCore-->>Worker: Opaque redeem token
    Worker-->>CapWidget: Redeem token
    Visitor->>AuthRoute: Submit cap_token and provider
    AuthRoute->>Captcha: verifyCaptchaToken()
    Captcha->>KV: Read and delete token
    KV-->>Captcha: Token validity
    Captcha-->>AuthRoute: Verification result
Loading

旧版验证码配置迁移流程图

flowchart TD
    Load[Load SiteConfig] --> HasSet{captcha_providers written?}
    HasSet -->|Yes| Active[Use ordered provider set]
    HasSet -->|No| Legacy{Legacy provider enabled?}
    Legacy -->|No| Off[Captcha remains disabled]
    Legacy -->|Yes| Seed[Seed captcha_providers with legacy provider]
    Seed --> Credentials[Copy legacy shared credentials to provider fields]
    Credentials --> Active
    Active --> Save[Admin saves settings]
    Save --> NewShape[Persist new configuration shape]
Loading

文件级变更

变更 详细信息 文件
将单一的全站验证码配置替换为一组按顺序排列、可切换的提供商,并支持按提供商配置凭据。
  • 新增 captcha_providers 排序、备用提供商选择、基于超时/手动的切换以及 localStorage 持久化。
  • 在服务器端验证中新增提供商标识处理和启用集合强制校验。
  • 新增从旧版提供商和共享凭据进行读取时迁移的功能,并更新机密信息脱敏、加密、管理员允许列表和日志记录。
  • 集中处理站点、OAuth 和团队加入载荷中的公共验证码描述信息。
  • 更新所有受验证码保护的流程及共享请求类型,以提交特定于提供商的验证证明。
shared/types.ts
worker/lib/config.ts
worker/lib/captchaPublic.ts
worker/middleware/captcha.ts
worker/lib/secretCrypto.ts
worker/lib/logger.ts
worker/routes/admin.ts
worker/routes/site.ts
worker/routes/oauth.ts
worker/routes/invite-registration.ts
worker/routes/auth.ts
src/lib/api.ts
src/components/Captcha.tsx
src/pages/Login.tsx
src/pages/Register.tsx
src/pages/JoinRegister.tsx
src/pages/VerifyChoose.tsx
src/pages/oauth/Verify2FA.tsx
src/pages/admin/AdminSettings.tsx
新增 GeeTest v4 集成,支持服务器端 HMAC 验证和可配置的服务中断处理策略。
  • 新增 GeeTest v4 浏览器渲染器和验证载荷。
  • 实现向 GeeTest 发送带 HMAC 签名的验证请求。
  • 新增故障开放/故障关闭配置,默认采用故障关闭。
worker/lib/geetest.ts
src/components/Captcha.tsx
shared/types.ts
worker/middleware/captcha.ts
src/pages/admin/AdminSettings.tsx
src/i18n/en.json
src/i18n/zh.json
新增 Cap 验证码支持,包括基于嵌入式 Worker/KV 的模式和外部 Standalone 模式。
  • 新增使用 capjs-core 的嵌入式挑战和兑换端点、签名挑战、KV 重放保护以及一次性兑换令牌。
  • 新增针对已配置 Standalone 端点的外部 Cap 验证。
  • 新增 Cap 小组件渲染器,以及用于 Node 专属可选依赖的嵌入式构建别名。
worker/lib/cap.ts
worker/routes/auth.ts
src/components/Captcha.tsx
package.json
wrangler.jsonc
worker/lib/stubs/empty.js
worker/middleware/captcha.ts
src/pages/admin/AdminSettings.tsx
扩展管理功能、配置文档、本地化内容以及针对新验证码模型的架构指导。
  • 新增有序提供商集合编辑功能,支持默认选择、备用提供商开关、重新排序以及按提供商配置的设置面板。
  • 以英文和中文记录提供商行为、迁移、GeeTest、Cap 模式以及配置键。
  • 新增验证码术语,并添加 ADR,说明可切换集合设计和兼容 Worker 的 Cap 方案。
src/pages/admin/AdminSettings.tsx
docs/configuration.md
docs/admin.md
docs/architecture.md
docs/zh/configuration.md
docs/zh/admin.md
docs/zh/architecture.md
docs/adr/0001-switchable-captcha-set.md
CONTEXT.md
新增按用户配置的 GPG 登录偏好,用于控制在 GPG 验证后是否仍要求已注册的 TOTP。
  • 新增数据库迁移以及用户偏好 API/存储字段。
  • 新增安全页面 UI 以及本地化的状态/错误消息。
  • 在 GPG 登录流程中应用该偏好,同时保留默认的必需设置。
worker/db/migrations/0074_gpg_login_skip_2fa.sql
worker/routes/user.ts
worker/routes/auth.ts
worker/types.ts
src/pages/Security.tsx
src/lib/api.ts
src/i18n/en.json
src/i18n/zh.json

提示和命令

与 Sourcery 交互

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。也可以使用 @sourcery-ai issue 回复审查评论,以从中创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。也可以在拉取请求中评论 @sourcery-ai title,以随时生成或重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。也可以在拉取请求中评论 @sourcery-ai summary,以随时生成或重新生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时生成或重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这项功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你想从新的审查开始,这项功能尤其有用——别忘了评论 @sourcery-ai review 以触发新的审查!

自定义使用体验

访问你的控制面板

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

This PR migrates captcha from one legacy provider to an ordered, manually switchable provider set with per-provider verification and backward-compatible read-time migration, adds GeeTest v4 and embedded/external Cap integrations across all gated flows, and supplies corresponding admin configuration, security handling, UI, types, tests/build wiring, localization, and documentation; it also introduces a separate per-account GPG-login TOTP opt-out.

Sequence diagram for switchable captcha verification

sequenceDiagram
    actor Visitor
    participant Captcha as Captcha UI
    participant GatedPage as Gated flow
    participant AuthRoute as Auth route
    participant Middleware as verifyCaptchaToken
    participant Provider as Selected provider verifier

    GatedPage->>Captcha: Render captcha descriptor
    Captcha->>Captcha: Select stored provider or default
    Captcha-->>Visitor: Show selected widget
    alt Verification succeeds
        Visitor->>Captcha: Solve widget
        Captcha->>GatedPage: onVerified(provider, proof)
        GatedPage->>AuthRoute: Submit provider-specific proof
        AuthRoute->>Middleware: verifyCaptchaToken(submission, ip, env)
        Middleware->>Middleware: Check provider is enabled
        Middleware->>Provider: Dispatch provider verification
        Provider-->>Middleware: Verification result
        Middleware-->>AuthRoute: CaptchaResult
        AuthRoute-->>Visitor: Continue gated action
    else Verification fails or timeout expires
        Captcha-->>Visitor: Reveal switch control
        Visitor->>Captcha: Switch provider
        Captcha->>Captcha: storeProvider(provider)
        Captcha-->>Visitor: Render alternate widget
    end
Loading

Sequence diagram for embedded Cap challenge redemption

sequenceDiagram
    actor Visitor
    participant CapWidget as Cap widget
    participant Worker as Prism Worker
    participant CapCore as capjs-core
    participant KV as KV_CACHE
    participant AuthRoute as Gated auth route
    participant Captcha as verifyCaptchaToken

    CapWidget->>Worker: POST /api/auth/cap/challenge
    Worker->>CapCore: issueCapChallenge()
    CapCore-->>Worker: Signed challenge JWT
    Worker-->>CapWidget: Challenge
    Visitor->>CapWidget: Solve proof-of-work
    CapWidget->>Worker: POST /api/auth/cap/redeem
    Worker->>CapCore: redeemCapChallenge()
    CapCore->>KV: consumeNonce
    KV-->>CapCore: Nonce available
    CapCore->>KV: Store redeem token
    CapCore-->>Worker: Opaque redeem token
    Worker-->>CapWidget: Redeem token
    Visitor->>AuthRoute: Submit cap_token and provider
    AuthRoute->>Captcha: verifyCaptchaToken()
    Captcha->>KV: Read and delete token
    KV-->>Captcha: Token validity
    Captcha-->>AuthRoute: Verification result
Loading

Flow diagram for legacy captcha configuration migration

flowchart TD
    Load[Load SiteConfig] --> HasSet{captcha_providers written?}
    HasSet -->|Yes| Active[Use ordered provider set]
    HasSet -->|No| Legacy{Legacy provider enabled?}
    Legacy -->|No| Off[Captcha remains disabled]
    Legacy -->|Yes| Seed[Seed captcha_providers with legacy provider]
    Seed --> Credentials[Copy legacy shared credentials to provider fields]
    Credentials --> Active
    Active --> Save[Admin saves settings]
    Save --> NewShape[Persist new configuration shape]
Loading

File-Level Changes

Change Details Files
Replaced the single site-wide captcha configuration with an ordered, switchable provider set and per-provider credentials.
  • Added captcha_providers ordering, alternate selection, timeout-based/manual switching, and localStorage persistence.
  • Added provider discriminator handling and enabled-set enforcement in server verification.
  • Added read-time migration from legacy provider and shared credentials, plus secret redaction, encryption, admin allowlist, and logging updates.
  • Centralized public captcha descriptors across site, OAuth, and team-join payloads.
  • Updated all captcha-gated flows and shared request types to submit provider-specific proof.
shared/types.ts
worker/lib/config.ts
worker/lib/captchaPublic.ts
worker/middleware/captcha.ts
worker/lib/secretCrypto.ts
worker/lib/logger.ts
worker/routes/admin.ts
worker/routes/site.ts
worker/routes/oauth.ts
worker/routes/invite-registration.ts
worker/routes/auth.ts
src/lib/api.ts
src/components/Captcha.tsx
src/pages/Login.tsx
src/pages/Register.tsx
src/pages/JoinRegister.tsx
src/pages/VerifyChoose.tsx
src/pages/oauth/Verify2FA.tsx
src/pages/admin/AdminSettings.tsx
Added GeeTest v4 integration with server-side HMAC validation and configurable outage posture.
  • Added a GeeTest v4 browser renderer and validation payload.
  • Implemented HMAC-signed validation requests to GeeTest.
  • Added fail-open/fail-closed configuration, defaulting to fail closed.
worker/lib/geetest.ts
src/components/Captcha.tsx
shared/types.ts
worker/middleware/captcha.ts
src/pages/admin/AdminSettings.tsx
src/i18n/en.json
src/i18n/zh.json
Added Cap captcha support in embedded Worker/KV-backed and external Standalone modes.
  • Added embedded challenge and redeem endpoints using capjs-core, signed challenges, KV replay protection, and single-use redeem tokens.
  • Added external Cap verification against a configured Standalone endpoint.
  • Added the Cap widget renderer and embedded-build aliases for Node-only optional dependencies.
worker/lib/cap.ts
worker/routes/auth.ts
src/components/Captcha.tsx
package.json
wrangler.jsonc
worker/lib/stubs/empty.js
worker/middleware/captcha.ts
src/pages/admin/AdminSettings.tsx
Expanded administration, configuration documentation, localization, and architectural guidance for the new captcha model.
  • Added ordered provider-set editing with default selection, alternate toggles, reordering, and per-provider settings panels.
  • Documented provider behavior, migration, GeeTest, Cap modes, and configuration keys in English and Chinese.
  • Added captcha terminology and an ADR describing the switchable-set design and Worker-compatible Cap approach.
src/pages/admin/AdminSettings.tsx
docs/configuration.md
docs/admin.md
docs/architecture.md
docs/zh/configuration.md
docs/zh/admin.md
docs/zh/architecture.md
docs/adr/0001-switchable-captcha-set.md
CONTEXT.md
Added a per-user GPG-login preference controlling whether enrolled TOTP remains required after GPG verification.
  • Added a database migration and user preference API/storage field.
  • Added Security-page UI and localized status/error messages.
  • Applied the preference in the GPG login flow while preserving the default requirement.
worker/db/migrations/0074_gpg_login_skip_2fa.sql
worker/routes/user.ts
worker/routes/auth.ts
worker/types.ts
src/pages/Security.tsx
src/lib/api.ts
src/i18n/en.json
src/i18n/zh.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 5, 2026

Copy link
Copy Markdown

Deploying prism-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: c52cd8f
Status: ✅  Deploy successful!
Preview URL: https://2ae6d650.siiway-prism.pages.dev
Branch Preview URL: https://feat-captcha-enhance-and-2fa.siiway-prism.pages.dev

View logs

@sourcery-ai sourcery-ai 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.

嘿——我发现了 3 个问题

面向 AI Agent 的提示
请处理本次代码审查中的评论:

## 单独评论

### 评论 1
<location path="src/components/Captcha.tsx" line_range="297-301" />
<code_context>
+          onError?.(t("captcha.geetestFailed"));
+          return;
+        }
+        initGeetest4(
+          { captchaId: captcha.geetest_captcha_id, product: "bind" },
+          (captchaObj) => {
+            geetestObjRef.current = captchaObj;
+            captchaObj.onReady(() => setGeetestReady(true));
+            captchaObj.onSuccess(() => {
+              const result = captchaObj.getValidate();
</code_context>
<issue_to_address>
**问题 (bug_risk):** GeeTest 对象从未传递给其声明的 `appendTo` 方法,因此 v4 小组件不会挂载到容器中,其 ready 回调也不会触发;唯一的验证按钮会一直处于禁用状态。

**触发条件:** GeeTest 是当前启用的提供商时。

**建议修复:** 初始化后调用 `captchaObj.appendTo(containerRef.current)` 或文档中指定的选择器,并使用该提供商支持的交互 API。
</issue_to_address>

### 评论 2
<location path="worker/lib/cap.ts" line_range="86-92" />
<code_context>
+  const result = await validateChallenge(secret, body, {
+    scope: CAP_SCOPE,
+    tokenTtlMs: TOKEN_TTL_MS,
+    consumeNonce: async (sigHex, ttlMs) => {
+      const key = `${NONCE_PREFIX}${sigHex}`;
+      if (await env.KV_CACHE.get(key)) return false;
+      await env.KV_CACHE.put(key, "1", {
+        expirationTtl: Math.ceil(ttlMs / 1000),
+      });
+      return true;
+    },
+  });
</code_context>
<issue_to_address>
**问题 (bug_risk):** 内置的 Cap 单次使用检查不是原子的:并发请求可能同时观察到 KV 中不存在 nonce 或令牌,随后同时写入/删除它,并成功兑换同一个 challenge 或令牌。

**触发条件:** 当针对同一个 Cap challenge 或兑换令牌的两个请求,在 KV 状态一致可见之前到达时。

**建议修复:** 使用原子比较并设置(compare-and-set)操作、由 Durable Object 支持的消费操作,或以其他方式串行化检查和消费操作,而不是分别执行 KV 读取和写入。
</issue_to_address>

### 评论 3
<location path="worker/lib/geetest.ts" line_range="69-72" />
<code_context>
+  failOpen: boolean,
+): Promise<boolean> {
+  const { lot_number, captcha_output, pass_token, gen_time } = output;
+  if (!lot_number || !captcha_output || !pass_token || !gen_time) {
+    // No usable output at all — nothing to validate. Governed by policy.
+    return failOpen;
+  }
+
</code_context>
<issue_to_address>
**🚨 问题 (security):** 启用 `geetest_fail_open` 后,包含缺失或格式错误的 GeeTest 字段的提交会在未联系 GeeTest 的情况下被接受,攻击者可以通过发送空的 `geetest` 对象绕过验证码。

**触发条件:** 管理员选择 GeeTest 故障放行模式,且请求未提供可用的 GeeTest 输出时。

**建议修复:** 始终拒绝结构无效或缺失的输出;仅在确认 GeeTest 传输层或服务发生故障时应用故障放行。

```suggestion
  if (!lot_number || !captcha_output || !pass_token || !gen_time) {
    // Structurally invalid or missing output is always rejected.
    return false;
  }
```
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查,请考虑分享它们 ✨
Original comment in English

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/components/Captcha.tsx" line_range="297-301" />
<code_context>
+          onError?.(t("captcha.geetestFailed"));
+          return;
+        }
+        initGeetest4(
+          { captchaId: captcha.geetest_captcha_id, product: "bind" },
+          (captchaObj) => {
+            geetestObjRef.current = captchaObj;
+            captchaObj.onReady(() => setGeetestReady(true));
+            captchaObj.onSuccess(() => {
+              const result = captchaObj.getValidate();
</code_context>
<issue_to_address>
**issue (bug_risk):** The GeeTest object is never passed to its declared `appendTo` method, so the v4 widget is not mounted into the container and its ready callback does not fire; the only verification button remains disabled indefinitely.

**Triggers:** When GeeTest is the active provider.

**Suggested fix:** Call `captchaObj.appendTo(containerRef.current)` or the documented selector after initialization, and use the provider's supported interaction API.
</issue_to_address>

### Comment 2
<location path="worker/lib/cap.ts" line_range="86-92" />
<code_context>
+  const result = await validateChallenge(secret, body, {
+    scope: CAP_SCOPE,
+    tokenTtlMs: TOKEN_TTL_MS,
+    consumeNonce: async (sigHex, ttlMs) => {
+      const key = `${NONCE_PREFIX}${sigHex}`;
+      if (await env.KV_CACHE.get(key)) return false;
+      await env.KV_CACHE.put(key, "1", {
+        expirationTtl: Math.ceil(ttlMs / 1000),
+      });
+      return true;
+    },
+  });
</code_context>
<issue_to_address>
**issue (bug_risk):** The embedded Cap single-use checks are non-atomic: concurrent requests can both observe an absent KV nonce or token, then both write/delete it and successfully redeem the same challenge or token.

**Triggers:** When two requests for the same Cap challenge or redeem token arrive before KV state is consistently visible.

**Suggested fix:** Use an atomic compare-and-set/ Durable Object-backed consume operation, or otherwise serialize the check-and-consume operation instead of separate KV reads and writes.
</issue_to_address>

### Comment 3
<location path="worker/lib/geetest.ts" line_range="69-72" />
<code_context>
+  failOpen: boolean,
+): Promise<boolean> {
+  const { lot_number, captcha_output, pass_token, gen_time } = output;
+  if (!lot_number || !captcha_output || !pass_token || !gen_time) {
+    // No usable output at all — nothing to validate. Governed by policy.
+    return failOpen;
+  }
+
</code_context>
<issue_to_address>
**🚨 issue (security):** With `geetest_fail_open` enabled, a submission containing missing or malformed GeeTest fields is accepted without contacting GeeTest, allowing an attacker to bypass the captcha by sending an empty `geetest` object.

**Triggers:** When administrators choose GeeTest fail-open mode and a request supplies no usable GeeTest output.

**Suggested fix:** Reject structurally invalid or missing output unconditionally; apply fail-open only to a confirmed GeeTest transport/service outage.

```suggestion
  if (!lot_number || !captcha_output || !pass_token || !gen_time) {
    // Structurally invalid or missing output is always rejected.
    return false;
  }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/components/Captcha.tsx
Comment thread worker/lib/cap.ts Outdated
Comment thread worker/lib/geetest.ts
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Preview deployed: https://prism-preview.siiway.workers.dev (shared preview Worker + database, so the newest PR deploy is what is live there).

- geetest: reject missing/malformed output unconditionally; only apply
  fail-open to a confirmed GeeTest transport/service outage, closing a bypass
  where an empty geetest object passed when fail-open was enabled (security).
- cap: make the embedded single-use checks atomic via the D1 replay-claim table
  (claimReplayValue) instead of racy KV get-then-put — the challenge nonce and
  the redeem token are now consumed exactly once under concurrency. KV still
  proves token authenticity/expiry; the atomic claim is the single-use gate.
- geetest widget: mount with product "popup" + appendTo(container) so the
  widget actually renders and onReady fires; removed the disconnected manual
  verify button.
@w9auto

w9auto commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @sourcery-ai — addressed all three in 093f4a3:

  1. GeeTest fail-open bypass (security)verifyGeetest now rejects structurally missing/malformed output unconditionally (and a signing failure too). Fail-open is applied only to a confirmed GeeTest outage (!res.ok or a network error), so an empty geetest object can no longer bypass the captcha when fail-open is enabled.
  2. Cap non-atomic single-use (bug_risk) — replaced the racy KV get-then-put with the existing atomic D1 replay-claim (claimReplayValue, conditional upsert) for both the challenge nonce (consumeNonce) and the redeem token. KV now only proves token authenticity/expiry; the atomic D1 claim is the single-use gate, so concurrent submissions can't redeem the same challenge/token twice. Reuses security_replay_claims + its sweep — no new migration.
  3. GeeTest widget never mounted (bug_risk) — switched to product: "popup" and call captchaObj.appendTo(containerRef.current) on init, so the widget renders and onReady fires; removed the disconnected manual verify button.

@wyf9

wyf9 commented Sep 5, 2026

Copy link
Copy Markdown
Member

🤔

Expand CONTEXT.md from the captcha-only glossary into the full project
vocabulary — identity (user, team-as-user, restricted account), team structure
(sub-team vs member group, effective role, site floor), apps & providers (OAuth
app vs source, first-party/official/verified/trusted, public client),
step-up/sudo, scopes & tokens, invitations, domains, captcha, and governance
(audit log, notice, notification ruleset). Add an AGENTS.md 'Domain model'
section directing agents to read and keep CONTEXT.md current.
@wyf9
wyf9 merged commit 3f22a86 into main Sep 6, 2026
4 checks passed
@wyf9
wyf9 deleted the feat/captcha-enhance-and-2fa branch September 6, 2026 00:04
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