Files
SingBox-Gopanel/internal/handler/auth_api.go
CN-JS-HuiBai 1ed31b9292
All checks were successful
build / build (api, amd64, linux) (push) Successful in -47s
build / build (api, arm64, linux) (push) Successful in -48s
build / build (api.exe, amd64, windows) (push) Successful in -47s
first commit
2026-04-17 09:49:16 +08:00

231 lines
5.9 KiB
Go

package handler
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"strings"
"time"
"xboard-go/internal/database"
"xboard-go/internal/model"
"xboard-go/internal/service"
"xboard-go/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type LoginRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
type RegisterRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
InviteCode *string `json:"invite_code"`
}
func Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, http.StatusBadRequest, "invalid request body")
return
}
var user model.User
if err := database.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
Fail(c, http.StatusUnauthorized, "email or password is incorrect")
return
}
if !utils.CheckPassword(req.Password, user.Password, user.PasswordAlgo, user.PasswordSalt) {
Fail(c, http.StatusUnauthorized, "email or password is incorrect")
return
}
token, err := utils.GenerateToken(user.ID, user.IsAdmin)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to create auth token")
return
}
now := time.Now().Unix()
_ = database.DB.Model(&model.User{}).Where("id = ?", user.ID).Update("last_login_at", now).Error
service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))
Success(c, gin.H{
"token": token,
"auth_data": token,
"is_admin": user.IsAdmin,
})
}
func Register(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, http.StatusBadRequest, "invalid request body")
return
}
var count int64
database.DB.Model(&model.User{}).Where("email = ?", req.Email).Count(&count)
if count > 0 {
Fail(c, http.StatusBadRequest, "email already exists")
return
}
hashedPassword, err := utils.HashPassword(req.Password)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to hash password")
return
}
now := time.Now().Unix()
tokenRaw := fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String()+req.Email)))
user := model.User{
Email: req.Email,
Password: hashedPassword,
UUID: uuid.New().String(),
Token: tokenRaw[:16],
CreatedAt: now,
UpdatedAt: now,
}
if err := database.DB.Create(&user).Error; err != nil {
Fail(c, http.StatusInternalServerError, "register failed")
return
}
if service.IsPluginEnabled(service.PluginUserAddIPv6) && user.PlanID != nil {
service.SyncIPv6ShadowAccount(&user)
}
token, err := utils.GenerateToken(user.ID, user.IsAdmin)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to create auth token")
return
}
service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))
Success(c, gin.H{
"token": token,
"auth_data": token,
"is_admin": user.IsAdmin,
})
}
func Token2Login(c *gin.Context) {
verify := strings.TrimSpace(c.Query("verify"))
if verify == "" {
Fail(c, http.StatusBadRequest, "verify token is required")
return
}
userID, ok := service.ResolveQuickLoginToken(verify)
if !ok {
Fail(c, http.StatusUnauthorized, "verify token is invalid or expired")
return
}
var user model.User
if err := database.DB.First(&user, userID).Error; err != nil {
Fail(c, http.StatusNotFound, "user not found")
return
}
token, err := utils.GenerateToken(user.ID, user.IsAdmin)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to create auth token")
return
}
service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))
_ = database.DB.Model(&model.User{}).Where("id = ?", user.ID).Update("last_login_at", time.Now().Unix()).Error
Success(c, gin.H{
"token": token,
"auth_data": token,
"is_admin": user.IsAdmin,
})
}
func SendEmailVerify(c *gin.Context) {
var req struct {
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, http.StatusBadRequest, "invalid email")
return
}
code, err := randomDigits(6)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to generate verify code")
return
}
service.StoreEmailVerifyCode(strings.ToLower(strings.TrimSpace(req.Email)), code, 10*time.Minute)
SuccessMessage(c, "email verify code generated", gin.H{
"email": req.Email,
"debug_code": code,
"expires_in": 600,
})
}
func ForgetPassword(c *gin.Context) {
var req struct {
Email string `json:"email" binding:"required,email"`
EmailCode string `json:"email_code" binding:"required"`
Password string `json:"password" binding:"required,min=8"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, http.StatusBadRequest, "invalid request body")
return
}
email := strings.ToLower(strings.TrimSpace(req.Email))
if !service.CheckEmailVerifyCode(email, strings.TrimSpace(req.EmailCode)) {
Fail(c, http.StatusBadRequest, "email verify code is invalid")
return
}
hashed, err := utils.HashPassword(req.Password)
if err != nil {
Fail(c, http.StatusInternalServerError, "failed to hash password")
return
}
updates := map[string]any{
"password": hashed,
"password_algo": nil,
"password_salt": nil,
"updated_at": time.Now().Unix(),
}
if err := database.DB.Model(&model.User{}).Where("email = ?", email).Updates(updates).Error; err != nil {
Fail(c, http.StatusInternalServerError, "password reset failed")
return
}
SuccessMessage(c, "password reset success", true)
}
func randomDigits(length int) (string, error) {
if length <= 0 {
return "", nil
}
buf := make([]byte, length)
if _, err := rand.Read(buf); err != nil {
return "", err
}
encoded := hex.EncodeToString(buf)
digits := make([]byte, 0, length)
for i := 0; i < len(encoded) && len(digits) < length; i++ {
digits = append(digits, '0'+(encoded[i]%10))
}
return string(digits), nil
}