Revoke a token from Kotlin, unsigned, following no redirect
Signing out of HEY ends the session behind a token with DELETE /oauth/tokens. The Kotlin client had no way to send it safely: written by hand through the client, the revocation goes out under /oauth/tokens.json, refreshes and resends on a 401, and follows a redirect with the token in its body.
What changes (Kotlin)
HeyClient.revokeToken(clientId, token):
- sends
client_idandtokenin a form body under the bare path, never in the query, where logs would keep the token; - is unsigned: the client's own credentials may be the very token being revoked, so a 401 (for example
invalid_clientfor another app's token) is the answer, not a reason to refresh them and send the revocation again; - follows no redirect (new
Operation.noRedirects()): a DELETE keeps its body on a 307/308, so a hop would hand the token to whatever host theLocationnames. Any 3xx is anapi_errorcarrying its status, with no hop.unsigned()alone still follows, as the storage reads it was made for want; - treats HEY's 200
{}for a token it no longer knows as no error; - is heard by the hooks as
OAuth.RevokeToken, a write.
/oauth routes sit outside the API model (drift-check-reverse skips them), and the Go, Rust and TypeScript token exchanges are hand-written for the same reason. So this is a hand-written convenience, not a generated operation.
Red first
The first commit runs the tests against the revocation a caller would write by hand (request(DELETE, "/oauth/tokens").form(...)). Three of the five fail:
- the path goes out as
/oauth/tokens.json; - a 401 is refreshed and sent again (it hit the mock's "no more responses" 500);
- a 307 is followed.
The two that pass (200 {} is no error; the hooks' name) pin what the fix must keep.
Verified
RevocationTest 5/5, make kt-check kt-check-drift, Kotlin conformance 219/219. Nothing outside kotlin/ changes. The repo-wide make check failures main already has (cargo deny's webpki-root-certs advisory; locally, Swift 6.3 warnings-as-errors) are untouched by this. Merges cleanly with #268 and #269.
Not done
- No conformance case. No other SDK revokes a token. The runners read only top-level cases, and only TypeScript can mark one not applicable, so a case would need a
RevokeTokenin four SDKs this change doesn't touch. - Revocation for Go, Rust, TypeScript and Swift: none has one today. That's a follow-up if wanted.
Summary by cubic
Adds a revokeToken(clientId, token) extension on HeyClient so signing out of HEY revokes the session token over DELETE /oauth/tokens, a route hand-written like the /oauth exchanges in the other SDKs.
- Sends
client_idandtokenin a form body under the bare path, never in the query where logs would keep the token. - Sends unsigned, with none of a caller's own
AuthStrategyheaders or account filter: a 401 is the answer, not a reason to refresh credentials and resend the revocation. - Follows no redirect (new
Operation.noRedirects()): a DELETE keeps its body on a 307/308, so a hop would hand the token to the redirect target. Any 3xx is taken as the answer without its body read, so a body lost mid-read cannot turn into a retried revocation; it is heard by the hooks as its status and surfaced as aHeyException.Api. - Treats HEY's 200
{}for a token it no longer knows as success and emits the hooks eventOAuth.RevokeTokenas a write; the README example shows the import a caller needs.