perf: improve compression reliability and deployment safety
This commit is contained in:
@@ -1,16 +1,11 @@
|
||||
use crate::auth;
|
||||
use crate::api::envelope::Envelope;
|
||||
use crate::auth;
|
||||
use crate::error::{AppError, ErrorCode};
|
||||
use crate::services::mail;
|
||||
use crate::state::AppState;
|
||||
|
||||
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::HeaderMap,
|
||||
routing::post,
|
||||
Json, Router,
|
||||
};
|
||||
use axum::{extract::State, http::HeaderMap, routing::post, Json, Router};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use rand::RngCore;
|
||||
@@ -106,8 +101,12 @@ async fn register(
|
||||
.await
|
||||
.map_err(map_unique_violation)?;
|
||||
|
||||
let (token, _expires_at) =
|
||||
auth::issue_jwt(&state.config.jwt_secret, state.config.jwt_expiry_hours, user.id, &user.role)?;
|
||||
let (token, _expires_at) = auth::issue_jwt(
|
||||
&state.config.jwt_secret,
|
||||
state.config.jwt_expiry_hours,
|
||||
user.id,
|
||||
&user.role,
|
||||
)?;
|
||||
|
||||
let verification_token = generate_token();
|
||||
let token_hash = sha256_hex(&verification_token);
|
||||
@@ -133,7 +132,9 @@ async fn register(
|
||||
|
||||
mail::send_verification_email(&state, &user.email, &user.username, &verification_url)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::MailSendFailed, "验证邮件发送失败").with_source(err))?;
|
||||
.map_err(|err| {
|
||||
AppError::new(ErrorCode::MailSendFailed, "验证邮件发送失败").with_source(err)
|
||||
})?;
|
||||
|
||||
let body = RegisterResponse {
|
||||
user: UserView {
|
||||
@@ -159,7 +160,10 @@ async fn login(
|
||||
) -> Result<Json<Envelope<LoginResponse>>, AppError> {
|
||||
let identity = req.email.trim();
|
||||
if identity.is_empty() {
|
||||
return Err(AppError::new(ErrorCode::InvalidRequest, "邮箱或用户名不能为空"));
|
||||
return Err(AppError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"邮箱或用户名不能为空",
|
||||
));
|
||||
}
|
||||
|
||||
let user = if identity.contains('@') {
|
||||
@@ -210,8 +214,12 @@ async fn login(
|
||||
|
||||
verify_password(&req.password, &user.password_hash)?;
|
||||
|
||||
let (token, expires_at) =
|
||||
auth::issue_jwt(&state.config.jwt_secret, state.config.jwt_expiry_hours, user.id, &user.role)?;
|
||||
let (token, expires_at) = auth::issue_jwt(
|
||||
&state.config.jwt_secret,
|
||||
state.config.jwt_expiry_hours,
|
||||
user.id,
|
||||
&user.role,
|
||||
)?;
|
||||
|
||||
Ok(Json(Envelope {
|
||||
success: true,
|
||||
@@ -241,7 +249,11 @@ async fn send_verification(
|
||||
let claims = auth::require_jwt(&state.config.jwt_secret, &headers)?;
|
||||
|
||||
// Rate limit: 1 per minute per user
|
||||
let key = format!("rate:send_verification:{}:{}", claims.sub, Utc::now().format("%Y%m%d%H%M"));
|
||||
let key = format!(
|
||||
"rate:send_verification:{}:{}",
|
||||
claims.sub,
|
||||
Utc::now().format("%Y%m%d%H%M")
|
||||
);
|
||||
let mut redis = state.redis.clone();
|
||||
let count: i64 = redis::cmd("INCR")
|
||||
.arg(&key)
|
||||
@@ -257,7 +269,10 @@ async fn send_verification(
|
||||
.unwrap_or(());
|
||||
}
|
||||
if count > 1 {
|
||||
return Err(AppError::new(ErrorCode::RateLimited, "发送过于频繁,请稍后再试"));
|
||||
return Err(AppError::new(
|
||||
ErrorCode::RateLimited,
|
||||
"发送过于频繁,请稍后再试",
|
||||
));
|
||||
}
|
||||
|
||||
let user = sqlx::query_as::<_, UserRow>(
|
||||
@@ -313,7 +328,9 @@ async fn send_verification(
|
||||
|
||||
mail::send_verification_email(&state, &user.email, &user.username, &verification_url)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::MailSendFailed, "验证邮件发送失败").with_source(err))?;
|
||||
.map_err(|err| {
|
||||
AppError::new(ErrorCode::MailSendFailed, "验证邮件发送失败").with_source(err)
|
||||
})?;
|
||||
|
||||
Ok(Json(Envelope {
|
||||
success: true,
|
||||
@@ -434,7 +451,8 @@ async fn forgot_password(
|
||||
state.config.public_base_url, reset_token
|
||||
);
|
||||
|
||||
let _ = mail::send_password_reset_email(&state, &user.email, &user.username, &reset_url).await;
|
||||
let _ =
|
||||
mail::send_password_reset_email(&state, &user.email, &user.username, &reset_url).await;
|
||||
}
|
||||
|
||||
Ok(Json(Envelope {
|
||||
@@ -497,12 +515,14 @@ async fn reset_password(
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "更新密码失败").with_source(err))?;
|
||||
|
||||
sqlx::query("UPDATE password_resets SET used_at = $2 WHERE token_hash = $1 AND used_at IS NULL")
|
||||
.bind(token_hash)
|
||||
.bind(now)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "更新重置记录失败").with_source(err))?;
|
||||
sqlx::query(
|
||||
"UPDATE password_resets SET used_at = $2 WHERE token_hash = $1 AND used_at IS NULL",
|
||||
)
|
||||
.bind(token_hash)
|
||||
.bind(now)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|err| AppError::new(ErrorCode::Internal, "更新重置记录失败").with_source(err))?;
|
||||
|
||||
tx.commit()
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user