67 lines
2.6 KiB
Markdown
67 lines
2.6 KiB
Markdown
# Password Reset, Logout, & Email Invite — Jul 2026
|
|
|
|
Features built Jul 9, 2026 for Shark Attack Fantasy League.
|
|
|
|
## Password Reset
|
|
|
|
### Backend endpoints
|
|
- `POST /api/auth/forgot-password` — accepts `{email}`, generates UUID token with 1-hour expiry, stores in `users.reset_token` / `users.reset_token_expires`, sends email via SMTP (Sho'Nuff account)
|
|
- `POST /api/auth/reset-password` — accepts `{token, new_password}`, validates token/expiry against DB, hashes new password, clears token
|
|
|
|
### DB migration
|
|
```sql
|
|
ALTER TABLE users ADD COLUMN reset_token TEXT;
|
|
ALTER TABLE users ADD COLUMN reset_token_expires TEXT;
|
|
```
|
|
|
|
### Frontend
|
|
- Landing page (index.html): "Forgot password?" link below login form
|
|
- Clicking switches to a email-only form
|
|
- Submit shows "Check your email for reset instructions"
|
|
- New page at `/reset-password.html` reads token from URL params, lets user set new password
|
|
|
|
### Email
|
|
- Subject: "🦈 Shark Attack Fantasy League — Password Reset"
|
|
- FROM: shonuff@germainebrown.com, BCC: g@germainebrown.com
|
|
- SMTP: mail.germainebrown.com:2525 with STARTTLS
|
|
- Reuses the same SMTP pattern as draft reminders
|
|
|
|
## Logout Fix
|
|
|
|
The logout function was calling an API endpoint without properly clearing localStorage. Fixed:
|
|
|
|
```js
|
|
function logout() {
|
|
localStorage.removeItem('shark_token');
|
|
localStorage.removeItem('shark_user');
|
|
window.location.href = 'index.html';
|
|
}
|
|
```
|
|
|
|
Redirect to `index.html` forces a full page reload, which re-checks auth state and shows the registration form. The bottom tab bar (`.hidden` by default) resets naturally.
|
|
|
|
## Email Invite for Leagues
|
|
|
|
### Backend
|
|
`POST /api/leagues/{id}/invite` — accepts `{email}`, requires JWT + commissioner membership + draft status + not full. Generates a signed JWT invite link (7-day expiry). Sends email with Sho'Nuff signature via SMTP. BCCs g@germainebrown.com.
|
|
|
|
### Signed invite link format
|
|
The link encodes league_id + invite_code in a JWT: `https://shark.iamgmb.com/?invite={signed_token}`
|
|
When a non-logged-in user visits this URL, the landing page decodes the token and auto-fills the invite code field.
|
|
|
|
### Backend implementation details
|
|
```python
|
|
class InviteRequest(BaseModel):
|
|
email: str
|
|
|
|
@app.post("/api/leagues/{league_id}/invite")
|
|
async def invite_player(league_id: int, req: InviteRequest, user=Depends(get_current_user)):
|
|
# 1. Verify commissioner + draft status + not full
|
|
# 2. Generate JWT invite token (7 day expiry)
|
|
# 3. Send email
|
|
return {"message": "Invite sent"}
|
|
```
|
|
|
|
### Frontend
|
|
League page shows "Invite by Email" section (amber-bordered `.settings-card`) only to commissioner. Email input + "Send Invite" button with inline success/error messages.
|