From 0ad6100984c522a9ed2c1ed0c545d88c0edd12f4 Mon Sep 17 00:00:00 2001 From: Bram Date: Fri, 9 Jan 2026 10:26:28 +0100 Subject: [PATCH] Allow pasting in 6 digit auth code input (#4475) --- .../allow_pasting_in_auth_code_input.json | 9 ++++ .../settings/twoFactorAuth/AuthCodeInput.vue | 54 +++++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 changelog/entries/unreleased/feature/allow_pasting_in_auth_code_input.json diff --git a/changelog/entries/unreleased/feature/allow_pasting_in_auth_code_input.json b/changelog/entries/unreleased/feature/allow_pasting_in_auth_code_input.json new file mode 100644 index 0000000000..42c62d9316 --- /dev/null +++ b/changelog/entries/unreleased/feature/allow_pasting_in_auth_code_input.json @@ -0,0 +1,9 @@ +{ + "type": "feature", + "message": "Allow pasting in 6 digit auth code input.", + "issue_origin": "github", + "issue_number": null, + "domain": "core", + "bullet_points": [], + "created_at": "2025-12-17" +} diff --git a/web-frontend/modules/core/components/settings/twoFactorAuth/AuthCodeInput.vue b/web-frontend/modules/core/components/settings/twoFactorAuth/AuthCodeInput.vue index 833c363d3a..ed4dc50c06 100644 --- a/web-frontend/modules/core/components/settings/twoFactorAuth/AuthCodeInput.vue +++ b/web-frontend/modules/core/components/settings/twoFactorAuth/AuthCodeInput.vue @@ -13,8 +13,10 @@ :class="{ 'auth-code-input__input--filled': allFilled }" @keyup="handleKeyUp" @keydown="handleKeyDown" + @paste="pasteAt(1, $event)" /> @@ -87,6 +98,7 @@ export default { number5: '', number6: '', }, + hasEmitted: false, } }, computed: { @@ -152,10 +164,25 @@ export default { return this.code.length === 6 }, }, + watch: { + allFilled(isFilled) { + if (isFilled && !this.hasEmitted) { + this.hasEmitted = true + this.$emit('all-filled', this.code) + } + if (!isFilled) { + this.hasEmitted = false + } + }, + }, mounted() { this.reset() }, methods: { + focusIndex(i) { + const el = this.$refs[`input${i}`] + if (el && typeof el.focus === 'function') el.focus() + }, reset() { this.values.number1 = '' this.values.number2 = '' @@ -164,6 +191,7 @@ export default { this.values.number5 = '' this.values.number6 = '' this.$refs.input1.focus() + this.hasEmitted = false }, sanitizeInput(value) { const sanitized = value.replace(/\D/g, '').slice(0, 1) @@ -192,11 +220,31 @@ export default { if (nextInput && nextInput.tagName === 'INPUT') { nextInput.focus() } + } + }, + pasteAt(startIndex, event) { + event.preventDefault() - if (this.allFilled) { - this.$emit('all-filled', this.code) - } + const raw = + (event.clipboardData && event.clipboardData.getData('text')) || + (window.clipboardData && window.clipboardData.getData('Text')) || + '' + + const digits = raw.replace(/\D/g, '') + const maxLen = 7 - startIndex + const chunk = digits.slice(0, maxLen) + + for (let i = startIndex; i <= 6; i++) { + this.values[`number${i}`] = '' } + + for (let offset = 0; offset < chunk.length; offset++) { + const i = startIndex + offset + this.values[`number${i}`] = chunk[offset] + } + + const nextIndex = Math.min(startIndex + chunk.length, 6) + this.$nextTick(() => this.focusIndex(nextIndex)) }, }, }