about summary refs log tree commit diff
path: root/src/bin
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-17 16:27:36 +0300
committerVika <vika@fireburn.ru>2024-08-18 00:12:56 +0300
commit9debc34ff000d7b2a54f71887da237725fb45826 (patch)
tree2e30f9fb119f3994fdf6450dc2eb6318251d002a /src/bin
parentc22267baea9facff8a3b25eeb3badcc16ac262e3 (diff)
downloadkittybox-9debc34ff000d7b2a54f71887da237725fb45826.tar.zst
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
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/kittybox-indieauth-helper.rs88
1 files changed, 44 insertions, 44 deletions
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<url::Url>
 }
 
-fn append_query_string<T: serde::Serialize>(
-    url: &url::Url,
-    query: T
-) -> Result<url::Url, Error> {
-    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::<AuthorizationResponse>();
     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<GrantResponse, IndieauthError> = 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::<GrantResponse>().await?))
+            } else {
+                Ok(Err(res.json::<IndieauthError>().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.")
     }
 }