119 lines
3.5 KiB
Rust
Raw Normal View History

//! 认证路由 — 登录/登出/当前用户信息
//!
//! - POST /api/auth/login — 手机号+密码登录,返回 JWT token
//! - POST /api/auth/logout — 登出(客户端清除 token 即可,服务端无状态)
//! - GET /api/auth/me — 获取当前登录用户信息及权限码
use axum::extract::State;
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};
use crate::commands::AppState;
/// 登录请求体
#[derive(Debug, Deserialize)]
pub(crate) struct LoginInput {
phone: String,
password: String,
}
/// POST /api/auth/login — 用户登录
pub async fn login(
State(state): State<AppState>,
Json(input): Json<LoginInput>,
) -> Result<Json<Value>, AppError> {
let pool = &state.mysql;
if input.phone.is_empty() || input.password.is_empty() {
return Err(AppError::BadRequest("手机号和密码不能为空".into()));
}
// 查询用户(含密码比对)
let user_row: Option<(i64, Option<String>, String, String, i32, i32)> = sqlx::query_as(
"SELECT id, name, phone, password, role, status FROM users WHERE phone = ?"
)
.bind(&input.phone)
.fetch_optional(pool)
.await?;
let (id, name, phone, stored_password, role, status) =
user_row.ok_or_else(|| AppError::Unauthorized("手机号或密码错误".into()))?;
// 检查账户状态
if status == 0 {
return Err(AppError::Forbidden("账户已被禁用".into()));
}
// 密码校验(argon2 哈希比对)
let valid = auth::verify_password(&input.password, &stored_password)
.map_err(|_| AppError::Internal("密码验证失败".into()))?;
if !valid {
return Err(AppError::Unauthorized("手机号或密码错误".into()));
}
// 生成 JWT token
let token = auth::generate_token(id, &phone)?;
// 查询用户权限码
let permissions = auth::load_user_permissions(pool, id).await?;
let perm_list: Vec<String> = permissions.into_iter().collect();
Ok(Json(json!({
"code": 0,
"data": {
"token": token,
"user": {
"id": id,
"name": name,
"phone": phone,
"role": role,
"permissions": perm_list,
}
},
"message": "登录成功"
})))
}
/// POST /api/auth/logout — 用户登出
pub async fn logout() -> Json<Value> {
// JWT 无状态,客户端清除 token 即可
Json(json!({ "code": 0, "data": null, "message": "登出成功" }))
}
/// GET /api/auth/me — 获取当前用户信息及权限
#[allow(clippy::type_complexity)]
pub async fn me(
user: CurrentUser,
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
let pool = &state.mysql;
// 查询用户详细信息
let row: Option<(i64, Option<String>, String, i32, Option<i64>)> = sqlx::query_as(
"SELECT id, name, phone, role, organization_id FROM users WHERE id = ?"
)
.bind(user.user_id)
.fetch_optional(pool)
.await?;
let (id, name, phone, role, org_id) =
row.ok_or_else(|| AppError::NotFound("用户不存在".into()))?;
let perm_list: Vec<String> = user.permissions.into_iter().collect();
Ok(Json(json!({
"code": 0,
"data": {
"id": id,
"name": name,
"phone": phone,
"role": role,
"organization_id": org_id,
"permissions": perm_list,
},
"message": "ok"
})))
}