Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions TESTING_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Test Issue #880 - TOTP codes from Apple Passwords wiped on login

## Install

Download `two-factor-fix-880.zip`, install via WP Admin -> Plugins -> Upload Plugin.

## What changed

- Removed `autocomplete="off"` from the 2FA login form so password managers can autofill
- Added `autocomplete="one-time-code"` to backup-codes input (TOTP and Email already had it)
- Removed 200ms JS blanker that wiped autofilled codes (two-factor-login.js)
- Rewrote space-insertion logic to be stateless (no more flag drift on autofill)

## Test

1. Enable TOTP for test user, add account to password manager
2. Log out, log in until 2FA prompt
3. Autofill the TOTP code from password manager
4. Expected: code stays in field, form auto-submits
5. Previously: code wiped 200ms after page load

Repeat with Email and Backup Codes providers. All three should accept autofilled codes.

## Edge cases

- Manually typing code still gets midpoint space inserted
- Pasting full code with space (123 456) accepted and auto-submits
- Clearing and retyping still works (no stale flag)

## Requirements

WordPress 6.7+, PHP 7.2.24+
Comment thread
faisalahammad marked this conversation as resolved.
Outdated
10 changes: 1 addition & 9 deletions class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,6 @@ public static function add_hooks( $compat ) {
public static function login_enqueue_scripts() {
$environment_prefix = file_exists( TWO_FACTOR_DIR . '/dist' ) ? '/dist' : '';

wp_register_script(
'two-factor-login',
plugins_url( $environment_prefix . '/providers/js/two-factor-login.js', __FILE__ ),
array(),
TWO_FACTOR_VERSION,
true
);

wp_register_script(
'two-factor-login-authcode',
plugins_url( $environment_prefix . '/providers/js/two-factor-login-authcode.js', __FILE__ ),
Expand Down Expand Up @@ -1127,7 +1119,7 @@ public static function login_html( $user, $login_nonce, $redirect_to, $error_msg
}
?>

<form name="validate_2fa_form" id="loginform" action="<?php echo esc_url( self::login_url( array( 'action' => $action ), 'login_post' ) ); ?>" method="post" autocomplete="off">
<form name="validate_2fa_form" id="loginform" action="<?php echo esc_url( self::login_url( array( 'action' => $action ), 'login_post' ) ); ?>" method="post">
<input type="hidden" name="provider" id="provider" value="<?php echo esc_attr( $provider_key ); ?>" />
<input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>" />
<input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo esc_attr( $login_nonce ); ?>" />
Expand Down
2 changes: 1 addition & 1 deletion providers/class-two-factor-backup-codes.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ public function authentication_page( $user ) {
?>
<p>
<label for="authcode"><?php esc_html_e( 'Recovery Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $code_placeholder ); ?>" data-digits="<?php echo esc_attr( $code_length ); ?>" />
<input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $code_placeholder ); ?>" autocomplete="one-time-code" data-digits="<?php echo esc_attr( $code_length ); ?>" />
</p>
<?php
/**
Expand Down
1 change: 0 additions & 1 deletion providers/class-two-factor-email.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,6 @@ public function authentication_page( $user ) {
<p class="two-factor-email-resend">
<input type="submit" class="button" name="<?php echo esc_attr( self::INPUT_NAME_RESEND_CODE ); ?>" value="<?php esc_attr_e( 'Resend Code', 'two-factor' ); ?>" />
</p>
<?php wp_enqueue_script( 'two-factor-login' ); ?>
<?php
}

Expand Down
2 changes: 0 additions & 2 deletions providers/class-two-factor-totp.php
Original file line number Diff line number Diff line change
Expand Up @@ -796,8 +796,6 @@ public function authentication_page( $user ) {
do_action( 'two_factor_after_authentication_input', $this );
?>
<?php
wp_enqueue_script( 'two-factor-login' );

submit_button( __( 'Verify', 'two-factor' ) );
Comment thread
faisalahammad marked this conversation as resolved.
Outdated
}

Expand Down
30 changes: 18 additions & 12 deletions providers/js/two-factor-login-authcode.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
/* global document */
( function() {
// Enforce numeric-only input for numeric inputmode elements.
// Enforce numeric-only input and normalize spacing for numeric inputmode
// authcode fields. Space insertion is derived from the current value
// (not a flag) so that autofilled codes — which deliver the full value
// in a single input event rather than character-by-character — are
// handled correctly.
var form = document.querySelector( '#loginform' ),
inputEl = document.querySelector( 'input.authcode[inputmode="numeric"]' ),
expectedLength = ( inputEl && inputEl.dataset ) ? inputEl.dataset.digits : 0,
spaceInserted = false;
expectedLength = ( inputEl && inputEl.dataset ) ? parseInt( inputEl.dataset.digits, 10 ) : 0,
halfLength = Math.floor( expectedLength / 2 );

if ( inputEl ) {
inputEl.addEventListener(
'input',
function() {
var value = this.value.replace( /[^0-9 ]/g, '' ).replace( /^\s+/, '' ),
var sanitized = this.value.replace( /[^0-9 ]/g, '' ).replace( /^\s+/, '' ),
digits = sanitized.replace( / /g, '' ),
submitControl;

if ( ! spaceInserted && expectedLength && value.length === Math.floor( expectedLength / 2 ) ) {
value += ' ';
spaceInserted = true;
} else if ( spaceInserted && ! this.value ) {
spaceInserted = false;
// Insert a space at the midpoint when only the first half has
// been entered and no space is present yet. Checking the
// current value (not a flag) ensures this also fires after
// the field is cleared and re-typed.
if ( halfLength && sanitized.length === halfLength && digits.length === halfLength && sanitized.indexOf( ' ' ) === -1 ) {
sanitized += ' ';
}

this.value = value;
this.value = sanitized;

// Auto-submit if it's the expected length.
if ( expectedLength && value.replace( / /g, '' ).length === parseInt( expectedLength, 10 ) ) {
// Auto-submit once the full code length is reached.
if ( expectedLength && digits.length === expectedLength ) {
if ( form && typeof form.requestSubmit === 'function' ) {
form.requestSubmit();
submitControl = form.querySelector( '[type="submit"]' );
Expand Down
11 changes: 0 additions & 11 deletions providers/js/two-factor-login.js

This file was deleted.

Loading