about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-26 21:00:24 +0300
committerVika <vika@fireburn.ru>2024-08-26 21:05:04 +0300
commit6d8e906f7c3d850120f94c7a784690442503aced (patch)
tree1d90d8998e5bd09a546b733bc6db7a24ce8230b2 /src
parent806f5fbfabd914d27ff3fb2e822e1c3869068859 (diff)
Explicitly allow caching IndieAuth client metadata
This might save a round-trip for clients that know how to cache
things. Such as Kittybox's HTTP fetcher.
Diffstat (limited to 'src')
-rw-r--r--src/login.rs25
1 files changed, 23 insertions, 2 deletions
diff --git a/src/login.rs b/src/login.rs
index e105bcc..646c832 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -1,11 +1,12 @@
-use std::borrow::Cow;
+use std::{borrow::Cow, str::FromStr};
 
 use futures_util::FutureExt;
 use axum::{extract::{FromRef, Host, Query, State}, http::HeaderValue, response::IntoResponse, Form};
-use axum_extra::extract::{cookie::{self, Cookie}, SignedCookieJar};
+use axum_extra::{extract::{cookie::{self, Cookie}, SignedCookieJar}, headers::HeaderMapExt, TypedHeader};
 use hyper::{header::{CACHE_CONTROL, LOCATION}, StatusCode};
 use kittybox_frontend_renderer::{Template, LoginPage, LogoutPage};
 use kittybox_indieauth::{AuthorizationResponse, Error, GrantType, PKCEVerifier, Scope, Scopes};
+use sha2::Digest;
 
 use crate::database::Storage;
 
@@ -321,7 +322,24 @@ async fn client_metadata<S: Storage + Send + Sync + 'static>(
     State(storage): State<S>,
     // XXX: blocked on https://github.com/hyperium/headers/pull/162
     //TypedHeader(accept): TypedHeader<axum_extra::headers::Accept>
+    cached: Option<TypedHeader<axum_extra::headers::IfNoneMatch>>,
 ) -> axum::response::Response {
+    let etag = {
+        let mut digest = sha2::Sha256::new();
+        digest.update(env!("CARGO_PKG_NAME").as_bytes());
+        digest.update(b" ");
+        digest.update(env!("CARGO_PKG_VERSION").as_bytes());
+        digest.update(b" ");
+        digest.update(crate::OAUTH2_SOFTWARE_ID.as_bytes());
+
+        let etag = String::from("W/") + &hex::encode(digest.finalize());
+        axum_extra::headers::ETag::from_str(&etag).unwrap()
+    };
+    if let Some(cached) = cached {
+        if cached.precondition_passes(&etag) {
+            return StatusCode::NOT_MODIFIED.into_response()
+        }
+    }
     let client_uri: url::Url = format!("https://{}/", host).parse().unwrap();
     let client_id: url::Url = {
         let mut url = client_uri.clone();
@@ -345,6 +363,9 @@ async fn client_metadata<S: Storage + Send + Sync + 'static>(
     let mut response = metadata.into_response();
     // Indicate to upstream caches this endpoint does different things depending on the Accept: header.
     response.headers_mut().append("Vary", HeaderValue::from_static("Accept"));
+    // Cache this metadata for an hour.
+    response.headers_mut().append("Cache-Control", HeaderValue::from_static("max-age=600"));
+    response.headers_mut().typed_insert(etag);
 
     response
 }