diff options
author | Vika <vika@fireburn.ru> | 2022-10-24 00:51:46 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2022-10-24 00:52:22 +0300 |
commit | 729bf77efb0812d0aed6234576fdd06effa5019e (patch) | |
tree | 79b9ed227fe6d320e0887e596d52f77d3e3ea532 /kittybox-rs/src/indieauth/mod.rs | |
parent | 3fd63e5e9d6a05e1dcd84a0ca6dd0930f3ef9cf6 (diff) | |
download | kittybox-729bf77efb0812d0aed6234576fdd06effa5019e.tar.zst |
indieauth: parse application metadata
Diffstat (limited to 'kittybox-rs/src/indieauth/mod.rs')
-rw-r--r-- | kittybox-rs/src/indieauth/mod.rs | 81 |
1 files changed, 58 insertions, 23 deletions
diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs index aaa3301..f71b5be 100644 --- a/kittybox-rs/src/indieauth/mod.rs +++ b/kittybox-rs/src/indieauth/mod.rs @@ -18,6 +18,8 @@ use kittybox_indieauth::{ GrantType, GrantRequest, GrantResponse, Profile, TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest, TokenData }; +use std::str::FromStr; +use std::ops::Deref; pub mod backend; #[cfg(feature = "webauthn")] @@ -145,11 +147,59 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>( Host(host): Host, Query(request): Query<AuthorizationRequest>, Extension(db): Extension<D>, + Extension(http): Extension<reqwest::Client>, Extension(auth): Extension<A> -) -> Html<String> { +) -> Response { let me = format!("https://{}/", host).parse().unwrap(); - // TODO fetch h-app from client_id - // TODO verify redirect_uri registration + let h_app = { + match http.get(request.client_id.clone()).send().await { + Ok(response) => { + let url = response.url().clone(); + let text = response.text().await.unwrap(); + match microformats::from_html(&text, url) { + Ok(mf2) => { + if let Some(relation) = mf2.rels.items.get(&request.redirect_uri) { + if !relation.rels.iter().any(|i| i == "redirect_uri") { + return (StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + "The redirect_uri provided was declared as \ + something other than redirect_uri.") + .into_response() + } + } else if request.redirect_uri.origin() != request.client_id.origin() { + return (StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + "The redirect_uri didn't match the origin \ + and wasn't explicitly allowed. You were being tricked.") + .into_response() + } + + mf2.items.iter() + .cloned() + .find(|i| (**i).borrow().r#type.iter() + .any(|i| *i == microformats::types::Class::from_str("h-app").unwrap() + || *i == microformats::types::Class::from_str("h-x-app").unwrap())) + .map(|i| serde_json::to_value(i.borrow().deref()).unwrap()) + }, + Err(err) => { + tracing::error!("Error parsing application metadata: {}", err); + return (StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + "Parsing application metadata failed.").into_response() + } + } + }, + Err(err) => { + tracing::error!("Error fetching application metadata: {}", err); + return (StatusCode::INTERNAL_SERVER_ERROR, + [("Content-Type", "text/plain")], + "Fetching application metadata failed.").into_response() + } + } + }; + + tracing::debug!("Application metadata: {:#?}", h_app); + Html(kittybox_frontend_renderer::Template { title: "Confirm sign-in via IndieAuth", blog_name: "Kittybox", @@ -159,26 +209,10 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>( request, credentials: auth.list_user_credential_types(&me).await.unwrap(), user: db.get_post(me.as_str()).await.unwrap().unwrap(), - // XXX parse MF2 - app: serde_json::json!({ - "type": [ - "h-app", - "h-x-app" - ], - "properties": { - "name": [ - "Quill" - ], - "logo": [ - "https://quill.p3k.io/images/quill-logo-144.png" - ], - "url": [ - "https://quill.p3k.io/" - ] - } - }) + app: h_app }.to_string(), }.to_string()) + .into_response() } #[derive(Deserialize, Debug)] @@ -753,7 +787,7 @@ async fn userinfo_endpoint_get<A: AuthBackend, D: Storage + 'static>( } } -pub fn router<A: AuthBackend, D: Storage + 'static>(backend: A, db: D) -> axum::Router { +pub fn router<A: AuthBackend, D: Storage + 'static>(backend: A, db: D, http: reqwest::Client) -> axum::Router { use axum::routing::{Router, get, post}; Router::new() @@ -785,7 +819,7 @@ pub fn router<A: AuthBackend, D: Storage + 'static>(backend: A, db: D) -> axum:: .route("/webauthn/pre_register", get( #[cfg(feature = "webauthn")] webauthn::webauthn_pre_register::<A, D>, - #[cfg(not(feature = "webauthn"))] || async { axum::http::StatusCode::NOT_FOUND } + #[cfg(not(feature = "webauthn"))] || std::future::ready(axum::http::StatusCode::NOT_FOUND) ) ) .layer(tower_http::cors::CorsLayer::new() @@ -799,6 +833,7 @@ pub fn router<A: AuthBackend, D: Storage + 'static>(backend: A, db: D) -> axum:: // If I could, I would've designed a separate trait for getting profiles // And made databases implement it, for example .layer(Extension(db)) + .layer(Extension(http)) ) .route( "/.well-known/oauth-authorization-server", |