hi-server/internal/middleware/authMiddleware.go
2026-01-27 03:13:15 -08:00

146 lines
5.2 KiB
Go

package middleware
import (
"context"
"fmt"
"strings"
jwtGo "github.com/golang-jwt/jwt/v5"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/jwt"
"github.com/perfect-panel/server/pkg/result"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
ctx := c.Request.Context()
jwtConfig := svc.Config.JwtAuth
// get token from header
token := c.GetHeader("Authorization")
if token == "" {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] token empty", logger.Field("path", c.Request.URL.Path))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenEmpty), "Token Empty"))
c.Abort()
return
}
// parse token
claims, err := jwt.ParseJwtToken(token, jwtConfig.AccessSecret)
if err != nil {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] parse token failed",
logger.Field("error", err.Error()),
)
// [AuthDebug] Try to parse unverified to see why it failed (expired or invalid signature)
parser := jwtGo.NewParser()
unverifiedToken, _, _ := parser.ParseUnverified(token, jwtGo.MapClaims{})
if unverifiedToken != nil {
if unverifiedClaims, ok := unverifiedToken.Claims.(jwtGo.MapClaims); ok {
logger.WithContext(c.Request.Context()).Errorw("[AuthDebug] Token Parsing Failure Details",
logger.Field("exp", unverifiedClaims["exp"]),
logger.Field("iat", unverifiedClaims["iat"]),
logger.Field("uid", unverifiedClaims["UserId"]),
logger.Field("sid", unverifiedClaims["SessionId"]),
logger.Field("sub", unverifiedClaims["sub"]),
)
}
}
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenExpire), "Token Invalid"))
c.Abort()
return
}
loginType := ""
if claims["LoginType"] != nil {
loginType = claims["LoginType"].(string)
}
// get user id from token
userId := int64(claims["UserId"].(float64))
// get session id from token
sessionId := claims["SessionId"].(string)
// get device id from token
var deviceId int64
if claims["DeviceId"] != nil {
deviceId = int64(claims["DeviceId"].(float64))
}
// get session id from redis
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
value, err := svc.Redis.Get(c, sessionIdCacheKey).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
logger.WithContext(c.Request.Context()).Infow("[AuthMiddleware] Session无效或已过期",
logger.Field("session_id", sessionId),
logger.Field("user_id", userId),
logger.Field("redis_key", sessionIdCacheKey),
logger.Field("ip", c.ClientIP()),
logger.Field("path", c.Request.URL.Path))
} else {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] Redis 查询失败",
logger.Field("error", err.Error()),
logger.Field("session_id", sessionId),
logger.Field("redis_key", sessionIdCacheKey))
}
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
//verify user id
if value != fmt.Sprintf("%v", userId) {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] user mismatch",
logger.Field("userId_in_token", userId),
logger.Field("userId_in_redis", value),
logger.Field("sessionId", sessionId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
userInfo, err := svc.UserModel.FindOne(c, userId)
if err != nil {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] user find failed", logger.Field("error", err.Error()), logger.Field("userId", userId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Database Query Error"))
c.Abort()
return
}
// admin verify
paths := strings.Split(c.Request.URL.Path, "/")
if tool.StringSliceContains(paths, "admin") && !*userInfo.IsAdmin {
logger.WithContext(c.Request.Context()).Errorw("[AuthMiddleware] not admin", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
c.Abort()
return
}
// Get TTL details for debugging
ttl, _ := svc.Redis.TTL(c.Request.Context(), sessionIdCacheKey).Result()
logger.WithContext(c.Request.Context()).Infow("[AuthMiddleware] auth ok",
logger.Field("userId", userId),
logger.Field("loginType", loginType),
logger.Field("path", c.Request.URL.Path),
logger.Field("session_ttl", ttl.Seconds()),
logger.Field("sessionId", sessionId),
logger.Field("deviceId", deviceId),
)
ctx = context.WithValue(ctx, constant.LoginType, loginType)
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
if deviceId > 0 {
ctx = context.WithValue(ctx, constant.CtxKeyDeviceID, deviceId)
}
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}