How the DeskcommCRM Authentication Rate Limiter Combines Upstash Redis with In-Memory Fallback
The rate limiter in lib/auth/rate-limit.ts delegates counting operations to checkRateLimit and peekRateLimit from lib/ai/dispatcher/rate-limit.ts, which automatically route to Upstash Redis (using SHA-256 hashed keys with INCR/EXPIRE commands) when available, or fall back to a process-local Map when Redis is unreachable, ensuring consistent fixed-window rate limiting across distributed and single-node deployments.
The melgarafael/DeskcommCRM repository implements a resilient dual-backend strategy for authentication rate limiting that requires no configuration to function in self-hosted environments while scaling seamlessly to distributed systems. By combining Upstash Redis with an in-memory fallback, the code in lib/auth/rate-limit.ts enforces request quotas per IP address and per user identifier without exposing raw client data to external storage.
Architecture of the Dual-Backend Rate Limiter
The authentication module does not implement storage logic directly. Instead, it imports generic rate limiting helpers from lib/ai/dispatcher/rate-limit.ts, which abstract the underlying storage mechanism behind a unified interface.
The Dispatcher Abstraction Layer
The checkRateLimit and peekRateLimit functions exported by lib/ai/dispatcher/rate-limit.ts provide the sole interface used by lib/auth/rate-limit.ts. This abstraction allows the authentication code to remain agnostic about whether counters reside in a distributed Redis cluster or a local JavaScript Map. When authRateLimited is called, it delegates immediately to these dispatcher functions with pre-constructed keys.
Fixed-Window Algorithm Implementation
Both backends implement a fixed-window rate limiting algorithm. The dispatcher increments a counter for the current time window and enforces an expiration TTL. In Redis, this maps to atomic INCR followed by EXPIRE commands. In the in-memory fallback, the same logic applies to JavaScript Map entries with timeout handling.
Upstash Redis Backend
When the environment provides a valid UPSTASH_REDIS_URL, the dispatcher initializes a Redis client and persists counters to external storage.
Privacy-Preserving Key Hashing
To prevent storing personally identifiable information in Redis, the auth module passes keys through the opaque() function before transmission. This function computes a SHA-256 hash of the IP address or user identifier, creating opaque tokens like auth:login:ip:a1b2c3.... Because raw client data never leaves the application server, the implementation maintains strict privacy even when using third-party Redis hosting.
Redis Command Strategy
The dispatcher executes atomic INCR and EXPIRE operations for each request. First, INCR increments the counter for the hashed key. If this is the first request in the window, EXPIRE sets the TTL to enforce the fixed-window duration. This approach ensures accurate counting without race conditions in distributed deployments.
In-Memory Fallback for Self-Hosted Deployments
If the Redis connection cannot be established—either because UPSTASH_REDIS_URL is undefined or the service is unreachable—the dispatcher transparently switches to a process-local Map storage.
Process-Local Map Storage
The fallback mechanism maintains counters in a JavaScript Map object local to the Node.js process. This implementation mirrors the Redis logic: values increment identically, and TTLs are enforced using JavaScript timeouts or timestamp comparisons. As noted in the source comments at lines 19-20 of rate-limit.ts, this fallback guarantees that rate limiting remains functional even on single-node self-hosted installations where Redis is not configured.
Runtime Backend Selection
The selection occurs at runtime within the dispatcher. The auth module calls checkRateLimit without specifying a storage engine, and the helper determines the active backend based on environment configuration. This design ensures zero-downtime operation across different infrastructure setups without code changes.
Implementation Details in lib/auth/rate-limit.ts
The auth-specific rate limiter constructs storage keys and orchestrates checks for both network-level and application-level identifiers.
Bucket Key Construction
The module generates two distinct bucket types for each action:
- IP-based buckets:
auth:{action}:ip:{opaque(ip)}(constructed at lines 89-91) - Identifier-based buckets:
auth:{action}:id:{opaque(identifier)}(constructed at lines 94-99)
These key patterns allow separate quotas for anonymous requests (by IP) and authenticated attempts (by user ID or email).
Two-Tier Checking Flow
For each authentication action, the limiter executes a sequential validation:
- Resolve the client IP using
clientIp(). - Check the IP bucket by calling
checkRateLimitwith the IP-based key. - Check the identifier bucket (if an email or token is provided) using the ID-based key.
- Block if either limit is exceeded—if
checkRateLimitreturnsallowed: false, the function returnstrueto signal rate limiting has triggered.
This dual-check prevents distributed attacks targeting single accounts while also protecting against brute-force attempts from specific networks.
Practical Usage Examples
The following patterns demonstrate standard integration for login protection and account lockout inspection.
Rate-Limited Login Endpoint
Use authRateLimited to block excessive login attempts before credential verification:
import { authRateLimited, AUTH_LIMITS } from "@/lib/auth/rate-limit";
async function handleLogin(email: string, password: string) {
// 1️⃣ Block the request if rate‑limit exceeded
if (await authRateLimited("login", email, AUTH_LIMITS.login)) {
return fail("Too many attempts", "RATE_LIMIT_EXCEEDED", 429);
}
// 2️⃣ Perform normal authentication …
const ok = await verifyPassword(email, password);
if (!ok) {
// Count a failed attempt for the identifier‑based bucket
await registrarFalhaDeLogin(email, AUTH_LIMITS.login);
return fail("Invalid credentials", "INVALID_CREDENTIALS", 401);
}
// 3️⃣ Successful login – no extra rate‑limit cost
return ok({ token: createSession(email) });
}
Peek Current Attempt Count
Use peekRateLimit to check lockout status without incrementing counters, useful for UI warnings:
import { peekRateLimit } from "@/lib/ai/dispatcher/rate-limit";
async function isAccountLocked(email: string) {
const attempts = await peekRateLimit(`auth:login_fail:id:${opaque(email)}`, 300);
return attempts >= AUTH_LIMITS.login.id!;
}
Summary
lib/auth/rate-limit.tsimplements the public API (authRateLimited,registrarFalhaDeLogin,contaBloqueadaPorFalhas) while delegating storage to the dispatcher.lib/ai/dispatcher/rate-limit.tsprovides the dual-backend abstraction, routing to Upstash Redis when available or a process-local Map as a fallback.- SHA-256 hashing via
opaque()ensures no raw IP addresses or emails are stored in external Redis instances. - Fixed-window algorithm uses
INCRandEXPIREcommands (or Map equivalents) to maintain accurate request counts with TTL enforcement. - Runtime backend selection allows the application to function out-of-the-box without Redis while scaling to distributed environments when configured.
Frequently Asked Questions
How does the rate limiter protect user privacy when using external Redis?
The auth module hashes all identifying information using the opaque() function before constructing Redis keys. By storing only SHA-256 hashes of IP addresses and identifiers using keys like auth:login:ip:{hash}, the system ensures that raw client data never leaves the application server, maintaining privacy even when using third-party Upstash Redis hosting.
What happens to rate limiting if the Redis connection fails?
The dispatcher automatically falls back to a process-local Map when Redis is unreachable or unconfigured. This fallback, documented at lines 19-20 of lib/ai/dispatcher/rate-limit.ts, maintains the same fixed-window counting logic in memory, ensuring that authentication rate limits continue to function on single-node deployments without external dependencies.
How are rate limit keys structured to support both IP and user-based tracking?
The system generates two key patterns: auth:{action}:ip:{opaque(ip)} for network-level throttling (lines 89-91) and auth:{action}:id:{opaque(identifier)} for account-level protection (lines 94-99). This dual-bucket approach allows separate configuration for anonymous request floods versus targeted brute-force attacks on specific user accounts.
Can the rate limiter operate without any external services?
Yes. When UPSTASH_REDIS_URL is not defined, the system uses the in-memory Map backend exclusively. This zero-dependency mode is ideal for self-hosted DeskcommCRM instances, providing full rate limiting protection without requiring Redis installation or network configuration, though counters will not persist across process restarts or share state across multiple server instances.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →