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.
53 lines
1.9 KiB
Vue
53 lines
1.9 KiB
Vue
<template>
|
|
<n-form label-placement="left" label-width="130">
|
|
<n-space vertical style="width:100%">
|
|
<n-form-item label="Site Key">
|
|
<n-input v-model:value="model.site_key" placeholder="Cloudflare Turnstile Site Key" autocomplete="off" />
|
|
</n-form-item>
|
|
<n-form-item label="Secret Key">
|
|
<n-input v-model:value="model.secret_key" type="text" show-password-on="click" class="jio-secret-input" autocomplete="new-password" @update:value="onSecretInput('secret_key', $event)" />
|
|
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
|
</n-form-item>
|
|
<n-form-item label="主题">
|
|
<n-select v-model:value="model.theme" :options="themeOptions" clearable />
|
|
</n-form-item>
|
|
<n-form-item label="尺寸">
|
|
<n-select v-model:value="model.size" :options="sizeOptions" clearable />
|
|
</n-form-item>
|
|
<n-tag v-if="isConfigured" type="success" size="small">✓ 已完整配置</n-tag>
|
|
<n-tag v-else type="warning" size="small">未完成配置</n-tag>
|
|
</n-space>
|
|
</n-form>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
|
|
const model = defineModel({ type: Object, default: () => ({}) })
|
|
const emit = defineEmits(['clear-secret', 'restore-secret'])
|
|
|
|
function clearSecret(field) {
|
|
emit('clear-secret', field)
|
|
model.value = { ...model.value, [field]: '' }
|
|
}
|
|
|
|
function onSecretInput(field, value) {
|
|
if (value !== '') emit('restore-secret', field)
|
|
}
|
|
|
|
const isConfigured = computed(() => {
|
|
const c = model.value
|
|
return !!(c.site_key && c.secret_key)
|
|
})
|
|
|
|
const themeOptions = [
|
|
{ label: '自动', value: 'auto' },
|
|
{ label: '浅色', value: 'light' },
|
|
{ label: '深色', value: 'dark' },
|
|
]
|
|
const sizeOptions = [
|
|
{ label: '正常', value: 'normal' },
|
|
{ label: '紧凑', value: 'compact' },
|
|
]
|
|
</script>
|