Files
web/server/handlers/auth.go

208 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}