zino_auth/
security_token.rs1use self::ParseSecurityTokenError::*;
2use super::AccessKeyId;
3use std::{fmt, time::Duration};
4use zino_core::{crypto, datetime::DateTime, encoding::base64, error::Error, warn};
5
6#[derive(Debug, Clone)]
8pub struct SecurityToken {
9 access_key_id: AccessKeyId,
11 expires_at: DateTime,
13 token: String,
15}
16
17impl SecurityToken {
18 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 #[inline]
43 pub fn access_key_id(&self) -> &AccessKeyId {
44 &self.access_key_id
45 }
46
47 #[inline]
49 pub fn expires_at(&self) -> DateTime {
50 self.expires_at
51 }
52
53 #[inline]
55 pub fn expires_in(&self) -> Duration {
56 self.expires_at.span_after_now().unwrap_or_default()
57 }
58
59 #[inline]
61 pub fn is_expired(&self) -> bool {
62 self.expires_at <= DateTime::now()
63 }
64
65 #[inline]
67 pub fn as_str(&self) -> &str {
68 self.token.as_str()
69 }
70
71 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#[derive(Debug)]
113pub enum ParseSecurityTokenError {
114 DecodeError(Error),
116 ParseExpiresError(Error),
118 ValidPeriodExpired(DateTime),
120 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 {}