From 9debc34ff000d7b2a54f71887da237725fb45826 Mon Sep 17 00:00:00 2001 From: Vika Date: Sat, 17 Aug 2024 16:27:36 +0300 Subject: kittybox-indieauth-helper: small cleanups - Properly catch IndieAuth errors when parsing grant responses - Use the new `into_query_pairs()` method to serialize the authorization request into the authorization endpoint URL - Migrate to `axum::serve` for simplicity in serving the callback hook --- src/bin/kittybox-indieauth-helper.rs | 88 ++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/bin/kittybox-indieauth-helper.rs b/src/bin/kittybox-indieauth-helper.rs index 4e82f8b..86a2a38 100644 --- a/src/bin/kittybox-indieauth-helper.rs +++ b/src/bin/kittybox-indieauth-helper.rs @@ -1,10 +1,13 @@ +use futures::{FutureExt, TryFutureExt}; use kittybox_indieauth::{ AuthorizationRequest, PKCEVerifier, PKCEChallenge, PKCEMethod, GrantRequest, Scope, - AuthorizationResponse, TokenData, GrantResponse + AuthorizationResponse, GrantResponse, + Error as IndieauthError }; use clap::Parser; -use std::{borrow::Cow, io::Write}; +use tokio::net::TcpListener; +use std::{borrow::Cow, future::IntoFuture, io::Write}; const DEFAULT_CLIENT_ID: &str = "https://kittybox.fireburn.ru/indieauth-helper.html"; const DEFAULT_REDIRECT_URI: &str = "http://localhost:60000/callback"; @@ -14,13 +17,11 @@ enum Error { #[error("i/o error: {0}")] IO(#[from] std::io::Error), #[error("http request error: {0}")] - HTTP(#[from] reqwest::Error), - #[error("urlencoded encoding error: {0}")] - UrlencodedEncoding(#[from] serde_urlencoded::ser::Error), + Http(#[from] reqwest::Error), #[error("url parsing error: {0}")] UrlParse(#[from] url::ParseError), #[error("indieauth flow error: {0}")] - IndieAuth(Cow<'static, str>) + IndieAuth(#[from] IndieauthError) } #[derive(Parser, Debug)] @@ -48,20 +49,6 @@ struct Args { redirect_uri: Option } -fn append_query_string( - url: &url::Url, - query: T -) -> Result { - let mut new_url = url.clone(); - let mut query = serde_urlencoded::to_string(query)?; - if let Some(old_query) = url.query() { - query.push('&'); - query.push_str(old_query); - } - new_url.set_query(Some(&query)); - - Ok(new_url) -} #[tokio::main] async fn main() -> Result<(), Error> { @@ -101,10 +88,13 @@ async fn main() -> Result<(), Error> { me: Some(args.me) }; - let indieauth_url = append_query_string( - &metadata.authorization_endpoint, - authorization_request - )?; + let indieauth_url = { + let mut url = metadata.authorization_endpoint.clone(); + let mut q = url.query_pairs_mut(); + q.extend_pairs(authorization_request.as_query_pairs()); + drop(q); + url + }; eprintln!("Please visit the following URL in your browser:\n\n {}\n", indieauth_url.as_str()); @@ -116,7 +106,7 @@ async fn main() -> Result<(), Error> { // Prepare a callback let (tx, rx) = tokio::sync::oneshot::channel::(); let server = { - use axum::{routing::get, extract::Query, response::IntoResponse}; + use axum::{extract::Query, response::IntoResponse}; let tx = std::sync::Arc::new(tokio::sync::Mutex::new(Some(tx))); @@ -145,12 +135,14 @@ async fn main() -> Result<(), Error> { use std::net::{SocketAddr, IpAddr, Ipv4Addr}; - let server = hyper::server::Server::bind( - &SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST),60000) - ) - .serve(router.into_make_service()); + let server = axum::serve( + TcpListener::bind( + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST),60000) + ).await.unwrap(), + router.into_make_service() + ); - tokio::task::spawn(server) + tokio::task::spawn(server.into_future()) }; let authorization_response = rx.await.unwrap(); @@ -170,11 +162,11 @@ async fn main() -> Result<(), Error> { if dbg!(authorization_response.iss.as_str()) == dbg!(metadata.issuer.as_str()) { eprintln!(" Done"); } else { - eprintln!(" Failed"); + eprintln!(" !! Failed !!"); #[cfg(not(debug_assertions))] std::process::exit(1); } - let grant_response: GrantResponse = http.post(metadata.token_endpoint) + let response: Result = http.post(metadata.token_endpoint) .form(&GrantRequest::AuthorizationCode { code: authorization_response.code, client_id: args.client_id, @@ -183,8 +175,13 @@ async fn main() -> Result<(), Error> { }) .header("Accept", "application/json") .send() - .await? - .json() + .and_then(|res| async move { + if res.status().is_success() { + Ok(Ok(res.json::().await?)) + } else { + Ok(Err(res.json::().await?)) + } + }) .await?; if let GrantResponse::AccessToken { @@ -193,17 +190,20 @@ async fn main() -> Result<(), Error> { access_token, expires_in, refresh_token, - token_type, - scope - } = grant_response { + scope, + .. + } = response? { eprintln!("Congratulations, {}, access token is ready! {}", - me.as_str(), - if let Some(exp) = expires_in { - format!("It expires in {exp} seconds.") - } else { - format!("It seems to have unlimited duration.") - } + profile.as_ref().and_then(|p| p.name.as_deref()).unwrap_or(me.as_str()), + if let Some(exp) = expires_in { + Cow::Owned(format!("It expires in {exp} seconds.")) + } else { + Cow::Borrowed("It seems to have unlimited duration.") + } ); + if let Some(scope) = scope { + eprintln!("The following scopes were granted: {}", scope.to_string()); + } println!("{}", access_token); if let Some(refresh_token) = refresh_token { eprintln!("Save this refresh token, it will come in handy:"); @@ -228,6 +228,6 @@ async fn main() -> Result<(), Error> { Ok(()) } else { - return Err(Error::IndieAuth(Cow::Borrowed("IndieAuth token endpoint did not return an access token grant."))); + unreachable!("Token endpoint did not return the token grant, but a different grant.") } } -- cgit 1.4.1