fix(admin): stop the browser autofilling credentials into the settings search box

A settings screen renders twenty-odd AppSecret and private-key fields as
type="password". Chrome reads any page carrying a password input as a login
form and fills the saved username and password into the first text box it
finds — the settings search field at the top of the panel.

Suppressing autocomplete on that one input could not fix it, because the
trigger is the password inputs elsewhere on the page. Secret fields now render
as text with CSS masking (-webkit-text-security: disc), so they still look
like ciphertext but no longer register as password inputs; the mask lifts on
focus so a freshly typed value can be checked. That covers the 21 schema-driven
fields through JioField plus the four captcha provider forms and the WeChat
template page.

The search box keeps a second layer of defence: a descriptive name and
autocomplete/autocorrect/spellcheck disabled, with the LastPass and 1Password
ignore markers set.

Verification: vite build succeeds and the emitted bundles no longer contain a
single type="password"; 980 admin/widget tests pass.

Note: assets/admin/build/ is gitignored, so the production build must be run on
deploy. -webkit-text-security is WebKit/Blink only — Firefox falls back to
plain text.
This commit is contained in:
kate522 committed 2026-11-04 18:53:44 +08:00
1 parent b1c3d86e44
commit bcba0f598a
8 files changed
+60 -7

No files matched your search

@@ -15,6 +15,8 @@
placeholder="搜索设置项..."
size="small"
clearable
name="jio-settings-search"
:input-props="searchInputProps"
>
<template #prefix>
<FaIcon icon="fa fas fa-search" :size="12" />
@@ -185,6 +187,18 @@ const slots = useSlots()
const searchQuery = ref('')
// 同一页面上存在大量 type="password" 的凭证字段,Chrome 会把整页当成登录
// 表单,并把保存的账号灌进第一个文本框(也就是这个搜索框)。改名成搜索语义,
// 并显式关掉各类自动填充,才能让浏览器不再识别它。
const searchInputProps = {
autocomplete: 'off',
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'false',
'data-lpignore': 'true',
'data-1p-ignore': 'true',
}
/** Evaluate dependsOn rule against current form data */
function matchDependsOn(dependsOn) {
if (!dependsOn) return true