zino_auth/
security_token.rs

1use self::ParseSecurityTokenError::*;
2use super::AccessKeyId;
3use std::{fmt, time::Duration};
4use zino_core::{crypto, datetime::DateTime, encoding::base64, error::Error, warn};
5
6/// Security token.
7#[derive(Debug, Clone)]
8pub struct SecurityToken {
9    /// Access key ID.
10    access_key_id: AccessKeyId,
11    /// Expires time.
12    expires_at: DateTime,
13    /// Token.
14    token: String,
15}
16
17impl SecurityToken {
18    /// Attempts to create a new instance.
19    pub fn try_new(
20        access_key_id: AccessKeyId,
21        expires_at: DateTime,
22        key: impl AsRef<[u8]>,
23    ) -> Result<Self, Error> {
24        fn inner(
25            access_key_id: AccessKeyId,
26            expires_at: DateTime,
27            key: &[u8],
28        ) -> Result<SecurityToken, Error> {
29            let signature = format!("{}:{}", &access_key_id, expires_at.timestamp());
30            let authorization = crypto::encrypt(signature.as_bytes(), key)?;
31            let token = base64::encode(authorization);
32            Ok(SecurityToken {
33                access_key_id,
34                expires_at,
35                token,
36            })
37        }
38        inner(access_key_id, expires_at, key.as_ref())
39    }
40
41    /// Returns the access key ID.
42    #[inline]
43    pub fn access_key_id(&self) -> &AccessKeyId {
44        &self.access_key_id
45    }
46
47    /// Returns the expires time.
48    #[inline]
49    pub fn expires_at(&self) -> DateTime {
50        self.expires_at
51    }
52
53    /// Returns the time when the security token will expire in.
54    #[inline]
55    pub fn expires_in(&self) -> Duration {
56        self.expires_at.span_after_now().unwrap_or_default()
57    }
58
59    /// Returns `true` if the security token has expired.
60    #[inline]
61    pub fn is_expired(&self) -> bool {
62        self.expires_at <= DateTime::now()
63    }
64
65    /// Returns a string slice.
66    #[inline]
67    pub fn as_str(&self) -> &str {
68        self.token.as_str()
69    }
70
71    /// Parses the token with the encryption key.
72    pub fn parse_with(token: String, key: &[u8]) -> Result<Self, ParseSecurityTokenError> {
73        let authorization = base64::decode(&token).map_err(|err| DecodeError(err.into()))?;
74        let signature = crypto::decrypt(&authorization, key)
75            .map_err(|_| DecodeError(warn!("fail to decrypt authorization")))?;
76        let signature_str = String::from_utf8_lossy(&signature);
77        if let Some((access_key_id, timestamp)) = signature_str.split_once(':') {
78            let timestamp = timestamp
79                .parse::<i64>()
80                .map_err(|err| ParseExpiresError(err.into()))?;
81            let expires_at = DateTime::from_timestamp(timestamp);
82            if expires_at >= DateTime::now() {
83                Ok(Self {
84                    access_key_id: access_key_id.into(),
85                    expires_at,
86                    token,
87                })
88            } else {
89                Err(ValidPeriodExpired(expires_at))
90            }
91        } else {
92            Err(InvalidFormat)
93        }
94    }
95}
96
97impl fmt::Display for SecurityToken {
98    #[inline]
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        self.token.fmt(f)
101    }
102}
103
104impl AsRef<[u8]> for SecurityToken {
105    #[inline]
106    fn as_ref(&self) -> &[u8] {
107        self.token.as_ref()
108    }
109}
110
111/// An error which can be returned when parsing a token.
112#[derive(Debug)]
113pub enum ParseSecurityTokenError {
114    /// An error that can occur while decoding.
115    DecodeError(Error),
116    /// An error which can occur while parsing a expires timestamp.
117    ParseExpiresError(Error),
118    /// Valid period expired.
119    ValidPeriodExpired(DateTime),
120    /// Invalid format.
121    InvalidFormat,
122}
123
124impl fmt::Display for ParseSecurityTokenError {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        match self {
127            DecodeError(err) => write!(f, "decode error: {err}"),
128            ParseExpiresError(err) => write!(f, "parse expires error: {err}"),
129            ValidPeriodExpired(expires) => write!(f, "expired at `{expires}`"),
130            InvalidFormat => write!(f, "invalid format"),
131        }
132    }
133}
134
135impl std::error::Error for ParseSecurityTokenError {}