Production URL: zk-password-vault.vercel.app
A security-first, monorepo password manager implementing a zero-knowledge cryptographic architecture — the server is treated as completely untrusted. All vault encryption, key derivation, and recovery operations are performed entirely client-side using the native Web Crypto API. The backend never handles plaintext credentials, master passwords, or raw key material.
Managed by TurboRepo, this monorepo co-locates the Next.js 14 frontend and its API Route Handlers in a single deployable package.
zk-password-manager/
├── apps/
│ ├── web/ # Next.js 14 (App Router) — UI + co-located API Route Handlers
│ │ └── src/
│ │ ├── app/api/ # Route Handlers (auth/*, vault/*)
│ │ ├── components/ # VaultDashboard, AuthForm, RecoverySetup
│ │ ├── lib/ # server-auth, validation, rate-limit, services
│ │ └── utils/ # encryption.utils, password-strength
│ └── api/ # Standalone API server (development)
├── packages/
│ ├── crypto/ # Client-side Web Crypto wrappers (PBKDF2, AES-GCM, HKDF)
│ ├── database/ # Prisma ORM schema + singleton client
│ └── shared/ # Zod validation schemas + shared TypeScript types
├── package.json # Monorepo root
└── turbo.json # Turbo pipeline configuration
| Key | Derivation | Purpose |
|---|---|---|
| KEK (Key Encryption Key) | PBKDF2-HMAC-SHA256, 100k iterations, unique vaultSalt |
Wraps/unwraps the VEK; never sent to server |
| VEK (Vault Encryption Key) | 256-bit random AES key generated at registration | Encrypts all vault items (AES-GCM 256-bit) |
| Recovery KEK | HKDF-SHA256 from 256-bit recovery key | Wraps a copy of the VEK for account recovery |
Changing the master password only requires re-wrapping the VEK — vault item ciphertexts are untouched.
- Client sends only the authentication hash (PBKDF2 output), never the raw master password.
- Server re-hashes it with Argon2id before storing or comparing.
- Database-backed
Sessiontable stores SHA-256 hashes of JWT access tokens. - Every authenticated request verifies both the JWT signature and an active session record.
- Sessions are revoked on: logout, logout-all, master password change, account recovery, 2FA reconfiguration, and account deletion (cascade).
- Duplicate sessions for the same device are prevented during login.
- Expired sessions are cleaned up automatically during authentication.
- Device information (browser + OS) is stored as the raw User-Agent string in the session record, then parsed dynamically into structured metadata (browser, OS, device type) using a shared monorepo utility, which the UI formats for display.
- Session revocation requests are routed to a dedicated endpoint path (
POST /api/auth/sessions/revoke) aligned across both Next.js and Express APIs.
| Step | Client Actions | Server Actions |
|---|---|---|
| 1 — Verify Identity | Derive Recovery KEK & hash from recovery key; submit hash | Lookup user by hash; return recovery-wrapped VEK + pre-generate new 2FA |
| 2 — New Password | Decrypt VEK with Recovery KEK; re-wrap with new KEK + new salt | (No DB writes yet) |
| 3 — Force 2FA | Scan new QR code; submit TOTP | Verify TOTP; atomic transaction: update password hash, wrapped VEK, 2FA secret; revoke all sessions; clear cookies; redirect to login with transient success message |
All confirmation and alert flows (Logout All Devices, Revoke Session, Delete Account, Regenerate Recovery Key, Reconfigure 2FA, Recovery Key Setup) use a unified custom modal system with a consistent dark design — no native browser alert(), confirm(), or prompt() dialogs.
A premium, dedicated Security & Architecture page (/security) outlines the cryptographic primitives and threat model of the password manager in a structured, benefit-oriented layout. It explains the purpose (why) and execution (how) of local key derivation, client-side encryption, emergency recovery, and infrastructure design.
To prevent generic request failure errors inside modals, a global Axios response interceptor intercepts all 401 Unauthorized responses. If a session expires or is invalidated elsewhere:
- Wipes the browser's local in-memory keys using
EncryptionService.clearSession(). - Automatically logs the user out and redirects to the login screen with an
error=session-expiredparameter. - Renders a clean red warning banner: "Your session has expired. Please sign in again."
Master Password ──► PBKDF2-HMAC-SHA256 (100k iter, vaultSalt)
│
┌──────────┴──────────┐
▼ ▼
Auth Hash (→ server) KEK (in-memory, non-extractable)
│
Wraps/Unwraps
▼
VEK (AES-GCM 256-bit)
│
Encrypts all vault items
[Browser] [Server DB]
Plaintext ──► AES-GCM(VEK) ──────────► encryptedBlob, iv, authTag
VEK ──► AES-GCM(KEK) ──────────► encryptedVEK, vekIV, vekAuthTag
- Zod schemas on every state-changing endpoint (registration, login, vault CRUD, recovery)
- Base64 validation on all cryptographic payload fields (IV, authTag, wrapped VEK)
- UUID validation on path parameters
- Upstash distributed rate limiting — 10 req/60s per IP across serverless instances; fails open if Redis is unavailable
- Account lockout — database-enforced 10-minute lockout after 5 failed login attempts
- Double verification on account deletion — requires both master password and TOTP
npm installCreate apps/web/.env.local (see .env.example):
DATABASE_URL="mysql://root:password@127.0.0.1:3306/zk_password_manager"
JWT_SECRET="your-super-secure-random-jwt-signing-key"
UPSTASH_REDIS_REST_URL="https://your-upstash-redis-url.upstash.io"
UPSTASH_REDIS_REST_TOKEN="your_upstash_redis_token"DATABASE_URL="mysql://root:password@127.0.0.1:3306/zk_password_manager" \
./packages/database/node_modules/.bin/prisma db push \
--schema=packages/database/prisma/schema.prismanpm run dev
# App served at http://localhost:3000npm run build| Technology | Purpose |
|---|---|
| Next.js 14 (App Router) | React UI + co-located serverless API Route Handlers |
| TypeScript | End-to-end type safety across all packages |
| Web Crypto API | Native browser AES-GCM, PBKDF2, HKDF — hardware-accelerated |
| Argon2id | Memory-hard server-side password hashing |
| Prisma ORM + MySQL | Type-safe database access; relational integrity with cascading deletes |
| JWT + HttpOnly Cookies | Stateless auth tokens; XSS-resistant transport |
| Otplib + QRCode | RFC 6238 TOTP 2FA with QR code generation |
| Upstash Redis | Distributed rate limiting across serverless instances |
| Zod | Runtime schema validation on all API payloads |
| TurboRepo | Monorepo orchestration, build caching, workspace dependency management |