Skip to content

Implement Upstash rate-limit driver

Problem

.cursor/rules/120-security.mdc requires Upstash Redis (@upstash/ratelimit) for production rate limiting. The original implementation was split:

  • LMS had a driver pattern in src/lib/rate-limit/*.
  • CMS used a separate in-memory limiter in src/lib/api/rate-limit.ts.

The split risked behavior drift and made production parity harder to maintain.

Implemented approach

  1. Added shared package @open-learning-hub/server/rate-limit in packages/server/ with:
    • memoryDriver (fixed-window, in-memory)
    • upstashDriver (@upstash/ratelimit + @upstash/redis)
    • selectRateLimitDriver (RATE_LIMIT_DRIVER-based)
  2. Migrated LMS and CMS to consume the shared driver surface.
  3. Kept app-specific policy wiring local:
    • LMS RATE_LIMIT_POLICIES + limit() wrapper remain in apps/lms/src/lib/rate-limit/index.ts.
    • CMS route-bucket rules remain in apps/cms/src/lib/api/rate-limit.ts.
  4. Documented RATE_LIMIT_DRIVER and UPSTASH_* in both .env.local.template files.
  5. Added shared package unit tests for memory/upstash drivers and updated CMS rate-limit tests for async driver calls.

Acceptance criteria

  • RATE_LIMIT_DRIVER=upstash works end-to-end in LMS and CMS.
  • Documented env vars present in .env.local.template and CI secrets.
  • Tests cover the standard policies (auth, sign-up, enroll, quiz, revalidate).
  • npm run check passes.

Out of scope

  • Moving away from sliding-window/fixed-window policies.
  • Multi-region / multi-key replication strategy.

Notes / decisions log

  • 2026-05-24: Ticket created. Deferred per current product priorities; reactivate when production scale-out is on the roadmap.
  • 2026-07-07: Implemented via shared package @open-learning-hub/server/rate-limit; LMS and CMS now share driver selection and Upstash integration.
  • 2026-07-08: CMS Vercel environments were updated to RATE_LIMIT_DRIVER=upstash with UPSTASH_REDIS_REST_URL/TOKEN mapped from existing KV integration values.
  • 2026-07-08: Shared driver now accepts KV_REST_API_URL/TOKEN as fallback names; follow-up remains to split LMS/CMS into separate Upstash stores for stronger isolation.