fix: sync rust api-server runtime and bindings
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{error::Error, fmt};
|
||||
use std::{error::Error, fmt, sync::Arc};
|
||||
|
||||
#[cfg(test)]
|
||||
use std::{
|
||||
@@ -15,17 +15,22 @@ use module_runtime::RuntimeSnapshotRecord;
|
||||
#[cfg(test)]
|
||||
use module_runtime::{SAVE_SNAPSHOT_VERSION, format_utc_micros};
|
||||
use platform_auth::{
|
||||
JwtConfig, JwtError, RefreshCookieConfig, RefreshCookieError, RefreshCookieSameSite,
|
||||
AccessTokenClaims, AccessTokenClaimsInput, AuthProvider, BindingStatus, JwtConfig, JwtError,
|
||||
RefreshCookieConfig, RefreshCookieError, RefreshCookieSameSite,
|
||||
sign_access_token, verify_access_token,
|
||||
SmsAuthConfig, SmsAuthProvider, SmsAuthProviderKind, SmsProviderError,
|
||||
};
|
||||
use platform_llm::{LlmClient, LlmConfig, LlmError};
|
||||
use platform_oss::{OssClient, OssConfig, OssError};
|
||||
use serde_json::Value;
|
||||
use spacetime_client::{SpacetimeClient, SpacetimeClientConfig, SpacetimeClientError};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::wechat_provider::{WechatProvider, build_wechat_provider};
|
||||
|
||||
const ADMIN_ROLE: &str = "admin";
|
||||
|
||||
// 当前阶段先保留最小共享状态壳,后续逐步接入配置、客户端与平台适配。
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AppState {
|
||||
@@ -33,6 +38,7 @@ pub struct AppState {
|
||||
#[allow(dead_code)]
|
||||
pub config: AppConfig,
|
||||
auth_jwt_config: JwtConfig,
|
||||
admin_runtime: Option<AdminRuntime>,
|
||||
refresh_cookie_config: RefreshCookieConfig,
|
||||
oss_client: Option<OssClient>,
|
||||
password_entry_service: PasswordEntryService,
|
||||
@@ -51,6 +57,34 @@ pub struct AppState {
|
||||
test_runtime_snapshot_store: Arc<Mutex<HashMap<String, RuntimeSnapshotRecord>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AdminRuntime {
|
||||
username: Arc<str>,
|
||||
password: Arc<str>,
|
||||
subject: Arc<str>,
|
||||
display_name: Arc<str>,
|
||||
token_ttl_seconds: u64,
|
||||
jwt_config: JwtConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AdminClaims {
|
||||
pub subject: String,
|
||||
pub username: String,
|
||||
pub issued_at: OffsetDateTime,
|
||||
pub expires_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AdminSession {
|
||||
pub subject: String,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub roles: Vec<String>,
|
||||
pub issued_at: OffsetDateTime,
|
||||
pub expires_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppStateInitError {
|
||||
Jwt(JwtError),
|
||||
@@ -67,6 +101,7 @@ impl AppState {
|
||||
config.jwt_secret.clone(),
|
||||
config.jwt_access_token_ttl_seconds,
|
||||
)?;
|
||||
let admin_runtime = build_admin_runtime(&config, &auth_jwt_config)?;
|
||||
let refresh_cookie_same_site =
|
||||
RefreshCookieSameSite::parse(&config.refresh_cookie_same_site).ok_or(
|
||||
RefreshCookieError::InvalidConfig("refresh cookie SameSite 取值非法"),
|
||||
@@ -123,6 +158,7 @@ impl AppState {
|
||||
Ok(Self {
|
||||
config,
|
||||
auth_jwt_config,
|
||||
admin_runtime,
|
||||
refresh_cookie_config,
|
||||
oss_client,
|
||||
password_entry_service,
|
||||
@@ -144,6 +180,10 @@ impl AppState {
|
||||
&self.auth_jwt_config
|
||||
}
|
||||
|
||||
pub fn admin_runtime(&self) -> Option<&AdminRuntime> {
|
||||
self.admin_runtime.as_ref()
|
||||
}
|
||||
|
||||
pub fn refresh_cookie_config(&self) -> &RefreshCookieConfig {
|
||||
&self.refresh_cookie_config
|
||||
}
|
||||
@@ -394,6 +434,90 @@ impl From<LlmError> for AppStateInitError {
|
||||
}
|
||||
}
|
||||
|
||||
impl AdminRuntime {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
!self.username.trim().is_empty() && !self.password.trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
||||
pub fn password(&self) -> &str {
|
||||
&self.password
|
||||
}
|
||||
|
||||
pub fn build_claims(&self, now: OffsetDateTime) -> Result<AdminClaims, String> {
|
||||
let expires_at = now
|
||||
.checked_add(time::Duration::seconds(
|
||||
i64::try_from(self.token_ttl_seconds)
|
||||
.map_err(|_| "后台 token TTL 超出 i64 上限".to_string())?,
|
||||
))
|
||||
.ok_or_else(|| "后台 token 过期时间计算溢出".to_string())?;
|
||||
Ok(AdminClaims {
|
||||
subject: self.subject.to_string(),
|
||||
username: self.username.to_string(),
|
||||
issued_at: now,
|
||||
expires_at,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sign_token(&self, claims: &AdminClaims) -> Result<String, String> {
|
||||
let jwt_claims = AccessTokenClaims::from_input(
|
||||
AccessTokenClaimsInput {
|
||||
user_id: claims.subject.clone(),
|
||||
session_id: format!("admin-session-{}", claims.username),
|
||||
provider: AuthProvider::Password,
|
||||
roles: vec![ADMIN_ROLE.to_string()],
|
||||
token_version: 1,
|
||||
phone_verified: false,
|
||||
binding_status: BindingStatus::Active,
|
||||
display_name: Some(self.display_name.to_string()),
|
||||
},
|
||||
&self.jwt_config,
|
||||
claims.issued_at,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
sign_access_token(&jwt_claims, &self.jwt_config).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn verify_token(&self, token: &str) -> Result<AccessTokenClaims, String> {
|
||||
verify_access_token(token, &self.jwt_config).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn validate_claims(&self, claims: &AccessTokenClaims) -> Result<AdminSession, String> {
|
||||
if claims.user_id() != self.subject.as_ref() {
|
||||
return Err("后台管理员主体不匹配".to_string());
|
||||
}
|
||||
if !claims.roles.iter().any(|role| role == ADMIN_ROLE) {
|
||||
return Err("当前令牌不是管理员令牌".to_string());
|
||||
}
|
||||
let issued_at = OffsetDateTime::from_unix_timestamp(claims.iat as i64)
|
||||
.map_err(|_| "后台令牌签发时间无效".to_string())?;
|
||||
let expires_at = OffsetDateTime::from_unix_timestamp(claims.exp as i64)
|
||||
.map_err(|_| "后台令牌过期时间无效".to_string())?;
|
||||
Ok(AdminSession {
|
||||
subject: claims.user_id().to_string(),
|
||||
username: self.username.to_string(),
|
||||
display_name: self.display_name.to_string(),
|
||||
roles: claims.roles.clone(),
|
||||
issued_at,
|
||||
expires_at,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_session(&self, claims: &AdminClaims) -> AdminSession {
|
||||
AdminSession {
|
||||
subject: claims.subject.clone(),
|
||||
username: claims.username.clone(),
|
||||
display_name: self.display_name.to_string(),
|
||||
roles: vec![ADMIN_ROLE.to_string()],
|
||||
issued_at: claims.issued_at,
|
||||
expires_at: claims.expires_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_oss_client(config: &AppConfig) -> Result<Option<OssClient>, AppStateInitError> {
|
||||
let has_any_oss_field = config.oss_bucket.is_some()
|
||||
|| config.oss_endpoint.is_some()
|
||||
@@ -441,6 +565,42 @@ fn build_llm_client(config: &AppConfig) -> Result<Option<LlmClient>, AppStateIni
|
||||
Ok(Some(LlmClient::new(llm_config)?))
|
||||
}
|
||||
|
||||
fn build_admin_runtime(
|
||||
config: &AppConfig,
|
||||
base_jwt_config: &JwtConfig,
|
||||
) -> Result<Option<AdminRuntime>, AppStateInitError> {
|
||||
let Some(username) = config
|
||||
.admin_username
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(password) = config
|
||||
.admin_password
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let jwt_config = JwtConfig::new(
|
||||
base_jwt_config.issuer().to_string(),
|
||||
config.jwt_secret.clone(),
|
||||
config.admin_token_ttl_seconds,
|
||||
)?;
|
||||
Ok(Some(AdminRuntime {
|
||||
username: Arc::<str>::from(username),
|
||||
password: Arc::<str>::from(password),
|
||||
subject: Arc::<str>::from(format!("admin:{username}")),
|
||||
display_name: Arc::<str>::from(format!("管理员 {username}")),
|
||||
token_ttl_seconds: config.admin_token_ttl_seconds,
|
||||
jwt_config,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use module_ai::{AiTaskKind, generate_ai_task_id};
|
||||
|
||||
Reference in New Issue
Block a user