Smail/backend/controllers/reauth.go
smail 659d98b589
Some checks are pending
Security Checks / security (push) Waiting to run
增加配置页就地身份确认
2026-09-07 17:13:06 +08:00

74 lines
2.6 KiB
Go

package controllers
import (
"net/http"
"strconv"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/services/auth"
"git.pcux.cn/xuans/Smail/session"
appcontext "git.pcux.cn/xuans/Smail/utils/context"
log "github.com/sirupsen/logrus"
)
const (
reauthUserFailureLimit = 10
reauthIPFailureLimit = 50
)
var (
reauthUserAttempts = newLoginLimiter(reauthUserFailureLimit)
reauthIPAttempts = newLoginLimiter(reauthIPFailureLimit)
)
type reauthRequest struct {
Password string `json:"password"`
}
// ReauthenticateCurrentUser 在不离开当前页面的情况下重新确认当前会话用户密码。
func ReauthenticateCurrentUser(ctx *appcontext.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 || session.Instance == nil {
writeReauthError(w, http.StatusUnauthorized, "REAUTH_SESSION_REQUIRED", "登录状态已失效,请重新登录")
return
}
var input reauthRequest
if err := decodeJSONRequest(w, req, &input); err != nil || input.Password == "" {
writeReauthError(w, http.StatusBadRequest, "REAUTH_REQUEST_INVALID", "请输入当前密码")
return
}
defer func() { input.Password = "" }()
userKey := reauthUserAttempts.accountKey(strconv.Itoa(ctx.UserID))
ipKey := reauthIPAttempts.ipKey(req)
if reauthUserAttempts.blocked(userKey) || reauthIPAttempts.blocked(ipKey) {
log.WithContext(ctx).Info("reauthentication rejected by rate limit")
writeReauthError(w, http.StatusTooManyRequests, "REAUTH_RATE_LIMITED", "尝试次数过多,请稍后再试")
return
}
user, err := auth.AuthenticateUserByID(ctx.UserID, input.Password)
if err != nil {
reauthUserAttempts.recordFailure(userKey)
reauthIPAttempts.recordFailure(ipKey)
log.WithContext(ctx).Info("reauthentication failed")
writeReauthError(w, http.StatusUnauthorized, "REAUTH_FAILED", "当前密码不正确")
return
}
if err := session.RefreshRecentAuthentication(req.Context(), user); err != nil {
log.WithContext(ctx).WithError(err).Warn("refresh recent authentication failed")
writeReauthError(w, http.StatusUnauthorized, "REAUTH_SESSION_CHANGED", "登录状态已变化,请重新登录")
return
}
reauthUserAttempts.clear(userKey)
response.NewSuccessResponse(map[string]any{"reauthenticated": true}).FPrint(w)
}
func writeReauthError(w http.ResponseWriter, status int, code string, message string) {
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
response.NewErrorResponse(response.ConfigErrorNumber(status), message, response.ConfigErrorData{Code: code}).FPrint(w)
}