hub: mail as the second factor for everyone (second_factor: email, §2.8.8)
tsk-qawqnetfhn6m done Maity Coder Claude 005 (maity-coder-claude-005)
Contract merged in #925 (cab90de), Oliver's go on the five defaults (req-7vzxayr148jb). Read §2.8.8 whole; touch points §2.6.1, §2.6.6, §2.8.1–§2.8.7, §12, §16.11.
second_factor: emailinGET/PATCH /admin/settings/sign-inandMAITY_SIGN_IN_SECOND_FACTOR; switching on via PATCH →202, code mailed to the admin,POST /admin/settings/sign-in/second-factor-email/confirm;mail_not_ready/no_verified_address. Hub refuses to start with envemailwhile mail is not complete+enabled.- Every first factor → second-factor session
via: email(15 min) unless TOTP enrolled (via: totp); mail with link/sign-in/second-factor/email#<token>+ six-digit code, first-factor method named, "never give this code"; token/code HMAC underMAITY_MAIL_SIGNING_KEY, bound to that session;POST /auth/second-factor/email(resend; from a full session: step-up code only); verify{email_code}/{email_token};GET /auth/sessionmail: sent|rate_limited|unavailable;session_created.second_factor: email; no good standing from the mail factor; setssecond_factor_at. - Sign-in link as first factor off (
email_link: false,missing: second_factor_email,POST /auth/email-link202 without send). - Address lost: account flag "ever verified" — backfill it in the migration for existing accounts with `address_status` verified or disabled (dzse P3, a sentence follows in the contract); lost → fail closed (
409 address_unverified,second_factor_failedemail_unavailableas inbox item); never had one → enrolment session; removing the address without TOTPforbidden/second_factor_required. - After an admin reset or own TOTP off: mail again (no forced enrolment) under
email. - Limits: 3 per session, 10/hour/human, first-factor mail counts.
- Migration through the queue with me first (id ≤ 32). KTW in hub/context, changelog fragment. Web is a separate card.