Release v1.45.0 — Fomalhaut: The Second Factor (core email 2FA + selectRaw bindings)
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/loginfor an enrolled user returns
achallenge_tokenand emails a 6-digit PIN; the client completes login atPOST /2fa/verify. Newsrc/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 latentclone()
column/binding mismatch.docs/SECURITY.md— documents the SQL-injection and XSS model.AdminPermissionMiddleware— drops the deadvalidateMfaToken()/X-MFA-Tokenplaceholder;require_mfanow reads only the
session handshake.
Key design points
AuthenticationService::authenticate()'s username/password branch is split intoverifyCredentials()+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 newLoginResponseShaper, so a 2FA-completed login is byte-for-byte identical to a direct login (same CSRF token +
login events)./2fa/verifyre-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'ssid) so
/2fa/disablecan't be ridden by a stolen token from another session.- PINs are bcrypt-hashed via
Glueful\Security\OTPand 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=falseis behavior-identical to pre-2FA (API-key provider path regression-tested).
Dependencies / rollout
- Requires
glueful/email-notification(email channel +two-factor-pintemplate) only if 2FA is enabled. - Requires the
010_AddTwoFactorEnabledToUsersmigration (api-skeleton^1.28.0) only if enabling 2FA. - See
CHANGELOG.md→ Upgrade Notes for the opt-in steps and theX-MFA-Tokenremoval note.