=== T2F Two-Screen Login Form ===
Contributors: t2ftech, thaissamendes
Tags: login, login form, authentication, shortcode, front-end login
Requires at least: 5.6
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.txt

An Amazon-style two-screen login: enter your email first, then your password on a second screen. Added to any page with a shortcode.

== Description ==

Most front-end login forms put the email and password on one screen. Big consumer
sites (Amazon, Google, PayPal...) split it in two: you type your email, press
**Continue**, and only then are you asked for your password.

**T2F Two-Screen Login Form** brings that flow to WordPress as a shortcode you can drop on
any page:

`[two_step_login]`

= How it works =

1. **Step 1 — identifier.** The visitor enters their email (optionally username too)
   and presses **Continue**. By default this transition happens entirely in the
   browser — no server request — so the endpoint only ever sees the final submit.
2. **Step 2 — password.** The email is shown with a **Change** link back to step 1.
   The visitor enters their password, optionally ticks **Remember me**, and signs in.
3. On success the browser is redirected (to `?redirect_to=` if present and on-site,
   otherwise to the page you configure — the WooCommerce account page by default,
   or the site home).

Everything happens without a full page reload until the final redirect.

= Keeping the load down =

Because step 1 is resolved client-side, a normal sign-in makes exactly one
`admin-ajax.php` call. Failed password attempts are rate-limited per IP address
and per email in a short rolling window *before* WordPress authentication runs,
and an off-screen honeypot field plus a minimum fill-time check drop obvious bot
submissions without the (CPU-heavy) password hash. Together these keep
credential-stuffing traffic from turning the login page into a load problem.

(When **Unknown accounts** is set to reveal missing accounts, step 1 still needs a
server round trip, since that answer can only come from the database.)

= Privacy =

By default the two possible step-1 responses are identical whether or not an account
exists, and failed logins return a single generic message — so the form cannot be
used to discover which email addresses have accounts. A setting lets you turn on
explicit "no account found" messages if you prefer Amazon's behaviour.

= Settings (Settings → Two-Screen Login) =

* **First step accepts** — email only, or email *or* username.
* **Unknown accounts** — stay silent (default) or say when no account matches.
* **Rate limiting** — throttle repeated failed logins per IP / email (on by default).
* **Redirect after login** — a URL, or blank for the account page / home.
* **Lost-password URL** — a URL, or blank for the default WordPress reset page.

= For developers =

`add_filter( 'tslf_redirect_url', function ( $url ) { return home_url( '/dashboard/' ); } );
add_filter( 'tslf_lostpassword_url', function ( $url ) { return '/forgot/'; } );
add_filter( 'tslf_template', function ( $path, $name, $args ) { return $path; }, 10, 3 );
add_filter( 'tslf_logged_in_notice', function ( $html, $user ) { return $html; }, 10, 2 );
add_action( 'tslf_logged_in', function ( $user ) { /* ... */ } );

// Abuse mitigation.
add_filter( 'tslf_throttle', function ( $c ) { $c['id_max'] = 5; return $c; } ); // window, ip_max, id_max
add_filter( 'tslf_min_fill_ms', function () { return 2000; } );
add_filter( 'tslf_client_ip', function ( $ip ) { return $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $ip; } );`

The two step templates (`templates/step-identifier.php`, `templates/step-password.php`)
can be swapped with the `tslf_template` filter. Style hooks are plain classes
(`.tslf`, `.tslf-form`, `.tslf-step`, `.tslf-error`, `.tslf-submit`) and CSS custom
properties (`--tslf-accent`, `--tslf-border`, …).

= Notes & limitations =

* This is a **front-end** form for a page of your choosing. It does not replace
  `wp-login.php` or change wp-admin.
* No social login, no 2FA — those are separate concerns handled by other plugins.
* If you serve the login page from a full-page cache, exclude it (or its nonce may
  age out for logged-out visitors after ~12 hours).
* Translations (including Brazilian Portuguese) are managed on
  translate.wordpress.org, not bundled with the plugin. The text domain is
  `t2f-two-screen-login`.

== Installation ==

1. Install and activate the plugin.
2. Create a page (e.g. "Sign in") and add the shortcode `[two_step_login]`.
3. Optionally review **Settings → Two-Screen Login**.
4. Point your theme's "Log in" links at that page.

== Frequently Asked Questions ==

= Does it work with WooCommerce? =

Yes. If WooCommerce is active and no redirect is configured, users land on the
My Account page after logging in. It does not otherwise depend on WooCommerce.

= Can visitors log in with their username instead of email? =

Yes — set **First step accepts** to "Email address or username".

= JavaScript is required? =

The stepped experience is JavaScript-driven, but the form also works with JS
disabled or blocked: it falls back to a full-page, step-by-step flow instead of
the in-browser transition. The bot timing check is skipped for no-JS submissions
(the honeypot and rate limiting still apply).

= Does it support "Remember me"? =

Yes, via a checkbox on the password step.

= Is the password ever exposed? =

No. It is sent once, over your site's normal (HTTPS) connection, to WordPress's
standard `wp_signon()` authentication — the same function core uses.

== Screenshots ==

1. Step 1 — the visitor enters their email address.
2. Step 2 — the password screen, with a "Change" link back to the email step.
3. Settings -> Two-Screen Login.

== Changelog ==

= 1.1.0 =
* Fix: the login form's `<form>` has no `action` attribute, so with JavaScript disabled or blocked it silently failed to sign anyone in — there was no server-side handler for that plain POST. A Post/Redirect/Get fallback now handles it and resumes the flow step by step.
* Fix (security): a failed password attempt could lock a *known* account out of the form for every visitor, from a single IP, without ever supplying a password. The per-identifier rate limit is now scoped to (IP, identifier) instead of the identifier alone.
* Fix (security): with "First step accepts" set to email only, an identifier that didn't resolve to an account could still reach WordPress authentication as a raw username, silently bypassing that setting for anyone who already knew a valid username + password.
* Fix: `?redirect_to=` was silently dropped on the default sign-in flow, since the request that authenticates carries no query string of its own. It's now carried forward as a hidden field.
* Fix: the non-JS fallback's redirect could 404 on a subdirectory install (e.g. `example.com/blog`) by doubling the site's own path prefix.
* Fix: the rate limiter's object-cache storage path had a non-atomic write that could race and undercount concurrent failed attempts under load; removed.
* Fix: `uninstall.php` only cleaned up the site it ran on, and missed the non-JS fallback's own transients. It's now multisite-aware and covers both transient prefixes.

= 1.0.0 =
* Initial release.
