420dedbc1e
- 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
98 lines
2.6 KiB
Rust
98 lines
2.6 KiB
Rust
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(())
|
|
}
|
|
}
|