randomayzer/docs/PRODUCTION_GUARDS.md

4.8 KiB

Randomayzer — Production Guards & Security Architecture

This document details the production integrity guards, concurrency safeguards, rate limiting, and idempotency semantics implemented in Randomayzer.


1. Idempotency Hardening & Request Fingerprinting

Contract

The API supports the standard Idempotency-Key HTTP header on state-mutating endpoints:

  • POST /api/giveaways (Giveaway creation)
  • POST /api/giveaways/[id]/participants (Participant import & enrichment)
  • POST /api/giveaways/[id]/snapshot (Participant snapshot locking)

Semantics

  1. Scoped Storage Key: Keys are composite and scoped by operation, giveawayId, and idempotencyKey: format: ${operation}:${giveawayId || 'global'}:${key} This prevents cross-endpoint and cross-giveaway key collision.
  2. Request Fingerprinting: Every request computes a canonical SHA-256 fingerprint: requestFingerprint = SHA256(canonicalStringify(requestPayload))
  3. Replay vs Conflict Handling:
    • Same Key + Same Request: Returns the previously cached status code and response body without re-executing.
    • Same Key + Different Request: Throws HTTP 409 Conflict with error code IDEMPOTENCY_KEY_REUSED.
  4. Key Validation & TTL:
    • Maximum key length is 128 characters (exceeding length returns 400 VALIDATION_ERROR).
    • Default TTL is 5 minutes with proactive cleanup and upper memory bounds.

2. Rate Limiting & Client Identity Resolution

Centralized Client IP Resolution (src/lib/client-ip.ts)

  • Untrusted Proxy Mode (Default): When TRUST_PROXY !== 'true', user-supplied X-Forwarded-For, X-Real-IP, or CF-Connecting-IP headers are strictly ignored to prevent IP spoofing attacks. The direct socket connection IP is used.
  • Trusted Proxy Mode (TRUST_PROXY=true): When deployed behind a verified reverse proxy (e.g. Nginx, Cloudflare, AWS ALB), TRUST_PROXY=true must be set. The resolver:
    • Enforces a maximum header length of 1024 characters (oversized headers are rejected as malformed).
    • Parses multi-value proxy chains (client, proxy1, proxy2), extracting and validating the leftmost IP.
    • Normalizes IPv4, IPv6 (including IPv4-mapped IPv6 ::ffff:192.0.2.1 and loopbacks ::1).
    • Validates IP syntax against IPv4/IPv6 standards.

Memory Limiter vs Multi-Instance Deployments

  • The built-in SlidingWindowRateLimiter is optimized for single-instance, serverless dev, and test environments.
  • In multi-instance or horizontal cluster deployments, rate limiting must be offloaded to an edge layer (e.g., Cloudflare Rate Limiting, Nginx limit_req) or a shared distributed cache (Redis/Valkey).

3. Winner Count Contract (Zero Under-Delivery)

Contract

Before conducting any draw, the system enforces:

\text{winnersCount} + \text{reserveWinnersCount} \le \text{eligibleParticipantsCount}

If the requested winners count plus reserve exceeds the locked snapshot's eligible participants:

  • The system returns HTTP 400 VALIDATION_ERROR (or 409 CONFLICT).
  • The system NEVER silently clamps or reduces the winners count.

4. Draw Concurrency & Terminal Retry Contract

Atomic Conditional Execution

  • Draw execution (POST /api/giveaways/[id]/draw) requires the giveaway to be in SNAPSHOT_LOCKED status with an existing snapshot.
  • The state transition from SNAPSHOT_LOCKED to DRAWN occurs atomically inside a database transaction (updateMany({ where: { id, status: 'SNAPSHOT_LOCKED' } })).
  • In a concurrent race (e.g. 20 or 100 simultaneous draw requests), exactly one request acquires the lock and transitions to DRAWN. All competing requests receive 409 Conflict.

Terminal State Replay (DRAW_ALREADY_COMPLETED)

  • Once a giveaway is in DRAWN or PUBLISHED status, subsequent draw attempts return:
    {
      "success": false,
      "error": {
        "code": "DRAW_ALREADY_COMPLETED",
        "message": "Giveaway has already been drawn and finalized. Repeat draws are not permitted."
      }
    }
    
  • Clients and UI treat DRAW_ALREADY_COMPLETED as a terminal final status.

5. Summary of Environment Variables

Variable Type Default Description
NODE_ENV string development Environment mode (production, test, development)
TRUST_PROXY boolean (true/false) false Enable only behind trusted upstream proxies
ALLOW_MEMORY_IDEMPOTENCY boolean false Silence production warning for memory idempotency in single-instance
ALLOW_MEMORY_RATE_LIMITER boolean false Silence production warning for memory rate limiter in single-instance
USE_VK_MOCK boolean false Explicitly enable VkMockProvider (forbidden in production unless true)
VK_SERVICE_TOKEN string undefined VK Application Service Access Token