Spaces:
Paused
Paused
| use std::sync::Arc; | |
| use axum::extract::State; | |
| use axum::http::HeaderMap; | |
| use axum::response::IntoResponse; | |
| use axum::routing::post; | |
| use axum::{Json, Router}; | |
| use serde::Deserialize; | |
| use crate::auth; | |
| use crate::config; | |
| use crate::database; | |
| use crate::state::{self, AppState}; | |
| pub struct LoginRequest { | |
| password: String, | |
| } | |
| fn is_https(headers: &HeaderMap) -> bool { | |
| headers | |
| .get("x-forwarded-proto") | |
| .and_then(|v| v.to_str().ok()) | |
| .map_or(false, |v| v == "https") | |
| } | |
| async fn login( | |
| State(state): State<Arc<AppState>>, | |
| headers: HeaderMap, | |
| Json(payload): Json<LoginRequest>, | |
| ) -> impl IntoResponse { | |
| let active_password = config::get_active_password(&state.settings, &state.db_pool); | |
| let input = payload.password.trim().to_string(); | |
| match active_password { | |
| Some(ref pwd) if auth::verify_password_auto(&input, pwd.trim()) => { | |
| tracing::info!("登录成功"); | |
| // Generate a fresh random session token, persist it alongside existing | |
| // settings so the middleware's server-side token check succeeds, then | |
| // set the cookie. This replaces the old sha256(password) cookie. | |
| let session_token = auth::generate_session_token(); | |
| let mut merged = database::get_app_settings_from_db(&state.db_pool) | |
| .unwrap_or_default(); | |
| merged.insert( | |
| "SESSION_TOKEN".to_string(), | |
| Some(session_token.clone()), | |
| ); | |
| if let Err(e) = database::save_app_settings_to_db(&state.db_pool, &merged) { | |
| tracing::error!("保存会话令牌失败: {}", e); | |
| return ( | |
| axum::http::StatusCode::INTERNAL_SERVER_ERROR, | |
| Json(serde_json::json!({ | |
| "status": "error", | |
| "message": "服务器错误" | |
| })), | |
| ) | |
| .into_response(); | |
| } | |
| // Refresh in-memory app_settings snapshot (do NOT restart the bot). | |
| if let Err(e) = state::apply_runtime_settings(state.clone(), false).await { | |
| tracing::warn!("刷新运行时配置失败 (可忽略): {}", e); | |
| } | |
| let cookie = auth::build_cookie(&session_token, is_https(&headers)); | |
| ( | |
| [(axum::http::header::SET_COOKIE, cookie)], | |
| Json(serde_json::json!({ | |
| "status": "ok", | |
| "message": "登录成功" | |
| })), | |
| ) | |
| .into_response() | |
| } | |
| _ => { | |
| tracing::warn!("登录失败:密码错误"); | |
| ( | |
| axum::http::StatusCode::UNAUTHORIZED, | |
| Json(serde_json::json!({ | |
| "status": "error", | |
| "message": "密码错误" | |
| })), | |
| ) | |
| .into_response() | |
| } | |
| } | |
| } | |
| async fn logout() -> impl IntoResponse { | |
| ( | |
| [(axum::http::header::SET_COOKIE, auth::build_clear_cookie())], | |
| Json(serde_json::json!({ | |
| "status": "ok", | |
| "message": "已退出登录" | |
| })), | |
| ) | |
| } | |
| pub fn router() -> Router<Arc<AppState>> { | |
| Router::new() | |
| .route("/api/auth/login", post(login)) | |
| .route("/api/auth/logout", post(logout)) | |
| } | |