- Bun v1.3.13+
- PostgreSQL database
- Redis instance
bun installCopy .env and configure your environment variables (see .env for defaults).
bunx prisma generate --schema=prisma/schema/schema.prisma
bunx prisma migrate dev --schema=prisma/schema/schema.prismabun run devStarts both the server (hot-reload) and worker in one terminal. Worker output is prefixed with [worker].
bun run build
bun run startAll endpoints are under /api/v1/auth.
Creates an account. Always returns the same generic message to prevent email enumeration.
{ "email": "user@example.com", "password": "securepass123", "name": "John" }Verifies email via magic link (click from email).
Query: ?token=<64-char-hex>&email=<email>
Verifies email manually.
{ "token": "64-char-hex", "email": "user@example.com" }Authenticates and returns JWT + session token.
{ "email": "user@example.com", "password": "securepass123" }- No email enumeration — all auth endpoints return generic messages
- Magic-link tokens —
crypto.randomBytes(32)→ 256-bit entropy, SHA-256 hashed in DB - Single-use — tokens marked with
usedAttimestamp after consumption - Short-lived — tokens expire in 15 minutes (configurable via
TOKEN_EXPIRY_MINUTES) - Old token invalidation — requesting a new token invalidates all previous unused tokens for that user+type
- Rate limiting — 10 requests per 15 minutes per email on auth routes
- Session tokens — stored as SHA-256 hashes; raw tokens returned only at creation
| Model | Purpose |
|---|---|
| User | email, password (hashed), role, verified, optional pending email |
| VerificationToken | hashed token, type (magic_link, reset_password, new_email), expiry, usedAt |
| Session | hashed token, refresh token, user-agent, IP, 30-day expiry |
Signup → generate token (raw + SHA-256)
→ invalidate old tokens for this user+type
→ store hash, return raw
→ email: GET /verify-email?token=<raw>&email=<email>
→ server hashes raw, finds record, marks usedAt
→ user verified, session created, JWT issued
Auth routes are limited to 10 requests per 15-minute window keyed by email address (IP fallback). Returns 429 Too Many Requests.
| Command | Description |
|---|---|
bun run dev |
Start dev server + worker |
bun run build |
Build server + worker |
bun run start |
Start production server + worker |
bun run typecheck |
TypeScript type checking |
bun run lint |
ESLint |
bun run format |
Prettier |
bun run test |
Jest tests |