242 lines
8.0 KiB
Rust
242 lines
8.0 KiB
Rust
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<String>,
|
|
pub expires_at: Option<chrono::DateTime<Utc>>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
|
|
pub struct AccessKeyResponse {
|
|
pub id: i64,
|
|
pub name: String,
|
|
pub access_key: Option<String>,
|
|
pub scopes: Vec<String>,
|
|
pub expires_at: Option<chrono::DateTime<Utc>>,
|
|
pub is_revoked: bool,
|
|
pub created_at: chrono::DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
|
|
pub struct AccessKeyListResponse {
|
|
pub access_keys: Vec<AccessKeyResponse>,
|
|
pub total: usize,
|
|
}
|
|
|
|
impl AppService {
|
|
pub async fn user_create_access_key(
|
|
&self,
|
|
context: &Session,
|
|
params: CreateAccessKeyParams,
|
|
) -> Result<AccessKeyResponse, AppError> {
|
|
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<String> =
|
|
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<AccessKeyListResponse, AppError> {
|
|
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<String> = 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<Uuid, AppError> {
|
|
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<char> = "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())
|
|
)
|
|
}
|
|
}
|