feat(service): expand service layer with new domain operations
- Add IM service modules: audit, channel roles, custom emojis, forum tags, integrations, invitations, repo links, slash commands, stages, voice, webhooks - Add PR service modules: review requests, templates - Add repo service modules: contributors, release assets, git extras (archive, branch rename, commit extras, diff/merge, tag, tree) - Add user service: social (follow/block) - Add internal auth service - Update existing service modules with expanded functionality - Remove deleted IM modules: articles, delivery trace, drafts, follows, messages, polls, presence, reactions, threads
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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<String>,
|
||||
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<String>,
|
||||
ttl_secs: Option<u64>,
|
||||
) -> 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<Option<ServiceIdentity>> {
|
||||
let key = format!("{API_KEY_PREFIX}{api_key}");
|
||||
let mut conn = self.redis.get_connection();
|
||||
|
||||
let json: Option<String> = 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user