forked from boos/BoosAPI
fix: persist runtime proxy token
This commit is contained in:
@@ -8,6 +8,7 @@ pub mod oauth;
|
||||
pub mod register_captcha;
|
||||
pub mod registration_limiter;
|
||||
pub mod route_rebuild;
|
||||
pub mod runtime_auth;
|
||||
pub mod runtime_logs;
|
||||
pub mod shared;
|
||||
pub mod workspace;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use sqlx::SqlitePool;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub const PROXY_TOKEN_SETTING_KEY: &str = "runtime.auth.proxy_token";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RuntimeAuthSettings {
|
||||
default_proxy_token: String,
|
||||
proxy_token: RwLock<String>,
|
||||
}
|
||||
|
||||
impl RuntimeAuthSettings {
|
||||
#[cfg(test)]
|
||||
pub fn from_token_for_test(proxy_token: impl Into<String>) -> Self {
|
||||
let proxy_token = proxy_token.into();
|
||||
Self {
|
||||
default_proxy_token: proxy_token.clone(),
|
||||
proxy_token: RwLock::new(proxy_token),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load(pool: &SqlitePool, default_proxy_token: String) -> anyhow::Result<Self> {
|
||||
let stored: Option<String> =
|
||||
sqlx::query_scalar("SELECT value FROM settings WHERE key = ?")
|
||||
.bind(PROXY_TOKEN_SETTING_KEY)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
let proxy_token = stored
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(default_proxy_token.trim())
|
||||
.to_string();
|
||||
Ok(Self {
|
||||
default_proxy_token,
|
||||
proxy_token: RwLock::new(proxy_token),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn proxy_token(&self) -> String {
|
||||
self.proxy_token.read().clone()
|
||||
}
|
||||
|
||||
pub async fn set_proxy_token(
|
||||
&self,
|
||||
pool: &SqlitePool,
|
||||
proxy_token: String,
|
||||
) -> anyhow::Result<String> {
|
||||
let normalized = normalize_proxy_token(&proxy_token)?;
|
||||
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
|
||||
.bind(PROXY_TOKEN_SETTING_KEY)
|
||||
.bind(&normalized)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
*self.proxy_token.write() = normalized.clone();
|
||||
Ok(normalized)
|
||||
}
|
||||
|
||||
pub fn proxy_token_masked(&self) -> String {
|
||||
mask_token(&self.proxy_token())
|
||||
}
|
||||
|
||||
pub fn using_default_proxy_token(&self) -> bool {
|
||||
self.proxy_token() == self.default_proxy_token
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_proxy_token(value: &str) -> anyhow::Result<String> {
|
||||
let token = value.trim();
|
||||
if !token.starts_with("sk-") {
|
||||
anyhow::bail!("下游访问令牌必须以 sk- 开头");
|
||||
}
|
||||
if token.chars().count() < 6 {
|
||||
anyhow::bail!("下游访问令牌至少 6 位(含 sk-)");
|
||||
}
|
||||
Ok(token.to_string())
|
||||
}
|
||||
|
||||
pub fn mask_token(value: &str) -> String {
|
||||
let token = value.trim();
|
||||
if token.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let suffix: String = token
|
||||
.chars()
|
||||
.rev()
|
||||
.take(4)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect();
|
||||
format!("••••{}", suffix)
|
||||
}
|
||||
Reference in New Issue
Block a user