Supabase JWT key was switched to ECC P-256, but the JWKS verification
only accepted RS256. Add ES256 to the accepted algorithms list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the level input from the boss defeat modal since the app doesn't
track levels elsewhere. Team selection is now just checkboxes without
requiring level entry.
- Remove level input UI from BossDefeatModal.tsx
- Add alembic migration to make boss_result_team.level nullable
- Update model and schemas to make level optional (defaults to null)
- Conditionally render level in boss result display
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add responsive 2-column layout for the encounters page:
- Desktop (lg, ≥1024px): Encounters on left, team in sticky right sidebar
- Mobile/tablet: Keep current stacked layout
The sidebar scrolls independently when team exceeds viewport height.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The implementation was already complete and merged - just needed
the beans marked as done after agent crash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Crash recovery for nuzlocke-tracker-f2hs: MFA feature was already
implemented and merged via PR #76. Verified code, tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Work was already committed (3bd24fc) and merged to develop.
Crash recovery bean nuzlocke-tracker-ks9c also resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds token expiry checking and automatic refresh to prevent intermittent
401 errors when the cached session token expires between interactions.
- Check token expiry (60s buffer) before each API call
- Add 401 interceptor that retries once with refreshed token
- Explicitly enable autoRefreshToken in Supabase client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PyJWKSetError is not a subclass of PyJWKClientError — they are siblings
under PyJWTError. The empty JWKS key set error was not being caught.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ruff format strips parentheses from `except (A, B):`, turning it into
Python 2 comma syntax that only catches the first exception. Use
separate except clauses so PyJWKClientError is actually caught.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local GoTrue signs JWTs with HS256, but the JWKS endpoint returns an
empty key set since there are no RSA keys. Fall back to HS256 shared
secret verification when JWKS fails, using SUPABASE_JWT_SECRET.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace symmetric HS256 JWT verification with asymmetric RS256 using JWKS.
Backend now fetches and caches public keys from Supabase's JWKS endpoint
instead of using a shared secret.
- Add cryptography dependency for RS256 support
- Use PyJWKClient to fetch/cache JWKS from {SUPABASE_URL}/.well-known/jwks.json
- Remove SUPABASE_JWT_SECRET from config, docker-compose, deploy workflow, .env
- Update tests to use RS256 tokens with mocked JWKS client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace symmetric HS256 JWT verification with asymmetric RS256 using JWKS.
Backend now fetches and caches public keys from Supabase's JWKS endpoint
instead of using a shared secret.
- Add cryptography dependency for RS256 support
- Use PyJWKClient to fetch/cache JWKS from {SUPABASE_URL}/.well-known/jwks.json
- Remove SUPABASE_JWT_SECRET from config, docker-compose, deploy workflow, .env
- Update tests to use RS256 tokens with mocked JWKS client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add MFA enrollment UI in new Settings page with QR code and backup secret
- Add TOTP challenge step to login flow for enrolled users
- Check AAL after login and show TOTP input when aal2 required
- Add disable MFA option with TOTP re-verification
- Only show MFA options for email/password users (not OAuth)
- Add Settings link to user dropdown menu
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Genlockes now inherit visibility from their first leg's run:
- Private runs make genlockes hidden from public listings
- All genlocke read endpoints now accept optional auth
- Returns 404 for private genlockes to non-owners
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Owner column to AdminRuns.tsx and AdminGenlockes.tsx
- Add owner filter dropdown to both admin pages
- Add owner field to GenlockeListItem schema (resolved from first leg's run)
- Update frontend types for GenlockeListItem
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add useAuth and canEdit logic to RunEncounters.tsx
- Guard all mutation triggers (Log Shiny, Log Egg, End Run, Randomize All,
HoF Edit, Boss Battle, route/team clicks, Advance to Next Leg)
- Update RunDashboard.tsx canEdit to be isOwner only (no unowned fallback)
- Add read-only banner for non-owner viewers in both pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add require_run_owner helper in auth.py that enforces ownership on
mutation endpoints. Unowned (legacy) runs are now read-only.
Applied ownership checks to:
- All 4 encounter mutation endpoints
- Both boss result mutation endpoints
- Run update/delete endpoints
- All 5 genlocke mutation endpoints (via first leg's run owner)
Also sets owner_id on run creation in genlockes.py (create_genlocke,
advance_leg) and adds 22 comprehensive ownership enforcement tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use explicit BossResult type instead of indexing potentially undefined
typeof bossResults. Add BossResultTeamMember type to tm parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>