Files

2.6 KiB

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

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

Logout Fix

The logout function was calling an API endpoint without properly clearing localStorage. Fixed:

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.

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

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.