Skip to main content

tryaudex_core/
azure.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use azure_core::credentials::{AccessToken, TokenCredential};
5use azure_identity::AzureCliCredential;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use crate::error::{AvError, Result};
10
11/// Temporary Azure credentials (short-lived OAuth2 access token).
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AzureTempCredentials {
14    pub access_token: String,
15    pub expires_at: DateTime<Utc>,
16}
17
18/// Issues short-lived Azure credentials via AzureCliCredential.
19///
20/// Works with Azure CLI credentials (`az login`).
21/// For other auth methods (service principals, managed identity),
22/// use the appropriate credential type from azure_identity.
23pub struct AzureCredentialIssuer {
24    credential: Arc<AzureCliCredential>,
25}
26
27impl AzureCredentialIssuer {
28    /// Create a new issuer using AzureCliCredential.
29    /// Requires `az login` to have been run.
30    pub fn new() -> Result<Self> {
31        let credential = AzureCliCredential::new(None).map_err(|e| {
32            AvError::Azure(format!(
33                "Failed to create Azure credential. Run `az login` first. Error: {}",
34                e
35            ))
36        })?;
37
38        Ok(Self { credential })
39    }
40
41    /// Get an access token for Azure Resource Manager.
42    ///
43    /// The token is scoped to the ARM management API by default.
44    /// Azure tokens are typically valid for 1 hour.
45    pub async fn issue(&self, _ttl: Duration) -> Result<AzureTempCredentials> {
46        // Azure Resource Manager scope
47        let scopes = &["https://management.azure.com/.default"];
48
49        let token: AccessToken = self
50            .credential
51            .get_token(scopes, None)
52            .await
53            .map_err(|e| AvError::Azure(format!("Failed to get Azure access token: {}", e)))?;
54
55        // Convert azure_core's OffsetDateTime to chrono::DateTime
56        let expires_at =
57            DateTime::from_timestamp(token.expires_on.unix_timestamp(), 0).unwrap_or_else(Utc::now);
58
59        Ok(AzureTempCredentials {
60            access_token: token.token.secret().to_string(),
61            expires_at,
62        })
63    }
64
65    /// Get an access token for a specific Azure service scope.
66    pub async fn issue_for_scope(
67        &self,
68        scope: &str,
69        _ttl: Duration,
70    ) -> Result<AzureTempCredentials> {
71        let scopes = &[scope];
72
73        let token: AccessToken = self.credential.get_token(scopes, None).await.map_err(|e| {
74            AvError::Azure(format!(
75                "Failed to get Azure access token for {}: {}",
76                scope, e
77            ))
78        })?;
79
80        let expires_at =
81            DateTime::from_timestamp(token.expires_on.unix_timestamp(), 0).unwrap_or_else(Utc::now);
82
83        Ok(AzureTempCredentials {
84            access_token: token.token.secret().to_string(),
85            expires_at,
86        })
87    }
88}