Skip to content

feat: token rotation, column encryption, HMAC auth, request coalescing#538

Merged
Smartdevs17 merged 1 commit into
Smartdevs17:mainfrom
vjuliaife:main
Jun 26, 2026
Merged

feat: token rotation, column encryption, HMAC auth, request coalescing#538
Smartdevs17 merged 1 commit into
Smartdevs17:mainfrom
vjuliaife:main

Conversation

@vjuliaife

Copy link
Copy Markdown
Contributor

Summary

Implements four security and performance issues in a single cohesive PR.


Issue #512 — Session Token Rotation (NIST SP 800-63B)

  • Opaque 32-byte refresh tokens; only SHA-256 hashes are stored in the database (raw tokens never persisted)
  • Token family tree: every issued token carries a familyId linking it to its chain
  • Replay detection: if a rotated (already-used) token is presented, the entire family is auto-revoked
    indicating token theft
  • Configurable absolute TTL (default 30 days) and sliding expiration (default 7 days inactivity)
  • Redis blacklist for immediate family revocation; falls back to DB query when Redis is unavailable
  • Dedicated refresh rate limit: 5 requests/minute per user via atomic Lua counter (on top of existing
    sliding-window limiter)
  • New routes on POST /api/v1/auth/login|refresh|revoke|revoke-all and GET /api/v1/auth/sessions for
    session management UI
  • Prisma RefreshToken model with familyId, tokenHash, absoluteExpiresAt, slidingExpiresAt,
    revokeReason
  • All token operations logged to auditService

New files: backend/src/auth/token-rotation.ts, backend/src/middleware/token-auth.ts,
backend/src/routes/token-refresh.ts


Issue #511 — Column-Level AES-256-GCM Encryption for PII

  • AES-256-GCM with a random 12-byte IV per value; format: $enc1$<iv>$<cipher>$<tag>
  • Tenant-specific DEKs derived via HKDF from COLUMN_ENCRYPTION_MASTER_KEY env var (envelope encryption)
  • Deterministic encryption for searchable fields (email, wallet address): IV derived from HMAC(key, fieldName+value) — enables exact-match WHERE queries without a plaintext search index
  • Key rotation: reEncrypt() helper uses COLUMN_ENCRYPTION_OLD_MASTER_KEY during transition window; DEK
    cache eviction via evictTenantKeyCache()
  • Transparent Prisma $extends middleware encrypts on write (create/update), decrypts on read (find*), and
    rewrites WHERE conditions for deterministic fields
  • Applied to: User.email, User.walletAddress, Payment.fromAddress, Payment.toAddress,
    SandboxAccount.email, SandboxAccount.walletAddress, AuditLog.ipAddress
  • Every decryption is logged to auditService for GDPR/SOC2 audit trail

New files: backend/src/encryption/column-encryptor.ts, backend/src/encryption/index.ts
Modified: backend/src/lib/prisma.ts — wraps Prisma client with withEncryptionMiddleware


Issue #510 — HMAC-SHA256 API Request Signing

  • Request signature payload: METHOD\nPATH\nTIMESTAMP\nNONCE\nSHA256(body)
  • Client headers: X-Signature: kid=<id>;hmac-sha256=<hex>, X-Timestamp: <unix ms>, X-Nonce: <random>
  • Server rejects requests with timestamp skew > 5 minutes
  • Nonce deduplication via Redis SETNX with 5-minute TTL; falls back to in-memory Map
  • Key rotation: multiple active SigningKey rows per tenant; old key expires after configurable overlap
    window (default 5 min) allowing in-flight requests to complete
  • Backward compatible: HMAC headers are optional; requests without them pass through (API keys still work)
  • In-memory key cache (5-minute TTL) with invalidateKeyCache() on revocation
  • Admin routes: GET/POST /api/v1/developers/signing-keys, DELETE /:keyId, POST /:keyId/rotate
  • SDK: HmacSigner class in packages/sdk/src/auth/hmac.ts for automatic request signing
  • Prisma SigningKey model with keyId, secretHash (never stores raw secret), isActive, expiresAt

New files: backend/src/middleware/hmac-auth.ts, backend/src/routes/signing-keys.ts,
packages/sdk/src/auth/hmac.ts
Modified: packages/sdk/src/index.ts — exports HmacSigner


Issue #509 — Backend Request Coalescing for Concurrent Identical GETs

  • In-process promise registry: first GET registers a pending promise; subsequent identical GETs await it
    instead of executing a new query
  • Coalesce key: SHA-256(method + path + querystring + auth-hash) — auth-context isolation prevents
    cross-user result sharing
  • Multi-instance coalescing: Redis distributed lock + result cache broadcasts completion to other instances
  • Configurable per endpoint: resultTtlMs (cache result after completion), timeoutMs (max wait for
    coalesced callers), enabled
  • Default opt-in endpoints: /api/v1/catalog, /api/v1/gas, /api/v1/pool/metrics
  • Error propagation: all waiters reject with the same error if the first request fails
  • Prometheus-style metrics: hit rate, average wait time, error count via GET /api/v1/coalesce/metrics
  • Memory GC: inflight promises are deleted from the registry immediately on completion or error

New file: backend/src/middleware/request-coalescer.ts


Schema Changes

Two new Prisma models added to backend/prisma/schema.prisma:

  • RefreshToken — token family tracking, hashed token storage, revocation metadata
  • SigningKey — HMAC key management with rotation support

Run prisma migrate dev to apply.

Test Plan

  • POST /api/v1/auth/login returns accessToken + refreshToken
  • POST /api/v1/auth/refresh with valid token issues new pair and revokes old
  • POST /api/v1/auth/refresh with already-rotated token returns TOKEN_FAMILY_COMPROMISED and revokes
    family
  • GET /api/v1/auth/sessions lists active token families
  • POST /api/v1/developers/signing-keys returns keyId + secret; secret not stored raw
  • POST /api/v1/developers/signing-keys/:id/rotate creates new key; old key expires after overlap
  • Request with valid HMAC headers passes; invalid signature → 401; replayed nonce → 401
  • Request without HMAC headers still passes (backward compat)
  • Column-level encryption: User.email stored encrypted in DB; queries by email still work
    (deterministic)
  • Concurrent identical GETs to /api/v1/catalog execute only one query; GET /api/v1/coalesce/metrics
    shows coalesced > 0

Closes #509
Closes #510
Closes #511
Closes #512

Smartdevs17#509, Smartdevs17#510, Smartdevs17#511, Smartdevs17#512)

Implements four security and performance improvements:

Issue Smartdevs17#512 — Refresh token rotation (NIST SP 800-63B)
- backend/src/auth/token-rotation.ts: opaque 32-byte tokens, SHA-256
  hashes in DB, token family tree with replay detection; reusing a
  rotated token auto-revokes the entire family (theft indicator)
- Configurable absolute TTL (30 days) and sliding expiration (7 days)
- Redis blacklist for immediate family revocation; falls back to DB
- Rate limit: 5 refresh requests/minute per user via Lua atomic counter
- Routes: POST /auth/login, /auth/refresh, /auth/revoke, /auth/revoke-all,
  GET /auth/sessions (session management UI)
- Prisma RefreshToken model with family tracking and revocation metadata

Issue Smartdevs17#511 — Column-level AES-256-GCM encryption for PII
- backend/src/encryption/column-encryptor.ts: AES-256-GCM with random
  IV per value; tenant DEKs derived via HKDF from COLUMN_ENCRYPTION_MASTER_KEY
- Deterministic encryption (HMAC-derived IV) for searchable fields
  (email exact-match lookups)
- Key rotation supported via COLUMN_ENCRYPTION_OLD_MASTER_KEY transition
- Prisma $extends middleware in encryption/index.ts: transparently
  encrypts on write, decrypts on read, rewrites WHERE for searchable fields
- Applied to User, Payment, SandboxAccount, AuditLog PII columns
- Audit log on every decryption; falls back to dev default in non-prod

Issue Smartdevs17#510 — HMAC-SHA256 server-to-server request signing
- backend/src/middleware/hmac-auth.ts: verifies X-Signature, X-Timestamp,
  X-Nonce; rejects requests outside ±5-minute window and replayed nonces
- Nonce dedup via Redis sorted set; falls back to in-memory Map
- Multi-key rotation: supports multiple active SigningKey rows per tenant
- Backward compatible: HMAC headers optional; API keys still work
- packages/sdk/src/auth/hmac.ts: HmacSigner class for client-side signing
- Routes: GET/POST/DELETE /developers/signing-keys, POST .../rotate
- Prisma SigningKey model with expiry and isActive for rotation overlap

Issue Smartdevs17#509 — Request coalescing for identical concurrent GET calls
- backend/src/middleware/request-coalescer.ts: in-process promise registry
  collapses concurrent identical GETs into one execution
- Key: SHA-256(method + path + query + auth-hash) — per-user isolation
- Multi-instance: Redis lock + result cache for cross-node coalescing
- Per-endpoint configuration (resultTtlMs, timeoutMs, enable/disable)
- Error propagation: all waiters receive the same error on failure
- Metrics: coalescing hit rate, average wait time via GET /coalesce/metrics
- Default opt-in: /api/v1/catalog, /api/v1/gas, /api/v1/pool/metrics

Closes Smartdevs17#509
Closes Smartdevs17#510
Closes Smartdevs17#511
Closes Smartdevs17#512
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

@vjuliaife is attempting to deploy a commit to the smartdevs17's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jun 26, 2026

Copy link
Copy Markdown

@vjuliaife Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Smartdevs17 Smartdevs17 merged commit 822b5d0 into Smartdevs17:main Jun 26, 2026
19 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants