Skip to content

feat: added global rate limiting#20

Merged
BuckyMcYolo merged 3 commits intomainfrom
dev
Mar 18, 2026
Merged

feat: added global rate limiting#20
BuckyMcYolo merged 3 commits intomainfrom
dev

Conversation

@BuckyMcYolo
Copy link
Copy Markdown
Owner

@BuckyMcYolo BuckyMcYolo commented Mar 18, 2026

Overview

This PR implements a comprehensive global rate-limiting system, message pinning functionality, and authentication enhancements using Redis. However, there is a critical architectural gap in the message pinning feature that would prevent real-time synchronization between clients.

Rate Limiting Implementation

  • Global Rate Limiter: Added middleware in apps/api/src/middleware/rate-limit.ts with configurable limits (100 requests/min per IP by default)
  • Specialized Rate Limiters:
    • writeRateLimit: 30 requests/min per user (for write operations)
    • Guild message rate limiter: Role-based limits using Redis for windowing
    • DM rate limiter: 45 messages/min per user
  • Redis Integration: Uses Redis for distributed rate limit tracking with windowed keys and TTL management
  • Graceful Degradation: Fails open if Redis is unavailable, allowing requests to proceed
  • Response Headers: Properly sets X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers

Message Pinning Feature

  • Database Schema: Added pinned: boolean field to messages table with default value false
  • API Endpoints:
    • PATCH /guilds/{guildSlug}/channels/{channelId}/messages/{messageId}/pin: Toggle pin state
    • GET /guilds/{guildSlug}/channels/{channelId}/pins: List pinned messages in a channel
  • Permission System: Updated permission model to include "pin" action for messages
  • Frontend UI:
    • Pin button in message action bar with visual feedback
    • Pinned messages panel accessible from right sidebar
    • "Pinned" indicator displayed on pinned messages

Critical Issue: Missing Real-Time Event Broadcasting

The message pinning feature has a significant architectural flaw: The REST API endpoint updates the database but does NOT emit WebSocket events to notify other connected clients. This is inconsistent with the established pattern used for other message operations (delete, edit, reaction toggle).

The codebase shows:

  • message:pin:toggled is defined in ServerToClientEvents (packages/realtime-types/src/events.ts)
  • Frontend hook (useMessagePinning) listens for this event: socket.on("message:pin:toggled", ...)
  • BUT: The realtime server has no handler to receive pin toggle requests and emit events
  • AND: The API handler doesn't trigger any WebSocket broadcast

This means:

  • Only the user who clicked pin/unpin would see the change via optimistic updates
  • Other users in the channel would NOT see the pin state change until they refresh
  • Real-time synchronization would fail entirely

Authentication Enhancements

  • Redis Storage: Integrated @better-auth/redis-storage with Redis client initialization using env.REDIS_URL
  • Auth Rate Limiting: Added rate limiting configuration with endpoint-specific rules:
    • Sign-in/up: 5 attempts per 60 seconds
    • Forgot/reset password: 3-5 attempts per 60 seconds
    • Two-factor: 3 attempts per 10 seconds
    • Global default: 100 requests per 60 seconds

Database and Schema Changes

  • Pinned field properly added to message schema with database indexing
  • No evidence of migration files being committed; unclear how schema changes are deployed to production
  • Drizzle ORM configured but migration execution mechanism not visible

Code Quality Observations

  • Strengths:

    • Consistent error handling patterns with proper HTTP status codes
    • Permission checks implemented for pin operations
    • Type-safe implementation with Zod schemas
    • Comprehensive UI component hierarchy for pinning feature
    • Proper Redis TTL management for rate limiting
  • Weaknesses:

    • Missing WebSocket event handler in realtime server
    • No tests visible for new functionality
    • Database migration execution not demonstrated
    • Incomplete real-time synchronization pattern

Confidence Score: 2/5

The PR demonstrates clear intent and has many well-implemented components (rate limiting, UI, permissions), but the missing WebSocket event emission for message pinning represents a critical gap that would cause the feature to malfunction in production. Other clients would not receive real-time updates when messages are pinned/unpinned. The architecture is incomplete and requires:

  1. Adding a WebSocket event handler in the realtime server to process and broadcast pin toggle events
  2. Either modifying the API to trigger realtime events or restructuring to use WebSocket for pin operations
  3. Database migration evidence and documentation

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 30559a52-00e7-4da0-a72b-08cc76d7c955

📥 Commits

Reviewing files that changed from the base of the PR and between 78d7b90 and 5772518.

📒 Files selected for processing (1)
  • apps/api/src/middleware/rate-limit.ts

📝 Walkthrough

Walkthrough

Adds Redis-backed rate limiting (API, realtime, auth), message pinning (DB + realtime event + API + UI), new pin-related routes and schemas, client-side pinning UI + hook, permission for pin action, and Redis wiring for auth storage.

Changes

Cohort / File(s) Summary
API middleware & wiring
apps/api/src/middleware/rate-limit.ts, apps/api/src/app.ts
New Redis-backed windowed rate limiter exported as rateLimiter, globalRateLimit, writeRateLimit; globalRateLimit applied globally in app startup.
Realtime limits & message shape
apps/realtime/src/services/rate-limit.ts, apps/realtime/src/index.ts, apps/realtime/src/services/messages.ts
Adds DM and connection rate limiting using Redis; integrates DM limit into message send flow; messages now include pinned: boolean.
Realtime types / events
packages/realtime-types/src/events.ts
RealtimeMessage now has pinned: boolean; new RealtimeMessagePinToggled type and "message:pin:toggled" server event added.
API pin endpoints & schemas
apps/api/src/routes/v1/channels/handlers.ts, .../index.ts, .../routes.ts, .../schema.ts
New endpoints to toggle a message pin and list pinned messages; OpenAPI wiring and schemas added.
Auth: Redis storage & rate limits
packages/auth/package.json, packages/auth/src/lib/auth.ts
Adds ioredis and @better-auth/redis-storage; initializes Redis client, configures secondaryStorage, and enables auth-level rateLimit with per-endpoint rules.
Permissions
packages/auth/src/lib/permissions.ts
Expanded message actions to include "pin" for owner/admin/warden roles.
Web UI: pinning UX & hooks
apps/web/src/components/chat/header.tsx, .../message-action-bar.tsx, .../message-item.tsx, .../message-list.tsx, apps/web/src/hooks/use-message-pinning.ts, apps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx
Adds header control, message action pin button, message item pin state, message-list prop passthrough, useMessagePinning hook (optimistic updates + realtime sync), and ChannelView wiring (canPin + toggle).
Pinned messages panel & sidebar types
apps/web/src/components/sidebar/right-panel/pinned-messages-panel.tsx, .../right-sidebar-panel.tsx, .../right-sidebar-types.ts
New right-sidebar panel for pinned messages, view type, and conditional rendering in the right sidebar.
Misc / Manifests
package.json, packages/auth/package.json
Added/updated Redis-related dependencies and minor manifest edits.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant API as API Server
    participant Middleware as Rate Limiter
    participant Redis as Redis
    participant Handler as Route Handler

    Client->>API: HTTP request
    API->>Middleware: globalRateLimit middleware
    Middleware->>Middleware: determine identifier (IP / keyExtractor)
    Middleware->>Redis: INCR ratelimit:api:<prefix>:<id>:<windowNum>
    Redis-->>Middleware: count
    alt first increment
        Middleware->>Redis: EXPIRE key (window + grace)
        Redis-->>Middleware: OK
    end
    alt count <= max
        Middleware->>API: next()
        API->>Handler: route executes
        Handler-->>Client: 200 OK (X-RateLimit headers)
    else count > max
        Middleware-->>Client: 429 Too Many Requests (Retry-After)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibbled keys and counted hops,

Redis carrots lined in ops,
Pins that sparkle, toggled light,
Events that hum into the night,
A rabbit's hop — the feed's delight.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'feat: added global rate limiting' is partially related to the changeset. While global rate limiting is one aspect of the changes, the PR encompasses significantly more functionality including message pinning features, authentication rate limiting, WebSocket connection rate limiting, and DM rate limiting—making the title incomplete and not representative of the main changes. Revise the title to better reflect the comprehensive scope, such as 'feat: add rate limiting and message pinning features' or break into separate PRs aligned with each feature area.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/middleware/rate-limit.ts`:
- Around line 37-73: The rateLimiter middleware currently calls getRedisClient()
and Redis commands (redis.incr, redis.expire) without error handling, which will
cause unhandled exceptions if Redis is down; wrap the Redis calls inside a
try/catch in the rateLimiter function, and on Redis errors either fail-open (log
the error via your logger and call await next() to allow the request through) or
fail-closed (return the 429/appropriate error response) depending on the desired
policy; ensure the catch logs the error and preserves headers behavior when
failing-open (e.g., skip setting X-RateLimit-* when Redis unavailable) and
reference rateLimiter, getRedisClient, redis.incr, and redis.expire so you can
locate and update the logic accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9cc05b5a-a4fe-4408-bc30-2235a7af2f2b

📥 Commits

Reviewing files that changed from the base of the PR and between a73b4dd and db55955.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/api/src/app.ts
  • apps/api/src/middleware/rate-limit.ts
  • apps/realtime/src/index.ts
  • apps/realtime/src/services/rate-limit.ts
  • packages/auth/package.json
  • packages/auth/src/lib/auth.ts

Comment thread apps/api/src/middleware/rate-limit.ts
@BuckyMcYolo BuckyMcYolo merged commit 656c488 into main Mar 18, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2026
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant