zino_auth/
access_key.rs

1use hmac::{
2    Hmac, Mac,
3    digest::{FixedOutput, KeyInit, MacMarker, Update},
4};
5use rand::{Rng, distr::Alphanumeric};
6use serde::{Deserialize, Serialize};
7use std::{borrow::Cow, fmt, iter};
8use zino_core::{
9    LazyLock,
10    application::{Agent, Application},
11    crypto::{self, Digest},
12    encoding::base64,
13    extension::TomlTableExt,
14    state::State,
15};
16
17/// Access key ID.
18#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct AccessKeyId(String);
20
21impl AccessKeyId {
22    /// Creates a new instance.
23    ///
24    /// It is generated by random alphanumeric characters.
25    pub fn new() -> Self {
26        let mut rng = rand::rng();
27        let chars: String = iter::repeat(())
28            .map(|_| rng.sample(Alphanumeric))
29            .map(char::from)
30            .take(20)
31            .collect();
32        Self(chars)
33    }
34
35    /// Creates a new instance with the specific length.
36    ///
37    /// It is generated by random alphanumeric characters.
38    pub fn with_length(length: u8) -> Self {
39        let mut rng = rand::rng();
40        let chars: String = iter::repeat(())
41            .map(|_| rng.sample(Alphanumeric))
42            .map(char::from)
43            .take(length.into())
44            .collect();
45        Self(chars)
46    }
47
48    /// Attempts to construct an instance by generating a [sqid](https://sqids.org/)
49    /// from a slice of numbers.
50    #[cfg(feature = "sqids")]
51    #[inline]
52    pub fn encode_sqids(numbers: &[u64]) -> Result<Self, sqids::Error> {
53        SQIDS_GENERATOR.encode(numbers).map(AccessKeyId)
54    }
55
56    /// Decodes `self` as a [sqid](https://sqids.org/) into a vector of numbers.
57    #[cfg(feature = "sqids")]
58    #[inline]
59    pub fn decode_sqids(&self) -> Vec<u64> {
60        SQIDS_GENERATOR.decode(self.as_str())
61    }
62
63    /// Attempts to construct an instance by generating [Sqids](https://sqids.org/) from a UUID.
64    #[cfg(feature = "sqids")]
65    #[inline]
66    pub fn encode_uuid(id: &zino_core::Uuid) -> Result<Self, sqids::Error> {
67        let (hi, lo) = id.as_u64_pair();
68        SQIDS_GENERATOR.encode(&[hi, lo]).map(AccessKeyId)
69    }
70
71    /// Decodes `self` as [Sqids](https://sqids.org/) into a UUID.
72    #[cfg(feature = "sqids")]
73    #[inline]
74    pub fn decode_uuid(&self) -> Option<zino_core::Uuid> {
75        if let [hi, lo] = SQIDS_GENERATOR.decode(self.as_str()).as_slice() {
76            Some(zino_core::Uuid::from_u64_pair(*hi, *lo))
77        } else {
78            None
79        }
80    }
81
82    /// Returns a string slice.
83    #[inline]
84    pub fn as_str(&self) -> &str {
85        self.0.as_str()
86    }
87}
88
89impl fmt::Display for AccessKeyId {
90    #[inline]
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        self.0.fmt(f)
93    }
94}
95
96impl AsRef<[u8]> for AccessKeyId {
97    #[inline]
98    fn as_ref(&self) -> &[u8] {
99        self.0.as_ref()
100    }
101}
102
103impl From<String> for AccessKeyId {
104    #[inline]
105    fn from(s: String) -> Self {
106        Self(s)
107    }
108}
109
110impl From<&str> for AccessKeyId {
111    #[inline]
112    fn from(s: &str) -> Self {
113        Self(s.to_owned())
114    }
115}
116
117impl<'a> From<Cow<'a, str>> for AccessKeyId {
118    #[inline]
119    fn from(s: Cow<'a, str>) -> Self {
120        Self(s.into_owned())
121    }
122}
123
124impl From<AccessKeyId> for String {
125    #[inline]
126    fn from(id: AccessKeyId) -> String {
127        id.0
128    }
129}
130
131/// Secrect access key.
132#[derive(Debug, Clone)]
133pub struct SecretAccessKey(Vec<u8>);
134
135impl SecretAccessKey {
136    /// Creates a new instance for the Access key ID.
137    #[inline]
138    pub fn new(access_key_id: &AccessKeyId) -> Self {
139        Self::with_key::<Hmac<Digest>>(access_key_id, SECRET_KEY.as_ref())
140    }
141
142    /// Creates a new instance with the specific key.
143    pub fn with_key<H>(access_key_id: &AccessKeyId, key: impl AsRef<[u8]>) -> Self
144    where
145        H: FixedOutput + KeyInit + MacMarker + Update,
146    {
147        fn inner<H>(access_key_id: &AccessKeyId, key: &[u8]) -> SecretAccessKey
148        where
149            H: FixedOutput + KeyInit + MacMarker + Update,
150        {
151            let mut mac = H::new_from_slice(key).expect("HMAC can take key of any size");
152            mac.update(access_key_id.as_ref());
153            SecretAccessKey(mac.finalize().into_bytes().to_vec())
154        }
155        inner::<H>(access_key_id, key.as_ref())
156    }
157
158    /// Returns a byte slice.
159    #[inline]
160    pub fn as_bytes(&self) -> &[u8] {
161        self.0.as_slice()
162    }
163}
164
165impl fmt::Display for SecretAccessKey {
166    #[inline]
167    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168        base64::encode(self.as_bytes()).fmt(f)
169    }
170}
171
172impl AsRef<[u8]> for SecretAccessKey {
173    #[inline]
174    fn as_ref(&self) -> &[u8] {
175        self.0.as_ref()
176    }
177}
178
179impl From<SecretAccessKey> for Vec<u8> {
180    #[inline]
181    fn from(s: SecretAccessKey) -> Vec<u8> {
182        s.0
183    }
184}
185
186/// Shared secret.
187static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
188    let app_config = State::shared().config();
189    let config = app_config.get_table("access-key").unwrap_or(app_config);
190    let checksum: [u8; 32] = config
191        .get_str("checksum")
192        .and_then(|checksum| checksum.as_bytes().try_into().ok())
193        .unwrap_or_else(|| {
194            let secret = config.get_str("secret").unwrap_or_else(|| {
195                tracing::warn!("auto-generated `secret` is used for deriving a secret key");
196                Agent::name()
197            });
198            crypto::digest(secret.as_bytes())
199        });
200    let info = config.get_str("info").unwrap_or("ZINO:ACCESS-KEY");
201    crypto::derive_key(info, &checksum)
202});
203
204/// The default generator for sqids.
205#[cfg(feature = "sqids")]
206static SQIDS_GENERATOR: LazyLock<sqids::Sqids> = LazyLock::new(sqids::Sqids::default);