Auth: restart only when a fresh code can help, at most once a minute
What does this MR do and why?
Two changes to !1309 (closed)'s restart.
Gate. Rate limiting, configuration errors (invalid_client and friends) and malformed responses are never fixed by a fresh code, so they go straight to the terminal response instead of spending a round trip.
Bound and storage. The restart marker moves out of the session into a dedicated cookie, gitlab-pages-auth-recovery, encoded with the session store's keys, at Path=/ with a 60 s Max-Age. A restart is refused only while that cookie records one younger than 60 seconds; the loop this guards against has a period of about a second, so the short window keeps a user who retries after a brief outage protected. Keeping the marker out of the session means a failing callback never rewrites a session cookie: a replayed callback keeps the user's valid token cookie, and no session cookie at another path can shadow the marker (the shadowing bug fixed late in !1309 (closed) becomes structurally impossible). The cookie names the flow's URI, so only that flow's success is counted on the recoveries counter and clears it; another project's sign-in on the same host leaves it alone. The bound itself is per host, which is what keeps two concurrent flows from restarting each other without limit.
Acceptance scenarios: an exchange that commits then fails restarts with a fresh code and the page is served after three exchanges; a replayed callback restarts and the page is served with the original token.
Part of a stack replacing !1333 (closed) for #1190 (closed), on top of !1309 (closed):
- stub contract mode (test-only)
- move token-fetch handling into its own file
- retry the exchange once in place
- bounded restart with a recovery cookie
- failure page at the requested page
Each MR targets the previous one's branch and retargets automatically when that merges.
How to validate
go test ./internal/auth/ and make acceptance ARGS='-run TestSignIn'.