Skip to content

fix(auth): 适配 Bitwarden / 1Password 密码管理器自动填充 - #55

Merged
one-ea merged 1 commit into
mainfrom
fix/admin-password-autofill
Apr 26, 2026
Merged

fix(auth): 适配 Bitwarden / 1Password 密码管理器自动填充#55
one-ea merged 1 commit into
mainfrom
fix/admin-password-autofill

Conversation

@one-ea

@one-ea one-ea commented Apr 26, 2026

Copy link
Copy Markdown
Owner

问题

主人反馈:Bitwarden 浏览器扩展在管理员登录密码框上没有显示自动填充图标。

根因

密码管理器(Bitwarden / 1Password / Chrome 自带)使用启发式扫描表单,必须看到 username + password 双字段 才会触发自动填充 UI。

项目中两处管理员登录表单只有孤立的 <input type="password">,缺少:

  • 配套的 username 字段(即便是 hidden)
  • name / id / autoComplete 属性
  • form 的 aria-label 语义

导致密码管理器无法识别为登录表单。

修复

按 W3C "Sign-in form best practices" 推荐的 hidden username 模式补全表单语义:

client/src/components/admin-gate.tsx(首页弹窗)

  • 增加 <input type="text" name="username" autoComplete="username" hidden readOnly>
  • password input 增加 name="password"idaria-label
  • form 增加 aria-label="管理员登录"

client/src/pages/admin/login.tsx(独立登录页)

  • 同上 hidden username
  • 补全此前缺失的 autoComplete="current-password"
  • name / id / aria-label

验证

  • 不影响视觉(纯语义/无障碍属性,未触碰任何 className)
  • 不影响交互(hidden input 不渲染、不参与 tab 顺序)
  • 不影响后端(后端仍只读取 password 字段)

关联规则

  • UI 设计规范:未触碰间距/颜色/字号
  • 双主题/响应式:N/A(无视觉变化)
  • Conventional Commits: fix(auth):

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@one-ea has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d344897-fffe-4427-bb2a-4e05c243bb6d

📥 Commits

Reviewing files that changed from the base of the PR and between 68b6b00 and f9ff9d4.

📒 Files selected for processing (2)
  • client/src/components/admin-gate.tsx
  • client/src/pages/admin/login.tsx
📝 Walkthrough

Walkthrough

本次 PR 涉及 Cloudflare 部署工作流优化和管理员登录表单增强。部署脚本新增 Windows 平台支持、先决条件检查(Wrangler 认证状态验证)和错误处理机制。README 文档更新部署说明,强调本地 CLI 和 GitHub Actions 的差异。管理员登录表单添加无障碍访问支持(ARIA 标签)和隐藏用户名字段。

Changes

Cohort / File(s) Summary
Cloudflare 部署工作流
.github/workflows/deploy-cloudflare.yml, README.md, scripts/deploy-cloudflare.mjs
部署脚本新增 Windows 平台 shell 检测、Wrangler 认证先决条件校验、命令执行错误处理(ENOENT 提示)。README 新增部署前置要求(npx wrangler login)和支持矩阵表,移除关于单一命令覆盖全链路的声明。
管理员登录表单加固
client/src/components/admin-gate.tsx, client/src/pages/admin/login.tsx
两个登录组件增加无障碍属性(aria-label)、显式隐藏用户名字段(username=admin,只读、脱离 tab 序列)、稳定的表单字段标识符(idnameautoComplete),确保浏览器自动填充和屏幕阅读器兼容。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

frontend, bug

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题完全符合 Conventional Commits 格式,type 为 fix,scope 为 auth,description 清晰描述了改动内容。
Description check ✅ Passed PR 描述详尽阐述了问题根源、修复方案、验证细节和相关规范,与代码改动完全吻合,结构清晰。
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/admin-password-autofill
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/admin-password-autofill

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 and usage tips.

@coderabbitai coderabbitai Bot added bug Something isn't working frontend 前端 (React/Vite) 相关变更 labels Apr 26, 2026

@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: 3

🧹 Nitpick comments (3)
client/src/components/admin-gate.tsx (1)

106-115: 可选优化:hidden 属性等同于 display:none,部分边缘场景密码管理器可能略过。

W3C 推荐方案中 hidden 通常可被 Bitwarden / 1Password / Chrome 现代版本识别,PR 描述也表明已实测通过。如未来发现少数密码管理器(或某些移动端 WebView)对 display:none 的字段不进行扫描,可改为离屏 CSS 方案,保持 DOM 可见性的同时仍对用户隐藏:

♻️ 备选方案(仅在未来出现兼容问题时考虑)
-              <input
-                type="text"
-                name="username"
-                value="admin"
-                autoComplete="username"
-                readOnly
-                hidden
-                tabIndex={-1}
-                aria-hidden="true"
-              />
+              <input
+                type="text"
+                name="username"
+                value="admin"
+                autoComplete="username"
+                readOnly
+                tabIndex={-1}
+                aria-hidden="true"
+                className="sr-only"
+                style={{ position: "absolute", left: "-9999px" }}
+              />

注:当前实现已可工作,此条仅作为可选备选。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/admin-gate.tsx` around lines 106 - 115, The current
hidden attribute on the username input (the element with name="username" /
value="admin") can cause some password managers to skip scanning; change the
hiding technique to an off-screen/visually-hidden CSS approach instead of using
the hidden attribute: remove the hidden attribute and tabIndex={-1} if
necessary, and apply a CSS class or inline styles that keep the element in the
DOM but visually off-screen (e.g., absolute positioning with large negative left
or a standard "sr-only"/visually-hidden class) while preserving readOnly and
aria-hidden as appropriate so password managers can still detect the field.
scripts/deploy-cloudflare.mjs (2)

108-127: wrangler whoami 探测建议捕获并展示失败输出,便于诊断

当前 spawnSync 默认 stdio 为 pipe,但探测失败时只把固定中文提示 push 进 errors,并没有把 wrangler 真正的报错(如网络问题、token 过期、wrangler 未安装)回显给用户。结合 Line 113 的 npx wrangler whoami 在首次执行时还可能触发包下载,等待期间也无任何提示。

♻️ 参考改动
-  if (!tokenPresent) {
-    const probe = spawnSync("npx", ["wrangler", "whoami"], {
-      cwd: projectRoot,
-      encoding: "utf8",
-      shell: SHELL,
-    });
-    if (probe.status !== 0) {
-      errors.push(
-        "未检测到 CLOUDFLARE_API_TOKEN,且本机 wrangler 未登录。请先 `npx wrangler login`,或导出 CLOUDFLARE_API_TOKEN 后重试。",
-      );
-    } else {
-      console.log("[ok] 已通过本机 wrangler 登录态。");
-    }
+  if (!tokenPresent) {
+    console.log("[..] 正在通过 `npx wrangler whoami` 检测本机登录态…");
+    const probe = spawnSync("npx", ["wrangler", "whoami"], {
+      cwd: projectRoot,
+      encoding: "utf8",
+      shell: SHELL,
+    });
+    if (probe.status !== 0) {
+      if (probe.stderr) process.stderr.write(probe.stderr);
+      errors.push(
+        "未检测到 CLOUDFLARE_API_TOKEN,且本机 wrangler 未登录。请先 `npx wrangler login`,或导出 CLOUDFLARE_API_TOKEN 后重试。",
+      );
+    } else {
+      console.log("[ok] 已通过本机 wrangler 登录态。");
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/deploy-cloudflare.mjs` around lines 108 - 127, The checkPrerequisites
function should capture and surface the output from the spawnSync probe (npx
wrangler whoami) when it fails: after calling spawnSync, inspect probe.stdout,
probe.stderr and probe.error (and probe.status) and include those details in the
string pushed to errors (or console output) so users see the real wrangler
failure (network, install, token expired, etc.); also consider logging a short
"running npx wrangler whoami (may download...)” message before invoking
spawnSync to indicate a possible delay. Reference: checkPrerequisites, the
spawnSync call invoking "npx" ["wrangler","whoami"], and the probe object
(probe.stdout/probe.stderr/probe.error/probe.status).

152-163: 预检逻辑与 hint 输出存在冗余,建议合并到 checkPrerequisites

printPrerequisiteHints()checkPrerequisites() 现在串行调用,对同一组环境变量分别发出 warnerror/ok 两套输出。当本机已通过 wrangler login 时,用户会先看到「[warn] 未检测到 CLOUDFLARE_API_TOKEN…」,紧接着又看到「[ok] 已通过本机 wrangler 登录态」,相互抵消易让人误以为有问题。建议下沉到 checkPrerequisites 单一信源。

♻️ 参考改动
-printPrerequisiteHints();
-checkPrerequisites();
+checkPrerequisites();

并将 CLOUDFLARE_ACCOUNT_ID 的“非 token 模式下也建议设置”提示合并进 checkPrerequisitesconsole.log 分支(保持 warning 语义但只输出一次)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/deploy-cloudflare.mjs` around lines 152 - 163, Remove the separate
printPrerequisiteHints() function and its invocation and consolidate its
environment-variable checks into checkPrerequisites(); specifically, move the
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID checks into checkPrerequisites()
so that checkPrerequisites() is the single source of truth for printing
status/warn/error messages, emit a single "[warn]" for missing
CLOUDFLARE_ACCOUNT_ID in tokenless (wrangler-login) flows inside
checkPrerequisites() (rather than a separate hint), and ensure the logic in
checkPrerequisites() decides whether to print "[ok] 已通过本机 wrangler 登录态" versus a
missing-token warning so users don't get conflicting messages from two functions
(keep references to printPrerequisiteHints and its call to delete them and
update checkPrerequisites accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/deploy-cloudflare.yml:
- Around line 3-8: The workflow currently auto-triggers on push to main via the
push: branches: [main] + paths filter (including client/**, server/**,
package.json, package-lock.json, scripts/deploy-cloudflare.mjs, and the workflow
file) while its header notes it is "awaiting end-to-end validation"; disable the
automatic push trigger by removing or commenting out the push: ... block and
retain only workflow_dispatch so deployments remain manual until E2E is
validated (ensure workflow_dispatch stays present and the paths/filter block is
not left active elsewhere).

In `@README.md`:
- Line 145: The README claims the startup script "pre-checks wrangler login,
Token, account ID and Node version" but scripts/deploy-cloudflare.mjs's
checkPrerequisites() only validates the first three; update to make behavior and
docs consistent by either adding a Node version check into checkPrerequisites()
(inspect process.versions.node, parse major, fail if NaN or <20 and push an
error) or by editing the README sentence to remove/adjust the Node version
claim; locate checkPrerequisites() in scripts/deploy-cloudflare.mjs and
implement the Node >=20 validation if you choose the script change.

In `@scripts/deploy-cloudflare.mjs`:
- Around line 6-8: Validate and sanitize CLI/env inputs before calling spawnSync
when SHELL (derived from IS_WIN) may be true: add strict format checks for
options.apiBase (ensure it matches https://...workers.dev), and for
options.branch and options.pagesProject (ensure they match /^[A-Za-z0-9._-]+$/),
and reject or escape values that fail validation; perform these checks before
you build the args and input arrays that are passed to child_process.spawnSync
(and any other spawnSync calls around the symbols referenced) so that untrusted
characters (&|>" etc.) cannot be injected when shell:true is used.

---

Nitpick comments:
In `@client/src/components/admin-gate.tsx`:
- Around line 106-115: The current hidden attribute on the username input (the
element with name="username" / value="admin") can cause some password managers
to skip scanning; change the hiding technique to an off-screen/visually-hidden
CSS approach instead of using the hidden attribute: remove the hidden attribute
and tabIndex={-1} if necessary, and apply a CSS class or inline styles that keep
the element in the DOM but visually off-screen (e.g., absolute positioning with
large negative left or a standard "sr-only"/visually-hidden class) while
preserving readOnly and aria-hidden as appropriate so password managers can
still detect the field.

In `@scripts/deploy-cloudflare.mjs`:
- Around line 108-127: The checkPrerequisites function should capture and
surface the output from the spawnSync probe (npx wrangler whoami) when it fails:
after calling spawnSync, inspect probe.stdout, probe.stderr and probe.error (and
probe.status) and include those details in the string pushed to errors (or
console output) so users see the real wrangler failure (network, install, token
expired, etc.); also consider logging a short "running npx wrangler whoami (may
download...)” message before invoking spawnSync to indicate a possible delay.
Reference: checkPrerequisites, the spawnSync call invoking "npx"
["wrangler","whoami"], and the probe object
(probe.stdout/probe.stderr/probe.error/probe.status).
- Around line 152-163: Remove the separate printPrerequisiteHints() function and
its invocation and consolidate its environment-variable checks into
checkPrerequisites(); specifically, move the CLOUDFLARE_API_TOKEN and
CLOUDFLARE_ACCOUNT_ID checks into checkPrerequisites() so that
checkPrerequisites() is the single source of truth for printing
status/warn/error messages, emit a single "[warn]" for missing
CLOUDFLARE_ACCOUNT_ID in tokenless (wrangler-login) flows inside
checkPrerequisites() (rather than a separate hint), and ensure the logic in
checkPrerequisites() decides whether to print "[ok] 已通过本机 wrangler 登录态" versus a
missing-token warning so users don't get conflicting messages from two functions
(keep references to printPrerequisiteHints and its call to delete them and
update checkPrerequisites accordingly).
🪄 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: CHILL

Plan: Pro

Run ID: 93701033-43ae-4f22-9bd1-9152418562ec

📥 Commits

Reviewing files that changed from the base of the PR and between 1b26a2d and 68b6b00.

📒 Files selected for processing (5)
  • .github/workflows/deploy-cloudflare.yml
  • README.md
  • client/src/components/admin-gate.tsx
  • client/src/pages/admin/login.tsx
  • scripts/deploy-cloudflare.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (actions)
🧰 Additional context used
📓 Path-based instructions (2)
client/src/pages/**

⚙️ CodeRabbit configuration file

client/src/pages/**: 页面级组件。审查时请关注: 1. 数据加载和错误处理是否完善 2. SEO 相关(页面标题、meta 标签) 3. 导航和路由是否正确

Files:

  • client/src/pages/admin/login.tsx
client/src/components/**

⚙️ CodeRabbit configuration file

client/src/components/**: 这是 React 前端组件目录。审查时请关注: 1. 是否同时兼容暗色和亮色主题(检查 CSS 变量和 data-theme) 2. 响应式布局是否完整(移动端/平板/桌面端) 3. 无障碍访问(aria 标签、键盘导航) 4. 组件是否保持单一职责

Files:

  • client/src/components/admin-gate.tsx
🔇 Additional comments (3)
client/src/components/admin-gate.tsx (1)

95-131: 整体实现符合 W3C "Sign-in form best practices",LGTM。

  • Hidden username 字段位于 password 之前,配合 autoComplete="username"current-password,符合密码管理器的启发式识别要求。
  • value="admin" + readOnly 避免 React 受控组件 warning;tabIndex={-1} + aria-hidden="true" 不污染键盘焦点链与无障碍树。
  • aria-label="管理员登录"aria-label="管理员密码" 在缺少可见 <label> 的场景下提供了等效的可访问名称;纯语义改动,未触碰 className,暗色/亮色主题与响应式布局不受影响。
client/src/pages/admin/login.tsx (1)

38-61: admin-gate.tsx 对称一致,LGTM。

  • Hidden username 字段、autoComplete="current-password"name/id/aria-label 与组件版表单一一对应,避免双入口表单语义不一致带来的密码管理器记忆错乱。
  • 两处 password input 使用了不同的 idadmin-login-password vs admin-gate-password),即使两者短时共存也不会触发 DOM 重复 id。
  • 仅语义/无障碍属性改动,未变更 className 与提交逻辑,后端仍按 password 字段处理,无回归风险。

如需进一步统一两处的 hidden username 模板,可抽出一个小组件(例如 HiddenUsernameInput)以 DRY,但当前重复度极低,YAGNI 取舍下可不必。

README.md (1)

138-152: 原始评论的前提有误,两份 auth tsx 文件已包含在 PR 中

根据 PR 文件清单,client/src/components/admin-gate.tsxclient/src/pages/admin/login.tsx 确实已包含在变更集中。通过检查代码,login.tsx 已正确实现了密码管理器自动填充适配:

  • 隐藏的 username input 设置 autoComplete="username"
  • 密码输入框设置 autoComplete="current-password"
  • 有明确注释说明这是为了让 Bitwarden / 1Password / Chrome 识别为登录表单

PR 标题与实际改动内容一致,无需调整。部署脚本/文档更新与 auth 修复可继续组织在同一 PR 中。

			> Likely an incorrect or invalid review comment.

Comment thread .github/workflows/deploy-cloudflare.yml
Comment thread README.md
Comment thread scripts/deploy-cloudflare.mjs
@one-ea
one-ea enabled auto-merge (squash) April 26, 2026 12:47
密码管理器靠启发式扫表单:必须看到 username + password 双字段才会
触发自动填充 UI。两处管理员登录表单只有孤立 password input,
导致 Bitwarden 浏览器扩展无法识别为登录表单。

- admin-gate.tsx 弹窗:增加 hidden username + name=password + aria-label
- pages/admin/login.tsx 全屏登录页:同上 + 补 autoComplete=current-password
- form 增加 aria-label='管理员登录' 提升语义
@one-ea
one-ea force-pushed the fix/admin-password-autofill branch from 68b6b00 to f9ff9d4 Compare April 26, 2026 12:50
@one-ea
one-ea merged commit 72e45e3 into main Apr 26, 2026
10 checks passed
@one-ea
one-ea deleted the fix/admin-password-autofill branch April 26, 2026 12:53
@one-ea one-ea mentioned this pull request Apr 26, 2026
@github-actions github-actions Bot mentioned this pull request May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working frontend 前端 (React/Vite) 相关变更

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant