Skip to content

Email users about low recovery codes and redirect to regenerate after the last one - #1015

Merged
masteradhoc merged 2 commits into
masterfrom
backup-code-reminders
Oct 24, 2026
Merged

masteradhoc merged 2 commits into
masterfrom
backup-code-reminders

Conversation

@masteradhoc

@masteradhoc masteradhoc commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

What?

Emails users when they are running low on, or have run out of, recovery codes. After a user logs in with their last code, they are sent straight to the recovery codes section of their profile.

Follow-up to #907.

Why?

#907 added admin notices that warn users when they are running low on recovery codes, and when they have none left. Those notices only appear in wp-admin. An email reaches users wherever they are. Redirecting to the recovery codes section after the last code saves them the extra step.

How?

  • Email: Two_Factor_Backup_Codes::validate_code() now calls the new notify_user_codes_running_low() after a code is used.
    • It sends when the remaining count is at or below the threshold (default 2), and always when no codes are left. This matches the existing notices.
    • It only sends if recovery codes are enabled for the user.
  • Shared threshold: The two_factor_backup_codes_low_threshold filter moved into a private get_low_codes_threshold() helper, so the notice and the email always use the same value. Name, default and arguments are unchanged.
  • Redirect: The new maybe_redirect_to_regenerate_codes() runs on two_factor_user_authenticated and two_factor_user_revalidated.
    • If the user just authenticated with a recovery code and has none left, it adds a login_redirect filter at PHP_INT_MAX pointing to get_edit_profile_url() . '#two-factor-backup-codes'.
    • It uses get_edit_profile_url() because get_edit_user_link() returns an empty string during login, before the current user is set.
    • Interim (modal) logins don't redirect, so they behave as before.
  • Docs: Both filters are added to the hook list in readme.txt. The threshold filter shipped in 0.17.0 but wasn't documented yet. The new tests are listed in TESTS.md.
  • Tests: 7 new tests in tests/providers/class-two-factor-backup-codes.php, plus an end-to-end login test in tests/class-two-factor-core.php.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Implementation, tests, documentation updates and this description. I reviewed and tested the changes before opening the PR.

Testing Instructions

  1. Log in as a user with the Authenticator App or Email method enabled.
  2. Generate 4 recovery codes: wp two-factor backup-codes generate <user> --count=4 --yes.
  3. To see the emails, use a mail catcher such as Mailpit, or a mail logging plugin.
  4. Log out, log in, and choose "Use a recovery code" at the two-factor prompt. Repeat once for each code:
    • 1st code (3 left): no email.
    • 2nd code (2 left): email "[Site] You are running low on recovery codes", saying 2 codes are left.
    • 3rd code (1 left): the same email, saying 1 code is left.
    • 4th code (none left): email "[Site] You are out of recovery codes". You are redirected to Profile → Recovery Codes instead of the dashboard.
  5. Log in again with your other method. You are not redirected.
  6. Optional: add add_filter( 'two_factor_backup_codes_low_threshold', '__return_zero' ); to an mu-plugin and repeat. Only the "out of recovery codes" email is sent.

Changelog Entry

Added - Email users when they are running low on, or have run out of, recovery codes, and redirect them to regenerate their codes after they use the last one.

Open WordPress Playground Preview

@masteradhoc masteradhoc added this to the 0.18.0 milestone Oct 2, 2026
@masteradhoc masteradhoc self-assigned this Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@masteradhoc
masteradhoc marked this pull request as ready for review October 2, 2026 21:03
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: masteradhoc <masteradhoc@git.wordpress.org>
Co-authored-by: georgestephanis <georgestephanis@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@georgestephanis georgestephanis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fine but a couple minor things that we may want to tidy first.

Comment thread providers/class-two-factor-backup-codes.php Outdated
Comment thread providers/class-two-factor-backup-codes.php Outdated
Comment thread providers/class-two-factor-backup-codes.php
Comment thread providers/class-two-factor-backup-codes.php
Comment thread providers/class-two-factor-backup-codes.php
@masteradhoc
masteradhoc merged commit e486392 into master Oct 24, 2026
37 of 39 checks passed
@masteradhoc
masteradhoc deleted the backup-code-reminders branch October 24, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants