What happens. When the e-stop is thrown in Revoke mode, it ends grants, browser sessions and remembered approvals, but leaves authorization codes alone. A code approved just before the throw stays valid for its 2-minute lifetime. If the operator releases the stop within that window, the client exchanges the code and gets a new access and refresh token pair. A code waiting in the decision store for a browser to collect survives in the same way.
How to reproduce.
- The Revoke hook calls only
revoke_grants.revoke_all(),revoke_browsers.revoke_all()andrevoke_remembered.forget_all()(crates/didbot-serve/src/routes/mod.rs:726-730). It never touchesauth.oauth.code_store. - The account-deletion hook does drop codes (
routes/mod.rs:762). The comment on that store explains why: a code is one hop short of a token (crates/didbot-serve/src/oauth/token.rs:121-128). - Codes live for 2 minutes (
token.rs:87)./oauth/tokenchecks the e-stop only while it is engaged (crates/didbot-serve/src/routes/oauth.rs:1536). - A local test in
tests/oauth_account_flow.rsran PAR,approveAuthorization,estop.throw(EstopMode::Revoke)andestop.release(), then exchanged the code. It got200 OKwith a freshaccess_tokenandrefresh_token.
Why it matters. Throwing Revoke and then releasing it is how an operator signs everything out at once. A code from just before the throw becomes a new 14-day login, so a sign-in the operator meant to end survives the Revoke.
Fixed by https://tangled.org/permadeath.com/didbot/pulls/1242, now on main.