diff options
-rw-r--r-- | src/lib.rs | 76 | ||||
-rw-r--r-- | src/micropub.rs | 5 |
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, } } |