diff --git a/Cargo.lock b/Cargo.lock index c5b54f2..2d66c0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,15 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "argon2" version = "0.5.3" @@ -336,6 +345,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -425,6 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -728,7 +749,7 @@ dependencies = [ [[package]] name = "icarus_auth" -version = "0.4.3" +version = "0.4.4" dependencies = [ "argon2", "axum", @@ -747,13 +768,15 @@ dependencies = [ "tower", "tracing-subscriber", "url", + "utoipa", + "utoipa-swagger-ui", "uuid", ] [[package]] name = "icarus_envy" -version = "0.3.1" -source = "git+ssh://git@git.kundeng.us/phoenix/icarus_envy.git?tag=v0.3.1-main-3cd42dab6b-006#3cd42dab6b2503609883f5f57ad3508755c34a2e" +version = "0.3.2" +source = "git+ssh://git@git.kundeng.us/phoenix/icarus_envy.git?tag=v0.3.2#d84a8144aedf02e1b459d67c4023a7e0833f89fd" dependencies = [ "const_format", "dotenvy", @@ -761,14 +784,15 @@ dependencies = [ [[package]] name = "icarus_models" -version = "0.5.5" -source = "git+ssh://git@git.kundeng.us/phoenix/icarus_models.git?tag=v0.5.5-devel-bd793db08e-111#bd793db08e06b256ffecd9f4528e55e3026fede7" +version = "0.5.6" +source = "git+ssh://git@git.kundeng.us/phoenix/icarus_models.git?tag=v0.5.6#2d6b550ae6721b41ecc3039799f6a5e873869077" dependencies = [ "josekit", "rand 0.9.1", "serde", "serde_json", "time", + "utoipa", "uuid", ] @@ -919,6 +943,7 @@ checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -985,6 +1010,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -1041,6 +1075,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.7" @@ -1456,6 +1500,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-embed" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1487,6 +1565,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.27" @@ -1627,6 +1714,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -2151,6 +2244,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -2207,6 +2306,49 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum", + "base64", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "1.17.0" @@ -2237,6 +2379,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2342,6 +2494,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2609,3 +2770,35 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zip" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 8d25e4a..c76b869 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icarus_auth" -version = "0.4.3" +version = "0.4.4" edition = "2024" rust-version = "1.88" @@ -18,8 +18,10 @@ argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x versio rand = { version = "0.9.1" } time = { version = "0.3.41", features = ["macros", "serde"] } josekit = { version = "0.10.3" } -icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.5.5-devel-bd793db08e-111" } -icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.1-main-3cd42dab6b-006" } +utoipa = { version = "5.4.0", features = ["axum_extras"] } +utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } +icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.5.6" } +icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.2" } [dev-dependencies] http-body-util = { version = "0.1.3" } diff --git a/src/callers/common.rs b/src/callers/common.rs index bf178b3..b33a3fe 100644 --- a/src/callers/common.rs +++ b/src/callers/common.rs @@ -1,7 +1,7 @@ pub mod response { use serde::{Deserialize, Serialize}; - #[derive(Deserialize, Serialize)] + #[derive(Deserialize, Serialize, utoipa::ToSchema)] pub struct TestResult { pub message: String, } @@ -11,11 +11,28 @@ pub mod endpoint { use super::*; use axum::{Extension, Json, http::StatusCode}; - // basic handler that responds with a static string + /// Endpoint to hit the root + /// basic handler that responds with a static string + #[utoipa::path( + get, + path = super::super::endpoints::ROOT, + responses( + (status = 200, description = "Test", body = &str), + ) + )] pub async fn root() -> &'static str { "Hello, World!" } + /// Endpoint to do a database ping + #[utoipa::path( + get, + path = super::super::endpoints::DBTEST, + responses( + (status = 200, description = "Successful ping of the db", body = super::response::TestResult), + (status = 400, description = "Failure in pinging the db", body = super::response::TestResult) + ) + )] pub async fn db_ping( Extension(pool): Extension, ) -> (StatusCode, Json) { diff --git a/src/callers/login.rs b/src/callers/login.rs index fb065e5..5772d86 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -1,21 +1,21 @@ pub mod request { use serde::{Deserialize, Serialize}; - #[derive(Default, Deserialize, Serialize)] + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] pub struct Request { pub username: String, pub password: String, } pub mod service_login { - #[derive(Debug, serde::Deserialize, serde::Serialize)] + #[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] pub struct Request { pub passphrase: String, } } pub mod refresh_token { - #[derive(Debug, serde::Deserialize, serde::Serialize)] + #[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] pub struct Request { pub access_token: String, } @@ -25,14 +25,14 @@ pub mod request { pub mod response { use serde::{Deserialize, Serialize}; - #[derive(Default, Deserialize, Serialize)] + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] pub struct Response { pub message: String, pub data: Vec, } pub mod service_login { - #[derive(Debug, Default, serde::Deserialize, serde::Serialize)] + #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] pub struct Response { pub message: String, pub data: Vec, @@ -40,7 +40,7 @@ pub mod response { } pub mod refresh_token { - #[derive(Debug, Default, serde::Deserialize, serde::Serialize)] + #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] pub struct Response { pub message: String, pub data: Vec, @@ -48,6 +48,7 @@ pub mod response { } } +/// Module for login endpoints pub mod endpoint { use axum::{Json, http::StatusCode}; @@ -72,6 +73,20 @@ pub mod endpoint { ) } + /// Endpoint to login + #[utoipa::path( + post, + path = super::super::endpoints::LOGIN, + request_body( + content = request::Request, + description = "Data required to login", + content_type = "application/json" + ), + responses( + (status = 200, description = "Successfully logged in", body = response::Response), + (status = 404, description = "Could not login with credentials", body = response::Response) + ) + )] pub async fn login( axum::Extension(pool): axum::Extension, Json(payload): Json, @@ -115,6 +130,20 @@ pub mod endpoint { } } + /// Endpoint to login as a service user + #[utoipa::path( + post, + path = super::super::endpoints::SERVICE_LOGIN, + request_body( + content = request::service_login::Request, + description = "Data required to login as a service user", + content_type = "application/json" + ), + responses( + (status = 200, description = "Login successful", body = response::Response), + (status = 400, description = "Error logging in with credentials", body = response::Response) + ) + )] pub async fn service_login( axum::Extension(pool): axum::Extension, axum::Json(payload): axum::Json, @@ -154,6 +183,22 @@ pub mod endpoint { } } + /// Endpoint to retrieve a refresh token + #[utoipa::path( + post, + path = super::super::endpoints::REFRESH_TOKEN, + request_body( + content = request::refresh_token::Request, + description = "Data required to retrieve a refresh token", + content_type = "application/json" + ), + responses( + (status = 200, description = "Refresh token generated", body = response::Response), + (status = 400, description = "Error verifying token", body = response::Response), + (status = 404, description = "Could not validate token", body = response::Response), + (status = 500, description = "Error extracting token", body = response::Response) + ) + )] pub async fn refresh_token( axum::Extension(pool): axum::Extension, axum::Json(payload): axum::Json, diff --git a/src/callers/register.rs b/src/callers/register.rs index b9d07e4..bd8711f 100644 --- a/src/callers/register.rs +++ b/src/callers/register.rs @@ -6,7 +6,7 @@ use crate::repo; pub mod request { use serde::{Deserialize, Serialize}; - #[derive(Default, Deserialize, Serialize)] + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] pub struct Request { #[serde(skip_serializing_if = "String::is_empty")] pub username: String, @@ -26,13 +26,28 @@ pub mod request { pub mod response { use serde::{Deserialize, Serialize}; - #[derive(Deserialize, Serialize)] + #[derive(Deserialize, Serialize, utoipa::ToSchema)] pub struct Response { pub message: String, pub data: Vec, } } +/// Endpoint to register a user +#[utoipa::path( + post, + path = super::endpoints::REGISTER, + request_body( + content = request::Request, + description = "Data required to register", + content_type = "application/json" + ), + responses( + (status = 201, description = "User created", body = response::Response), + (status = 404, description = "User already exists", body = response::Response), + (status = 400, description = "Issue creating user", body = response::Response) + ) +)] pub async fn register_user( axum::Extension(pool): axum::Extension, Json(payload): Json, diff --git a/src/main.rs b/src/main.rs index e052521..c13b7f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,8 +19,31 @@ mod init { Router, routing::{get, post}, }; + use utoipa::OpenApi; use crate::callers; + use callers::common as common_callers; + use callers::login as login_caller; + use callers::register as register_caller; + use login_caller::endpoint as login_endpoints; + use login_caller::response as login_responses; + use register_caller::response as register_responses; + + #[derive(utoipa::OpenApi)] + #[openapi( + paths( + common_callers::endpoint::db_ping, common_callers::endpoint::root, + register_caller::register_user, + login_endpoints::login, login_endpoints::service_login, login_endpoints::refresh_token + ), + components(schemas(common_callers::response::TestResult, + register_responses::Response, + login_responses::Response, login_responses::service_login::Response, login_responses::refresh_token::Response)), + tags( + (name = "Icarus Auth API", description = "Auth API for Icarus API") + ) + )] + struct ApiDoc; pub async fn routes() -> Router { // build our application with a route @@ -58,7 +81,13 @@ mod init { icarus_auth::db::migrations(&pool).await; - routes().await.layer(axum::Extension(pool)) + routes() + .await + .merge( + utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi()), + ) + .layer(axum::Extension(pool)) } }