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