package middleware import ( "errors" "net/http" "strconv" "strings" "kra/internal/biz" "kra/internal/server/httpx" "kra/internal/service" "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" ) const claimsKey = "admin_claims" var refreshTokens singleflight.Group func Auth(auth *service.AuthService) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("x-token") if token == "" { token, _ = c.Cookie("x-token") } if token == "" { httpx.NoAuth(c, "未登录或非法访问,请登录") return } value, err, _ := refreshTokens.Do(token, func() (any, error) { return auth.AuthenticateToken(c.Request.Context(), token) }) if err != nil { message := "无法处理此token" switch { case errors.Is(err, biz.ErrTokenExpired): message = "登录已过期,请重新登录" case errors.Is(err, biz.ErrTokenMalformed): message = "这不是一个token" case errors.Is(err, biz.ErrTokenSignatureInvalid): message = "无效签名" case errors.Is(err, biz.ErrTokenNotValidYet): message = "token尚未激活" case errors.Is(err, biz.ErrTokenDisabled): message = "您的帐户异地登陆或令牌失效" } httpx.SetTokenCookie(c, "", -1) httpx.NoAuth(c, message) return } authentication := value.(*biz.TokenAuthentication) if authentication.Refreshed != nil { c.Header("new-token", authentication.Refreshed.Value) c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10)) httpx.SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) } c.Set(claimsKey, authentication.Claims) c.Next() } } func Claims(c *gin.Context) *biz.AuthClaims { value, _ := c.Get(claimsKey) claims, _ := value.(*biz.AuthClaims) return claims } func MustChangePassword() gin.HandlerFunc { return func(c *gin.Context) { claims := Claims(c) if claims == nil || !claims.MustChangePwd { c.Next() return } path := strings.TrimSuffix(c.Request.URL.Path, "/") if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") { c.Next() return } c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"}) } }