package middleware import ( "context" "errors" "net/http" "server/internal/config" "server/internal/db" "server/internal/db/sqlc" "server/internal/model/auth" "server/internal/pkg/cache" "server/internal/pkg/cache/cachekey" "server/internal/pkg/errs" "server/internal/pkg/httputil" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5" ) type AuthMiddleware struct { store *db.Store cache *cache.Caches cfg *config.Config } func NewAuthMiddleware(store *db.Store, cache *cache.Caches, cfg *config.Config) *AuthMiddleware { return &AuthMiddleware{ cache: cache, store: store, cfg: cfg, } } func (m *AuthMiddleware) hasApiPermission(ctx context.Context, uid int32, requestMethod string, requestPath string) (bool, error) { var ( apis []sqlc.ListUserApisRow err error ) // 先从缓存中获取api数据 k := cachekey.UserApiPermissions(uid) apis, err = cache.GetOrSetJSON[[]sqlc.ListUserApisRow](ctx, m.cache, k, 0, func() ([]sqlc.ListUserApisRow, error) { return m.store.ListUserApis(ctx, uid) }) if err != nil { return false, err } for _, api := range apis { if api.Method != requestMethod { continue } if api.Path == requestPath { return true, nil } } return false, nil } func (m *AuthMiddleware) getAuthState(ctx context.Context, id int32) (*sqlc.GetUserAuthStateRow, error) { return cache.GetOrSetJSON[*sqlc.GetUserAuthStateRow](ctx, m.cache, cachekey.UserAuthState(id), m.cfg.JWTConfig.Expire, func() (*sqlc.GetUserAuthStateRow, error) { state, err := m.store.GetUserAuthState(ctx, id) if err != nil { return nil, err } return &state, nil }) } func (m *AuthMiddleware) Middleware(router chi.Router) func(handler http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() userCtx := GetUserContext(ctx) if userCtx == nil || userCtx.UserID == 0 { httputil.Fail(w, errs.ErrUnauthorized) return } // 判断token version state, err := m.getAuthState(ctx, userCtx.UserID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { httputil.Fail(w, errs.ErrInvalidToken) // 用户被删 return } httputil.Fail(w, errs.ErrInternalServer) // 基础设施故障,不杀会话 return } if state.TokenVersion != userCtx.TokenVersion { httputil.Fail(w, errs.ErrInvalidToken) // 被踢 → 401 return } // 判断是否有管理员权限 isAdmin := auth.IsAdmin(userCtx.UserID) userCtx.IsAdmin = isAdmin if isAdmin { next.ServeHTTP(w, r.WithContext(ctx)) return } // 如果不是管理员 需要判断api权限 rctx := chi.NewRouteContext() requestMethod := r.Method // https://github.com/go-chi/chi/pull/872 api := router.Find(rctx, requestMethod, r.URL.Path) hasPermission, err := m.hasApiPermission(ctx, userCtx.UserID, requestMethod, api) if err != nil { httputil.Fail(w, err) return } if !hasPermission { httputil.Fail(w, errs.ErrPermissionDenied) return } next.ServeHTTP(w, r) }) } }