# Auth Redirect Loop - Visual Diagram

## Current Behavior (BROKEN)

```
┌──────────────────────────────────────────────────────────────────┐
│ User clicks "Dev Login"                                          │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ GET /dev/login                                                   │
│                                                                  │
│ 1. create_dev_session(me) → returns "abc123xyz"                 │
│ 2. response.set_cookie("session", "abc123xyz")                  │
│ 3. flash("DEV MODE: Logged in")  ← This triggers Flask session! │
│    Flask writes: session = {_flashes: ["message"]}              │
│ 4. return redirect("/admin/")                                   │
│                                                                  │
│ ⚠️  Cookie "session" is now Flask session data, NOT auth token! │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ Browser → GET /admin/                                            │
│ Cookie: session={_flashes: ["message"]}  ← WRONG DATA!           │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ @require_auth decorator                                          │
│                                                                  │
│ 1. session_token = request.cookies.get("session")               │
│    → Gets: {_flashes: ["message"]}  ← Not a token!              │
│ 2. verify_session("{_flashes: ...}")                            │
│    → hash("{_flashes: ...}") doesn't match any DB session       │
│    → Returns None                                                │
│ 3. if not session_info:                                          │
│       session["next"] = request.url  ← More Flask session!       │
│       return redirect("/admin/login")                            │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ Browser → GET /admin/login                                       │
│ User sees: Login page (NOT dashboard)                            │
│ Result: REDIRECT LOOP ❌                                         │
└──────────────────────────────────────────────────────────────────┘
```

## Fixed Behavior (CORRECT)

```
┌──────────────────────────────────────────────────────────────────┐
│ User clicks "Dev Login"                                          │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ GET /dev/login                                                   │
│                                                                  │
│ 1. create_dev_session(me) → returns "abc123xyz"                 │
│ 2. response.set_cookie("starpunk_session", "abc123xyz")          │
│ 3. flash("DEV MODE: Logged in")                                  │
│    Flask writes: session = {_flashes: ["message"]}              │
│ 4. return redirect("/admin/")                                   │
│                                                                  │
│ ✅ Two separate cookies:                                         │
│    - starpunk_session = "abc123xyz"     (auth token)            │
│    - session = {_flashes: ["message"]}  (Flask session)         │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ Browser → GET /admin/                                            │
│ Cookie: starpunk_session=abc123xyz                               │
│ Cookie: session={_flashes: ["message"]}                          │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ @require_auth decorator                                          │
│                                                                  │
│ 1. session_token = request.cookies.get("starpunk_session")      │
│    → Gets: "abc123xyz"  ✅ Correct auth token!                  │
│ 2. verify_session("abc123xyz")                                  │
│    → hash("abc123xyz") matches DB session                       │
│    → Returns: {me: "https://dev.example.com", ...}              │
│ 3. if session_info:  ✅ Valid session!                           │
│       g.user = session_info                                      │
│       g.me = session_info["me"]                                  │
│       return dashboard()  ← Continues to dashboard!             │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│ Browser → Renders /admin/ dashboard                              │
│ User sees: Dashboard with notes ✅                               │
│ Flash message: "DEV MODE: Logged in" ✅                          │
│ Result: SUCCESS! No redirect loop! ✅                            │
└──────────────────────────────────────────────────────────────────┘
```

## Cookie Collision Visualization

### BEFORE (Broken)

```
┌─────────────────────────────────────────────────┐
│                  BROWSER                        │
│                                                 │
│  Cookies for localhost:5000:                    │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │ Name: session                           │   │
│  │ Value: {_flashes: ["message"]}          │   │
│  │                                         │   │
│  │ ❌ CONFLICT: This should be auth token!│   │
│  │ Flask overwrote our auth token!        │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
└─────────────────────────────────────────────────┘
```

### AFTER (Fixed)

```
┌─────────────────────────────────────────────────┐
│                  BROWSER                        │
│                                                 │
│  Cookies for localhost:5000:                    │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │ Name: starpunk_session                  │   │
│  │ Value: abc123xyz...                     │   │
│  │ Purpose: Auth token ✅                  │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │ Name: session                           │   │
│  │ Value: {_flashes: ["message"]}          │   │
│  │ Purpose: Flask session ✅               │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  ✅ Two separate cookies, no conflict!         │
└─────────────────────────────────────────────────┘
```

## Timeline of Events

### Broken Flow

```
Time  Request              Cookie State                    Auth State
────────────────────────────────────────────────────────────────────────
T0    GET /dev/login       (none)                          Not authed
T1    ↓ set_cookie         session = "abc123xyz"           Token set ✅
T2    ↓ flash()            session = {_flashes: [...]}     OVERWRITTEN ❌
T3    302 → /admin/        session = {_flashes: [...]}     Token LOST ❌
T4    GET /admin/          session = {_flashes: [...]}     Not authed ❌
T5    ↓ @require_auth      verify("{_flashes...}") = None  FAIL ❌
T6    302 → /admin/login   session = {_flashes: [...]}     Not authed ❌
T7    GET /admin/login     session = {_flashes: [...]}     Not authed ❌
      → User sees login page (LOOP!) ❌
```

### Fixed Flow

```
Time  Request              Cookie State                         Auth State
─────────────────────────────────────────────────────────────────────────────
T0    GET /dev/login       (none)                               Not authed
T1    ↓ set_cookie         starpunk_session = "abc123xyz"       Token set ✅
T2    ↓ flash()            session = {_flashes: [...]}          Flask data ✅
                           starpunk_session = "abc123xyz"       Token preserved ✅
T3    302 → /admin/        starpunk_session = "abc123xyz"       Authed ✅
                           session = {_flashes: [...]}
T4    GET /admin/          starpunk_session = "abc123xyz"       Authed ✅
T5    ↓ @require_auth      verify("abc123xyz") = {me: ...}      SUCCESS ✅
T6    Render dashboard     starpunk_session = "abc123xyz"       Authed ✅
      → User sees dashboard ✅
```

## Request/Response Detail

### Broken Request/Response Cycle

```
REQUEST 1: GET /dev/login
═══════════════════════════════════════════════════════════════════
RESPONSE 1:
HTTP/1.1 302 Found
Location: /admin/
Set-Cookie: session={_flashes: [...]}; HttpOnly; SameSite=Lax
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            ❌ Flask overwrote our auth token!

───────────────────────────────────────────────────────────────────

REQUEST 2: GET /admin/
Cookie: session={_flashes: [...]}
        ^^^^^^^^^^^^^^^^^^^^^^^^
        ❌ Sending Flask session data, not auth token!
═══════════════════════════════════════════════════════════════════
RESPONSE 2:
HTTP/1.1 302 Found
Location: /admin/login
        ❌ @require_auth rejected (no valid token)
```

### Fixed Request/Response Cycle

```
REQUEST 1: GET /dev/login
═══════════════════════════════════════════════════════════════════
RESPONSE 1:
HTTP/1.1 302 Found
Location: /admin/
Set-Cookie: starpunk_session=abc123xyz; HttpOnly; SameSite=Lax
            ✅ Auth token in separate cookie
Set-Cookie: session={_flashes: [...]}; HttpOnly; SameSite=Lax
            ✅ Flask session in separate cookie

───────────────────────────────────────────────────────────────────

REQUEST 2: GET /admin/
Cookie: starpunk_session=abc123xyz
        ✅ Sending correct auth token!
Cookie: session={_flashes: [...]}
        ✅ Flask session data also sent (for flash messages)
═══════════════════════════════════════════════════════════════════
RESPONSE 2:
HTTP/1.1 200 OK
Content-Type: text/html

<html>
  <!-- Dashboard renders successfully! ✅ -->
</html>
```

## Code Comparison

### Setting the Cookie

```python
# BEFORE (Broken)
response.set_cookie(
    "session",           # ❌ Conflicts with Flask
    session_token,
    httponly=True,
    secure=False,
    samesite="Lax",
)

# AFTER (Fixed)
response.set_cookie(
    "starpunk_session",  # ✅ No conflict!
    session_token,
    httponly=True,
    secure=False,
    samesite="Lax",
)
```

### Reading the Cookie

```python
# BEFORE (Broken)
session_token = request.cookies.get("session")
# Gets Flask session data, not our token! ❌

# AFTER (Fixed)
session_token = request.cookies.get("starpunk_session")
# Gets our auth token correctly! ✅
```

## Why Flash Triggers the Problem

Flask's `flash()` function writes to the session:

```python
# When you call:
flash("DEV MODE: Logged in", "warning")

# Flask internally does:
session['_flashes'] = [("warning", "DEV MODE: Logged in")]

# Which triggers:
response.set_cookie("session", serialize(session), ...)

# This OVERWRITES any cookie named "session"!
```

## The Key Insight

**Flask owns the `session` cookie name. We cannot use it.**

Flask reserves this cookie for its own session management (flash messages, session["key"] storage, etc.). When we try to use the same cookie name for our auth token, Flask overwrites it whenever session data is modified.

**Solution**: Use our own namespace → `starpunk_session`

## Architectural Principle Established

**Cookie Naming Convention**: All application cookies MUST use an application-specific prefix to avoid conflicts with framework-reserved names.

- Framework cookies: `session`, `csrf_token`, etc. (owned by Flask)
- Application cookies: `starpunk_session`, `starpunk_*` (owned by StarPunk)

This separation ensures:
1. No name collisions
2. Clear ownership
3. Easier debugging (you know which cookie is which)
4. Standards compliance
