entelix_cloud/foundry/
credential.rs1use std::sync::Arc;
12use std::time::{Duration, Instant};
13
14use async_trait::async_trait;
15use azure_core::credentials::TokenCredential;
16use secrecy::SecretString;
17
18use crate::CloudError;
19use crate::refresh::{TokenRefresher, TokenSnapshot};
20
21pub const FOUNDRY_SCOPE: &str = "https://cognitiveservices.azure.com/.default";
23
24pub struct FoundryCredentialProvider {
27 inner: Arc<dyn TokenCredential>,
28 scopes: Vec<String>,
29}
30
31impl FoundryCredentialProvider {
32 pub fn new(cred: Arc<dyn TokenCredential>, scopes: &[&str]) -> Self {
37 Self {
38 inner: cred,
39 scopes: scopes.iter().map(|s| (*s).to_owned()).collect(),
40 }
41 }
42}
43
44#[async_trait]
45impl TokenRefresher<SecretString> for FoundryCredentialProvider {
46 async fn refresh(&self) -> Result<TokenSnapshot<SecretString>, CloudError> {
47 let scopes_ref: Vec<&str> = self.scopes.iter().map(String::as_str).collect();
48 let token =
49 self.inner
50 .get_token(&scopes_ref, None)
51 .await
52 .map_err(|e| CloudError::Credential {
53 message: format!("get_token: {e}"),
54 source: Some(Box::new(e)),
55 })?;
56 let value = SecretString::from(token.token.secret().to_owned());
57 let expires_at = offset_to_instant(token.expires_on);
58 Ok(TokenSnapshot { value, expires_at })
59 }
60}
61
62fn offset_to_instant(at: time::OffsetDateTime) -> Instant {
63 let now_t = time::OffsetDateTime::now_utc();
64 let now_inst = Instant::now();
65 let delta_ms = (at - now_t).whole_milliseconds();
66 if delta_ms <= 0 {
67 return now_inst;
68 }
69 now_inst + Duration::from_millis(u64::try_from(delta_ms).unwrap_or(u64::MAX)) }