1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Serialize, Deserialize)]
4pub struct CachedJwks {
5 pub keys: Vec<CachedJwk>,
6 pub fetched_at_ms: f64,
7}
8
9#[derive(Clone, Serialize, Deserialize)]
10pub struct CachedJwk {
11 pub kty: String,
12 pub kid: String,
13 pub n: String,
14 pub e: String,
15}
16
17impl CachedJwks {
18 pub fn is_expired(&self, now_ms: f64, ttl_ms: f64) -> bool {
19 now_ms - self.fetched_at_ms > ttl_ms
20 }
21}
22
23#[cfg(feature = "kv")]
24mod kv_cache {
25 use super::{CachedJwk, CachedJwks};
26 use js_sys::Date;
27 use worker::kv::KvStore;
28
29 const JWKS_CACHE_TTL_MS: f64 = 10.0 * 60.0 * 1000.0; const KV_TTL_SECONDS: u64 = 15 * 60; fn cache_key(team_domain: &str) -> String {
33 format!("jwks:{}", team_domain)
34 }
35
36 pub async fn get_cached_jwks(kv: &KvStore, team_domain: &str) -> Option<CachedJwks> {
37 let key = cache_key(team_domain);
38
39 match kv.get(&key).json::<CachedJwks>().await {
40 Ok(Some(cached)) => {
41 if cached.is_expired(Date::now(), JWKS_CACHE_TTL_MS) {
42 None
43 } else {
44 Some(cached)
45 }
46 }
47 Ok(None) => None,
48 Err(_) => None,
49 }
50 }
51
52 pub async fn set_cached_jwks(
53 kv: &KvStore,
54 team_domain: &str,
55 keys: Vec<CachedJwk>,
56 ) -> Result<(), worker::Error> {
57 let key = cache_key(team_domain);
58 let cached = CachedJwks {
59 keys,
60 fetched_at_ms: Date::now(),
61 };
62
63 kv.put(&key, &cached)
64 .map_err(|e| worker::Error::RustError(format!("KV put error: {e}")))?
65 .expiration_ttl(KV_TTL_SECONDS)
66 .execute()
67 .await
68 .map_err(|e| worker::Error::RustError(format!("KV put error: {e}")))
69 }
70}
71
72#[cfg(feature = "kv")]
73pub use kv_cache::{get_cached_jwks, set_cached_jwks};