use crate::AppService; use crate::error::AppError; use base64::Engine; use chrono::Utc; use models::users::{user_activity_log, user_token}; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use uuid::Uuid; #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct CreateAccessKeyParams { pub name: String, pub scopes: Vec, pub expires_at: Option>, } #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct AccessKeyResponse { pub id: i64, pub name: String, pub access_key: Option, pub scopes: Vec, pub expires_at: Option>, pub is_revoked: bool, pub created_at: chrono::DateTime, } #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct AccessKeyListResponse { pub access_keys: Vec, pub total: usize, } impl AppService { pub async fn user_create_access_key( &self, context: &Session, params: CreateAccessKeyParams, ) -> Result { let user_uid = context.user().ok_or(AppError::Unauthorized)?; let access_key = self.user_generate_access_key(); let access_key_hash = self.user_hash_access_key(&access_key); let access_key_model = user_token::ActiveModel { user: Set(user_uid), name: Set(params.name.clone()), token_hash: Set(access_key_hash), scopes: Set(serde_json::to_value(params.scopes.clone()).unwrap()), expires_at: Set(params.expires_at), is_revoked: Set(false), created_at: Set(Utc::now()), updated_at: Set(Utc::now()), ..Default::default() }; let created_access_key = access_key_model.insert(&self.db).await?; let _ = user_activity_log::ActiveModel { user_uid: Set(Some(user_uid)), action: Set("access_key_create".to_string()), ip_address: Set(context.ip_address()), user_agent: Set(context.user_agent()), details: Set(serde_json::json!({ "access_key_name": params.name.clone(), "access_key_id": created_access_key.id, "scopes": params.scopes.clone() })), created_at: Set(Utc::now()), ..Default::default() } .insert(&self.db) .await; let scopes: Vec = serde_json::from_value(created_access_key.scopes).unwrap_or_default(); Ok(AccessKeyResponse { id: created_access_key.id, name: created_access_key.name, access_key: Some(access_key), scopes, expires_at: created_access_key.expires_at, is_revoked: created_access_key.is_revoked, created_at: created_access_key.created_at, }) } pub async fn user_list_access_keys( &self, context: &Session, ) -> Result { let user_uid = context.user().ok_or(AppError::Unauthorized)?; let access_keys = user_token::Entity::find() .filter(user_token::Column::User.eq(user_uid)) .order_by_desc(user_token::Column::CreatedAt) .all(&self.db) .await?; let total = access_keys.len(); let access_keys = access_keys .into_iter() .map(|ak| { let scopes: Vec = serde_json::from_value(ak.scopes).unwrap_or_default(); AccessKeyResponse { id: ak.id, name: ak.name, access_key: None, scopes, expires_at: ak.expires_at, is_revoked: ak.is_revoked, created_at: ak.created_at, } }) .collect(); Ok(AccessKeyListResponse { access_keys, total }) } pub async fn user_revoke_access_key( &self, context: &Session, access_key_id: i64, ) -> Result<(), AppError> { let user_uid = context.user().ok_or(AppError::Unauthorized)?; let access_key = user_token::Entity::find_by_id(access_key_id) .filter(user_token::Column::User.eq(user_uid)) .one(&self.db) .await? .ok_or(AppError::NotFound("Access key not found".to_string()))?; let mut active_access_key: user_token::ActiveModel = access_key.clone().into(); active_access_key.is_revoked = Set(true); active_access_key.updated_at = Set(Utc::now()); active_access_key.update(&self.db).await?; let _ = user_activity_log::ActiveModel { user_uid: Set(Some(user_uid)), action: Set("access_key_revoke".to_string()), ip_address: Set(context.ip_address()), user_agent: Set(context.user_agent()), details: Set(serde_json::json!({ "access_key_name": access_key.name, "access_key_id": access_key_id })), created_at: Set(Utc::now()), ..Default::default() } .insert(&self.db) .await; Ok(()) } pub async fn user_delete_access_key( &self, context: &Session, access_key_id: i64, ) -> Result<(), AppError> { let user_uid = context.user().ok_or(AppError::Unauthorized)?; let access_key = user_token::Entity::find_by_id(access_key_id) .filter(user_token::Column::User.eq(user_uid)) .one(&self.db) .await? .ok_or(AppError::NotFound("Access key not found".to_string()))?; let _ = user_activity_log::ActiveModel { user_uid: Set(Some(user_uid)), action: Set("access_key_delete".to_string()), ip_address: Set(context.ip_address()), user_agent: Set(context.user_agent()), details: Set(serde_json::json!({ "access_key_name": access_key.name.clone(), "access_key_id": access_key_id })), created_at: Set(Utc::now()), ..Default::default() } .insert(&self.db) .await; user_token::Entity::delete(access_key.into_active_model()) .exec(&self.db) .await?; Ok(()) } pub async fn user_verify_access_key(&self, access_key: String) -> Result { let access_key_hash = self.user_hash_access_key(&access_key); let access_key_model = user_token::Entity::find() .filter(user_token::Column::TokenHash.eq(access_key_hash)) .filter(user_token::Column::IsRevoked.eq(false)) .one(&self.db) .await? .ok_or(AppError::Unauthorized)?; if let Some(expires_at) = access_key_model.expires_at { if expires_at < Utc::now() { return Err(AppError::Unauthorized); } } Ok(access_key_model.user) } fn user_generate_access_key(&self) -> String { use std::time::{SystemTime, UNIX_EPOCH}; let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let chars: Vec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" .chars() .collect(); let mut access_key = String::with_capacity(68); access_key.push_str("gda_"); let mut state = now as u64; for _ in 0..64 { state = state.wrapping_mul(1103515245).wrapping_add(12345); access_key.push(chars[(state as usize) % chars.len()]); } access_key } fn user_hash_access_key(&self, access_key: &str) -> String { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(access_key.as_bytes()); format!( "{}", base64::prelude::BASE64_STANDARD.encode(&hasher.finalize().to_vec()) ) } }