Skip to content

Release v1.45.0 — Fomalhaut: The Second Factor (core email 2FA + selectRaw bindings)

Choose a tag to compare

@MichaelSowah MichaelSowah released this 28 May 00:10
· 186 commits to main since this release
4c5daf9

Summary

Ships framework v1.45.0 "Fomalhaut". Two headline features plus security docs and cleanups:

  • Core email-PIN 2FA (opt-in, off by default). When TWO_FACTOR_ENABLED=true, POST /auth/login for an enrolled user returns
    a challenge_token and emails a 6-digit PIN; the client completes login at POST /2fa/verify. New src/Auth/TwoFactor/ services
    (TwoFactorService, ChallengeTokenIssuer, JtiBlocklist), TwoFactorController (/2fa/enable|verify|disable,
    /2fa/* routes aren't registered, the column is never read, and login behaves exactly as before.
  • QueryBuilder::selectRaw() parameter bindings. selectRaw(string $expression, array $bindings = []) binds positional ?
    values — closing the last unsafe-by-design gap in the SELECT clause. Backward compatible; also fixes a latent clone()
    column/binding mismatch.
  • docs/SECURITY.md — documents the SQL-injection and XSS model.
  • AdminPermissionMiddleware — drops the dead validateMfaToken()/X-MFA-Token placeholder; require_mfa now reads only the
    session handshake.

Key design points

  • AuthenticationService::authenticate()'s username/password branch is split into verifyCredentials() + issueSession() to
    expose a "verified user, no session yet" gate; the token/API-key provider short-circuit is unchanged. Both login paths shape
    responses via a new LoginResponseShaper, so a 2FA-completed login is byte-for-byte identical to a direct login (same CSRF token +
    login events).
  • /2fa/verify re-validates the account (existence, status allowlist, 2FA-still-enabled) before minting a session via
    TokenManager::createUserSession, and writes a session-scoped freshness marker (keyed by the issued token's sid) so
    /2fa/disable can't be ridden by a stolen token from another session.
  • PINs are bcrypt-hashed via Glueful\Security\OTP and cached with a strictly-projected user array — no password hash can leak
    through the cache.

Test plan

  • composer test — Unit 827 pass, Integration 93 pass, 0 failures.
  • New tests: TwoFactorServiceTest (15 scenarios — allowlist projection, provider plumbing, re-validation throws, session-scoped
    freshness, replay), ChallengeTokenIssuerTest (6), JtiBlocklistTest (4).
  • composer phpcs + PHPStan clean on all changed files.
  • Verified TWO_FACTOR_ENABLED=false is behavior-identical to pre-2FA (API-key provider path regression-tested).

Dependencies / rollout

  • Requires glueful/email-notification (email channel + two-factor-pin template) only if 2FA is enabled.
  • Requires the 010_AddTwoFactorEnabledToUsers migration (api-skeleton ^1.28.0) only if enabling 2FA.
  • See CHANGELOG.mdUpgrade Notes for the opt-in steps and the X-MFA-Token removal note.