Files
91-api-server/internal/handler/auth_handler.go
yuqianhe 3e27017e42 feat: add auth, db, gateway, email, ratelimit, geoblock modules and new handlers
- New CLI commands: key, user
- New internal modules: auth (middleware, password, user), db (db, logs, products), email, gateway (gateway, hooks, middlewares), handler (admin, auth, dev, log, product, settings), middleware/geoblock, ratelimit
- New demo frontend page
- Updated config, server, analytics, stats, and existing frontend pages
2026-06-26 21:32:51 +09:00

256 lines
7.6 KiB
Go

package handler
import (
"net/http"
"strconv"
"strings"
"time"
"api-server/internal/auth"
"api-server/internal/db"
"github.com/gin-gonic/gin"
)
// AuthHandler 认证处理器
type AuthHandler struct {
db *db.DB
cookieSecure bool
}
// NewAuthHandler 创建认证处理器
func NewAuthHandler(database *db.DB, cookieSecure bool) *AuthHandler {
return &AuthHandler{db: database, cookieSecure: cookieSecure}
}
// RegisterRoutes 注册路由
func (h *AuthHandler) RegisterRoutes(r *gin.RouterGroup) {
r.POST("/auth/register", h.Register)
r.POST("/auth/login", h.Login)
r.POST("/auth/logout", h.Logout)
r.GET("/auth/me", auth.RequireAuth(h.db), h.Me)
r.PUT("/auth/profile", auth.RequireAuth(h.db), h.UpdateProfile)
r.PUT("/auth/password", auth.RequireAuth(h.db), h.UpdatePassword)
}
// Register 用户注册
func (h *AuthHandler) Register(c *gin.Context) {
// 检查是否开放注册
regEnabled, _ := h.db.GetSystemSetting("registration_enabled")
if regEnabled == "0" || regEnabled == "" {
c.JSON(http.StatusForbidden, gin.H{"code": 1, "msg": "注册已关闭"})
return
}
// 检查维护模式
mm, _ := h.db.GetSystemSetting("maintenance_mode")
if mm == "1" {
c.JSON(http.StatusServiceUnavailable, gin.H{"code": 1, "msg": "系统维护中,请稍后再试"})
return
}
var req struct {
Email string `json:"email"`
Password string `json:"password"`
Name string `json:"name"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
// 密码规则校验
pwdRule := auth.PasswordRuleFromSettings(
h.db.GetSystemSettingOrDefault("password_min_length", "6"),
h.db.GetSystemSettingOrDefault("password_require_upper", "0"),
h.db.GetSystemSettingOrDefault("password_require_lower", "0"),
h.db.GetSystemSettingOrDefault("password_require_digit", "0"),
h.db.GetSystemSettingOrDefault("password_require_special", "0"),
)
if ok, msg := auth.ValidatePassword(req.Password, pwdRule); !ok {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": msg})
return
}
req.Email = strings.TrimSpace(strings.ToLower(req.Email))
hash, err := auth.HashPassword(req.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "密码加密失败"})
return
}
user, err := h.db.CreateUser(req.Email, hash, req.Name)
if err != nil {
if strings.Contains(err.Error(), "已注册") {
c.JSON(http.StatusConflict, gin.H{"code": 1, "msg": "该邮箱已注册"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
expiryHours, _ := h.db.GetSystemSetting("auth_token_expiry_hours")
expiry := 7 * 24 * time.Hour
if h, err := strconv.Atoi(expiryHours); err == nil && h > 0 {
expiry = time.Duration(h) * time.Hour
}
token, _ := auth.GenerateTokenWithExpiry(user, expiry)
expirySec := int(expiry.Seconds())
c.SetCookie("token", token, expirySec, "/", "", h.cookieSecure, true)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "注册成功",
"data": gin.H{
"token": token,
"user": userResponse(user),
},
})
}
// Login 用户登录
func (h *AuthHandler) Login(c *gin.Context) {
var req struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供邮箱和密码"})
return
}
req.Email = strings.TrimSpace(strings.ToLower(req.Email))
user, err := h.db.GetUserByEmail(req.Email)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "邮箱或密码错误"})
return
}
if !auth.CheckPassword(req.Password, user.PasswordHash) {
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "邮箱或密码错误"})
return
}
// 检查是否维护模式(仅阻止非管理员)
mm, _ := h.db.GetSystemSetting("maintenance_mode")
if mm == "1" && user.Role != "admin" {
c.JSON(http.StatusServiceUnavailable, gin.H{"code": 1, "msg": "系统维护中,请稍后再试"})
return
}
// 检查是否需要邮箱验证
verifyReq, _ := h.db.GetSystemSetting("email_verification_required")
if verifyReq == "1" && !user.EmailVerified {
c.JSON(http.StatusForbidden, gin.H{"code": 403, "msg": "请先验证邮箱后再登录"})
return
}
// 按设置生成 token 过期时间
expiryHours, _ := h.db.GetSystemSetting("auth_token_expiry_hours")
expiry := 7 * 24 * time.Hour
if h, err := strconv.Atoi(expiryHours); err == nil && h > 0 {
expiry = time.Duration(h) * time.Hour
}
token, _ := auth.GenerateTokenWithExpiry(user, expiry)
expirySec := int(expiry.Seconds())
c.SetCookie("token", token, expirySec, "/", "", h.cookieSecure, true)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "登录成功",
"data": gin.H{
"token": token,
"user": userResponse(user),
},
})
}
// Logout 退出登录
func (h *AuthHandler) Logout(c *gin.Context) {
c.SetCookie("token", "", -1, "/", "", h.cookieSecure, true)
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已退出"})
}
// Me 当前用户信息
func (h *AuthHandler) Me(c *gin.Context) {
user := auth.GetUser(c)
if user == nil {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "未登录"})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": userResponse(user),
})
}
func (h *AuthHandler) UpdateProfile(c *gin.Context) {
user := auth.GetUser(c)
var req struct {
Name string `json:"name"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if err := h.db.UpdateUser(user.ID, req.Name); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
updated, err := h.db.GetUserByID(user.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": userResponse(updated), "msg": "已更新"})
}
func (h *AuthHandler) UpdatePassword(c *gin.Context) {
user := auth.GetUser(c)
var req struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供原密码和新密码"})
return
}
// 密码规则校验
pwdRule := auth.PasswordRuleFromSettings(
h.db.GetSystemSettingOrDefault("password_min_length", "6"),
h.db.GetSystemSettingOrDefault("password_require_upper", "0"),
h.db.GetSystemSettingOrDefault("password_require_lower", "0"),
h.db.GetSystemSettingOrDefault("password_require_digit", "0"),
h.db.GetSystemSettingOrDefault("password_require_special", "0"),
)
if ok, msg := auth.ValidatePassword(req.NewPassword, pwdRule); !ok {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": msg})
return
}
if !auth.CheckPassword(req.OldPassword, user.PasswordHash) {
c.JSON(http.StatusForbidden, gin.H{"code": 403, "msg": "原密码错误"})
return
}
hash, err := auth.HashPassword(req.NewPassword)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "密码加密失败"})
return
}
if err := h.db.UpdatePassword(user.Email, hash); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "密码已修改"})
}
func userResponse(u *db.User) gin.H {
return gin.H{
"id": u.ID,
"email": u.Email,
"name": u.Name,
"role": u.Role,
"email_verified": u.EmailVerified,
"disabled": u.Disabled,
"points": u.Points,
}
}