Skip to content

Repository files navigation

keywarden

Auth and API-key custody engine — issuance, validation, and revocation for multi-tenant services. Built to reason clearly about token lifecycle, replay protection, and safe key revocation under concurrent access, not to be a CRUD-auth starter kit.

Stack: NestJS · PostgreSQL · Prisma 7 (driver adapter) · Redis · BullMQ · JWT · bcrypt · Docker Compose · Nginx


Why this exists

Most "auth boilerplate" repos stop at register/login. The interesting engineering problems in an API-key system show up after that: how you rotate refresh tokens without opening a replay window, how you revoke a key without leaving a stale-cache path to bypass it, and how your schema behaves when a user is deleted but their usage history needs to survive. This repo is where I work through those problems directly, without leaning on an off-the-shelf auth-as-a-service provider — the point is to own the decisions, not to configure someone else's.


Architecture & tradeoffs

1. Refresh token rotation with replay protection

Access tokens are short-lived JWTs. Refresh tokens are opaque, bcrypt-hashed before storage, and delivered via an httpOnly cookie. On every refresh, the old token is invalidated and a new one issued (rotation) — if a hashed refresh token is presented that's already been rotated out, that's treated as a replay signal and the session is revoked rather than silently re-issued. This trades a small amount of complexity (tracking token lineage) for closing the window where a leaked refresh token can be reused indefinitely.

2. Schema design: SetNull on UsageLog foreign keys

UsageLog rows reference the ApiKey that generated them. On key deletion, the FK is set to SetNull rather than Cascade — usage history is treated as an audit trail that should outlive the key it was generated by. This is a deliberate tradeoff: it means usage queries need to handle a nullable key reference, but it prevents deleting a key from silently erasing billing/usage evidence.

3. Revoke endpoint: hard-delete vs. soft-delete (open decision)

Currently working through DELETE /api-key/:id. Given the SetNull design above, a hard delete on ApiKey is schema-safe (usage logs survive via SetNull), but a soft-delete (revokedAt timestamp, key excluded from active-key queries) preserves the ability to show "this key existed and was revoked on X" in an audit UI, and makes accidental revocation reversible. Leaning toward soft-delete for auditability, at the cost of every active-key query needing a revokedAt IS NULL filter.

4. Prisma 7 driver adapter migration

Migrated PrismaService to the Prisma 7 driver adapter pattern rather than staying on the legacy engine binary. This decouples the query engine from a bundled native binary, which matters for the Docker image size and for avoiding platform-specific engine mismatches between local dev and the containerized build.

5. Environment separation

.env (local) and .env.docker (containerized) are kept separate and loaded via Compose's env_file:, with Joi-based validation wired into ConfigModule so a missing or malformed env var fails at boot, not at first request.

(Benchmarks — validation latency, throughput under load — are on the roadmap once the revoke endpoint lands; not included here to avoid citing numbers that haven't been measured yet.)


What's implemented

  • Auth module — register, login (access + refresh JWT pair), bcrypt-hashed refresh tokens, httpOnly cookies, rotation with replay protection, logout with session revocation
  • Global JwtAuthGuard with a @Public() opt-out decorator (Reflector.createDecorator)
  • API key module — create (prefixed key via crypto.randomBytes, bcrypt hash storage, masked keyPreview for display), list (user-scoped, active-only, newest-first, Prisma select-level filtering)
  • Prisma schemaUser, RefreshToken, ApiKey, UsageLog with hashed fields and indexed FKs
  • Full Docker Compose dev environment — Postgres, Redis, Nginx, healthcheck-gated startup, multi-stage Dockerfile with Prisma client generation at a custom output path

In progress

  • DELETE /api-key/:id revoke endpoint (see tradeoff #3 above)
  • Rate limiting via Redis + BullMQ
  • Usage logging middleware

Running locally

cp .env.example .env
docker compose up --build

Postgres, Redis, and Nginx come up behind healthchecks before the API container starts. Prisma migrations run on boot.


Testing

Jest for unit tests, supertest for E2E. Endpoint testing during development via Postman; DB state verified via Prisma Studio / raw SQL where a UI would hide the actual query behavior.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages