# Authentication Architecture

This document explains the design decisions and security features of the Super Sync Server authentication system.

## Overview

The server uses **stateless JWT authentication** with:

- **Token versioning** for revocation (no blacklist needed)
- **Email verification** for new accounts
- **Account lockout** protection against brute force

## Design Decisions

### Why Stateless JWTs (Not Stored in DB)

JWTs are issued but never persisted to the database.

**Benefits:**

- No session store needed - scales horizontally without shared state
- Reduced DB load - no lookup on every authenticated request
- Simpler architecture - no session cleanup jobs

**Trade-off:**

- Cannot revoke individual tokens - only all tokens via version increment
- Token remains valid until expiry (7 days) unless version bumped

### Why Token Versioning (Not Blacklisting)

Each user has a `tokenVersion` integer. JWTs include this version, and verification checks it matches the current DB value.

```
Token issued with version 5 → User changes password → DB version becomes 6 → Token rejected (5 ≠ 6)
```

**Benefits:**

- O(1) storage per user (single integer vs. unbounded blacklist)
- No cleanup jobs needed (blacklists grow indefinitely)
- Instant revocation of ALL tokens with one DB update

**Trade-off:**

- Cannot selectively revoke one device's token
- All devices must re-authenticate when version changes

**When version increments:**

- Password change
- Explicit "log out all devices" action
- Token replacement (`/api/replace-token`)

### Why Verification Tokens Are Plain Strings

Email verification tokens are stored as plain 64-character hex strings (32 random bytes).

**Why this is acceptable:**

1. **Cryptographically unguessable** - 256 bits of entropy from `crypto.randomBytes(32)`
2. **One-time use** - Cleared immediately after successful verification
3. **Short-lived** - 24-hour expiry
4. **Low-value target** - Only activates an account, grants no ongoing access

**Trade-off:**

- If DB is compromised, attacker could verify pending (unverified) accounts
- Minimal impact: they still don't know the password

**Alternative considered:** Hashing verification tokens (like password reset tokens in some systems) would add complexity with minimal security benefit for this use case.

### Why bcrypt with 12 Rounds

**Why bcrypt:**

- Industry standard, battle-tested
- Built-in salt generation
- Resistant to GPU/ASIC attacks (memory-hard)

**Why 12 rounds:**

- ~250ms on modern hardware (balances security and UX)
- OWASP recommends 10+ rounds
- Adjustable if hardware improves

## Security Features

| Feature                  | Implementation        | Value               |
| ------------------------ | --------------------- | ------------------- |
| Password hashing         | bcrypt                | 12 rounds           |
| Password minimum         | Zod validation        | 12 characters       |
| JWT signing              | HMAC-SHA256           | Secret min 32 chars |
| JWT expiry               | Configurable          | 7 days              |
| Verification token       | `crypto.randomBytes`  | 32 bytes (256 bits) |
| Verification expiry      | Time-based            | 24 hours            |
| Lockout threshold        | Failed attempts       | 5 attempts          |
| Lockout duration         | Time-based            | 15 minutes          |
| Timing attack mitigation | Dummy hash comparison | Always compare      |

### Timing Attack Mitigation

Even when a user doesn't exist, the login flow compares the provided password against a dummy hash. This ensures the response time is consistent whether the user exists or not, preventing attackers from enumerating valid emails.

```typescript
const dummyHash = '$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW';
const hashToCompare = user ? user.passwordHash : dummyHash;
await bcrypt.compare(password, hashToCompare);
```

## Token Lifecycle

```
┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Register   │────▶│ Verification     │────▶│ Verified        │
│  (email +   │     │ Token (24h)      │     │ Account         │
│  password)  │     │ sent via email   │     │                 │
└─────────────┘     └──────────────────┘     └────────┬────────┘
                                                      │
                                                      ▼
                                             ┌─────────────────┐
                                             │  Login          │
                                             │  (email +       │
                                             │  password)      │
                                             └────────┬────────┘
                                                      │
                                                      ▼
                                             ┌─────────────────┐
                                             │  JWT (7 days)   │
                                             │  contains:      │
                                             │  - userId       │
                                             │  - email        │
                                             │  - tokenVersion │
                                             └────────┬────────┘
                                                      │
                              ┌────────────────────────┴────────────────────────┐
                              │                                                 │
                              ▼                                                 ▼
                    ┌─────────────────┐                               ┌─────────────────┐
                    │ Token expires   │                               │ Password change │
                    │ (after 7 days)  │                               │ tokenVersion++  │
                    │                 │                               │                 │
                    │ User must       │                               │ ALL tokens      │
                    │ re-login        │                               │ invalidated     │
                    └─────────────────┘                               └─────────────────┘
```

## API Reference

See [README.md](../README.md) for endpoint documentation.

**Password requirements:**

- Minimum 12 characters (validated via Zod schema in `api.ts`)

**JWT Secret requirements:**

- Minimum 32 characters
- Generate with: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`

## Configuration

All auth-related constants are defined in `src/auth.ts`:

```typescript
const MIN_JWT_SECRET_LENGTH = 32;
const BCRYPT_ROUNDS = 12;
const JWT_EXPIRY = '7d';
const VERIFICATION_TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
const MAX_FAILED_LOGIN_ATTEMPTS = 5;
const LOCKOUT_DURATION_MS = 15 * 60 * 1000; // 15 minutes
```

To modify these values, edit `src/auth.ts` and rebuild.

## Future Considerations

Features not currently implemented but could be added:

- **2FA/MFA** - TOTP or WebAuthn
- **Refresh tokens** - Separate short-lived access + long-lived refresh
- **Per-device token revocation** - Track device IDs in JWT
- **Rate limiting per-user** - Currently only IP-based for auth endpoints
