about summary refs log tree commit diff
path: root/kittybox-rs/src/bin/kittybox-indieauth-helper.rs
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/src/bin/kittybox-indieauth-helper.rs')
-rw-r--r--kittybox-rs/src/bin/kittybox-indieauth-helper.rs233
1 files changed, 0 insertions, 233 deletions
diff --git a/kittybox-rs/src/bin/kittybox-indieauth-helper.rs b/kittybox-rs/src/bin/kittybox-indieauth-helper.rs
deleted file mode 100644
index 3377ec3..0000000
--- a/kittybox-rs/src/bin/kittybox-indieauth-helper.rs
+++ /dev/null
@@ -1,233 +0,0 @@
-use kittybox_indieauth::{
-    AuthorizationRequest, PKCEVerifier,
-    PKCEChallenge, PKCEMethod, GrantRequest, Scope,
-    AuthorizationResponse, TokenData, GrantResponse
-};
-use clap::Parser;
-use std::{borrow::Cow, io::Write};
-
-const DEFAULT_CLIENT_ID: &str = "https://kittybox.fireburn.ru/indieauth-helper.html";
-const DEFAULT_REDIRECT_URI: &str = "http://localhost:60000/callback";
-
-#[derive(Debug, thiserror::Error)]
-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),
-    #[error("url parsing error: {0}")]
-    UrlParse(#[from] url::ParseError),
-    #[error("indieauth flow error: {0}")]
-    IndieAuth(Cow<'static, str>)
-}
-
-#[derive(Parser, Debug)]
-#[clap(
-    name = "kittybox-indieauth-helper",
-    author = "Vika <vika@fireburn.ru>",
-    version = env!("CARGO_PKG_VERSION"),
-    about = "Retrieve an IndieAuth token for debugging",
-    long_about = None
-)]
-struct Args {
-    /// Profile URL to use for initiating IndieAuth metadata discovery.
-    #[clap(value_parser)]
-    me: url::Url,
-    /// Scopes to request for the token.
-    ///
-    /// All IndieAuth scopes are supported, including arbitrary custom scopes.
-    #[clap(short, long)]
-    scope: Vec<Scope>,
-    /// Client ID to use when requesting a token.
-    #[clap(short, long, value_parser, default_value = DEFAULT_CLIENT_ID)]
-    client_id: url::Url,
-    /// Redirect URI to declare. Note: This will break the flow, use only for testing UI.
-    #[clap(long, value_parser)]
-    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> {
-    let args = Args::parse();
-
-    let http: reqwest::Client = {
-        #[allow(unused_mut)]
-        let mut builder = reqwest::Client::builder()
-            .user_agent(concat!(
-                env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")
-            ));
-
-        builder.build().unwrap()
-    };
-
-    let redirect_uri: url::Url = args.redirect_uri
-        .clone()
-        .unwrap_or_else(|| DEFAULT_REDIRECT_URI.parse().unwrap());
-    
-    eprintln!("Checking .well-known for metadata...");
-    let metadata = http.get(args.me.join("/.well-known/oauth-authorization-server")?)
-        .header("Accept", "application/json")
-        .send()
-        .await?
-        .json::<kittybox_indieauth::Metadata>()
-        .await?;
-
-    let verifier = PKCEVerifier::new();
-    
-    let authorization_request = AuthorizationRequest {
-        response_type: kittybox_indieauth::ResponseType::Code,
-        client_id: args.client_id.clone(),
-        redirect_uri: redirect_uri.clone(),
-        state: kittybox_indieauth::State::new(),
-        code_challenge: PKCEChallenge::new(&verifier, PKCEMethod::default()),
-        scope: Some(kittybox_indieauth::Scopes::new(args.scope)),
-        me: Some(args.me)
-    };
-
-    let indieauth_url = append_query_string(
-        &metadata.authorization_endpoint,
-        authorization_request
-    )?;
-
-    eprintln!("Please visit the following URL in your browser:\n\n   {}\n", indieauth_url.as_str());
-
-    if args.redirect_uri.is_some() {
-        eprintln!("Custom redirect URI specified, won't be able to catch authorization response.");
-        std::process::exit(0);
-    }
-    
-    // Prepare a callback
-    let (tx, rx) = tokio::sync::oneshot::channel::<AuthorizationResponse>();
-    let server = {
-        use axum::{routing::get, extract::Query, response::IntoResponse};
-
-        let tx = std::sync::Arc::new(tokio::sync::Mutex::new(Some(tx)));
-        
-        let router = axum::Router::new()
-            .route("/callback", axum::routing::get(
-                move |query: Option<Query<AuthorizationResponse>>| async move {
-                    if let Some(Query(response)) = query {
-                        if let Some(tx) = tx.lock_owned().await.take() {
-                            tx.send(response).unwrap();
-                        
-                            (axum::http::StatusCode::OK,
-                             [("Content-Type", "text/plain")],
-                             "Thank you! This window can now be closed.")
-                                .into_response()
-                        } else {
-                            (axum::http::StatusCode::BAD_REQUEST,
-                             [("Content-Type", "text/plain")],
-                             "Oops. The callback was already received. Did you click twice?")
-                                .into_response()
-                        }
-                    } else {
-                        axum::http::StatusCode::BAD_REQUEST.into_response()
-                    }
-                }
-            ));
-
-        use std::net::{SocketAddr, IpAddr, Ipv4Addr};
-
-        let server = hyper::server::Server::bind(
-            &SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST),60000)
-        )
-            .serve(router.into_make_service());
-
-        tokio::task::spawn(server)
-    };
-    
-    let authorization_response = rx.await.unwrap();
-
-    // Clean up after the server
-    tokio::task::spawn(async move {
-        // Wait for the server to settle -- it might need to send its response
-        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
-        // Abort the future -- this should kill the server
-        server.abort();
-    });
-
-    eprintln!("Got authorization response: {:#?}", authorization_response);
-    eprint!("Checking issuer field...");
-    std::io::stderr().lock().flush()?;
-    
-    if dbg!(authorization_response.iss.as_str()) == dbg!(metadata.issuer.as_str()) {
-        eprintln!(" Done");
-    } else {
-        eprintln!(" Failed");
-        #[cfg(not(debug_assertions))]
-        std::process::exit(1);
-    }
-    let grant_response: GrantResponse = http.post(metadata.token_endpoint)
-        .form(&GrantRequest::AuthorizationCode {
-            code: authorization_response.code,
-            client_id: args.client_id,
-            redirect_uri,
-            code_verifier: verifier
-        })
-        .header("Accept", "application/json")
-        .send()
-        .await?
-        .json()
-        .await?;
-
-    if let GrantResponse::AccessToken {
-        me,
-        profile,
-        access_token,
-        expires_in,
-        refresh_token,
-        token_type,
-        scope
-    } = grant_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.")
-                  }
-        );
-        println!("{}", access_token);
-        if let Some(refresh_token) = refresh_token {
-            eprintln!("Save this refresh token, it will come in handy:");
-            println!("{}", refresh_token);
-        };
-
-        if let Some(profile) = profile {
-            eprintln!("\nThe token endpoint returned some profile information:");
-            if let Some(name) = profile.name {
-                eprintln!(" - Name: {name}")
-            }
-            if let Some(url) = profile.url {
-                eprintln!(" - URL: {url}")
-            }
-            if let Some(photo) = profile.photo {
-                eprintln!(" - Photo: {photo}")
-            }
-            if let Some(email) = profile.email {
-                eprintln!(" - Email: {email}")
-            }
-        }
-
-        Ok(())
-    } else {
-        return Err(Error::IndieAuth(Cow::Borrowed("IndieAuth token endpoint did not return an access token grant.")));
-    }
-}