208 lines
5.8 KiB
Go
208 lines
5.8 KiB
Go
package handlers
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"go.mongodb.org/mongo-driver/v2/bson"
|
||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||
|
||
"yh_web/server/config"
|
||
"yh_web/server/models"
|
||
"yh_web/server/pkg/logger"
|
||
"yh_web/server/utils"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/golang-jwt/jwt/v5"
|
||
)
|
||
|
||
const jwtSecret = "yh_web_admin_jwt_secret_change_in_production"
|
||
const jwtExpire = 24 * time.Hour
|
||
|
||
type LoginInput struct {
|
||
Username string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
}
|
||
|
||
type Claims struct {
|
||
UserID string `json:"user_id"`
|
||
Username string `json:"username"`
|
||
RoleID int `json:"role_id"`
|
||
Role string `json:"role"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
// ParseClaimsFromTokenString 解析 Authorization Bearer 或裸 JWT;失败返回 false
|
||
func ParseClaimsFromTokenString(tokenStr string) (*Claims, bool) {
|
||
tokenStr = strings.TrimSpace(tokenStr)
|
||
if len(tokenStr) > 7 && strings.EqualFold(tokenStr[:7], "bearer ") {
|
||
tokenStr = strings.TrimSpace(tokenStr[7:])
|
||
}
|
||
if tokenStr == "" {
|
||
return nil, false
|
||
}
|
||
var claims Claims
|
||
token, err := jwt.ParseWithClaims(tokenStr, &claims, func(t *jwt.Token) (interface{}, error) {
|
||
return []byte(jwtSecret), nil
|
||
})
|
||
if err != nil || !token.Valid {
|
||
return nil, false
|
||
}
|
||
return &claims, true
|
||
}
|
||
|
||
// LivePublishAllowed 仅允许已登录后台账号发起 WebRTC 推流(与 AuthRequired 身份范围一致)
|
||
func LivePublishAllowed(tokenStr string) bool {
|
||
claims, ok := ParseClaimsFromTokenString(tokenStr)
|
||
if !ok {
|
||
return false
|
||
}
|
||
if claims.RoleID != models.RoleIDSuperAdmin && !(claims.RoleID == models.RoleIDSuperUser && claims.Role == "admin") {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// Login 后台登录,仅 role_id=9527 超级管理员可登录
|
||
func Login(c *gin.Context) {
|
||
var input LoginInput
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户名和密码不能为空"})
|
||
return
|
||
}
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
|
||
coll := config.GetDB(config.DBName).Collection("users")
|
||
var user models.User
|
||
err := coll.FindOne(ctx, bson.M{"username": input.Username}).Decode(&user)
|
||
if err != nil {
|
||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||
// 尝试用手机号登录
|
||
err = coll.FindOne(ctx, bson.M{"mobile": input.Username}).Decode(&user)
|
||
}
|
||
if err != nil {
|
||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||
return
|
||
}
|
||
logger.Err("handlers/auth", "[Login] FindOne error: %v", err)
|
||
resp := gin.H{"error": "登录失败,请稍后重试"}
|
||
if gin.Mode() == gin.DebugMode {
|
||
resp["debug"] = err.Error()
|
||
}
|
||
c.JSON(http.StatusInternalServerError, resp)
|
||
return
|
||
}
|
||
}
|
||
|
||
// 超级管理员(9527)或超级用户(role_id=0, role=admin)可登录后台
|
||
if user.RoleID != models.RoleIDSuperAdmin && !(user.RoleID == models.RoleIDSuperUser && user.Role == "admin") {
|
||
c.JSON(http.StatusForbidden, gin.H{"error": "无后台访问权限"})
|
||
return
|
||
}
|
||
|
||
roleID := user.RoleID
|
||
if roleID == 0 && user.Role == "admin" {
|
||
roleID = models.RoleIDSuperAdmin
|
||
}
|
||
|
||
hashed := utils.HashPassword(input.Password)
|
||
if hashed != user.Password {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||
return
|
||
}
|
||
|
||
claims := Claims{
|
||
UserID: user.ID.Hex(),
|
||
Username: user.Username,
|
||
RoleID: roleID,
|
||
Role: user.Role,
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtExpire)),
|
||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||
},
|
||
}
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
tokenStr, err := token.SignedString([]byte(jwtSecret))
|
||
if err != nil {
|
||
logger.Err("handlers/auth", "JWT SignedString error: %v", err)
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"token": tokenStr,
|
||
"user": gin.H{
|
||
"id": user.ID.Hex(),
|
||
"username": user.Username,
|
||
"role_id": roleID,
|
||
"role": user.Role,
|
||
},
|
||
"expires_in": int64(jwtExpire.Seconds()),
|
||
})
|
||
}
|
||
|
||
// AuthRequired 鉴权中间件,要求 role_id=9527
|
||
func AuthRequired() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
tokenStr := c.GetHeader("Authorization")
|
||
if tokenStr == "" {
|
||
tokenStr = c.Query("token")
|
||
}
|
||
claims, ok := ParseClaimsFromTokenString(tokenStr)
|
||
if !ok {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 仅超级管理员或超级用户(role_id=0, role=admin)可访问后台
|
||
if claims.RoleID != models.RoleIDSuperAdmin && !(claims.RoleID == models.RoleIDSuperUser && claims.Role == "admin") {
|
||
c.JSON(http.StatusForbidden, gin.H{"error": "无后台访问权限"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
c.Set("user_id", claims.UserID)
|
||
c.Set("username", claims.Username)
|
||
c.Set("role_id", claims.RoleID)
|
||
c.Set("role", claims.Role)
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// SuperUserAuthRequired 超级用户鉴权:仅 role_id=0 且 role=admin 可访问(如短信平台配置)
|
||
// 集团超级用户 username=admin 只能配置集团信息,不能配置短信
|
||
func SuperUserAuthRequired() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
tokenStr := c.GetHeader("Authorization")
|
||
if tokenStr == "" {
|
||
tokenStr = c.Query("token")
|
||
}
|
||
claims, ok := ParseClaimsFromTokenString(tokenStr)
|
||
if !ok {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 仅 role_id=9527 且 role=admin 可配置短信平台
|
||
if claims.RoleID != models.RoleIDSuperAdmin || claims.Role != "admin" {
|
||
c.JSON(http.StatusForbidden, gin.H{"error": "仅超级管理员可配置短信平台"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
c.Set("user_id", claims.UserID)
|
||
c.Set("username", claims.Username)
|
||
c.Set("role_id", claims.RoleID)
|
||
c.Set("role", claims.Role)
|
||
c.Next()
|
||
}
|
||
}
|