Summary#
- Implements
GET /oauth/authorize— validatesclient_id,redirect_uri,response_type,code_challenge_method; renders a neobrutal server-rendered consent page showing the client app name and requested scopes - Implements
POST /oauth/authorize— re-validates all params against the DB (tamper-resistant hidden fields), generates a 43-char base64url auth code stored inoauth_authorization_codeswith a 60-second TTL, redirects toredirect_uri?code=...&state=...; denial redirects witherror=access_denied - Adds
store_authorization_codeandget_single_account_didtodb/oauth.rs - Adds Bruno collection entries for GET and POST (
seq: 13andseq: 14) - 21 new tests across DB and route layers
Consent Page (neobrutal design)#

Yellow badge, cream background, thick black borders with offset shadow, monospaced scope tags, green APPROVE / white DENY buttons.
Security notes#
redirect_uriis validated against the client's registeredredirect_urisbefore any redirect is issued — unknown clients and mismatched URIs return an HTML error page, never a redirect- POST handler re-validates
client_idandredirect_urifrom the DB regardless of form field values (hidden fields could be tampered) - PKCE
code_challenge_methodmust be exactlyS256;plainis rejected - Auth codes are single-use, expire after 60 seconds
Test plan#
-
cargo test -p relay oauth_authorize— 15 route tests pass -
cargo test -p relay db::oauth— 6 DB tests pass -
cargo clippy --workspace -- -D warnings— no warnings -
GET /oauth/authorizewith a registered client renders the consent page in a browser - Approving redirects back to
redirect_uriwithcode=andstate=params - Denying redirects back with
error=access_denied - Unknown
client_idreturns 400 HTML error (no redirect) - Mismatched
redirect_urireturns 400 HTML error (no redirect) -
code_challenge_method=plainredirects witherror=invalid_request