From ef6b0f6ee3b769356f7bc852eb240e26f2d895cf Mon Sep 17 00:00:00 2001 From: Vika Date: Fri, 15 Nov 2024 07:17:20 +0300 Subject: Actually use refresh tokens This code is untested. I guess I'll need to revisit this in about a week, when my access token expires. Then I'll be able to see if it correctly refreshes. --- src/components/signin.rs | 154 ++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 89 deletions(-) (limited to 'src/components') diff --git a/src/components/signin.rs b/src/components/signin.rs index 156686e..53c3670 100644 --- a/src/components/signin.rs +++ b/src/components/signin.rs @@ -37,7 +37,7 @@ pub struct SignIn { } #[derive(Debug, thiserror::Error)] -enum Error { +pub enum Error { #[error("glib error: {0}")] Glib(#[from] glib::Error), #[error("indieauth error: {0}")] @@ -67,6 +67,66 @@ pub enum Input { Callback(Result), } +pub async fn get_metadata(http: soup::Session, url: glib::Uri) -> Result<(Metadata, glib::Uri), Error> { + // Fire off a speculative request at the well-known URI. This could + // improve UX by parallelizing how we query the user's website. + // + // Note that exposing the metadata at the .well-known URI is + // RECOMMENDED though optional according to IndieAuth specification + // § 4.1.1, so we could use that if it's there to speed up the + // process. + let metadata = relm4::spawn_local( + SignIn::well_known_metadata(http.clone(), url.clone()) + ); + let msg = soup::Message::from_uri("GET", &url); + let body = http.send_future(&msg, glib::Priority::DEFAULT).await?; + + let mf2 = microformats::from_reader( + std::io::BufReader::new(body.into_read()), + url.to_string().parse().unwrap() + )?; + + let rels = mf2.rels.by_rels(); + let metadata_url = if let Some(url) = rels + .get("indieauth-metadata") + .map(Vec::as_slice) + .and_then(<[_]>::first) + { + glib::Uri::parse(url.as_str(), glib::UriFlags::NONE).unwrap() + } else { + // I intentionally refuse to support older IndieAuth versions. + // The new versions are superior by providing more features that + // were previously proprietary extensions, and are more clearer in + // general. + return Err(Error::MetadataNotFound) + }; + + let micropub_uri = if let Some(url) = rels + .get("micropub") + .map(Vec::as_slice) + .and_then(<[_]>::first) + { + glib::Uri::parse(url.as_str(), glib::UriFlags::NONE).unwrap() + } else { + return Err(Error::MicropubLinkNotFound) + }; + + if let Ok(Some(metadata)) = metadata.await { + Ok((metadata, micropub_uri)) + } else { + let msg = soup::Message::from_uri("GET", &metadata_url); + msg.request_headers().unwrap().append("Accept", "application/json"); + match http.send_and_read_future(&msg, glib::Priority::DEFAULT).await { + Ok(body) if msg.status() == soup::Status::Ok => { + let metadata = serde_json::from_slice(&body)?; + Ok((metadata, micropub_uri)) + }, + Ok(_) => Err(Error::MetadataEndpointFailed(msg.status())), + Err(err) => Err(err.into()), + } + } +} + fn callback_handler(sender: AsyncComponentSender) -> impl Fn(&soup::Server, &soup::ServerMessage, &str, std::collections::HashMap<&str, &str>) { move |server, msg, _, _| { let server = ObjectExt::downgrade(server); @@ -125,7 +185,7 @@ fn callback_handler(sender: AsyncComponentSender) -> impl Fn(&soup::Serv } impl SignIn { - fn scopes() -> kittybox_indieauth::Scopes { + pub fn scopes() -> kittybox_indieauth::Scopes { kittybox_indieauth::Scopes::new(vec![ kittybox_indieauth::Scope::Profile, kittybox_indieauth::Scope::Create, @@ -314,93 +374,9 @@ impl AsyncComponent for SignIn { }, }; - let (metadata, micropub_uri) = { - // Fire off a speculative request at the well-known URI. This could - // improve UX by parallelizing how we query the user's website. - // - // Note that exposing the metadata at the .well-known URI is - // RECOMMENDED though optional according to IndieAuth specification - // § 4.1.1, so we could use that if it's there to speed up the - // process. - let metadata = relm4::spawn_local( - Self::well_known_metadata(self.http.clone(), url.clone()) - ); - let msg = soup::Message::from_uri("GET", &url); - let body = match self.http.send_future(&msg, glib::Priority::DEFAULT).await { - Ok(body) => body, - Err(err) => { - return self.bail_out( - widgets, sender, - err.into() - ); - }, - }; - - let mf2 = match microformats::from_reader( - std::io::BufReader::new(body.into_read()), - url.to_string().parse().unwrap() - ) { - Ok(mf2) => mf2, - Err(err) => { - return self.bail_out( - widgets, sender, - err.into() - ); - } - }; - - let rels = mf2.rels.by_rels(); - let metadata_url = if let Some(url) = rels - .get("indieauth-metadata") - .map(Vec::as_slice) - .and_then(<[_]>::first) - { - glib::Uri::parse(url.as_str(), glib::UriFlags::NONE).unwrap() - } else { - // I intentionally refuse to support older IndieAuth versions. - // The new versions are superior by providing more features that - // were previously proprietary extensions, and are more clearer in - // general. - return self.bail_out( - widgets, sender, - Error::MetadataNotFound - ); - }; - - let micropub_uri = if let Some(url) = rels - .get("micropub") - .map(Vec::as_slice) - .and_then(<[_]>::first) - { - glib::Uri::parse(url.as_str(), glib::UriFlags::NONE).unwrap() - } else { - return self.bail_out( - widgets, sender, - Error::MicropubLinkNotFound - ); - }; - - if let Ok(Some(metadata)) = metadata.await { - (metadata, micropub_uri) - } else { - let msg = soup::Message::from_uri("GET", &metadata_url); - msg.request_headers().unwrap().append("Accept", "application/json"); - match self.http.send_and_read_future(&msg, glib::Priority::DEFAULT).await { - Ok(body) if msg.status() == soup::Status::Ok => { - match serde_json::from_slice(&body) { - Ok(metadata) => (metadata, micropub_uri), - Err(err) => return self.bail_out(widgets, sender, err.into()), - } - }, - Ok(_) => { - return self.bail_out( - widgets, sender, - Error::MetadataEndpointFailed(msg.status()) - ) - }, - Err(err) => return self.bail_out(widgets, sender, err.into()), - } - } + let (metadata, micropub_uri) = match get_metadata(self.http.clone(), url.clone()).await { + Ok((metadata, micropub_uri)) => (metadata, micropub_uri), + Err(err) => return self.bail_out(widgets, sender, err) }; let auth_request = AuthorizationRequest { -- cgit 1.4.1