diff --git a/src/controllers/session.rs b/src/controllers/session.rs index c73869e52ec..44266c356a3 100644 --- a/src/controllers/session.rs +++ b/src/controllers/session.rs @@ -1,7 +1,5 @@ use axum::Json; use axum::extract::{FromRequestParts, Query}; -use axum_extra::json; -use axum_extra::response::ErasedJson; use diesel::prelude::*; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; @@ -20,28 +18,30 @@ use crate::views::EncodableMe; use crates_io_github::GithubUser; use crates_io_session::SessionExtension; +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct BeginResponse { + #[schema( + example = "https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg" + )] + pub url: String, + + #[schema(example = "b84a63c4ea3fcb4ac84")] + pub state: String, +} + /// Begin authentication flow. /// /// This route will return an authorization URL for the GitHub OAuth flow including the crates.io /// `client_id` and a randomly generated `state` secret. /// /// see -/// -/// ## Response Body Example -/// -/// ```json -/// { -/// "state": "b84a63c4ea3fcb4ac84", -/// "url": "https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg" -/// } -/// ``` #[utoipa::path( get, path = "/api/private/session/begin", tag = "session", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(BeginResponse))), )] -pub async fn begin_session(app: AppState, session: SessionExtension) -> ErasedJson { +pub async fn begin_session(app: AppState, session: SessionExtension) -> Json { let (url, state) = app .github_oauth .authorize_url(oauth2::CsrfToken::new_random) @@ -51,7 +51,8 @@ pub async fn begin_session(app: AppState, session: SessionExtension) -> ErasedJs let state = state.secret().to_string(); session.insert("github_oauth_state".to_string(), state.clone()); - json!({ "url": url.to_string(), "state": state }) + let url = url.to_string(); + Json(BeginResponse { url, state }) } #[derive(Clone, Debug, Deserialize, FromRequestParts)] @@ -74,25 +75,11 @@ pub struct AuthorizeQuery { /// /// - `code` – temporary code received from the GitHub API **(Required)** /// - `state` – state parameter received from the GitHub API **(Required)** -/// -/// ## Response Body Example -/// -/// ```json -/// { -/// "user": { -/// "email": "foo@bar.org", -/// "name": "Foo Bar", -/// "login": "foobar", -/// "avatar": "https://avatars.githubusercontent.com/u/1234", -/// "url": null -/// } -/// } -/// ``` #[utoipa::path( get, path = "/api/private/session/authorize", tag = "session", - responses((status = 200, description = "Successful Response")), + responses((status = 200, description = "Successful Response", body = inline(EncodableMe))), )] pub async fn authorize_session( query: AuthorizeQuery, diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 3571e0fd50b..f1d2a7f545b 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -1174,10 +1174,56 @@ expression: response.json() }, "/api/private/session/authorize": { "get": { - "description": "This route is called from the GitHub API OAuth flow after the user accepted or rejected\nthe data access permissions. It will check the `state` parameter and then call the GitHub API\nto exchange the temporary `code` for an API token. The API token is returned together with\nthe corresponding user information.\n\nsee \n\n## Query Parameters\n\n- `code` – temporary code received from the GitHub API **(Required)**\n- `state` – state parameter received from the GitHub API **(Required)**\n\n## Response Body Example\n\n```json\n{\n \"user\": {\n \"email\": \"foo@bar.org\",\n \"name\": \"Foo Bar\",\n \"login\": \"foobar\",\n \"avatar\": \"https://avatars.githubusercontent.com/u/1234\",\n \"url\": null\n }\n}\n```", + "description": "This route is called from the GitHub API OAuth flow after the user accepted or rejected\nthe data access permissions. It will check the `state` parameter and then call the GitHub API\nto exchange the temporary `code` for an API token. The API token is returned together with\nthe corresponding user information.\n\nsee \n\n## Query Parameters\n\n- `code` – temporary code received from the GitHub API **(Required)**\n- `state` – state parameter received from the GitHub API **(Required)**", "operationId": "authorize_session", "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "owned_crates": { + "description": "The crates that the authenticated user owns.", + "items": { + "properties": { + "email_notifications": { + "deprecated": true, + "type": "boolean" + }, + "id": { + "description": "The opaque identifier of the crate.", + "example": 123, + "format": "int32", + "type": "integer" + }, + "name": { + "description": "The name of the crate.", + "example": "serde", + "type": "string" + } + }, + "required": [ + "id", + "name", + "email_notifications" + ], + "type": "object" + }, + "type": "array" + }, + "user": { + "$ref": "#/components/schemas/AuthenticatedUser", + "description": "The authenticated user." + } + }, + "required": [ + "user", + "owned_crates" + ], + "type": "object" + } + } + }, "description": "Successful Response" } }, @@ -1189,10 +1235,31 @@ expression: response.json() }, "/api/private/session/begin": { "get": { - "description": "This route will return an authorization URL for the GitHub OAuth flow including the crates.io\n`client_id` and a randomly generated `state` secret.\n\nsee \n\n## Response Body Example\n\n```json\n{\n \"state\": \"b84a63c4ea3fcb4ac84\",\n \"url\": \"https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg\"\n}\n```", + "description": "This route will return an authorization URL for the GitHub OAuth flow including the crates.io\n`client_id` and a randomly generated `state` secret.\n\nsee ", "operationId": "begin_session", "responses": { "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "state": { + "example": "b84a63c4ea3fcb4ac84", + "type": "string" + }, + "url": { + "example": "https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Aorg", + "type": "string" + } + }, + "required": [ + "url", + "state" + ], + "type": "object" + } + } + }, "description": "Successful Response" } },