339 lines
10 KiB
Go
339 lines
10 KiB
Go
package handlers
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"net/mail"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"kenaito-notebook-server/services"
|
|
)
|
|
|
|
func isValidEmail(email string) bool {
|
|
_, err := mail.ParseAddress(email)
|
|
return err == nil
|
|
}
|
|
|
|
type AuthHandler struct {
|
|
db *services.Database
|
|
auth *services.AuthService
|
|
email *services.EmailService
|
|
verify *services.VerificationService
|
|
}
|
|
|
|
func NewAuthHandler(db *services.Database, auth *services.AuthService, email *services.EmailService, verify *services.VerificationService) *AuthHandler {
|
|
return &AuthHandler{db: db, auth: auth, email: email, verify: verify}
|
|
}
|
|
|
|
type RegisterRequest struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
Code string `json:"code" binding:"required"`
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
type SendCodeRequest struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Purpose string `json:"purpose" binding:"required"`
|
|
}
|
|
|
|
type VerifyCodeRequest struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Code string `json:"code" binding:"required"`
|
|
Purpose string `json:"purpose" binding:"required"`
|
|
}
|
|
|
|
type ResetPasswordRequest struct {
|
|
Email string `json:"email" binding:"required"`
|
|
Code string `json:"code" binding:"required"`
|
|
NewPassword string `json:"newPassword" binding:"required"`
|
|
}
|
|
|
|
// SendCode 发送验证码
|
|
func (h *AuthHandler) SendCode(c *gin.Context) {
|
|
var req SendCodeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
log.Printf("[Auth] SendCode bind error: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
|
return
|
|
}
|
|
|
|
if !isValidEmail(req.Email) {
|
|
log.Printf("[Auth] SendCode invalid email: %s", req.Email)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
|
return
|
|
}
|
|
|
|
if req.Purpose != "register" && req.Purpose != "reset" {
|
|
log.Printf("[Auth] SendCode invalid purpose: %s", req.Purpose)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求类型"})
|
|
return
|
|
}
|
|
|
|
// 注册时检查邮箱是否已注册
|
|
if req.Purpose == "register" {
|
|
existing, _ := h.db.GetUserByEmail(req.Email)
|
|
if existing != nil {
|
|
log.Printf("[Auth] SendCode email already registered: %s", req.Email)
|
|
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// 重置密码时检查邮箱是否存在
|
|
if req.Purpose == "reset" {
|
|
existing, _ := h.db.GetUserByEmail(req.Email)
|
|
if existing == nil {
|
|
log.Printf("[Auth] SendCode email not found for reset: %s", req.Email)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "邮箱未注册"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// 限频检查
|
|
if !h.verify.CheckRateLimit(req.Email, req.Purpose) {
|
|
log.Printf("[Auth] SendCode rate limit exceeded: %s, purpose: %s", req.Email, req.Purpose)
|
|
c.JSON(http.StatusTooManyRequests, gin.H{"error": "发送过于频繁,请 60 秒后重试"})
|
|
return
|
|
}
|
|
|
|
code := h.verify.GenerateCode(req.Email, req.Purpose)
|
|
if err := h.email.SendVerificationCode(req.Email, code, req.Purpose); err != nil {
|
|
log.Printf("[Auth] SendCode send email failed: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "发送验证码失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
|
|
}
|
|
|
|
// VerifyCode 验证验证码
|
|
func (h *AuthHandler) VerifyCode(c *gin.Context) {
|
|
var req VerifyCodeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
log.Printf("[Auth] VerifyCode bind error: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
|
return
|
|
}
|
|
|
|
if err := h.verify.VerifyCode(req.Email, req.Code, req.Purpose); err != nil {
|
|
log.Printf("[Auth] VerifyCode failed: email=%s, purpose=%s, err=%v", req.Email, req.Purpose, err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "验证码正确"})
|
|
}
|
|
|
|
// Register 用户注册
|
|
func (h *AuthHandler) Register(c *gin.Context) {
|
|
var req RegisterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
log.Printf("[Auth] Register bind error: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
|
return
|
|
}
|
|
|
|
if !isValidEmail(req.Email) {
|
|
log.Printf("[Auth] Register invalid email: %s", req.Email)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
|
return
|
|
}
|
|
|
|
// 检查邮箱是否已注册
|
|
existing, _ := h.db.GetUserByEmail(req.Email)
|
|
if existing != nil {
|
|
log.Printf("[Auth] Register email already registered: %s", req.Email)
|
|
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
|
return
|
|
}
|
|
|
|
// 验证并消耗验证码
|
|
if err := h.verify.UseCode(req.Email, req.Code, "register"); err != nil {
|
|
log.Printf("[Auth] Register use code failed: email=%s, err=%v", req.Email, err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 哈希密码
|
|
hashedPassword, err := h.auth.HashPassword(req.Password)
|
|
if err != nil {
|
|
log.Printf("[Auth] Register hash password failed: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
|
return
|
|
}
|
|
|
|
// 创建用户
|
|
user, err := h.db.CreateUser(req.Email, hashedPassword)
|
|
if err != nil {
|
|
log.Printf("[Auth] Register create user failed: email=%s, err=%v", req.Email, err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
|
return
|
|
}
|
|
|
|
// 生成 token
|
|
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
|
if err != nil {
|
|
log.Printf("[Auth] Register generate token failed: userID=%d, err=%v", user.ID, err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"token": token,
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
// Login 用户登录
|
|
func (h *AuthHandler) Login(c *gin.Context) {
|
|
var req LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
log.Printf("[Auth] Login bind error: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写邮箱和密码"})
|
|
return
|
|
}
|
|
|
|
if !isValidEmail(req.Email) {
|
|
log.Printf("[Auth] Login invalid email: %s", req.Email)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
|
return
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := h.db.GetUserByEmail(req.Email)
|
|
if err != nil {
|
|
log.Printf("[Auth] Login user not found: %s, err=%v", req.Email, err)
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
|
return
|
|
}
|
|
|
|
// 验证密码
|
|
if !h.auth.CheckPassword(req.Password, user.HashedPassword) {
|
|
log.Printf("[Auth] Login password mismatch: email=%s", req.Email)
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
|
return
|
|
}
|
|
|
|
// 生成 token
|
|
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
|
if err != nil {
|
|
log.Printf("[Auth] Login generate token failed: userID=%d, err=%v", user.ID, err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "登录失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": token,
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
// ResetPassword 重置密码
|
|
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
|
var req ResetPasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
log.Printf("[Auth] ResetPassword bind error: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
|
return
|
|
}
|
|
|
|
if !isValidEmail(req.Email) {
|
|
log.Printf("[Auth] ResetPassword invalid email: %s", req.Email)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
|
return
|
|
}
|
|
|
|
// 验证并消耗验证码
|
|
if err := h.verify.UseCode(req.Email, req.Code, "reset"); err != nil {
|
|
log.Printf("[Auth] ResetPassword use code failed: email=%s, err=%v", req.Email, err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := h.db.GetUserByEmail(req.Email)
|
|
if err != nil {
|
|
log.Printf("[Auth] ResetPassword user not found: %s, err=%v", req.Email, err)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
|
return
|
|
}
|
|
|
|
// 哈希新密码
|
|
hashedPassword, err := h.auth.HashPassword(req.NewPassword)
|
|
if err != nil {
|
|
log.Printf("[Auth] ResetPassword hash password failed: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
|
return
|
|
}
|
|
|
|
// 更新密码
|
|
if err := h.db.UpdatePassword(user.ID, hashedPassword); err != nil {
|
|
log.Printf("[Auth] ResetPassword update password failed: userID=%d, err=%v", user.ID, err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "密码重置成功"})
|
|
}
|
|
|
|
// GetMe 获取当前用户信息
|
|
func (h *AuthHandler) GetMe(c *gin.Context) {
|
|
userID, exists := c.Get("userID")
|
|
if !exists {
|
|
log.Printf("[Auth] GetMe userID not found in context")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
|
return
|
|
}
|
|
|
|
user, err := h.db.GetUserByID(userID.(int64))
|
|
if err != nil {
|
|
log.Printf("[Auth] GetMe user not found: userID=%v, err=%v", userID, err)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// AuthMiddleware JWT 认证中间件
|
|
func AuthMiddleware(auth *services.AuthService) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
log.Printf("[Auth] Middleware missing Authorization header: ip=%s", c.ClientIP())
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
// 提取 Bearer token
|
|
parts := strings.SplitN(authHeader, " ", 2)
|
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
|
log.Printf("[Auth] Middleware invalid auth format: ip=%s", c.ClientIP())
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证格式"})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
payload, err := auth.ValidateToken(parts[1])
|
|
if err != nil {
|
|
log.Printf("[Auth] Middleware token validation failed: ip=%s, err=%v", c.ClientIP(), err)
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
// 将用户信息存入上下文
|
|
c.Set("userID", payload.UserID)
|
|
c.Set("email", payload.Email)
|
|
c.Next()
|
|
}
|
|
}
|