Stop pretending the two-factor field can work #77

Closed
opened 2026-08-26 21:45:55 +00:00 by jcoffey-dev · 0 comments
Owner

Closes #75 as far as it can be closed without implementing OAuth, and adds the real fix to the roadmap.

It's ours, not Stalwart's

The reporter guessed Stalwart; it isn't. Stalwart accepts a TOTP code only through an OAuth flow — its own web interface is an OAuth client, which is exactly why signing in there works — and it offers only the authorization-code and device flows. There is no password grant, so a client holding a username and password has nowhere to exchange them plus a code for a token.

The concatenated password$code form ihasmail sends is not a route the server has, and appears never to have been. What the README recorded as "confirmed live" was enabling and disabling 2FA, never signing in with a code — so this path was never tested.

The codebase already knew. Turning 2FA on in ihasmail mints an app password and reseals the session onto it, precisely because a plain password stops working from that moment. The sign-in page was the one place still assuming otherwise.

The reporter's own evidence fits exactly: app password works (Stalwart's documented route for non-OAuth clients, and it bypasses TOTP), Stalwart's own login works (OAuth), ihasmail 401s .

What changed — no new capability, just honesty

A 401 on a sign-in carrying a code now explains itself, points at app passwords, and says the password is probably fine. A sign-in without a code is untouched, so an ordinary typo still reads as an ordinary typo — there's a test for each.

The field stays and is honest. Removing it would leave someone with 2FA finding nothing at all, which is worse than a field that explains the situation and sends them somewhere that works. It also stays forward-compatible with servers that do accept it.

The README's false claim is corrected, not quietly dropped — it said Stalwart "accepts password$code", and it doesn't.

Backlog

Real 2FA is now in the roadmap as what it actually is: an OAuth implementation. Stalwart offers authorization-code and device flows, so ihasmail would hand sign-in to Stalwart's login and come back with a token. That's arguably a better posture than today's sealed password — a refresh token rather than a credential — but it replaces ihasmail's own sign-in page for those users and may need an OAuth client registered.

265 web + 77 server tests, typecheck and build clean.

Merged 2026-08-26 as coffey-labs/ihasmail@450a38f7cd

Rebuilt from: git history, session transcript.

Closes #75 as far as it can be closed without implementing OAuth, and adds the real fix to the roadmap. ## It's ours, not Stalwart's The reporter guessed Stalwart; it isn't. Stalwart accepts a TOTP code **only through an OAuth flow** — its own web interface is an OAuth client, which is exactly why signing in *there* works — and it offers only the authorization-code and device flows. **There is no password grant**, so a client holding a username and password has nowhere to exchange them plus a code for a token. The concatenated `password$code` form ihasmail sends is not a route the server has, and appears never to have been. What the README recorded as "confirmed live" was **enabling and disabling** 2FA, never *signing in* with a code — so this path was never tested. **The codebase already knew.** Turning 2FA *on* in ihasmail mints an app password and reseals the session onto it, precisely because a plain password stops working from that moment. The sign-in page was the one place still assuming otherwise. The reporter's own evidence fits exactly: app password works ✅ (Stalwart's documented route for non-OAuth clients, and it bypasses TOTP), Stalwart's own login works ✅ (OAuth), ihasmail 401s ❌. ## What changed — no new capability, just honesty **A 401 on a sign-in carrying a code now explains itself**, points at app passwords, and says the password is probably fine. A sign-in *without* a code is untouched, so an ordinary typo still reads as an ordinary typo — there's a test for each. **The field stays and is honest.** Removing it would leave someone with 2FA finding nothing at all, which is worse than a field that explains the situation and sends them somewhere that works. It also stays forward-compatible with servers that do accept it. **The README's false claim is corrected**, not quietly dropped — it said Stalwart "accepts `password$code`", and it doesn't. ## Backlog Real 2FA is now in the roadmap as what it actually is: an OAuth implementation. Stalwart offers authorization-code and device flows, so ihasmail would hand sign-in to Stalwart's login and come back with a token. That's arguably a **better** posture than today's sealed password — a refresh token rather than a credential — but it replaces ihasmail's own sign-in page for those users and may need an OAuth client registered. 265 web + 77 server tests, typecheck and build clean. **Merged** 2026-08-26 as coffey-labs/ihasmail@450a38f7cd86 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.