From e43313210269b8e48fe35b17ac416c9ba88ae4f3 Mon Sep 17 00:00:00 2001 From: Vika Date: Sun, 18 Aug 2024 00:30:15 +0300 Subject: feat: logins!! yes you can finally sign in this is also supposed to show private posts intended for you! maybe i can also reveal my email to those who sign in! :3 --- src/indieauth/mod.rs | 96 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 29 deletions(-) (limited to 'src/indieauth') diff --git a/src/indieauth/mod.rs b/src/indieauth/mod.rs index 06ec1f7..e466d98 100644 --- a/src/indieauth/mod.rs +++ b/src/indieauth/mod.rs @@ -7,14 +7,10 @@ use axum::{ }; #[cfg_attr(not(feature = "webauthn"), allow(unused_imports))] use axum_extra::extract::cookie::{CookieJar, Cookie}; -use axum_extra::{TypedHeader, headers::{authorization::Bearer, Authorization}}; +use axum_extra::{headers::{authorization::Bearer, Authorization, ContentType, HeaderMapExt}, TypedHeader}; use crate::database::Storage; use kittybox_indieauth::{ - Metadata, IntrospectionEndpointAuthMethod, RevocationEndpointAuthMethod, - Scope, Scopes, PKCEMethod, Error, ErrorKind, ResponseType, - AuthorizationRequest, AuthorizationResponse, - GrantType, GrantRequest, GrantResponse, Profile, - TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest, TokenData + AuthorizationRequest, AuthorizationResponse, ClientMetadata, Error, ErrorKind, GrantRequest, GrantResponse, GrantType, IntrospectionEndpointAuthMethod, Metadata, PKCEMethod, Profile, ProfileUrl, ResponseType, RevocationEndpointAuthMethod, Scope, Scopes, TokenData, TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest }; use std::str::FromStr; @@ -99,15 +95,7 @@ impl , St: Clone + Send + Sync + 'static> axum::ext pub async fn metadata( Host(host): Host ) -> Metadata { - let issuer: url::Url = format!( - "{}://{}/", - if cfg!(debug_assertions) { - "http" - } else { - "https" - }, - host - ).parse().unwrap(); + let issuer: url::Url = format!("https://{}/", host).parse().unwrap(); let indieauth: url::Url = issuer.join("/.kittybox/indieauth/").unwrap(); Metadata { @@ -146,11 +134,22 @@ async fn authorization_endpoint_get( State(http): State, State(auth): State ) -> Response { - let me = format!("https://{host}/").parse().unwrap(); - let h_app = { + let me: url::Url = format!("https://{host}/").parse().unwrap(); + // XXX: attempt fetching OAuth application metadata + let h_app: ClientMetadata = if request.client_id.domain().unwrap() == "localhost" && me.domain().unwrap() != "localhost" { + // If client is localhost, but we aren't localhost, generate synthetic metadata. + tracing::warn!("Client is localhost, not fetching metadata"); + let mut metadata = ClientMetadata::new(request.client_id.clone(), request.client_id.clone()).unwrap(); + + metadata.client_name = Some("Your locally hosted app".to_string()); + + metadata + } else { tracing::debug!("Sending request to {} to fetch metadata", request.client_id); - match http.get(request.client_id.clone()).send().await { - Ok(response) => { + let metadata_request = http.get(request.client_id.clone()) + .header("Accept", "application/json, text/html"); + match metadata_request.send().await.and_then(|res| res.error_for_status()) { + Ok(response) if response.headers().typed_get::() == Some(ContentType::html()) => { let url = response.url().clone(); let text = response.text().await.unwrap(); tracing::debug!("Received {} bytes in response", text.len()); @@ -172,7 +171,7 @@ async fn authorization_endpoint_get( .into_response() } - mf2.items + if let Some(app) = mf2.items .iter() .find(|&i| i.r#type.iter() .any(|i| { @@ -181,23 +180,60 @@ async fn authorization_endpoint_get( }) ) .cloned() - .map(|i| { - serde_json::to_value(&i).unwrap() - }) + { + // Create a synthetic metadata document. Be forgiving. + let mut metadata = ClientMetadata::new( + request.client_id.clone(), + app.properties.get("url") + .and_then(|v| v.first()) + .and_then(|i| match i { + microformats::types::PropertyValue::Url(url) => Some(url.clone()), + _ => None + }) + .unwrap_or_else(|| request.client_id.clone()) + ).unwrap(); + + metadata.client_name = app.properties.get("name") + .and_then(|v| v.first()) + .and_then(|i| match i { + microformats::types::PropertyValue::Plain(name) => Some(name.to_owned()), + _ => None + }); + + metadata + } else { + return (StatusCode::BAD_REQUEST, [("Content-Type", "text/plain")], "No h-app or JSON application metadata found.").into_response() + } }, Err(err) => { tracing::error!("Error parsing application metadata: {}", err); - return (StatusCode::BAD_REQUEST, - [("Content-Type", "text/plain")], - "Parsing application metadata failed.").into_response() + return ( + StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + "Parsing h-app metadata failed.").into_response() } } }, + Ok(response) => match response.json::().await { + Ok(client_metadata) => { + client_metadata + }, + Err(err) => { + tracing::error!("Error parsing JSON application metadata: {}", err); + return ( + StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + format!("Parsing OAuth2 JSON app metadata failed: {}", err) + ).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() + return ( + StatusCode::BAD_REQUEST, + [("Content-Type", "text/plain")], + format!("Fetching app metadata failed: {}", err) + ).into_response() } } }; @@ -233,6 +269,7 @@ struct AuthorizationConfirmation { request: AuthorizationRequest } +#[tracing::instrument(skip(auth, credential))] async fn verify_credential( auth: &A, website: &url::Url, @@ -272,6 +309,7 @@ async fn authorization_endpoint_confirm( authorization_method: credential, request: mut auth } = confirmation; + match verify_credential(&backend, &website, credential, challenge_id).await { Ok(verified) => if !verified { error!("User failed verification, bailing out."); -- cgit 1.4.1