use super::{is_valid_host_name, is_valid_node_id, AppState, AuthenticatedNode}; use axum::{ extract::{Extension, State}, http::StatusCode, response::IntoResponse, Json, }; use common::models::{NodeHeartbeatRequest, NodeRegisterRequest}; use serde_json::json; use tracing::{info, warn}; pub async fn register_node( State(state): State, auth_node: Option>, Json(req): Json, ) -> Result { // 入口白名单校验 if !is_valid_node_id(&req.node_id) { return Err(crate::api::AppError::BadRequest( "非法的节点 ID(仅允许字母、数字、点、下划线、连字符,长度 1-128)".to_string(), )); } if !is_valid_host_name(&req.host_name) { return Err(crate::api::AppError::BadRequest( "非法的主机名(仅允许可打印 ASCII,长度 1-128)".to_string(), )); } // 已认证已拿到 Token 的节点刷新元数据配置 if let Some(Extension(ref auth)) = auth_node { if auth.node_id == req.node_id { let _ = state.db.register_node(&req).await; info!("已授权节点 {} 刷新配置成功", req.node_id); return Ok(( StatusCode::OK, Json(json!({ "status": "approved", "message": "节点配置更新成功", "node_token": null, })), )); } } // 申请注册新节点(免凭据提交申请,进入 pending_approval 状态) match state.db.register_node(&req).await { Ok(true) => { info!( "接收到新节点 {} 的注册申请,已加入待审批 (pending_approval) 队列", req.node_id ); Ok(( StatusCode::OK, Json(json!({ "status": "pending_approval", "message": "节点注册申请已成功提交!请在管理 Dashboard 控制台上点击【同意接入】授权该节点", "node_token": null, })), )) } Ok(false) => { // 节点已处于待审批或已存在列表 Ok(( StatusCode::OK, Json(json!({ "status": "pending_approval", "message": "节点注册申请等待管理员审批中", "node_token": null, })), )) } Err(e) => Err(e.into()), } } #[derive(serde::Deserialize)] pub struct CheckNodeStatusRequest { pub node_id: String, } /// POST /api/node/check_status — Node 端轮询检查审批结果。 pub async fn check_node_status( State(state): State, Json(req): Json, ) -> Result { if !is_valid_node_id(&req.node_id) { return Err(crate::api::AppError::BadRequest( "非法的节点 ID 参数".to_string(), )); } // 尝试拉取取走即焚的暂存明文 Token match state.db.take_pending_node_token(&req.node_id).await { Ok(Some(raw_token)) => { info!( "节点 {} 的注册申请已被管理员审批同意,下发专属 Token", req.node_id ); Ok(( StatusCode::OK, Json(json!({ "status": "approved", "message": "节点已通过审批授权", "node_token": raw_token, })), )) } Ok(None) | Err(_) => { // 查节点表状态 match state.db.get_node_exists(&req.node_id).await { Ok(true) => Ok(( StatusCode::OK, Json(json!({ "status": "pending_approval", "message": "等待管理员在控制台点击同意", "node_token": null, })), )), _ => Ok(( StatusCode::OK, Json(json!({ "status": "rejected", "message": "节点注册申请未通过或已被移除", "node_token": null, })), )), } } } } pub async fn heartbeat_node( State(state): State, Extension(auth_node): Extension, Json(req): Json, ) -> Result { // 身份绑定校验:请求体声称的 node_id 必须与 token 绑定的 node_id 一致, // 杜绝「持有 A 节点 token 却冒充 B 节点发心跳」的跨节点越权。 if req.node_id != auth_node.node_id { warn!( "节点心跳身份校验失败:token 绑定 node={},但请求体声称 node_id={}", auth_node.node_id, req.node_id ); return Err(crate::api::AppError::Forbidden( "node_id 与凭据不匹配".to_string(), )); } match state.db.heartbeat_node(&req).await { Ok(_) => Ok(Json(json!({"status": "ok"}))), Err(e) => Err(e.into()), } }