# Authentication And MFA

## Request Authentication

`src/index.php` resolves the requested page after `helpers.php` loads the account into these constants:

- `ACCOUNT_NAME` is the authenticated username or null.
- `ACCOUNT_DATA` is the authenticated account row or null.
- `PAGE` is the final resolved route.

User-panel routes are private by default. Unauthenticated requests load the public `login` controller and view in place while preserving the original request URL. Routes with `hasToken` such as `chpass` and `chemail` remain available for token-based access.

After route authorization, an authenticated account with a configured MFA secret loads `mfa` in place unless the current page is `mfa` or `signout`. MFA returns to the original request URL. Direct login or MFA requests with no return URL fall back to `account-panel`.

## MFA Feature

MFA is implemented in shared code and the active model override:

- `src/core/modules/Mfa/Mfa.php` handles TOTP secrets, QR generation, verification, and the verified session marker.
- `src/model/base/mfa.model.php` reads and updates `accounts.otpsecret`.
- `src/model/{source}/mfa.model.php` may override the account identifier field for a source.
- `src/pages/private/mfa/` contains enrollment, challenge, removal, and confirmation-email handling.

The `mfa` route is a user-panel route with `mfaExempt` metadata. `mfaIsEnabled()` allows the feature when the section is enabled, and also permits PM/GM accounts when the section is disabled. Do not replace this with a controller-only check because the global redirect and route visibility use the same capability.

Enrollment flow:

1. A valid CSRF token and account email are required.
2. A pending secret is stored in `$_SESSION['mfa_pending_secret']`.
3. The QR payload is generated from the pending secret using the server name as issuer.
4. The enrollment view shows the QR code and the raw pending secret together in an info box. Users can scan the QR code or manually add the account to Google Authenticator, Authy, or another 2FA app with that secret.
5. A valid six-digit TOTP stores the secret in `accounts.otpsecret`, marks the session verified, and redirects to `from`.

Challenge flow:

1. Login succeeds but `mfaIsRequired()` detects an account secret without `$_SESSION['mfa_verified']`.
2. The original request URL is internally rendered as the MFA screen.
3. A valid code calls `mfaMarkVerified()`, regenerates the session ID, stores a hash of the secret, and redirects to the original request URL.

Removal verifies the current TOTP, clears `accounts.otpsecret`, clears the MFA session markers, and sends a confirmation email. Invalid codes are recorded against both the client IP and account limiter scopes.

## Session Rules

- `$_SESSION['mfa_pending_secret']` exists only during enrollment and is removed after successful activation.
- `$_SESSION['mfa_verified']` and `$_SESSION['mfa_verified_secret']` authorize the current session for the current secret.
- Verification stores a SHA-256 hash, not the TOTP secret itself.
- Any secret removal must call `mfaClearVerification()`.
- Any successful verification must call `mfaMarkVerified()`.

## Routing And Languages

Pretty URLs are rewritten by `src/.htaccess`. The language-only rule matches one to three lowercase characters, so a three-character route can collide with it. `/mfa` must remain an explicit rewrite before that rule; otherwise it is interpreted as `lang=mfa&page=index`. Localized routes such as `/en/mfa` use the generic localized-page rule.

When adding a short route, check it against the language-only rewrite rule and add an explicit rule or narrow the language pattern if necessary. Preserve query parameters on explicit rewrites with `QSA`, especially MFA's `from` parameter.

## Security Requirements

- Use `isValidToken()` for every MFA state-changing POST.
- Normalize submitted OTP input through `mfaVerify()`; it accepts digits only and requires six digits.
- Apply both `mfa_ip` and `mfa_account` limits before verification and record failures only after an allowed attempt fails.
- Keep the redirect destination constrained to the existing plain-route validation in `mfa.controller.php`.
- Keep QR generation local with the configured Bacon QR provider; do not expose the secret through a remote QR service.
