Skip to main content

cli_engine/auth/
credential.rs

1use chrono::{DateTime, Duration, Utc};
2use serde::{Deserialize, Serialize};
3
4/// Cache TTL used when a credential has `cached_at`.
5pub const CACHE_TTL: Duration = Duration::minutes(30);
6
7/// Credential returned by an auth provider.
8///
9/// Field names and omission behavior match the provider JSON contract. Empty
10/// strings are accepted because some providers omit optional values.
11#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
12pub struct Credential {
13    /// Access token used by transport injectors.
14    #[serde(default)]
15    pub token: String,
16    /// Explicit expiration timestamp.
17    #[serde(default)]
18    pub expires_at: String,
19    /// Cache creation timestamp. When present, [`CACHE_TTL`] determines expiry.
20    #[serde(default, skip_serializing_if = "String::is_empty")]
21    pub cached_at: String,
22    /// Provider that produced this credential.
23    #[serde(default, skip_serializing_if = "String::is_empty")]
24    pub provider: String,
25    /// Environment this credential targets.
26    #[serde(default, skip_serializing_if = "String::is_empty")]
27    pub env: String,
28    /// Environment alias accepted from provider responses.
29    #[serde(default, skip_serializing_if = "String::is_empty")]
30    pub realm: String,
31    /// Human-readable identity.
32    #[serde(default, skip_serializing_if = "String::is_empty")]
33    pub identity: String,
34    /// Subject identifier.
35    #[serde(default, skip_serializing_if = "String::is_empty")]
36    pub sub: String,
37    /// Account type associated with the credential.
38    #[serde(default, skip_serializing_if = "String::is_empty")]
39    pub account_type: String,
40}
41
42impl Credential {
43    /// Returns the timestamp used for status display.
44    #[must_use]
45    pub fn effective_expiry(&self) -> String {
46        if let Ok(cached_at) = DateTime::parse_from_rfc3339(&self.cached_at) {
47            return (cached_at.with_timezone(&Utc) + CACHE_TTL)
48                .to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
49        }
50        self.expires_at.clone()
51    }
52
53    /// Reports whether the credential is expired.
54    ///
55    /// Invalid `expires_at` values are treated as expired. Credentials without
56    /// either `expires_at` or `cached_at` are treated as not expired.
57    #[must_use]
58    pub fn is_expired(&self) -> bool {
59        if let Ok(cached_at) = DateTime::parse_from_rfc3339(&self.cached_at) {
60            return Utc::now() > cached_at.with_timezone(&Utc) + CACHE_TTL;
61        }
62        if self.expires_at.is_empty() {
63            return false;
64        }
65        match DateTime::parse_from_rfc3339(&self.expires_at) {
66            Ok(expires_at) => Utc::now() > expires_at.with_timezone(&Utc),
67            Err(_) => true,
68        }
69    }
70}