123 lines
3.7 KiB
Go
123 lines
3.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"kra/internal/conf"
|
|
"kra/internal/server/httpx"
|
|
"kra/internal/service"
|
|
"kra/pkg/adminauth"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
const claimsKey = "admin_claims"
|
|
|
|
var refreshTokens singleflight.Group
|
|
|
|
type refreshedToken struct {
|
|
token string
|
|
expiresAt int64
|
|
}
|
|
|
|
func Auth(runtime *conf.Runtime, settings *service.SettingsService) 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
|
|
}
|
|
secret := ""
|
|
config := runtime.Admin()
|
|
if config != nil && config.Jwt != nil {
|
|
secret = config.Jwt.SigningKey
|
|
}
|
|
claims, err := adminauth.Parse(token, secret)
|
|
if err != nil {
|
|
message := "无法处理此token"
|
|
switch {
|
|
case errors.Is(err, adminauth.ErrTokenExpired):
|
|
message = "登录已过期,请重新登录"
|
|
case errors.Is(err, adminauth.ErrTokenMalformed):
|
|
message = "这不是一个token"
|
|
case errors.Is(err, adminauth.ErrTokenSignatureInvalid):
|
|
message = "无效签名"
|
|
case errors.Is(err, adminauth.ErrTokenNotValidYet):
|
|
message = "token尚未激活"
|
|
}
|
|
httpx.SetTokenCookie(c, "", -1)
|
|
httpx.NoAuth(c, message)
|
|
return
|
|
}
|
|
if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled {
|
|
httpx.SetTokenCookie(c, "", -1)
|
|
httpx.NoAuth(c, "您的帐户异地登陆或令牌失效")
|
|
return
|
|
}
|
|
if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second {
|
|
expires, buffer := 7*24*time.Hour, time.Duration(claims.BufferTime)*time.Second
|
|
issuer := claims.Issuer
|
|
config = runtime.Admin()
|
|
if config != nil && config.Jwt != nil {
|
|
if config.Jwt.ExpiresTime != nil {
|
|
expires = config.Jwt.ExpiresTime.AsDuration()
|
|
}
|
|
if config.Jwt.BufferTime != nil {
|
|
buffer = config.Jwt.BufferTime.AsDuration()
|
|
}
|
|
if config.Jwt.Issuer != "" {
|
|
issuer = config.Jwt.Issuer
|
|
}
|
|
}
|
|
value, refreshErr, _ := refreshTokens.Do(token, func() (any, error) {
|
|
newToken, newClaims, generateErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
|
|
if generateErr != nil {
|
|
return nil, generateErr
|
|
}
|
|
if rotateErr := settings.RotateActiveToken(c.Request.Context(), claims.Username, token, newToken, expires); rotateErr != nil {
|
|
return nil, rotateErr
|
|
}
|
|
return refreshedToken{token: newToken, expiresAt: newClaims.ExpiresAt.Unix()}, nil
|
|
})
|
|
if refreshErr == nil {
|
|
refreshed := value.(refreshedToken)
|
|
c.Header("new-token", refreshed.token)
|
|
c.Header("new-expires-at", strconv.FormatInt(refreshed.expiresAt, 10))
|
|
httpx.SetTokenCookie(c, refreshed.token, int(expires.Seconds()))
|
|
}
|
|
}
|
|
c.Set(claimsKey, claims)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func Claims(c *gin.Context) *adminauth.Claims {
|
|
value, _ := c.Get(claimsKey)
|
|
claims, _ := value.(*adminauth.Claims)
|
|
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: "密码已过期,请先修改密码"})
|
|
}
|
|
}
|