diff --git a/keepassxc-browser/content/totp-field.js b/keepassxc-browser/content/totp-field.js index a6c4b54..8a99aea 100644 --- a/keepassxc-browser/content/totp-field.js +++ b/keepassxc-browser/content/totp-field.js @@ -39,17 +39,23 @@ kpxcTOTPIcons.deleteHiddenIcons = function() { kpxcUI.deleteHiddenIcons(kpxcTOTPIcons.icons, 'kpxc-totp-field'); }; +kpxcTOTPIcons.autoCompleteIsOneTimeCode = function(field) { + if (!field) { + return false; + } + + return field.getLowerCaseAttribute('autocomplete') === 'one-time-code'; +}; + // Quick check for a valid TOTP field kpxcTOTPIcons.isAcceptedTOTPField = function(field) { const id = field.getLowerCaseAttribute('id'); const name = field.getLowerCaseAttribute('name'); - const autocomplete = field.getLowerCaseAttribute('autocomplete'); const placeholder = field.getLowerCaseAttribute('placeholder'); // Checks if the field id, name or placeholder includes some of the acceptedOTPFields but not any from ignoredTypes - if (autocomplete === 'one-time-code' - || (acceptedOTPFields.some(f => (id && id.includes(f)) || (name && name.includes(f) || placeholder && placeholder.includes(f))) || acceptedParents.some(s => field.closest(s))) - && !ignoredTypes.some(f => (id && id.includes(f)) || (name && name.includes(f) || placeholder && placeholder.includes(f)))) { + if ((acceptedOTPFields.some(f => (id && id.includes(f)) || (name && name.includes(f) || placeholder && placeholder.includes(f))) || acceptedParents.some(s => field.closest(s))) + && !ignoredTypes.some(f => (id && id.includes(f)) || (name && name.includes(f) || placeholder && placeholder.includes(f)))) { return true; } @@ -61,6 +67,11 @@ kpxcTOTPIcons.isAcceptedTOTPField = function(field) { }; kpxcTOTPIcons.isValid = function(field, forced) { + // Always accept 'one-time-code' + if (kpxcTOTPIcons.autoCompleteIsOneTimeCode(field)) { + return true; + } + if (!field || !kpxcTOTPIcons.isAcceptedTOTPField(field)) { return false; } diff --git a/tests/assert.js b/tests/assert.js index 554964c..443d488 100644 --- a/tests/assert.js +++ b/tests/assert.js @@ -52,11 +52,10 @@ async function assertTOTPField(classStr, properties, testName, expectedResult) { const input = kpxcUI.createElement('input', classStr, properties); document.body.appendChild(input); - const isAccepted = kpxcTOTPIcons.isAcceptedTOTPField(input); const isValid = kpxcTOTPIcons.isValid(input); document.body.removeChild(input); - kpxcAssert(isAccepted && isValid, expectedResult, Tests.TOTP_FIELDS, testName); + kpxcAssert(isValid, expectedResult, Tests.TOTP_FIELDS, testName); } async function assertSearchField(classStr, properties, testName, expectedResult) { diff --git a/tests/tests.js b/tests/tests.js index 304d6fb..fcd3254 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -103,6 +103,7 @@ async function testTotpFields() { [ '', { id: '2fa', type: 'text', maxLength: '6' }, 'Generic 2FA field', true ], [ '', { id: '2fa', type: 'text', maxLength: '4' }, 'Ignore if field maxLength too small', false ], [ '', { id: '2fa', type: 'text', maxLength: '12' }, 'Ignore if field maxLength too long', false ], + [ '', { id: '2fa', type: 'text', maxLength: '12', autocomplete: 'one-time-code' }, 'Accept if one-time-code', true ], [ '', { id: 'username', type: 'text', }, 'Ignore a generic input field', false ], [ '', { type: 'password', }, 'Ignore a password input field', false ], [ // Protonmail