authn_resolver/domain/
service.rs1use std::sync::Arc;
7use std::time::Duration;
8
9use authn_resolver_sdk::{
10 AuthNResolverPluginClient, AuthNResolverPluginSpecV1, AuthenticationResult,
11 ClientCredentialsRequest,
12};
13use modkit::client_hub::{ClientHub, ClientScope};
14use modkit::plugins::{GtsPluginSelector, choose_plugin_instance};
15use modkit::telemetry::ThrottledLog;
16use modkit_macros::domain_model;
17use tracing::info;
18use types_registry_sdk::{ListQuery, TypesRegistryClient};
19
20use super::error::DomainError;
21
22const UNAVAILABLE_LOG_THROTTLE: Duration = Duration::from_secs(10);
24
25#[domain_model]
29pub struct Service {
30 hub: Arc<ClientHub>,
31 vendor: String,
32 selector: GtsPluginSelector,
33 unavailable_log_throttle: ThrottledLog,
34}
35
36impl Service {
37 #[must_use]
39 pub fn new(hub: Arc<ClientHub>, vendor: String) -> Self {
40 Self {
41 hub,
42 vendor,
43 selector: GtsPluginSelector::new(),
44 unavailable_log_throttle: ThrottledLog::new(UNAVAILABLE_LOG_THROTTLE),
45 }
46 }
47
48 async fn get_plugin(&self) -> Result<Arc<dyn AuthNResolverPluginClient>, DomainError> {
50 let instance_id = self.selector.get_or_init(|| self.resolve_plugin()).await?;
51 let scope = ClientScope::gts_id(instance_id.as_ref());
52
53 if let Some(client) = self
54 .hub
55 .try_get_scoped::<dyn AuthNResolverPluginClient>(&scope)
56 {
57 Ok(client)
58 } else {
59 if self.unavailable_log_throttle.should_log() {
60 tracing::warn!(
61 plugin_gts_id = %instance_id,
62 vendor = %self.vendor,
63 "Plugin client not registered yet"
64 );
65 }
66 Err(DomainError::PluginUnavailable {
67 gts_id: instance_id.to_string(),
68 reason: "client not registered yet".into(),
69 })
70 }
71 }
72
73 #[tracing::instrument(skip_all, fields(vendor = %self.vendor))]
75 async fn resolve_plugin(&self) -> Result<String, DomainError> {
76 info!("Resolving authn_resolver plugin");
77
78 let registry = self
79 .hub
80 .get::<dyn TypesRegistryClient>()
81 .map_err(|e| DomainError::TypesRegistryUnavailable(e.to_string()))?;
82
83 let plugin_type_id = AuthNResolverPluginSpecV1::gts_schema_id().clone();
84
85 let instances = registry
86 .list(
87 ListQuery::new()
88 .with_pattern(format!("{plugin_type_id}*"))
89 .with_is_type(false),
90 )
91 .await?;
92
93 let gts_id = choose_plugin_instance::<AuthNResolverPluginSpecV1>(
94 &self.vendor,
95 instances.iter().map(|e| (e.gts_id.as_str(), &e.content)),
96 )?;
97 info!(plugin_gts_id = %gts_id, "Selected authn_resolver plugin instance");
98
99 Ok(gts_id)
100 }
101
102 #[tracing::instrument(skip_all)]
109 pub async fn authenticate(
110 &self,
111 bearer_token: &str,
112 ) -> Result<AuthenticationResult, DomainError> {
113 let plugin = self.get_plugin().await?;
114 plugin
115 .authenticate(bearer_token)
116 .await
117 .map_err(DomainError::from)
118 }
119
120 #[tracing::instrument(skip_all, fields(client_id = %request.client_id))]
127 pub async fn exchange_client_credentials(
128 &self,
129 request: &ClientCredentialsRequest,
130 ) -> Result<AuthenticationResult, DomainError> {
131 let plugin = self.get_plugin().await?;
132 plugin
133 .exchange_client_credentials(request)
134 .await
135 .map_err(DomainError::from)
136 }
137}