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#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AzureTempCredentials {
14 pub access_token: String,
15 pub expires_at: DateTime<Utc>,
16}
17
18pub struct AzureCredentialIssuer {
24 credential: Arc<AzureCliCredential>,
25}
26
27impl AzureCredentialIssuer {
28 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 pub async fn issue(&self, _ttl: Duration) -> Result<AzureTempCredentials> {
46 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 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 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}