1use rmcp::transport::auth::OAuthClientConfig;
10use rmcp::transport::streamable_http_client::StreamableHttpClient;
11use rmcp::transport::{AuthClient, AuthorizationManager};
12use rmcp_client as rmcp;
13
14use crate::upstream::config::UpstreamOauthRegistration;
15use crate::upstream::types::OauthError;
16
17use super::{DynamicClientRegistrationUse, TokenRefreshState, UpstreamOauthManager, now_unix};
18
19impl UpstreamOauthManager {
20 pub async fn build_auth_client(
28 &self,
29 subject: &str,
30 ) -> Result<AuthClient<reqwest::Client>, OauthError> {
31 let started = std::time::Instant::now();
32 let lock = self.locks.acquire(&self.upstream.name, subject);
33 let _guard = lock.lock().await;
34
35 let mut manager = self
36 .configured_authorization_manager(
37 subject,
38 DynamicClientRegistrationUse::StoredCredentials,
39 )
40 .await
41 .inspect_err(|e| {
42 tracing::warn!(
43 upstream = %self.upstream.name,
44 provider = %self.oauth_provider_label(),
45 subject,
46 scope = %self.oauth_scope_label(),
47 kind = e.kind(),
48 elapsed_ms = started.elapsed().as_millis(),
49 fallback = "reauthorization_required",
50 "upstream oauth: failed to build auth client manager"
51 );
52 })?;
53 let initialized = manager.initialize_from_store().await.map_err(|e| {
54 tracing::warn!(
55 upstream = %self.upstream.name,
56 provider = %self.oauth_provider_label(),
57 subject,
58 scope = %self.oauth_scope_label(),
59 kind = "internal_error",
60 elapsed_ms = started.elapsed().as_millis(),
61 fallback = "reauthorization_required",
62 "upstream oauth: failed to initialize auth client from credential store"
63 );
64 OauthError::Internal(format!("initialize from store: {e}"))
65 })?;
66
67 if !initialized {
68 tracing::warn!(
69 upstream = %self.upstream.name,
70 provider = %self.oauth_provider_label(),
71 subject,
72 scope = %self.oauth_scope_label(),
73 kind = "oauth_needs_reauth",
74 elapsed_ms = started.elapsed().as_millis(),
75 fallback = "reauthorization_required",
76 "upstream oauth: no stored credentials for auth client"
77 );
78 return Err(OauthError::NeedsReauth(format!(
79 "no stored credentials for upstream '{}' subject '{subject}'",
80 self.upstream.name
81 )));
82 }
83
84 let credential_row = self.credential_row(subject).await?;
85 let refresh_state = credential_row
86 .as_ref()
87 .and_then(|row| TokenRefreshState::from_row(row, now_unix().ok()?));
88 let refresh_due = refresh_state
89 .as_ref()
90 .is_some_and(TokenRefreshState::refresh_due);
91 if let Some(state) = refresh_state.as_ref() {
92 self.log_expiring_token(subject, state, started.elapsed().as_millis());
93 self.log_refresh_attempt(subject, state, started.elapsed().as_millis());
94 }
95
96 if refresh_due
97 && self
98 .refresh_failures
99 .recently_failed(&self.upstream.name, subject)
100 {
101 tracing::warn!(
102 upstream = %self.upstream.name,
103 provider = %self.oauth_provider_label(),
104 subject,
105 scope = %self.oauth_scope_label(),
106 kind = "oauth_needs_reauth",
107 elapsed_ms = started.elapsed().as_millis(),
108 fallback = "reauthorization_required",
109 "upstream oauth: token refresh skipped, recently failed"
110 );
111 return Err(OauthError::NeedsReauth(format!(
112 "upstream '{}' subject '{subject}' refresh failed recently; skipping retry until cooldown elapses",
113 self.upstream.name
114 )));
115 }
116
117 manager.get_access_token().await.map_err(|e| {
118 let mapped = super::map_auth_error(e);
119 if refresh_due {
120 self.refresh_failures
121 .record_failure(&self.upstream.name, subject);
122 tracing::warn!(
123 upstream = %self.upstream.name,
124 provider = %self.oauth_provider_label(),
125 subject,
126 scope = %self.oauth_scope_label(),
127 kind = mapped.kind(),
128 elapsed_ms = started.elapsed().as_millis(),
129 fallback = "reauthorization_required",
130 "upstream oauth: token refresh failed"
131 );
132 }
133 mapped
134 })?;
135
136 self.refresh_failures.clear(&self.upstream.name, subject);
137 if refresh_due {
138 tracing::info!(
139 upstream = %self.upstream.name,
140 provider = %self.oauth_provider_label(),
141 subject,
142 scope = %self.oauth_scope_label(),
143 elapsed_ms = started.elapsed().as_millis(),
144 fallback = "none",
145 "upstream oauth: token refresh succeeded"
146 );
147 }
148
149 drop(rustls::crypto::ring::default_provider().install_default());
152 Ok(AuthClient::new(reqwest::Client::new(), manager))
153 }
154
155 pub async fn build_auth_client_with<C>(
162 &self,
163 subject: &str,
164 http_client: C,
165 ) -> Result<AuthClient<C>, OauthError>
166 where
167 C: StreamableHttpClient,
168 {
169 let started = std::time::Instant::now();
170 let lock = self.locks.acquire(&self.upstream.name, subject);
171 let _guard = lock.lock().await;
172
173 let mut manager = self
174 .configured_authorization_manager(
175 subject,
176 DynamicClientRegistrationUse::StoredCredentials,
177 )
178 .await
179 .inspect_err(|e| {
180 tracing::warn!(
181 upstream = %self.upstream.name,
182 provider = %self.oauth_provider_label(),
183 subject,
184 scope = %self.oauth_scope_label(),
185 kind = e.kind(),
186 elapsed_ms = started.elapsed().as_millis(),
187 fallback = "reauthorization_required",
188 "upstream oauth: failed to build auth client manager (with_client)"
189 );
190 })?;
191 let initialized = manager.initialize_from_store().await.map_err(|e| {
192 tracing::warn!(
193 upstream = %self.upstream.name,
194 provider = %self.oauth_provider_label(),
195 subject,
196 scope = %self.oauth_scope_label(),
197 kind = "internal_error",
198 elapsed_ms = started.elapsed().as_millis(),
199 fallback = "reauthorization_required",
200 "upstream oauth: failed to initialize auth client from credential store (with_client)"
201 );
202 OauthError::Internal(format!("initialize from store: {e}"))
203 })?;
204
205 if !initialized {
206 tracing::warn!(
207 upstream = %self.upstream.name,
208 provider = %self.oauth_provider_label(),
209 subject,
210 scope = %self.oauth_scope_label(),
211 kind = "oauth_needs_reauth",
212 elapsed_ms = started.elapsed().as_millis(),
213 fallback = "reauthorization_required",
214 "upstream oauth: no stored credentials for auth client (with_client)"
215 );
216 return Err(OauthError::NeedsReauth(format!(
217 "no stored credentials for upstream '{}' subject '{subject}'",
218 self.upstream.name
219 )));
220 }
221
222 let credential_row = self.credential_row(subject).await?;
223 let refresh_state = credential_row
224 .as_ref()
225 .and_then(|row| TokenRefreshState::from_row(row, now_unix().ok()?));
226 let refresh_due = refresh_state
227 .as_ref()
228 .is_some_and(TokenRefreshState::refresh_due);
229 if let Some(state) = refresh_state.as_ref() {
230 self.log_expiring_token(subject, state, started.elapsed().as_millis());
231 self.log_refresh_attempt(subject, state, started.elapsed().as_millis());
232 }
233
234 if refresh_due
235 && self
236 .refresh_failures
237 .recently_failed(&self.upstream.name, subject)
238 {
239 tracing::warn!(
240 upstream = %self.upstream.name,
241 provider = %self.oauth_provider_label(),
242 subject,
243 scope = %self.oauth_scope_label(),
244 kind = "oauth_needs_reauth",
245 elapsed_ms = started.elapsed().as_millis(),
246 fallback = "reauthorization_required",
247 "upstream oauth: token refresh skipped, recently failed (with_client)"
248 );
249 return Err(OauthError::NeedsReauth(format!(
250 "upstream '{}' subject '{subject}' refresh failed recently; skipping retry until cooldown elapses",
251 self.upstream.name
252 )));
253 }
254
255 manager.get_access_token().await.map_err(|e| {
256 let mapped = super::map_auth_error(e);
257 if refresh_due {
258 self.refresh_failures
259 .record_failure(&self.upstream.name, subject);
260 tracing::warn!(
261 upstream = %self.upstream.name,
262 provider = %self.oauth_provider_label(),
263 subject,
264 scope = %self.oauth_scope_label(),
265 kind = mapped.kind(),
266 elapsed_ms = started.elapsed().as_millis(),
267 fallback = "reauthorization_required",
268 "upstream oauth: token refresh failed (with_client)"
269 );
270 }
271 mapped
272 })?;
273
274 self.refresh_failures.clear(&self.upstream.name, subject);
275 if refresh_due {
276 tracing::info!(
277 upstream = %self.upstream.name,
278 provider = %self.oauth_provider_label(),
279 subject,
280 scope = %self.oauth_scope_label(),
281 elapsed_ms = started.elapsed().as_millis(),
282 fallback = "none",
283 "upstream oauth: token refresh succeeded (with_client)"
284 );
285 }
286
287 Ok(AuthClient::new(http_client, manager))
288 }
289
290 pub async fn refresh_auth_client(&self, subject: &str) -> Result<(), OauthError> {
296 let started = std::time::Instant::now();
297 let lock = self.locks.acquire(&self.upstream.name, subject);
298 let _guard = lock.lock().await;
299
300 let mut manager = self
301 .configured_authorization_manager(
302 subject,
303 DynamicClientRegistrationUse::StoredCredentials,
304 )
305 .await
306 .inspect_err(|e| {
307 tracing::warn!(
308 upstream = %self.upstream.name,
309 provider = %self.oauth_provider_label(),
310 subject,
311 scope = %self.oauth_scope_label(),
312 kind = e.kind(),
313 elapsed_ms = started.elapsed().as_millis(),
314 fallback = "reauthorization_required",
315 "upstream oauth: failed to build refresh manager"
316 );
317 })?;
318 let initialized = manager.initialize_from_store().await.map_err(|e| {
319 tracing::warn!(
320 upstream = %self.upstream.name,
321 provider = %self.oauth_provider_label(),
322 subject,
323 scope = %self.oauth_scope_label(),
324 kind = "internal_error",
325 elapsed_ms = started.elapsed().as_millis(),
326 fallback = "reauthorization_required",
327 "upstream oauth: failed to initialize refresh manager from credential store"
328 );
329 OauthError::Internal(format!("initialize from store: {e}"))
330 })?;
331
332 if !initialized {
333 return Err(OauthError::NeedsReauth(format!(
334 "no stored credentials for upstream '{}' subject '{subject}'",
335 self.upstream.name
336 )));
337 }
338
339 let scopes_owned = self.oauth_config()?.scopes.clone().unwrap_or_default();
350 let scopes: Vec<&str> = scopes_owned.iter().map(String::as_str).collect();
351 let client_cfg = self
352 .resolve_client_config(
353 &mut manager,
354 subject,
355 &scopes,
356 DynamicClientRegistrationUse::StoredCredentials,
357 )
358 .await?;
359 manager.configure_client(client_cfg).map_err(|e| {
360 OauthError::Internal(format!(
361 "re-configure client with credentials after store init: {e}"
362 ))
363 })?;
364
365 manager
366 .refresh_token()
367 .await
368 .map_err(super::map_auth_error)?;
369 tracing::info!(
370 upstream = %self.upstream.name,
371 provider = %self.oauth_provider_label(),
372 subject,
373 scope = %self.oauth_scope_label(),
374 elapsed_ms = started.elapsed().as_millis(),
375 "upstream oauth: status refresh succeeded"
376 );
377 Ok(())
378 }
379
380 pub(super) async fn resolve_client_config(
381 &self,
382 manager: &mut AuthorizationManager,
383 subject: &str,
384 scopes: &[&str],
385 dynamic_registration_use: DynamicClientRegistrationUse,
386 ) -> Result<OAuthClientConfig, OauthError> {
387 let oauth_cfg = self.oauth_config()?;
388 match &oauth_cfg.registration {
389 UpstreamOauthRegistration::Preregistered {
390 client_id,
391 client_secret_env,
392 } => {
393 let secret = match client_secret_env.as_deref() {
394 None => None,
395 Some(var) => {
396 let val = std::env::var(var).unwrap_or_default();
397 if val.is_empty() {
398 return Err(OauthError::Internal(format!(
399 "client_secret_env '{var}' is configured but env var '{var}' is not set or is empty"
400 )));
401 }
402 Some(val)
403 }
404 };
405
406 let mut cfg = OAuthClientConfig::new(client_id.clone(), self.redirect_uri.as_str());
407 if let Some(s) = secret {
408 cfg = cfg.with_client_secret(s);
409 }
410 cfg = cfg.with_scopes(scopes.iter().map(|s| s.to_string()).collect());
411 Ok(cfg)
412 }
413 UpstreamOauthRegistration::Dynamic => {
414 match dynamic_registration_use {
423 DynamicClientRegistrationUse::StoredCredentials => {
424 if let Some(row) = self
425 .sqlite
426 .find_upstream_oauth_credentials(&self.upstream.name, subject)
427 .await
428 .map_err(|e| OauthError::Internal(e.to_string()))?
429 {
430 let mut cfg =
431 OAuthClientConfig::new(row.client_id, self.redirect_uri.as_str());
432 cfg = cfg.with_scopes(scopes.iter().map(|s| s.to_string()).collect());
433 return Ok(cfg);
434 }
435
436 return Err(OauthError::NeedsReauth(format!(
437 "no stored credentials for upstream '{}' subject '{subject}'",
438 self.upstream.name
439 )));
440 }
441 DynamicClientRegistrationUse::CompleteAuthorization => {
442 if let Some(client_id) = self
447 .sqlite
448 .find_dynamic_client_registration(&self.upstream.name, subject)
449 .await
450 .map_err(|e| OauthError::Internal(e.to_string()))?
451 {
452 let mut cfg =
453 OAuthClientConfig::new(client_id, self.redirect_uri.as_str());
454 cfg = cfg.with_scopes(scopes.iter().map(|s| s.to_string()).collect());
455 return Ok(cfg);
456 }
457
458 return Err(OauthError::NeedsReauth(format!(
459 "no dynamic client registration for upstream '{}' subject '{subject}'",
460 self.upstream.name
461 )));
462 }
463 DynamicClientRegistrationUse::BeginAuthorization => {}
464 }
465
466 let cfg = manager
470 .register_client("soma", self.redirect_uri.as_str(), scopes)
471 .await
472 .map_err(|e| OauthError::Internal(format!("dynamic registration: {e}")))?;
473
474 self.sqlite
475 .save_dynamic_client_registration(&self.upstream.name, subject, &cfg.client_id)
476 .await
477 .map_err(|e| OauthError::Internal(e.to_string()))?;
478
479 let canonical_client_id = self
481 .sqlite
482 .find_dynamic_client_registration(&self.upstream.name, subject)
483 .await
484 .map_err(|e| OauthError::Internal(e.to_string()))?
485 .ok_or_else(|| {
486 OauthError::Internal(
487 "dynamic registration saved but read-back returned nothing".to_string(),
488 )
489 })?;
490
491 let mut canonical_cfg =
492 OAuthClientConfig::new(canonical_client_id, self.redirect_uri.as_str());
493 canonical_cfg =
494 canonical_cfg.with_scopes(scopes.iter().map(|s| s.to_string()).collect());
495 Ok(canonical_cfg)
496 }
497 UpstreamOauthRegistration::ClientMetadataDocument { url } => {
498 let parsed = url::Url::parse(url).map_err(|e| {
503 OauthError::Internal(format!("invalid client_metadata_document url: {e}"))
504 })?;
505 if parsed.scheme() != "https" {
506 return Err(OauthError::Internal(format!(
507 "client_metadata_document url must use https, got `{}`",
508 parsed.scheme()
509 )));
510 }
511 let cfg = OAuthClientConfig::new(url.clone(), self.redirect_uri.as_str())
512 .with_scopes(scopes.iter().map(|s| s.to_string()).collect());
513 Ok(cfg)
514 }
515 }
516 }
517}