feat: 添加邮件功能第三阶段 - 密码重置
实现通过邮件自助重置密码功能: - 新增发送密码重置邮件API (/api/forgot-password) - 新增密码重置页面路由 (/reset-password/<token>) - 新增确认密码重置API (/api/reset-password-confirm) 新增文件: - templates/email/reset_password.html - 密码重置邮件模板 - templates/reset_password.html - 密码重置页面 修改文件: - email_service.py - 添加密码重置相关函数 - send_password_reset_email() - verify_password_reset_token() - confirm_password_reset() - app.py - 添加密码重置相关API - templates/login.html - 忘记密码支持两种方式: - 启用邮件功能:通过邮件自助重置 - 未启用邮件:提交申请等待管理员审核 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
95
app.py
95
app.py
@@ -846,6 +846,101 @@ def get_email_verify_status():
|
||||
})
|
||||
|
||||
|
||||
# ==================== 密码重置(邮件方式)API ====================
|
||||
|
||||
@app.route('/api/forgot-password', methods=['POST'])
|
||||
@require_ip_not_locked
|
||||
def forgot_password():
|
||||
"""发送密码重置邮件"""
|
||||
data = request.json
|
||||
email = data.get('email', '').strip()
|
||||
captcha_session = data.get('captcha_session', '')
|
||||
captcha_code = data.get('captcha', '').strip()
|
||||
|
||||
if not email:
|
||||
return jsonify({"error": "请输入邮箱"}), 400
|
||||
|
||||
# 获取客户端IP
|
||||
client_ip = get_client_ip()
|
||||
|
||||
# 检查IP限流
|
||||
allowed, error_msg = check_ip_rate_limit(client_ip)
|
||||
if not allowed:
|
||||
return jsonify({"error": error_msg}), 429
|
||||
|
||||
# 验证验证码
|
||||
success, message = verify_and_consume_captcha(captcha_session, captcha_code, captcha_storage, MAX_CAPTCHA_ATTEMPTS)
|
||||
if not success:
|
||||
is_locked = record_failed_captcha(client_ip)
|
||||
if is_locked:
|
||||
return jsonify({"error": "验证码错误次数过多,IP已被锁定1小时"}), 429
|
||||
return jsonify({"error": message}), 400
|
||||
|
||||
# 检查邮件功能是否启用
|
||||
email_settings = email_service.get_email_settings()
|
||||
if not email_settings.get('enabled', False):
|
||||
return jsonify({"error": "邮件功能未启用,请联系管理员"}), 400
|
||||
|
||||
# 查找用户(防止用户枚举,统一返回成功消息)
|
||||
user = database.get_user_by_email(email)
|
||||
if user and user.get('status') == 'approved':
|
||||
# 发送重置邮件
|
||||
result = email_service.send_password_reset_email(
|
||||
email=email,
|
||||
username=user['username'],
|
||||
user_id=user['id']
|
||||
)
|
||||
if not result['success']:
|
||||
logger.error(f"密码重置邮件发送失败: {result['error']}")
|
||||
# 即使失败也返回统一消息,防止信息泄露
|
||||
|
||||
# 统一返回成功消息
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "如果该邮箱已注册,您将收到密码重置邮件"
|
||||
})
|
||||
|
||||
|
||||
@app.route('/reset-password/<token>')
|
||||
def reset_password_page(token):
|
||||
"""密码重置页面"""
|
||||
result = email_service.verify_password_reset_token(token)
|
||||
if result:
|
||||
return render_template('reset_password.html', token=token, valid=True, error_message='')
|
||||
else:
|
||||
return render_template('reset_password.html', token=token, valid=False,
|
||||
error_message='重置链接无效或已过期,请重新申请密码重置')
|
||||
|
||||
|
||||
@app.route('/api/reset-password-confirm', methods=['POST'])
|
||||
def reset_password_confirm():
|
||||
"""确认密码重置"""
|
||||
data = request.json
|
||||
token = data.get('token', '').strip()
|
||||
new_password = data.get('new_password', '').strip()
|
||||
|
||||
if not token or not new_password:
|
||||
return jsonify({"error": "参数不完整"}), 400
|
||||
|
||||
# 验证密码强度
|
||||
is_valid, error_msg = validate_password(new_password)
|
||||
if not is_valid:
|
||||
return jsonify({"error": error_msg}), 400
|
||||
|
||||
# 验证并消费token
|
||||
result = email_service.confirm_password_reset(token)
|
||||
if not result:
|
||||
return jsonify({"error": "重置链接无效或已过期"}), 400
|
||||
|
||||
# 更新用户密码
|
||||
user_id = result['user_id']
|
||||
if database.admin_reset_user_password(user_id, new_password):
|
||||
logger.info(f"用户密码重置成功: user_id={user_id}")
|
||||
return jsonify({"success": True, "message": "密码重置成功"})
|
||||
else:
|
||||
return jsonify({"error": "密码重置失败"}), 500
|
||||
|
||||
|
||||
# ==================== 验证码API ====================
|
||||
import random
|
||||
from task_checkpoint import get_checkpoint_manager, TaskStage
|
||||
|
||||
Reference in New Issue
Block a user