auth-client.tsfeat(security): make the security settings page mean what it says
Three controls on Settings -> Security were wired to nothing. The two-factor
switch ran a setTimeout that console.logged its values; the login-notifications
switch had no storage, no trigger and no sender behind its fourteen translations;
Delete Account had no onClick at all. A user could turn 2FA on and believe they
had it.
Two-factor, for an explicit allowlist (currently one user, at his organization's
request). better-auth's twoFactor plugin alone would have recorded 2FA without
ever enforcing it: emailAndPassword is disabled and every sign-in call site is
signIn.social, but the plugin's hook matcher covers only /sign-in/{email,username,
phone-number}, so the Google OAuth callback is never challenged. The plugin is
registered for its primitives only -- TOTP secrets, encryption at rest,
constant-time verify, single-use recovery codes, none of which should be
hand-rolled -- and the enforcement is Cedar's own gate in http/app.ts, keyed on a
per-session marker. Per-session so that a new sign-in is challenged again and
revoking a session revokes its clearance with it. Two traps are documented where
they bite: the marker is read from the database because the 30-day session cookie
cache would otherwise report a stale value forever, and the first successful
verification rotates the session while echoing back the PRE-rotation token.
Enrolling also enforces, or the settings toggle would be decoration for anyone off
the allowlist. The asymmetry is deliberate -- un-enrolling opts you out only if the
allowlist was not what put you there -- and /two-factor/disable is rejected by the
gate for required users, not merely hidden in the UI.
Login notifications fire on a device not seen before, not on every sign-in.
Comparing raw user_agent strings looks correct and is not: one live account shows
46 sessions across 6 user agents but only 3 real devices, because Chrome 148, 149,
150 and 151 each got their own row. That version would have sent three false "new
device" warnings for routine auto-updates, and an alert that cries wolf monthly is
one nobody reads. It compares a digit-stripped signature, tested against those
exact strings. Delivered over email rather than the primary channel, which can be
Slack reached with the very Google account the alert is about. Default off:
defaulting it true would start mailing every existing user the moment this deploys.
Deploy order matters. two_factor_auth.sql is additive and must land BEFORE this
code -- registering the plugin makes better-auth select two_factor_enabled on
every user query, so a missing column is a full outage, not a degraded feature.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 27, 2026, 11:36 PMauth-proxy.tsUse same-origin APIs on AWS app surfacesMar 31, 2026, 2:34 PM