A 403 is not an expired session

Signing in on a fresh install said "Your session expired. Sign out and back
in to keep going." — advice that cannot work, because signing in again hits
the same rule and says the same thing. There is no way out of that loop from
inside the app.

RelayAccounts mapped 401 and 403 to the same Unauthorized. They are not the
same: 401 means the token was refused and signing in again IS the fix; 403
means a rule refused this — too many devices, a retired device, an
unverified address — and the relay writes a sentence naming which one and
what to do about it. RelayClient already made this distinction; RelayAccounts
did not, so which client a path happened to use decided whether its refusals
were legible.

The 403 sentence now reaches the screen instead of "That isn't allowed on
this server", which replaced an answer somebody could act on with one they
could not. Only when it is really a sentence: a JSON fragment or a bare code
keeps the generic line rather than putting a payload on screen.

Predates tonight's merges — checked against fa92581 — and is why a sign-in
failure could not be diagnosed from the device.
This commit is contained in:
Paul Hitt
2026-09-03 19:22:27 -04:00
parent 3f0e36fffc
commit eff8cdb493
2 changed files with 26 additions and 2 deletions
@@ -174,7 +174,18 @@ class RelayAccounts(
// Play Store to fix a typo.
code == "challenge_required" -> RelayException.ChallengeRequired(message)
code == "challenge_failed" -> RelayException.ChallengeFailed(message)
it.code == 401 || it.code == 403 -> RelayException.Unauthorized(message)
// 🔴 **A 403 is not an expired session.** 401 means the token
// was refused and signing in again is the fix. 403 means a
// RULE refused this — too many devices, a retired device, an
// unverified address — and the relay writes a sentence saying
// which. Reporting those as "your session expired, sign out and
// back in" sends somebody round a loop that cannot end: they
// sign in again, hit the same rule, and are told the same
// wrong thing. Observed tonight on a fresh install.
it.code == 401 -> RelayException.Unauthorized(message)
it.code == 403 && code == "email_unverified" ->
RelayException.EmailUnverified(message)
it.code == 403 -> RelayException.Http(403, message)
it.code == 404 -> RelayException.NotFound(path)
it.code == 409 -> RelayException.Http(409, message)
it.code == 429 ->