Files
mce/internal/controller/auth_api_login.go
Victor_Jay bbcd992614 feat: 封禁用户允许登录但禁止写操作
- Session 结构体新增 Status 字段,Create/Validate 同步状态
- AuthService.Login 移除封禁检查,允许封禁用户登录
- 新增 BannedWriteGuard 中间件,拦截 POST/PUT/DELETE 写操作
- 封禁用户自动签到跳过
- 所有需登录的 API 路由组应用 BannedWriteGuard
- AdminService.UpdateUserStatus 保留 DestroyByUID 确保状态刷新
2026-05-31 21:20:04 +08:00

114 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"net/http"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"github.com/gin-gonic/gin"
)
// clientIP 获取客户端真实 IP考虑反向代理
func clientIP(c *gin.Context) string {
if fwd := c.GetHeader("X-Forwarded-For"); fwd != "" {
return fwd
}
if real := c.GetHeader("X-Real-IP"); real != "" {
return real
}
return c.ClientIP()
}
// Login 登录 API含双维度限流
func (ac *AuthController) Login(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
email := req.Email
ip := clientIP(c)
// --- 限流:账户维度(原子检查+递增)---
acctResult := ac.rateLimiter.AllowAccount(email)
if acctResult.Blocked {
common.Error(c, http.StatusTooManyRequests, acctResult.Message)
return
}
// --- 限流IP 维度(原子检查+递增)---
ipResult := ac.rateLimiter.AllowIP(ip)
if ipResult.Blocked {
common.Error(c, http.StatusTooManyRequests, ipResult.Message)
return
}
user, err := ac.authService.Login(req, ip)
if err != nil {
// 失败计数已在 AllowAccount/AllowIP 中原子递增,无需额外记录
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrUserLocked:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
case common.ErrMaintenanceMode:
common.Error(c, http.StatusForbidden, "社区正在维护中,仅站长可登录")
case common.ErrNeedsConfirmRestore:
// 注销账号登录 → 需要二次确认恢复
c.JSON(http.StatusOK, gin.H{
"success": true,
"action": "confirm_restore",
"message": "你的账号正在注销中,登录将撤销注销并恢复账号",
})
default:
common.Error(c, http.StatusInternalServerError, "登录失败,请稍后重试")
}
return
}
// 登录成功 → 清除失败计数
ac.rateLimiter.Clear(email, ip)
// 创建服务端 session
sid, err := ac.sessionManager.Create(user, req.RememberMe, ip, c.GetHeader("User-Agent"))
if err != nil {
common.Error(c, http.StatusInternalServerError, "登录失败,请稍后重试")
return
}
common.SetSessionCookie(c, sid, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "登录成功")
}
// ConfirmRestore 二次确认恢复已注销账号
func (ac *AuthController) ConfirmRestore(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, http.StatusBadRequest, "请检查输入")
return
}
user, err := ac.authService.ConfirmRestore(req, clientIP(c))
if err != nil {
switch err {
case common.ErrInvalidCred:
common.Error(c, http.StatusUnauthorized, "邮箱或密码错误")
default:
common.Error(c, http.StatusInternalServerError, "操作失败,请稍后重试")
}
return
}
sid, err := ac.sessionManager.Create(user, req.RememberMe, clientIP(c), c.GetHeader("User-Agent"))
if err != nil {
common.Error(c, http.StatusInternalServerError, "操作失败,请稍后重试")
return
}
common.SetSessionCookie(c, sid, req.RememberMe, ac.cfg)
common.OkWithMessage(c, user, "注销已撤销,欢迎回来")
}