# Password Reset Flow, Logout Fix, and My Regions Only Filter (Jul 2026)

## 1. Password Reset Flow

### Architecture

```
index.html (Forgot Password form)
  │ POST /api/auth/forgot-password {email}
  ▼
server.py: forgot_password()
  ├─ Checks if email exists (does NOT reveal existence — returns same message either way)
  ├─ Generates UUID reset_token, sets reset_token_expires = now + 1 hour
  ├─ Sends styled HTML email via SMTP
  │    ├─ From: shonuff@germainebrown.com
  │    ├─ BCC: g@germainebrown.com
  │    ├─ SMTP: mail.germainebrown.com:2525 (STARTTLS)
  │    └─ Link: reset-password.html?token={uuid}
  └─ Returns success message

reset-password.html (standalone page)
  │ POST /api/auth/reset-password {token, new_password}
  ▼
server.py: reset_password()
  ├─ Finds user by reset_token
  ├─ Validates expiry (1 hour)
  ├─ Hashes new password with bcrypt
  ├─ Clears reset_token + reset_token_expires
  └─ Returns success message
```

### New files
- `/var/www/shark-game/reset-password.html` — standalone reset page that reads `?token=` from URL, shows password/confirm fields, calls `/api/auth/reset-password`, handles success/invalid states

### New models (server.py)
```python
class ForgotPasswordRequest(BaseModel):
    email: str

class ResetPasswordRequest(BaseModel):
    token: str
    new_password: str = Field(min_length=6)
```

### New endpoint signatures
```python
POST /api/auth/forgot-password
Body: {"email": "user@example.com"}
→ 200: {"message": "If that email is registered, you'll receive password reset instructions."}

POST /api/auth/reset-password
Body: {"token": "uuid-string", "new_password": "newpass123"}
→ 200: {"message": "Password has been reset successfully. You can now log in with your new password."}
→ 400: "Invalid or expired reset token." / "Reset token has expired."
```

### SMTP config
```python
SMTP_HOST = os.environ.get("DRE_SMTP_RELAY", "mail.germainebrown.com:2525")
SMTP_USER = os.environ.get("DRE_SMTP_USER", "shonuff@germainebrown.com")
SMTP_PASS = os.environ.get("DRE_SMTP_PASS", "Catches.bullets1985")
PASSWORD_RESET_FROM = "shonuff@germainebrown.com"
PASSWORD_RESET_BCC = "g@germainebrown.com"
PASSWORD_RESET_BASE_URL = os.environ.get("PASSWORD_RESET_BASE_URL", "http://shark-attack-fantasy-league.com")
```

Reuses the existing DRE SMTP env vars. Port defaults to 587, parsed from `host:port` format.

### Email HTML template
Dark-themed, matches game aesthetic (deep ocean background, amber CTA button). Includes:
- Shark emoji + "Password Reset" heading
- CTA button linking to reset-password.html?token=...
- Note about 1-hour expiry

### DB migration
```python
conn.execute("ALTER TABLE users ADD COLUMN reset_token TEXT")
conn.execute("ALTER TABLE users ADD COLUMN reset_token_expires TEXT")
```

### Frontend (index.html)
- "Forgot password?" link appears below login password field when login form is shown
- Clicking it switches to a single-email-field form with "Send Reset Link" button
- On success, form innerHTML is replaced with success message (emoji + "Check your email!")
- Submit button hidden, "modeToggle" link shows "← Back to login"
- `backToLogin()` function handles navigation back to login mode, preserving `isLoginMode` state
- `isForgotPasswordMode` state variable disambiguates submit from login/register

## 2. Logout Fix

### Problem
The old `logout()` function tried partial UI cleanup: it cleared localStorage + hidden the bottom nav + called `toggleMode()` to reset to register state — but didn't handle the case where the page was on a different screen (e.g. league screen wasn't hidden, error messages remained, form fields retained values).

### Fix
Replace multi-line UI cleanup with a full page redirect:

```javascript
function logout() {
    localStorage.removeItem('shark_token');
    localStorage.removeItem('shark_user');
    window.location.href = 'index.html';
}
```

This causes a full page reload, which:
- Resets all JS state variables
- Re-shows the auth form fresh (DOMContentLoaded handler checks for token, finds none, shows auth form)
- Clears any lingering UI artifacts (hidden elements, stale error messages, form field values)
- Works regardless of which screen the user was on
- Avoids maintenance burden when new UI elements are added

### Rule of thumb
For logout on single-page-ish apps that track auth state in JS variables plus DOM:
- **Bad:** Manually reset each piece of state — fragile, easy to miss new additions
- **Good:** Clear credentials, then redirect to force a clean page load — the init logic handles the "no token" case

## 3. My Regions Only Notification Filter

### What it does
A per-user toggle (`notify_my_regions_only`) that, when enabled, only sends score-event push notifications for regions the user has drafted in any league. Draft alerts and non-region notifications pass through regardless.

### Architecture

```
users table: notify_my_regions_only INTEGER DEFAULT 0
    ↓
send_push_notification(user_id, title, body, ..., region_id, event_type)
    ↓
If notify_my_regions_only AND region_id is not None AND event_type != "draft":
    Check draft_picks WHERE user_id=? AND region_id=?
    If no match: return (skip notification)
```

### Implementation details

**send_push_notification() signature change:**
```python
# Before:
def send_push_notification(user_id, title, body, icon="/shark-icon.png", event_type=None):

# After:
def send_push_notification(user_id, title, body, icon="/shark-icon.png", event_type=None, region_id=None):
```

**Filter logic inserted after notification type check and before quiet hours check:**
```python
# 2b. Check "My Regions Only" preference
if user["notify_my_regions_only"] and region_id is not None and event_type != "draft":
    has_region = conn.execute(
        "SELECT id FROM draft_picks WHERE user_id = ? AND region_id = ? LIMIT 1",
        (user_id, region_id),
    ).fetchone()
    if not has_region:
        return  # User only wants notifications for their drafted regions
```

**Caller updated:** In the score event creation endpoint, `send_push_notification(...)` now passes `region_id=req.region_id`.

**DB migration:**
```python
conn.execute("ALTER TABLE users ADD COLUMN notify_my_regions_only INTEGER DEFAULT 0")
```

**Settings API:**
- `GET /api/settings` returns `notify_my_regions_only: bool`
- `PATCH /api/settings` accepts `notify_my_regions_only: true/false`

### Key design decisions
- Only applies to score events (sighting/bite/fatality), not draft alerts — users always get draft notifications regardless
- Checks across ALL leagues, not just the one that triggered the event — if a user drafted Florida East Coast in League A but the event comes from League B, they still get the notification if they drafted that region anywhere
- Silent skip rather than error — no exception thrown, the notification is simply not sent
- The flag is stored on the `users` table (not per-league or per-device) since it's a global preference
