Skip to content

Commit 3a881a2

Browse files
committed
File uploads, better UI, UA restrictions
Restrict use to "Cloud Phone" user agent Allow QR uploads file image files Better UI (colors, sizing, blink animation, etc) Soft keys and keyboard navigation
1 parent e5e3e0d commit 3a881a2

8 files changed

Lines changed: 1090 additions & 78 deletions

File tree

CLAUDE.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
MFA Sync Service - A secure, ephemeral MFA secret key sharing service designed for devices without cameras. Allows secure transfer of TOTP secrets via short-lived, encrypted, PIN-based exchanges.
8+
9+
## Essential Commands
10+
11+
### Development
12+
```bash
13+
# Install dependencies
14+
npm install
15+
16+
# Start development server (auto-restarts Redis daemon + Node with --watch)
17+
npm run dev
18+
19+
# Start production server
20+
npm start
21+
```
22+
23+
### Docker Operations
24+
```bash
25+
# Build Docker image
26+
npm run docker:build
27+
28+
# Start services (app + Redis + nginx)
29+
npm run docker:run
30+
31+
# Stop services
32+
npm run docker:stop
33+
34+
# View logs
35+
npm run docker:logs
36+
```
37+
38+
### Code Quality
39+
```bash
40+
# Format code
41+
npx prettier --write .
42+
```
43+
44+
## Architecture
45+
46+
### Core Technology Stack
47+
- **Runtime**: Node.js 24+ with ES modules (`"type": "module"`)
48+
- **Framework**: Express 5.x
49+
- **Storage**: Redis (in-memory ephemeral storage with TTL)
50+
- **Security**: Helmet, CORS, express-rate-limit, AES-256-GCM encryption
51+
52+
### Key Architectural Patterns
53+
54+
**Ephemeral Secret Exchange Flow**:
55+
1. Admin device scans QR code → extracts TOTP secret
56+
2. POST `/api/store` → encrypts secret with random key, generates random 6-digit PIN, stores in Redis with TTL
57+
3. Feature phone retrieves via POST `/api/retrieve` with PIN
58+
4. Secret auto-deletes after retrieval (one-time use) or on TTL expiration
59+
60+
**Security Layers**:
61+
- **Encryption at Rest**: Secrets encrypted with AES-256-GCM before Redis storage, unique key per secret
62+
- **Ephemeral Storage**: Redis TTL (default 120s) + immediate deletion after retrieval
63+
- **Rate Limiting**: 10 secrets per 15min (store), 20 attempts per 5min (retrieve)
64+
- **CSP + SRI**: Nonce-based Content Security Policy with Subresource Integrity hashes
65+
66+
### Critical Files
67+
68+
**`server.js`** (main application):
69+
- Express server setup with security middleware
70+
- Four main endpoints: `/api/store`, `/api/retrieve`, `/api/health`, `/api/check/:pin`
71+
- Encryption/decryption functions using `crypto.createCipheriv/createDecipheriv`
72+
- PIN generation: `generatePIN()` - creates 6-digit random PIN
73+
- Note: Uses custom middleware for SRI injection and CSP nonces
74+
75+
**`middleware/sri-injector.js`**:
76+
- Injects Subresource Integrity (SRI) hashes into HTML responses
77+
- Automatically adds `integrity="sha384-..."` to `<script>` and `<link>` tags
78+
- Uses Cheerio for HTML parsing and manipulation
79+
- Manages file hash cache via `HashCache`
80+
81+
**`middleware/csp-nonce.js`**:
82+
- Generates unique nonce per request for Content Security Policy
83+
- Implements strict CSP: blocks inline scripts/styles without nonce, blocks all frames, enforces HTTPS
84+
- Stores nonce in `res.locals.nonce` for use by SRI injector
85+
86+
**`middleware/static-html.js`**:
87+
- Custom static file middleware for serving HTML with security attributes
88+
- Coordinates CSP nonce injection with SRI hash injection
89+
90+
**`public/` directory**:
91+
- `index.html`: Feature phone interface (PIN entry + TOTP display)
92+
- `sync.html`: Admin interface (QR scanner + secret submission)
93+
- All use inline JavaScript with Web Crypto API for client-side TOTP generation
94+
95+
### Redis Data Structure
96+
97+
```
98+
Key: mfa:{pin}
99+
Value: JSON {
100+
encrypted: string, // AES-256-GCM encrypted secret
101+
iv: string, // Initialization vector (hex)
102+
authTag: string, // GCM auth tag (hex)
103+
encryptionKey: string, // 32-byte key (hex)
104+
label: string, // Account label
105+
issuer: string, // Service issuer
106+
createdAt: number // Timestamp
107+
}
108+
TTL: 120 seconds (configurable via PIN_TTL)
109+
110+
Key: mfa:{pin}:retrieved
111+
Value: "true" | "false"
112+
TTL: Matches main key TTL
113+
```
114+
115+
### Environment Variables
116+
117+
Required/important configuration:
118+
- `PORT` (default: 3000) - Server port
119+
- `REDIS_URL` (default: `redis://localhost:6379`) - Redis connection string
120+
- `ALLOWED_ORIGINS` (default: `*`) - CORS origins (comma-separated for multiple)
121+
- `PIN_TTL` (default: 120) - PIN expiration time in seconds
122+
- `NODE_ENV` (default: development) - Environment mode
123+
124+
## Important Implementation Details
125+
126+
### Encryption Implementation
127+
- Algorithm: AES-256-GCM (authenticated encryption)
128+
- Each secret gets a unique random 32-byte encryption key
129+
- Key is stored alongside encrypted data in Redis (not separately)
130+
- IV (Initialization Vector) is 16 bytes random per encryption
131+
- Auth tag ensures integrity and authenticity
132+
133+
### Rate Limiting Strategy
134+
- Store endpoint: 10 requests per 15 minutes (prevents spam)
135+
- Retrieve endpoint: 20 requests per 5 minutes (allows reasonable retry attempts but prevents brute force)
136+
- Rate limits are IP-based via express-rate-limit
137+
138+
### Secret Validation
139+
- TOTP secrets must match regex: `/^[A-Z2-7]+=*$/` (Base32 alphabet)
140+
- PINs must match: `/^\d{6,8}$/` (6-8 digits numeric only)
141+
142+
### One-Time Retrieval Mechanism
143+
1. Check `mfa:{pin}:retrieved` flag before allowing retrieval
144+
2. If "true", return 410 Gone status
145+
3. On successful retrieval: set flag to "true" with KEEPTTL, delete main key
146+
4. This ensures secrets can only be retrieved once even within TTL window
147+
148+
## Working with This Codebase
149+
150+
### Adding New API Endpoints
151+
Follow the existing pattern in `server.js`:
152+
- Apply appropriate rate limiters
153+
- Validate input with regex before processing
154+
- Use try-catch with proper error logging
155+
- Return consistent JSON error format: `{ error: "message" }`
156+
157+
### Modifying Security Policies
158+
- CSP directives: Edit `middleware/csp-nonce.js`
159+
- SRI behavior: Edit `middleware/sri-injector.js`
160+
- Rate limits: Edit limiters in `server.js` (lines 49-59)
161+
162+
### Deployment Considerations
163+
- Always use HTTPS in production (nginx reverse proxy included in docker-compose)
164+
- Set restrictive `ALLOWED_ORIGINS` (never use `*` in production)
165+
- Monitor Redis memory usage (configured with 256MB max + LRU eviction)
166+
- Keep `PIN_TTL` short (5-10 minutes recommended for security)

0 commit comments

Comments
 (0)