# How the DeskcommCRM Authentication Rate Limiter Combines Upstash Redis with In-Memory Fallback

> Discover how DeskcommCRM's rate limiter seamlessly integrates Upstash Redis with an in-memory fallback for robust authentication. Learn about its efficient fallback strategy.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: internals
- Published: 2026-09-13

---

**The rate limiter in [`lib/auth/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/rate-limit.ts) delegates counting operations to `checkRateLimit` and `peekRateLimit` from [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) provide the sole interface used by [`lib/auth/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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:

1. **Resolve the client IP** using `clientIp()`.
2. **Check the IP bucket** by calling `checkRateLimit` with the IP-based key.
3. **Check the identifier bucket** (if an email or token is provided) using the ID-based key.
4. **Block if either limit is exceeded**—if `checkRateLimit` returns `allowed: false`, the function returns `true` to 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:

```typescript
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:

```typescript
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.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/rate-limit.ts)** implements the public API (`authRateLimited`, `registrarFalhaDeLogin`, `contaBloqueadaPorFalhas`) while delegating storage to the dispatcher.
- **[`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts)** provides 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 `INCR` and `EXPIRE` commands (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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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.