From 12c96ceaeaabe0aa7d0d2c70497886d9a5610f10 Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 19 Jul 2022 05:27:39 +0300 Subject: kittybox-indieauth: convert Error into axum::response::Response This requires the `axum` feature to be enabled, to prevent unwanted dependencies (e.g. in client apps or when using a different framework, since the library doesn't concern itself with I/O) --- kittybox-rs/Cargo.lock | 2 ++ kittybox-rs/Cargo.toml | 1 + kittybox-rs/indieauth/Cargo.toml | 13 ++++++++++++ kittybox-rs/indieauth/src/lib.rs | 14 +++++++++++++ kittybox-rs/src/indieauth/mod.rs | 44 ++++++++++++++++++++-------------------- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/kittybox-rs/Cargo.lock b/kittybox-rs/Cargo.lock index b44fa6f..716b10d 100644 --- a/kittybox-rs/Cargo.lock +++ b/kittybox-rs/Cargo.lock @@ -1544,7 +1544,9 @@ dependencies = [ name = "kittybox-indieauth" version = "0.1.0" dependencies = [ + "axum-core", "data-encoding", + "http", "rand 0.8.5", "serde", "serde_json", diff --git a/kittybox-rs/Cargo.toml b/kittybox-rs/Cargo.toml index 1d1d9fd..556a85b 100644 --- a/kittybox-rs/Cargo.toml +++ b/kittybox-rs/Cargo.toml @@ -38,6 +38,7 @@ path = "./templates" [dependencies.kittybox-indieauth] version = "0.1.0" path = "./indieauth" +features = ["axum"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/kittybox-rs/indieauth/Cargo.toml b/kittybox-rs/indieauth/Cargo.toml index 80511f9..a81fd51 100644 --- a/kittybox-rs/indieauth/Cargo.toml +++ b/kittybox-rs/indieauth/Cargo.toml @@ -3,6 +3,10 @@ name = "kittybox-indieauth" version = "0.1.0" edition = "2021" +[features] +default = [] +axum = ["axum-core", "serde_json", "http"] + [dev-dependencies] serde_json = "^1.0.64" # A JSON serialization file format serde_urlencoded = "^0.7.0" # `x-www-form-urlencoded` meets Serde @@ -16,3 +20,12 @@ features = ["serde"] [dependencies.serde] # A generic serialization/deserialization framework version = "^1.0.125" features = ["derive"] +[dependencies.axum-core] +version = "^0.2.6" +optional = true +[dependencies.serde_json] +version = "^1.0.64" +optional = true +[dependencies.http] +version = "^0.2.7" +optional = true \ No newline at end of file diff --git a/kittybox-rs/indieauth/src/lib.rs b/kittybox-rs/indieauth/src/lib.rs index b461fea..cb99146 100644 --- a/kittybox-rs/indieauth/src/lib.rs +++ b/kittybox-rs/indieauth/src/lib.rs @@ -144,6 +144,7 @@ pub enum GrantResponse { #[serde(skip_serializing_if = "Option::is_none")] profile: Option, access_token: String, + // TODO replace with std::time::Duration #[serde(skip_serializing_if = "Option::is_none")] expires_in: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -176,6 +177,7 @@ pub struct TokenData { pub me: Url, pub client_id: Url, pub scope: Scopes, + // TODO replace these two with std::time::SystemTime #[serde(skip_serializing_if = "Option::is_none")] pub exp: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -359,6 +361,18 @@ impl std::fmt::Display for self::Error { } } +#[cfg(feature = "axum")] +impl axum_core::response::IntoResponse for self::Error { + fn into_response(self) -> axum_core::response::Response { + use http::StatusCode; + + (StatusCode::BAD_REQUEST, + [("Content-Type", "application/json")], + serde_json::to_vec(&self).unwrap()) + .into_response() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs index a059211..2c15e72 100644 --- a/kittybox-rs/src/indieauth/mod.rs +++ b/kittybox-rs/src/indieauth/mod.rs @@ -110,37 +110,37 @@ async fn authorization_endpoint_post( GrantRequest::AuthorizationCode { code, client_id, redirect_uri, code_verifier } => { let request: AuthorizationRequest = match backend.get_code(&code).await { Ok(Some(request)) => request, - Ok(None) => return Json(Error { + Ok(None) => return Error { kind: ErrorKind::InvalidGrant, msg: Some("The provided authorization code is invalid.".to_string()), error_uri: None - }).into_response(), + }.into_response(), Err(err) => { tracing::error!("Error retrieving auth request: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } }; if client_id != request.client_id { - return Json(Error { + return Error { kind: ErrorKind::InvalidGrant, msg: Some("This authorization code isn't yours.".to_string()), error_uri: None - }).into_response() + }.into_response() } if redirect_uri != request.redirect_uri { - return Json(Error { + return Error { kind: ErrorKind::InvalidGrant, msg: Some("This redirect_uri doesn't match the one the code has been sent to.".to_string()), error_uri: None - }).into_response() + }.into_response() } if !request.code_challenge.verify(code_verifier) { - return Json(Error { + return Error { kind: ErrorKind::InvalidGrant, msg: Some("The PKCE challenge failed.".to_string()), // are RFCs considered human-readable? 😝 error_uri: "https://datatracker.ietf.org/doc/html/rfc7636#section-4.6".parse().ok() - }).into_response() + }.into_response() } let profile = if request.scope .map(|s| s.has(&Scope::Profile)) @@ -154,11 +154,11 @@ async fn authorization_endpoint_post( Json(GrantResponse::ProfileUrl { me, profile }).into_response() }, - _ => Json(Error { + _ => Error { kind: ErrorKind::InvalidGrant, msg: Some("The provided grant_type is unusable on this endpoint.".to_string()), error_uri: "https://indieauth.spec.indieweb.org/#redeeming-the-authorization-code".parse().ok() - }).into_response() + }.into_response() } } } @@ -209,11 +209,11 @@ async fn token_endpoint_post( // TODO load the information corresponding to the code let request: AuthorizationRequest = match backend.get_code(&code).await { Ok(Some(request)) => request, - Ok(None) => return Json(Error { + Ok(None) => return Error { kind: ErrorKind::InvalidGrant, msg: Some("The provided authorization code is invalid.".to_string()), error_uri: None - }).into_response(), + }.into_response(), Err(err) => { tracing::error!("Error retrieving auth request: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); @@ -223,11 +223,11 @@ async fn token_endpoint_post( let me: url::Url = format!("https://{}/", host).parse().unwrap(); let scope = if let Some(scope) = request.scope { scope } else { - return Json(Error { + return Error { kind: ErrorKind::InvalidScope, msg: Some("Tokens cannot be issued if no scopes are requested.".to_string()), error_uri: "https://indieauth.spec.indieweb.org/#access-token-response".parse().ok() - }).into_response(); + }.into_response(); }; if client_id != request.client_id { return Error { @@ -244,11 +244,11 @@ async fn token_endpoint_post( }.into_response() } if !request.code_challenge.verify(code_verifier) { - return Json(Error { + return Error { kind: ErrorKind::InvalidGrant, msg: Some("The PKCE challenge failed.".to_string()), error_uri: "https://datatracker.ietf.org/doc/html/rfc7636#section-4.6".parse().ok() - }).into_response(); + }.into_response(); } let profile = if scope.has(&Scope::Profile) { @@ -288,11 +288,11 @@ async fn token_endpoint_post( GrantRequest::RefreshToken { refresh_token, client_id, scope } => { let data = match backend.get_refresh_token(&refresh_token).await { Ok(Some(token)) => token, - Ok(None) => return Json(Error { + Ok(None) => return Error { kind: ErrorKind::InvalidGrant, msg: Some("This refresh token is not valid.".to_string()), error_uri: None - }).into_response(), + }.into_response(), Err(err) => { tracing::error!("Error retrieving refresh token: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response() @@ -300,20 +300,20 @@ async fn token_endpoint_post( }; if data.client_id != client_id { - return Json(Error { + return Error { kind: ErrorKind::InvalidGrant, msg: Some("This refresh token is not yours.".to_string()), error_uri: None - }).into_response(); + }.into_response(); } let scope = if let Some(scope) = scope { if !data.scope.has_all(scope.as_ref()) { - return Json(Error { + return Error { kind: ErrorKind::InvalidScope, msg: Some("You can't request additional scopes through the refresh token grant.".to_string()), error_uri: None - }).into_response(); + }.into_response(); } scope -- cgit 1.4.1