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::{InstanceQuery, 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_instances(InstanceQuery::new().with_pattern(format!("{plugin_type_id}*")))
87 .await?;
88
89 let gts_id = choose_plugin_instance::<AuthNResolverPluginSpecV1>(
90 &self.vendor,
91 instances.iter().map(|e| (e.id.as_ref(), &e.object)),
92 )?;
93 info!(plugin_gts_id = %gts_id, "Selected authn_resolver plugin instance");
94
95 Ok(gts_id)
96 }
97
98 #[tracing::instrument(skip_all)]
105 pub async fn authenticate(
106 &self,
107 bearer_token: &str,
108 ) -> Result<AuthenticationResult, DomainError> {
109 let plugin = self.get_plugin().await?;
110 plugin
111 .authenticate(bearer_token)
112 .await
113 .map_err(DomainError::from)
114 }
115
116 #[tracing::instrument(skip_all, fields(client_id = %request.client_id))]
123 pub async fn exchange_client_credentials(
124 &self,
125 request: &ClientCredentialsRequest,
126 ) -> Result<AuthenticationResult, DomainError> {
127 let plugin = self.get_plugin().await?;
128 plugin
129 .exchange_client_credentials(request)
130 .await
131 .map_err(DomainError::from)
132 }
133}