/api/v1/oauth_consent
The data behind the consent screen. Defined in
crates/rest/src/oauth_consent.rs.
Internal to the official website. These two routes back the /consent
page of the Angular application. They are not versioned in practice, they have
no stability guarantee, and an application has no reason to call them: the
supported entry point is GET /oauth/authorize, documented in
Eternaltwin for OAuth and listed under
Internal endpoints.
They live under /api/v1/ rather than under /oauth/ on purpose: everything
under /oauth/ is proxied straight to the backend and never reaches the
website, while the consent page has to be an Angular route. Keeping its data
endpoints on the API path lets the proxy rule stay a plain prefix.
The path is oauth_consent, with an underscore, as it is mounted in
crates/rest/src/lib.rs.
The pending authorization
Both routes carry the authorization request as it arrived at
/oauth/authorize — in the query string for the read, inside the body for the
write:
| Name | Type | Meaning |
|---|---|---|
client_id | string | The client's key or UUID. |
redirect_uri | string | Callback the client asked for. |
response_type | string | code. |
scope | string | Space-separated scope tokens: base, forum:read, forum:write, forum:moderate. |
state | string | Opaque value echoed back to the client. |
Nothing about the pending request is trusted because it came back from the
browser. Both routes re-validate it from scratch, exactly as
/oauth/authorize does, so the page cannot be made to describe a client that
would be refused a moment later.
GET /
Describes what the pending request asks for.
Requires a signed-in user who has accepted the terms of service — the page is
reached by a redirect from /oauth/authorize, which already sends a guest to
the sign-in screen and an unaccepting user to /tos-accept.
Example
GET /api/v1/oauth_consent?client_id=eternalfest%40clients&response_type=code&scope=forum%3Awrite&state=xyz Cookie: sid=b8be19ef-2d61-44de-b7d2-9c34ccb8a763
{
"client": {
"type": "OauthClient",
"id": "d19e61a3-83d3-410f-84ec-49aaab841559",
"key": "eternalfest@clients",
"display_name": "Eternalfest"
},
"app_uri": "https://eternalfest.net/",
"requested_scopes": {
"base": true,
"forum_read": true,
"forum_write": true,
"forum_moderate": false
},
"approved_scopes": {
"base": true,
"forum_read": false,
"forum_write": false,
"forum_moderate": false
}
}
| Field | Meaning |
|---|---|
client | Who is asking. Display name and key come from the store, never from the request. |
app_uri | Homepage of the application, so the user can tell which "Eternalfest" is asking. |
requested_scopes | What this request asks for, with the hierarchy already resolved: forum:moderate implies forum:write, which implies forum:read. |
approved_scopes | What the user had already approved for this client, or null if they were never asked. |
The screen uses the two scope sets to separate "this is new" from "you already agreed to this".
Errors
| Status | Body | When |
|---|---|---|
| 400 | {"error": "invalid authorization request"} | Unknown client, callback that does not match, scope the client may not request. |
| 401 | {"error": "not signed in"} | The session expired, or the terms have not been accepted. |
| 500 | {"error": "internal error"} | — |
POST /
Records the answer and says where to send the browser.
Body
The five authorization fields above, flattened, plus:
| Field | Type | Meaning |
|---|---|---|
approved | boolean | true to approve, false to deny. |
Example
POST /api/v1/oauth_consent
Content-Type: application/json
Cookie: sid=b8be19ef-2d61-44de-b7d2-9c34ccb8a763
{
"client_id": "eternalfest@clients",
"response_type": "code",
"scope": "forum:write",
"state": "xyz",
"approved": true
}
{
"redirect_uri": "https://eternalfest.net/oauth/callback?code=…&state=xyz"
}
Both answers end at the client's registered callback: approving with a
code, denying with the RFC 6749 access_denied error. A client that asked a
question is owed an answer — one left waiting on a redirect that never comes
cannot tell a refusal from a network failure.
Approving a request wider than a standing consent stores the union of the two, never the replacement.
Errors
Same as the read: 400 for an invalid request, 401 when not signed in, 500 otherwise.