authplane_sdk/cache/
metadata_cache.rs1use std::sync::Arc;
9
10use serde_json::Value;
11
12use crate::cache::document_cache::{DocumentCache, DocumentChangeCallback, DocumentFetcherFn};
13use crate::transport::validate_fetch_url;
14use crate::{AuthplaneError, FetchSettings};
15
16pub type MetadataChangeCallback = DocumentChangeCallback;
18
19#[derive(Clone)]
21pub struct MetadataCache {
22 inner: Arc<DocumentCache>,
23 expected_issuer: String,
24 fetch_settings: FetchSettings,
25}
26
27impl std::fmt::Debug for MetadataCache {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("MetadataCache")
30 .field("expected_issuer", &self.expected_issuer)
31 .field("inner", &self.inner)
32 .finish()
33 }
34}
35
36impl MetadataCache {
37 pub fn new(
43 fetcher: DocumentFetcherFn,
44 expected_issuer: impl Into<String>,
45 fetch_settings: FetchSettings,
46 refresh_seconds: u64,
47 on_change: Option<MetadataChangeCallback>,
48 ) -> Self {
49 let inner = DocumentCache::with_error_factory(
50 fetcher,
51 refresh_seconds,
52 "metadata",
53 on_change,
54 Box::new(metadata_error_factory),
55 );
56 Self {
57 inner,
58 expected_issuer: crate::errors::normalize_issuer(&expected_issuer.into()).to_string(),
59 fetch_settings,
60 }
61 }
62
63 pub fn document_cache(&self) -> Arc<DocumentCache> {
65 self.inner.clone()
66 }
67
68 pub async fn aclose(&self) {
70 self.inner.aclose().await;
71 }
72
73 pub async fn get_metadata(&self) -> Result<Value, AuthplaneError> {
75 let document = self.inner.get(false).await?;
76 self.validate_issuer(&document)?;
77 Ok(document)
78 }
79
80 pub async fn refresh(&self) -> Result<Value, AuthplaneError> {
82 let document = self.inner.get(true).await?;
83 self.validate_issuer(&document)?;
84 Ok(document)
85 }
86
87 pub(crate) async fn refresh_strict(&self) -> Result<Value, AuthplaneError> {
90 let document = self.inner.refresh_strict().await?;
91 self.validate_issuer(&document)?;
92 Ok(document)
93 }
94
95 pub async fn endpoint(&self, key: &str) -> Result<String, AuthplaneError> {
97 let metadata = self.get_metadata().await?;
98 let value = metadata
99 .get(key)
100 .and_then(Value::as_str)
101 .filter(|value| !value.is_empty())
102 .ok_or_else(|| {
103 metadata_error_factory(&format!("AS metadata missing required '{key}' endpoint"))
104 })?
105 .to_string();
106 validate_fetch_url(&value, &self.fetch_settings, &format!("{key} URL"))?;
107 Ok(value)
108 }
109
110 pub async fn get_jwks_uri(&self) -> Result<String, AuthplaneError> {
113 self.endpoint("jwks_uri").await
114 }
115
116 pub async fn get_token_endpoint(&self) -> Result<String, AuthplaneError> {
118 self.endpoint("token_endpoint").await
119 }
120
121 pub async fn get_introspection_endpoint(&self) -> Result<String, AuthplaneError> {
123 self.endpoint("introspection_endpoint").await
124 }
125
126 pub async fn get_revocation_endpoint(&self) -> Result<String, AuthplaneError> {
128 self.endpoint("revocation_endpoint").await
129 }
130
131 fn validate_issuer(&self, document: &Value) -> Result<(), AuthplaneError> {
132 let discovered = document
133 .get("issuer")
134 .and_then(Value::as_str)
135 .map(crate::errors::normalize_issuer)
136 .ok_or_else(|| metadata_error_factory("AS metadata missing 'issuer' field"))?;
137 if discovered != self.expected_issuer {
138 return Err(metadata_error_factory(&format!(
139 "AS metadata issuer mismatch: configured {:?}, discovered {:?}",
140 self.expected_issuer, discovered
141 )));
142 }
143 Ok(())
144 }
145}
146
147fn metadata_error_factory(message: &str) -> AuthplaneError {
153 crate::errors::metadata_error(message)
154}