-
Notifications
You must be signed in to change notification settings - Fork 174
Description
Summary
The TOTP setup on the profile page uses a two-step save flow:
- "Submit" button (inline, next to the code input) — fires a REST API call (
POST /two-factor/1.0/totp) that validates the authenticator code and saves_two_factor_totp_key. - "Update Profile" button (bottom of page) — submits the main profile form, which saves
_two_factor_enabled_providerswith whatever checkboxes are checked.
If a user checks the TOTP "Enabled" checkbox, skips the "Submit" verification button, and clicks "Update Profile" directly, _two_factor_enabled_providers is saved with Two_Factor_Totp listed but _two_factor_totp_key was never written. This creates an inconsistent state.
Steps to Reproduce
- Go to your profile page with Two Factor active.
- Check the "Enabled" checkbox next to Time Based One-Time Password (TOTP).
- Do not scan the QR code or click the "Submit" verification button.
- Scroll down and click "Update Profile."
- Check user meta:
_two_factor_enabled_providersnow includesTwo_Factor_Totp, but_two_factor_totp_keyis empty.
Expected Behavior
The server-side profile save handler (personal_options_update / edit_user_profile_update) should validate that the TOTP key exists in user meta before allowing Two_Factor_Totp to be included in _two_factor_enabled_providers. If the key is missing, the provider should be silently removed from the enabled list and/or an admin notice should warn the user that TOTP setup is incomplete.
The PR #643 improved the JS side (auto-checking the Enabled box after successful REST verification), but the server side still allows saving an invalid provider configuration.
Impact
The inconsistent state causes Two_Factor_Totp::is_available_for_user() to return false (no key), which causes get_primary_provider_for_user() to silently fall back to another provider (typically Backup Codes). See #796 for the downstream impact of the silent fallback.
This is easy to trigger accidentally — the "Update Profile" button is the large, prominent button at the bottom of the page, while the TOTP "Submit" verification button is a small inline button that users naturally overlook.
Suggested Fix
In the personal_options_update handler where _two_factor_enabled_providers is saved, add a check:
if ( in_array( 'Two_Factor_Totp', $providers, true ) ) {
$key = get_user_meta( $user_id, self::SECRET_META_KEY, true );
if ( empty( $key ) ) {
// Remove TOTP from enabled providers — setup was never completed.
$providers = array_diff( $providers, array( 'Two_Factor_Totp' ) );
// Optionally: set an admin notice warning the user.
}
}Environment
- Two Factor 0.14.2
- WordPress 6.7.x
- Tested on both MySQL and SQLite backends
Related Issues
- Silent fallback to recovery codes when TOTP key is missing #796 — Silent fallback to recovery codes when TOTP key is missing (the downstream consequence of this bug)
- Activating multiple methods is confusing and can fail silently #157 — General UX confusion around activating multiple methods
- Enable TOTP method when method is configured #643 — Auto-enables TOTP checkbox after REST verification (JS-side improvement)
- Backup codes are saved before user intends #507 — Analogous issue for Backup Codes (saved before user intends)
Metadata
Metadata
Assignees
Labels
Type
Projects
Status