zino-auth 0.15.0

Authentication and authorization for zino.
Documentation
use hmac::{
    Hmac, Mac,
    digest::{FixedOutput, KeyInit, MacMarker, Update},
};
use rand::{RngExt, distr::Alphanumeric};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt, iter};
use zino_core::{
    LazyLock,
    application::{Agent, Application},
    crypto::{self, Digest},
    encoding::base64,
    extension::TomlTableExt,
    state::State,
};

/// Access key ID.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccessKeyId(String);

impl AccessKeyId {
    /// Creates a new instance.
    ///
    /// It is generated by random alphanumeric characters.
    pub fn new() -> Self {
        let mut rng = rand::rng();
        let chars: String = iter::repeat(())
            .map(|_| rng.sample(Alphanumeric))
            .map(char::from)
            .take(20)
            .collect();
        Self(chars)
    }

    /// Creates a new instance with the specific length.
    ///
    /// It is generated by random alphanumeric characters.
    pub fn with_length(length: u8) -> Self {
        let mut rng = rand::rng();
        let chars: String = iter::repeat(())
            .map(|_| rng.sample(Alphanumeric))
            .map(char::from)
            .take(length.into())
            .collect();
        Self(chars)
    }

    /// Attempts to construct an instance by generating a [sqid](https://sqids.org/)
    /// from a slice of numbers.
    #[cfg(feature = "sqids")]
    #[inline]
    pub fn encode_sqids(numbers: &[u64]) -> Result<Self, sqids::Error> {
        SQIDS_GENERATOR.encode(numbers).map(AccessKeyId)
    }

    /// Decodes `self` as a [sqid](https://sqids.org/) into a vector of numbers.
    #[cfg(feature = "sqids")]
    #[inline]
    pub fn decode_sqids(&self) -> Vec<u64> {
        SQIDS_GENERATOR.decode(self.as_str())
    }

    /// Attempts to construct an instance by generating [Sqids](https://sqids.org/) from a UUID.
    #[cfg(feature = "sqids")]
    #[inline]
    pub fn encode_uuid(id: &zino_core::Uuid) -> Result<Self, sqids::Error> {
        let (hi, lo) = id.as_u64_pair();
        SQIDS_GENERATOR.encode(&[hi, lo]).map(AccessKeyId)
    }

    /// Decodes `self` as [Sqids](https://sqids.org/) into a UUID.
    #[cfg(feature = "sqids")]
    #[inline]
    pub fn decode_uuid(&self) -> Option<zino_core::Uuid> {
        if let [hi, lo] = SQIDS_GENERATOR.decode(self.as_str()).as_slice() {
            Some(zino_core::Uuid::from_u64_pair(*hi, *lo))
        } else {
            None
        }
    }

    /// Returns a string slice.
    #[inline]
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl fmt::Display for AccessKeyId {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl AsRef<[u8]> for AccessKeyId {
    #[inline]
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl From<String> for AccessKeyId {
    #[inline]
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl From<&str> for AccessKeyId {
    #[inline]
    fn from(s: &str) -> Self {
        Self(s.to_owned())
    }
}

impl<'a> From<Cow<'a, str>> for AccessKeyId {
    #[inline]
    fn from(s: Cow<'a, str>) -> Self {
        Self(s.into_owned())
    }
}

impl From<AccessKeyId> for String {
    #[inline]
    fn from(id: AccessKeyId) -> String {
        id.0
    }
}

/// Secrect access key.
#[derive(Debug, Clone)]
pub struct SecretAccessKey(Vec<u8>);

impl SecretAccessKey {
    /// Creates a new instance for the Access key ID.
    #[inline]
    pub fn new(access_key_id: &AccessKeyId) -> Self {
        Self::with_key::<Hmac<Digest>>(access_key_id, SECRET_KEY.as_ref())
    }

    /// Creates a new instance with the specific key.
    pub fn with_key<H>(access_key_id: &AccessKeyId, key: impl AsRef<[u8]>) -> Self
    where
        H: FixedOutput + KeyInit + MacMarker + Update,
    {
        fn inner<H>(access_key_id: &AccessKeyId, key: &[u8]) -> SecretAccessKey
        where
            H: FixedOutput + KeyInit + MacMarker + Update,
        {
            let mut mac = H::new_from_slice(key).expect("HMAC can take key of any size");
            mac.update(access_key_id.as_ref());
            SecretAccessKey(mac.finalize().into_bytes().to_vec())
        }
        inner::<H>(access_key_id, key.as_ref())
    }

    /// Returns a byte slice.
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_slice()
    }
}

impl fmt::Display for SecretAccessKey {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        base64::encode(self.as_bytes()).fmt(f)
    }
}

impl AsRef<[u8]> for SecretAccessKey {
    #[inline]
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl From<SecretAccessKey> for Vec<u8> {
    #[inline]
    fn from(s: SecretAccessKey) -> Vec<u8> {
        s.0
    }
}

/// Shared secret.
static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
    let app_config = State::shared().config();
    let config = app_config.get_table("access-key").unwrap_or(app_config);
    let checksum: [u8; 32] = config
        .get_str("checksum")
        .and_then(|checksum| checksum.as_bytes().try_into().ok())
        .unwrap_or_else(|| {
            let secret = config.get_str("secret").unwrap_or_else(|| {
                tracing::warn!("auto-generated `secret` is used for deriving a secret key");
                Agent::name()
            });
            crypto::digest(secret.as_bytes())
        });
    let info = config.get_str("info").unwrap_or("ZINO:ACCESS-KEY");
    crypto::derive_key(info, &checksum)
});

/// The default generator for sqids.
#[cfg(feature = "sqids")]
static SQIDS_GENERATOR: LazyLock<sqids::Sqids> = LazyLock::new(sqids::Sqids::default);