fix: add autocomplete="one-time-code" for TOTP autofill support

Add a hidden input with autocomplete="one-time-code" so password managers
(1Password, Bitwarden, Chrome, Apple Keychain) can detect and auto-fill
TOTP verification codes during 2FA login.
This commit is contained in:
hoobnn 2026-05-12 13:53:38 +08:00
parent 62ccd0ff39
commit 4467922199

View File

@ -24,6 +24,18 @@
<!-- Code Input -->
<div class="mb-6">
<!-- Hidden input for password manager autofill (autocomplete="one-time-code") -->
<input
ref="hiddenOtpInputRef"
type="text"
inputmode="numeric"
autocomplete="one-time-code"
maxlength="6"
class="pointer-events-none absolute left-0 top-0 h-px w-px opacity-0"
aria-hidden="true"
tabindex="-1"
@input="handleHiddenOtpInput"
/>
<div class="flex justify-center gap-2">
<input
v-for="(_, index) in 6"
@ -33,6 +45,7 @@
maxlength="1"
inputmode="numeric"
pattern="[0-9]"
autocomplete="off"
class="h-12 w-10 rounded-lg border border-gray-300 text-center text-lg font-semibold focus:border-primary-500 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
:disabled="verifying"
@input="handleCodeInput($event, index)"
@ -82,6 +95,7 @@ const appStore = useAppStore()
const verifying = ref(false)
const code = ref<string[]>(['', '', '', '', '', ''])
const inputRefs = ref<(HTMLInputElement | null)[]>([])
const hiddenOtpInputRef = ref<HTMLInputElement | null>(null)
// Watch for code changes and auto-submit when 6 digits are entered
watch(
@ -104,6 +118,10 @@ defineExpose({
inputRefs.value.forEach(input => {
if (input) input.value = ''
})
// Clear hidden autofill input
if (hiddenOtpInputRef.value) {
hiddenOtpInputRef.value.value = ''
}
nextTick(() => {
inputRefs.value[0]?.focus()
})
@ -126,6 +144,26 @@ const handleCodeInput = (event: Event, index: number) => {
}
}
// Handle autofill from password managers via the hidden autocomplete="one-time-code" input
const handleHiddenOtpInput = (event: Event) => {
const input = event.target as HTMLInputElement
const digits = input.value.replace(/[^0-9]/g, '').slice(0, 6).split('')
digits.forEach((digit, i) => {
code.value[i] = digit
if (inputRefs.value[i]) {
inputRefs.value[i]!.value = digit
}
})
for (let i = digits.length; i < 6; i++) {
code.value[i] = ''
if (inputRefs.value[i]) {
inputRefs.value[i]!.value = ''
}
}
}
const handleKeydown = (event: KeyboardEvent, index: number) => {
if (event.key === 'Backspace') {
const input = event.target as HTMLInputElement