From 2673f48ade3d7c693ed6822ad54036e0d9cf17a8 Mon Sep 17 00:00:00 2001 From: roaming97 Date: Fri, 18 Apr 2025 23:59:17 -0600 Subject: [PATCH] Docs authorization for protected routes --- TODO.md | 3 ++- src/doc.rs | 24 ++++++++++++++++++++++-- src/routes/channel.rs | 6 +++--- src/routes/video.rs | 6 +++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index 176d1fc..762af79 100755 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,8 @@ Tasks that have been completed in commits before this one have been omitted from - [x] /video PUT route - [x] /channel PUT route -- [x] OpenAPI documentation +- [x] OpenAPI docs +- [x] Add authorization for protected routes in API docs ## Planned features diff --git a/src/doc.rs b/src/doc.rs index a11ea55..5f2e0be 100755 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,4 +1,10 @@ -use utoipa::OpenApi; +use utoipa::{ + Modify, OpenApi, + openapi::{ + Components, + security::{ApiKey, ApiKeyValue, SecurityScheme}, + }, +}; use utoipa_swagger_ui::SwaggerUi; // Because apparently the OpenApi macro can't import these on its own @@ -36,10 +42,24 @@ use crate::routes::{ info( title = "Almond API", description = "Interface to archive YouTube videos." - ) + ), + modifiers(&SecurityAddon), )] struct ApiDoc; +struct SecurityAddon; + +impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + let components = openapi.components.get_or_insert_with(Components::default); + + components.add_security_scheme( + "authKey", + SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description("almond-api-key", "The API key/password that must be sent at the request header for protected routes."))), + ); + } +} + pub fn docs_router() -> SwaggerUi { SwaggerUi::new("/docs").url("/api-doc/openapi.json", ApiDoc::openapi()) } diff --git a/src/routes/channel.rs b/src/routes/channel.rs index c928268..da96a53 100755 --- a/src/routes/channel.rs +++ b/src/routes/channel.rs @@ -104,7 +104,7 @@ pub struct UploadChannelQuery { } /// Upload a channel's metadata to the database -#[utoipa::path(post, path = "/channel", request_body = UploadChannelQuery, params(("url" = String, Query), ("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 200, description = "Channel already exists, skipping"), (status = 201, description = "Channel uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload channel to database")))] +#[utoipa::path(post, path = "/channel", request_body = UploadChannelQuery, params(("url" = String, Query)), tags = ["channel"], responses((status = 200, description = "Channel already exists, skipping"), (status = 201, description = "Channel uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload channel to database")), security(("authKey" = [])))] pub async fn upload_channel( State(state): State, Query(query): Query, @@ -177,7 +177,7 @@ pub async fn upload_channel( } /// Update an existing channel from the database -#[utoipa::path(put, path = "/channel/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 204, description = "Channel updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update channel from database")))] +#[utoipa::path(put, path = "/channel/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 204, description = "Channel updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update channel from database")), security(("authKey" = [])))] pub async fn update_channel(State(state): State, Path(id): Path) -> StatusCode { match sqlx::query_as!(Channel, "SELECT * FROM channel WHERE youtube_id = ?", id) .fetch_optional(&state.pool) @@ -222,7 +222,7 @@ pub async fn update_channel(State(state): State, Path(id): Path, Path(id): Path) -> StatusCode { match sqlx::query!("DELETE FROM channel WHERE youtube_id = ?", id) .fetch_optional(&state.pool) diff --git a/src/routes/video.rs b/src/routes/video.rs index 4950d18..2139c2c 100755 --- a/src/routes/video.rs +++ b/src/routes/video.rs @@ -85,7 +85,7 @@ pub struct UploadVideoQuery { } /// Upload a video to the database -#[utoipa::path(post, path = "/video", request_body = UploadVideoQuery, params(("url" = String, Query), ("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 200, description = "Video already exists, skipping"), (status = 201, description = "Video uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload video to database")))] +#[utoipa::path(post, path = "/video", request_body = UploadVideoQuery, params(("url" = String, Query), ("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 200, description = "Video already exists, skipping"), (status = 201, description = "Video uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload video to database")), security(("authKey" = [])))] pub async fn upload_video( State(state): State, Query(query): Query, @@ -162,7 +162,7 @@ pub async fn upload_video( } /// Update an existing video from the database -#[utoipa::path(put, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update video from database")))] +#[utoipa::path(put, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update video from database")), security(("authKey" = [])))] pub async fn update_video(State(state): State, Path(id): Path) -> StatusCode { match sqlx::query_as!(Video, "SELECT * FROM video WHERE youtube_id = ?", id) .fetch_optional(&state.pool) @@ -205,7 +205,7 @@ pub async fn update_video(State(state): State, Path(id): Path) } /// Delete a video from the database -#[utoipa::path(delete, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video deleted successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to delete video from database")))] +#[utoipa::path(delete, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video deleted successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to delete video from database")), security(("authKey" = [])))] pub async fn delete_video(State(state): State, Path(id): Path) -> StatusCode { match sqlx::query!("DELETE FROM video WHERE youtube_id = ?", id) .fetch_optional(&state.pool)