sunbeam-g2v 0.4.0

Sunbeam Service Framework - A ConnectRPC-based framework for building microservices
//! API key authentication primitives.

use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use super::error::AuthError;

/// A stored API key record.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ApiKeyRow {
    /// Key identifier.
    pub id: String,
    /// Tenant that owns this key.
    pub tenant_id: String,
    /// SHA-256 hash of the key material.
    pub key_hash: String,
    /// Human-readable name.
    pub name: String,
    /// Scopes granted to this key.
    pub scopes: Vec<String>,
}

/// Store that resolves an API key hash to its record.
#[async_trait]
pub trait TenantApiKeyStore: Send + Sync + 'static {
    /// Look up an API key by its hash.
    async fn get_by_hash(&self, hash: &str) -> Result<ApiKeyRow, AuthError>;
}

/// Hash raw API key material with SHA-256 and hex-encode.
pub fn hash_api_key(key: &str) -> String {
    use sha2::{Digest, Sha256};
    let mut hasher = Sha256::new();
    hasher.update(key.as_bytes());
    hex::encode(hasher.finalize())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hash_api_key_is_deterministic_and_hex() {
        let h1 = hash_api_key("my-secret-key");
        let h2 = hash_api_key("my-secret-key");
        assert_eq!(h1, h2);
        assert_eq!(h1.len(), 64);
        assert!(h1.chars().all(|c| c.is_ascii_hexdigit()));
    }

    #[test]
    fn hash_api_key_differs_for_different_keys() {
        let h1 = hash_api_key("key-one");
        let h2 = hash_api_key("key-two");
        assert_ne!(h1, h2);
    }
}