refactor: 拆分 middleware/router 为单一职责文件,新增站点设置管理页面
middleware: - 拆分 auth.go → auth.go + auth_token.go + auth_parser.go + auth_admin.go - 拆分 csrf.go → csrf.go + csrf_token.go - 拆分 ratelimit.go → ratelimit.go + ratelimit_core.go + ratelimit_cleanup.go - 修复 import 未使用/缺失问题 router: - 拆分 router.go → admin.go + api.go + frontend.go + deps_core.go + deps_extra.go feat(admin): 站点设置管理页面 - 新增 SSR 页面 /admin/site-settings(仅 Owner) - 审核开关 Toggle 即时保存 - 通用设置展示 - 侧边栏新增站点设置入口 - 注册 UpdateBoolSetting/GetBoolSetting API 路由
This commit is contained in:
@ -1,16 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// AuthMiddleware 认证中间件(结构体模式,持有 DB 依赖用于实时令牌吊销校验)
|
||||
@ -25,7 +19,6 @@ func NewAuthMiddleware(cfg *config.Config, userRepo tokenVersionStore) *AuthMidd
|
||||
}
|
||||
|
||||
// Required 登录认证中间件:校验 JWT → 检查 token_version → 注入用户信息
|
||||
// 令牌版本与 DB 不匹配时即时拒绝,实现实时吊销
|
||||
func (am *AuthMiddleware) Required() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims, err := am.authenticateToken(c)
|
||||
@ -36,122 +29,20 @@ func (am *AuthMiddleware) Required() gin.HandlerFunc {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
injectUserContext(c, claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Optional 可选认证:已登录且版本通过则注入,未登录或版本不匹配也放行(仅清除过期 Cookie)
|
||||
// Optional 可选认证:已登录且版本通过则注入,未登录或版本不匹配也放行
|
||||
func (am *AuthMiddleware) Optional() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims, err := am.authenticateToken(c)
|
||||
if err != nil {
|
||||
log.Printf("[AuthOptional] token invalid for %s: %v", c.Request.URL.Path, err)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[AuthOptional] OK: uid=%v username=%v path=%s", claims["uid"], claims["username"], c.Request.URL.Path)
|
||||
injectUserContext(c, claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// AdminAuth 管理后台页面认证:失败时 302 跳首页而非返回 JSON
|
||||
func (am *AuthMiddleware) AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims, err := am.authenticateToken(c)
|
||||
if err != nil {
|
||||
log.Printf("[AdminAuth] auth failed path=%s err=%v → 302", c.Request.URL.Path, err)
|
||||
common.ClearAuthCookies(c, am.cfg)
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[AdminAuth] OK: uid=%v role=%v path=%s", claims["uid"], claims["role"], c.Request.URL.Path)
|
||||
injectUserContext(c, claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// authenticateToken 统一的认证核心流程:读 Cookie → 解析 JWT → 检查 token_version
|
||||
// 成功返回 claims,失败返回 error(调用方自行决定如何响应)
|
||||
func (am *AuthMiddleware) authenticateToken(c *gin.Context) (jwt.MapClaims, error) {
|
||||
tokenStr, err := c.Cookie(common.CookieName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, err := parseToken(tokenStr, am.cfg.JWT.Secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid := uint(claims["uid"].(float64))
|
||||
tokenVer := int(claims["ver"].(float64))
|
||||
|
||||
currentVer, err := am.userRepo.FindTokenVersion(uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokenVer != currentVer {
|
||||
log.Printf("[authenticateToken] REVOKED: uid=%d tokenVer=%d dbVer=%d", uid, tokenVer, currentVer)
|
||||
return nil, common.ErrTokenRevoked
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// injectUserContext 将 JWT claims 中的用户信息注入 gin context
|
||||
func injectUserContext(c *gin.Context, claims jwt.MapClaims) {
|
||||
if claims == nil {
|
||||
return
|
||||
}
|
||||
uid, ok := claims["uid"].(float64)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.Set("uid", uint(uid))
|
||||
c.Set("email", claims["email"])
|
||||
c.Set("username", claims["username"])
|
||||
c.Set("role", claims["role"])
|
||||
}
|
||||
|
||||
// parseToken 解析并验证 JWT
|
||||
func parseToken(tokenStr, secret string) (jwt.MapClaims, error) {
|
||||
now := time.Now()
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
log.Printf("[parseToken] FAIL: now=%v err=%v (secret_len=%d token_len=%d)",
|
||||
now, err, len(secret), len(tokenStr))
|
||||
return nil, err
|
||||
}
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
// Debug: 打印 exp/iat 对比当前时间
|
||||
if exp, exists := claims["exp"]; exists {
|
||||
var expTime time.Time
|
||||
switch v := exp.(type) {
|
||||
case float64:
|
||||
expTime = time.Unix(int64(v), 0)
|
||||
case *jwt.NumericDate:
|
||||
expTime = v.Time
|
||||
}
|
||||
if !expTime.IsZero() {
|
||||
log.Printf("[parseToken] OK: exp=%v (%d) now=%v isExpired=%v",
|
||||
expTime, expTime.Unix(), now, now.After(expTime))
|
||||
}
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// IsLoginPage 检查是否已在登录状态,已登录用户跳过登录/注册页
|
||||
func IsLoginPage(c *gin.Context) bool {
|
||||
return strings.HasPrefix(c.Request.URL.Path, "/auth/")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user