akas 2.4.18

AKAS: API Key Authorization Server
use crate::utils::{is_key_valid, sha256_hex};
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

pub const FORMAT_SHA256: &str = "sha256";
pub const FORMAT_PLAIN: &str = "plain";

#[derive(Debug)]
pub struct AppConfig {
    pub admin_key: String,
    pub no_admin_key: bool,
    pub local: bool,
    pub enable_metrics: bool,
    pub port: u16,
    pub log_level: String,
    pub original_length: u16,
    pub metadata_length: u16,
    pub key_length: u16,
    pub key_prefix: String,
    pub auth_counter: Arc<prometheus::IntCounterVec>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ApiKey(String);

impl ApiKey {
    /// Create a new ApiKey instance if the key is valid.
    pub fn new(raw: &str, length: u16, prefix: &str) -> Option<Self> {
        if is_key_valid(raw, length, prefix) {
            Some(ApiKey(raw.to_string()))
        } else {
            None
        }
    }

    pub fn hash(&self) -> String {
        sha256_hex(&self.0)
    }
}

#[derive(Debug, Serialize)]
pub struct Headers {
    pub forwarded_for: String,
    pub original_host: String,
    pub original_uri: String,
    pub metadata: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyFormat {
    Sha256,
    Plain,
}

impl KeyFormat {
    /// Convert from string
    pub fn from_str(s: &str) -> Option<Self> {
        match s {
            FORMAT_SHA256 => Some(KeyFormat::Sha256),
            FORMAT_PLAIN => Some(KeyFormat::Plain),
            _ => None,
        }
    }

    /// Convert enum -> &str
    pub fn as_str(&self) -> &'static str {
        match self {
            KeyFormat::Sha256 => FORMAT_SHA256,
            KeyFormat::Plain => FORMAT_PLAIN,
        }
    }
}

#[derive(Debug, MultipartForm)]
pub struct LoadForm {
    pub file: TempFile,
    pub json: Option<MPJson<LoadMetadata>>,
}

#[derive(Debug, Deserialize)]
pub struct LoadMetadata {
    pub format: Option<String>,
    pub hash_input_file: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct StatusResponse {
    pub log_level: String,
    pub no_admin_key: bool,
    pub local: bool,
    pub enable_metrics: bool,
    pub original_length: u16,
    pub metadata_length: u16,
    pub file_hash: String,
    pub file_date: String,
    pub file_key_count: u32,
    pub key_length: u16,
    pub key_prefix: String,
}

#[cfg(test)]
mod api_key_tests {
    use super::*;
    use crate::utils;

    #[test]
    fn test_api_key_new_valid() {
        let key = "prefix12345";
        let api_key = ApiKey::new(key, 11, "prefix");
        assert!(api_key.is_some());
    }

    #[test]
    fn test_api_key_new_invalid_length() {
        let key = "prefix1";
        let api_key = ApiKey::new(key, 10, "prefix");
        assert!(api_key.is_none());
    }

    #[test]
    fn test_api_key_new_invalid_prefix() {
        let key = "noprefix123";
        let api_key = ApiKey::new(key, 11, "prefix");
        assert!(api_key.is_none());
    }

    #[test]
    fn test_api_key_hash() {
        let key = "testkey";
        let api_key = ApiKey::new(key, 7, "test").unwrap();
        assert_eq!(api_key.hash(), utils::sha256_hex(key));
    }
}

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

    #[test]
    fn test_key_format_from_str() {
        assert_eq!(KeyFormat::from_str("sha256"), Some(KeyFormat::Sha256));
        assert_eq!(KeyFormat::from_str("plain"), Some(KeyFormat::Plain));
        assert_eq!(KeyFormat::from_str("unknown"), None);
    }

    #[test]
    fn test_key_format_as_str() {
        assert_eq!(KeyFormat::Sha256.as_str(), "sha256");
        assert_eq!(KeyFormat::Plain.as_str(), "plain");
    }
}