# Central SaaS Phase 2 Review: Developer Auth & Sessions

**Date:** 2026-05-19
**Status:** COMPLETE
**VPS:** root@187.77.140.128
**Project root:** /opt/devxura

---

## What Was Implemented

Added real developer authentication and session handling to the central SaaS, replacing the anonymous/no-auth state from Phase 1.

### 1. Password Handling
- **scrypt-based hashing** (Node built-in `crypto.scryptSync`, 64-byte output, 16-byte salt)
- **Timing-safe comparison** (`crypto.timingSafeEqual`) to prevent timing attacks
- **Minimum password length: 8 characters** enforced at registration
- Passwords stored as `password_hash` + `password_salt` in `developers` table
- No extra npm dependencies required

### 2. Session Management
- **Token-based sessions** (80-char hex random tokens via `crypto.randomBytes`)
- **SHA-256 hashed** before storage in `sessions` table (never store raw tokens)
- **24-hour TTL** with `expires_at` column enforcement
- Session tokens passed via `Authorization: Bearer <token>` header
- Indexed on `token_hash` and `developer_id` for fast lookups

### 3. Auth Routes Added
| Route | Method | Auth | Purpose |
|-------|--------|------|---------|
| `/auth/register` | POST | No | Create developer account with password, returns session token |
| `/auth/login` | POST | No | Validate email/password, create session, return token |
| `/auth/logout` | POST | Yes | Destroy session (invalidate token) |
| `/auth/me` | GET | Yes | Return authenticated developer profile |

### 4. Protected Routes
- **`PATCH /licenses/state`** — now requires valid session; developer can only modify their own licenses (cross-developer access returns 403 forbidden)
- **`POST /auth/logout`** — requires valid session
- **`GET /auth/me`** — requires valid session

### 5. Public Routes (unchanged)
- `GET /health`, `GET /plans` — always public
- `POST /signup` — remains public (backward compatible, no password required, no session returned)
- `POST /runtime-nodes/register`, `POST /runtime-nodes/check-in` — public (nodes auth with license keys, not sessions)

### 6. Validation / Error Handling
- `401 unauthorized` — missing or expired session
- `401 invalid_credentials` — wrong email or password
- `400 credentials_required` — missing email or password
- `400 password_required` — registration without password
- `400 weak_password` — password shorter than 8 chars
- `400 duplicate_email` — email already registered
- `400 forbidden` — cross-developer license access
- `500 internal_error` — unexpected server errors

---

## Files Changed

| File | Change |
|------|--------|
| `apps/central-app/src/auth.mjs` | NEW — 88 lines, password hashing + session management |
| `apps/central-app/src/server.mjs` | UPDATED — 368 lines, auth routes + route protection |
| `packages/db/migrations/0006_auth_sessions.sql` | NEW — ALTER developers + CREATE sessions table |

---

## How Sessions/Auth Work

1. **Registration:** Developer sends `POST /auth/register` with email, password, name, planId. Server creates developer row (with scrypt-hashed password), creates trial + license, creates session token, returns session.
2. **Login:** Developer sends `POST /auth/login` with email + password. Server looks up developer by email, verifies password with `crypto.timingSafeEqual`, creates new session, returns token.
3. **Authenticated requests:** Client sends `Authorization: Bearer <token>` header. Server hashes token, looks up in sessions table (with expiry check), returns developer if valid.
4. **Logout:** Client sends `POST /auth/logout` with valid session. Server deletes session row.
5. **Token never exposed:** Only the SHA-256 hash of the token is stored in the database.

---

## What Was Verified

All 13 tests passed on VPS against Postgres:

1. ✅ Register with password → session token returned
2. ✅ GET /auth/me → authenticated developer profile
3. ✅ Login → new session token
4. ✅ Wrong password → 401 invalid_credentials
5. ✅ Missing credentials → 400 credentials_required
6. ✅ Protected route without auth → 401 unauthorized
7. ✅ Protected route with auth → 200, state updated
8. ✅ Cross-developer license access → 403 forbidden
9. ✅ Logout → session destroyed
10. ✅ Session invalid after logout → 401
11. ✅ Duplicate email → 400 duplicate_email
12. ✅ Weak password → 400 weak_password
13. ✅ Old signup still works (no password required) → backward compatible

---

## What Is Still Pending For Phase 3

- **Passwordless login / magic links** — Phase 3 could add email-based login
- **Token refresh** — sessions currently expire after 24h; refresh tokens would extend sessions
- **Rate limiting** — login attempts are not rate-limited
- **Account settings** — no endpoint to change password, email, or name
- **Multi-session support** — multiple devices can create independent sessions (works but no management UI)
- **Stripe billing integration** — explicitly excluded per Phase 2 spec
- **Team features** — explicitly excluded per Phase 2 spec

---

## Blockers Found

**None.** Phase 2 is fully functional and passed all tests.
