summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs76
-rw-r--r--src/micropub.rs5
2 files changed, 72 insertions, 9 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 3a104ba..111be23 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,7 +19,7 @@ pub mod components {
     // pub(crate) use tag_pill::{TagPill, TagPillDelete}
 
     pub mod signin;
-    pub use signin::{SignIn, Output as SignInOutput};
+    pub use signin::{SignIn, Output as SignInOutput, Error as SignInError};
 
     pub mod preferences;
     pub use preferences::Preferences;
@@ -52,10 +52,10 @@ pub struct App {
 impl App {
     async fn authorize(schema: &libsecret::Schema, http: soup::Session, data: Box<components::SignInOutput>) -> Result<micropub::Client, glib::Error> {
         let mut attributes = std::collections::HashMap::new();
-        let _me = data.me.to_string();
+        let me = data.me.to_string();
         let _micropub = data.micropub.to_string();
         let scope = data.scope.to_string();
-        attributes.insert(secrets::ME, _me.as_str());
+        attributes.insert(secrets::ME, me.as_str());
         attributes.insert(secrets::TOKEN_KIND, secrets::ACCESS_TOKEN);
         attributes.insert(secrets::MICROPUB, _micropub.as_str());
         attributes.insert(secrets::SCOPE, scope.as_str());
@@ -100,7 +100,7 @@ impl App {
         }
 
         Ok(micropub::Client::new(
-            http.clone(), data.micropub.clone(), data.access_token.clone()
+            http.clone(), data.micropub.clone(), data.access_token.clone(), me
         ))
     }
 
@@ -218,6 +218,63 @@ impl App {
         }
     }
 
+    async fn revoke_token(http: soup::Session, me: String, token: String) -> Result<Option<()>, components::SignInError> {
+        let url = glib::Uri::parse(
+            me.as_str(),
+            glib::UriFlags::SCHEME_NORMALIZE
+        )?;
+
+        let (metadata, _) = crate::components::signin::get_metadata(http.clone(), url).await?;
+
+        let endpoint = match metadata.revocation_endpoint {
+            Some(endpoint) => match metadata.revocation_endpoint_auth_methods_supported {
+                Some(methods) => if methods.iter().any(|i| matches!(i, kittybox_indieauth::RevocationEndpointAuthMethod::None)) {
+                    glib::Uri::parse(endpoint.as_str(), glib::UriFlags::NONE).unwrap()
+                } else {
+                    tracing::warn!("couldn't revoke token: revocation endpoint doesn't support unauthenticated requests\n(Note: this is a violation of the IndieAuth specification)");
+                    return Ok(None)
+                },
+                None => {
+                    tracing::warn!("couldn't revoke token: revocation endpoint doesn't support unauthenticated requests\n(Note: this is a violation of the IndieAuth specification)");
+                    return Ok(None)
+                }
+            },
+            None => {
+                tracing::warn!("couldn't revoke token: revocation endpoint not found");
+                return Ok(None)
+            }
+        };
+        let msg = soup::Message::from_uri("POST", &endpoint);
+        let headers = msg.request_headers().unwrap();
+        headers.append("Accept", "application/json");
+        msg.set_request_body_from_bytes(
+            Some("application/x-www-form-urlencoded"),
+            Some(&glib::Bytes::from_owned(
+                serde_urlencoded::to_string(
+                    kittybox_indieauth::TokenRevocationRequest { token }
+                ).unwrap().into_bytes()
+            ))
+        );
+
+        match http.send_and_read_future(&msg, glib::Priority::DEFAULT).await {
+            Ok(_) if msg.status() == soup::Status::Ok => {
+                Ok(Some(()))
+            },
+            Ok(body) => {
+                tracing::warn!("couldn't revoke token: revocation endpoint returned non-200: {:?}", msg.status());
+                match serde_json::from_slice::<kittybox_indieauth::Error>(&body) {
+                    Ok(err) => tracing::warn!("revocation endpoint returned an error: {}", err),
+                    Err(_) => tracing::warn!("couldn't parse revocation endpoint error, response verbatim follows:\n{}", String::from_utf8_lossy(&body))
+                }
+                Ok(None)
+            },
+            Err(err) => {
+                tracing::warn!("couldn't revoke token: error contacting revocation endpoint: {:?}", err);
+                Err(err.into())
+            }
+        }
+    }
+
     async fn get_login_state(schema: &libsecret::Schema, http: soup::Session) -> Result<Option<micropub::Client>, glib::Error> {
         let mut retrievables = libsecret::password_search_future(Some(schema), {
             let mut attrs = std::collections::HashMap::default();
@@ -248,6 +305,7 @@ impl App {
                         },
                     };
 
+                let me = attrs.get(crate::secrets::ME).unwrap().to_string();
                 let micropub = crate::micropub::Client::new(
                     http.clone(),
                     micropub_uri,
@@ -255,7 +313,8 @@ impl App {
                         .unwrap()
                         .text()
                         .unwrap()
-                        .to_string()
+                        .to_string(),
+                    me.clone()
                 );
 
                 // Skip the token if we can't access ?q=config
@@ -267,11 +326,11 @@ impl App {
 
                         match Self::refresh_token(
                             schema, http.clone(),
-                            attrs.get(crate::secrets::ME).unwrap().to_string()
+                            me.clone()
                         ).await {
                             Ok(None) => continue,
                             Err(err) => {
-                                tracing::warn!("error refreshing Micropub token for {}: {}", attrs.get(crate::secrets::ME).unwrap(), err);
+                                tracing::warn!("error refreshing Micropub token for {}: {}", &me, err);
                                 continue
                             },
                             Ok(Some(micropub)) => return Ok(Some(micropub))
@@ -478,6 +537,9 @@ impl AsyncComponent for App {
                         Some(&self.secret_schema),
                         Default::default(),
                     ).await;
+                    let _ = Self::revoke_token(
+                        self.http.clone(), micropub.me, micropub.access_token
+                    ).await;
                 }
             },
             Input::Authorize(data) => {
diff --git a/src/micropub.rs b/src/micropub.rs
index 04fa893..b2f1e73 100644
--- a/src/micropub.rs
+++ b/src/micropub.rs
@@ -3,6 +3,7 @@ pub use kittybox_util::micropub::{Error as MicropubError, Config, QueryType};
 
 #[derive(Debug)]
 pub struct Client {
+    pub me: String,
     micropub: String,
     pub access_token: String,
 
@@ -22,11 +23,11 @@ pub enum Error {
 }
 
 impl Client {
-    pub fn new(http: soup::Session, uri: glib::Uri, token: String) -> Self {
+    pub fn new(http: soup::Session, uri: glib::Uri, token: String, me: String) -> Self {
         Self {
             micropub: uri.to_string(),
             access_token: token,
-
+            me,
             http,
         }
     }