use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::cache::redis::AppRedis; use crate::error::{AppError, AppResult}; const API_KEY_PREFIX: &str = "internal:auth:"; const DEFAULT_TTL_SECS: u64 = 86400 * 30; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceIdentity { pub service_name: String, pub service_id: String, pub scopes: Vec, pub issued_at: i64, pub expires_at: i64, } #[derive(Clone)] pub struct InternalAuthService { redis: AppRedis, } impl InternalAuthService { pub fn new(redis: AppRedis) -> Self { Self { redis } } pub async fn issue_api_key( &self, service_name: &str, scopes: Vec, ttl_secs: Option, ) -> AppResult<(String, ServiceIdentity)> { let ttl = ttl_secs.unwrap_or(DEFAULT_TTL_SECS); let now = chrono::Utc::now().timestamp(); let expires_at = now + ttl as i64; let identity = ServiceIdentity { service_name: service_name.to_string(), service_id: Uuid::now_v7().to_string(), scopes, issued_at: now, expires_at, }; let api_key = format!("im_{}", Uuid::now_v7()); let key = format!("{API_KEY_PREFIX}{api_key}"); let json = serde_json::to_string(&identity)?; let mut conn = self.redis.get_connection(); redis::Cmd::new() .arg("SETEX") .arg(&key) .arg(ttl) .arg(&json) .query_async::<()>(&mut conn) .await .map_err(AppError::Redis)?; Ok((api_key, identity)) } pub async fn verify_api_key(&self, api_key: &str) -> AppResult> { let key = format!("{API_KEY_PREFIX}{api_key}"); let mut conn = self.redis.get_connection(); let json: Option = redis::Cmd::new() .arg("GET") .arg(&key) .query_async(&mut conn) .await .map_err(AppError::Redis)?; match json { Some(j) => { let identity: ServiceIdentity = serde_json::from_str(&j)?; Ok(Some(identity)) } None => Ok(None), } } pub async fn revoke_api_key(&self, api_key: &str) -> AppResult<()> { let key = format!("{API_KEY_PREFIX}{api_key}"); let mut conn = self.redis.get_connection(); redis::Cmd::new() .arg("DEL") .arg(&key) .query_async::<()>(&mut conn) .await .map_err(AppError::Redis)?; Ok(()) } }