Fix local login flow, add new auth epic
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
---
|
||||
# nuzlocke-tracker-2zwg
|
||||
title: Protect frontend routes with ProtectedRoute and AdminRoute
|
||||
status: todo
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-03-21T10:06:20Z
|
||||
updated_at: 2026-03-21T10:06:24Z
|
||||
parent: nuzlocke-tracker-ce4o
|
||||
blocked_by:
|
||||
- nuzlocke-tracker-5svj
|
||||
---
|
||||
|
||||
Use the existing \`ProtectedRoute\` component (currently unused) and create an \`AdminRoute\` component to guard routes in \`App.tsx\`.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Wrap \`/runs/new\` and \`/genlockes/new\` with \`ProtectedRoute\` (requires login)
|
||||
- [ ] Create \`AdminRoute\` component that checks \`isAdmin\` from \`useAuth()\`, redirects to \`/\` with a toast/message if not admin
|
||||
- [ ] Wrap all \`/admin/*\` routes with \`AdminRoute\`
|
||||
- [ ] Ensure \`/runs\` and \`/runs/:runId\` remain accessible to everyone (public run viewing)
|
||||
- [ ] Verify deep-linking works (e.g., visiting \`/admin/games\` while logged out redirects to login, then back to \`/admin/games\` after auth)
|
||||
|
||||
## Files to change
|
||||
|
||||
- \`frontend/src/App.tsx\` — wrap routes
|
||||
- \`frontend/src/components/ProtectedRoute.tsx\` — already exists, verify it works
|
||||
- \`frontend/src/components/AdminRoute.tsx\` — new file
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# nuzlocke-tracker-5svj
|
||||
title: Expose admin status to frontend via user API
|
||||
status: todo
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-03-21T10:06:20Z
|
||||
updated_at: 2026-03-21T10:06:24Z
|
||||
parent: nuzlocke-tracker-ce4o
|
||||
blocked_by:
|
||||
- nuzlocke-tracker-dwah
|
||||
---
|
||||
|
||||
The frontend needs to know if the current user is an admin so it can show/hide the Admin nav link and protect admin routes client-side.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Add `is_admin` field to the user response schema (`/api/users/me` endpoint)
|
||||
- [ ] Update `AuthContext` to fetch `/api/users/me` after login and store `isAdmin` in context
|
||||
- [ ] Expose `isAdmin` boolean from `useAuth()` hook
|
||||
- [ ] Handle edge case: user exists in Supabase but not yet in local DB (first login creates user row with `is_admin=false`)
|
||||
|
||||
## Files to change
|
||||
|
||||
- `backend/src/app/schemas/user.py` or equivalent — add `is_admin` to response
|
||||
- `backend/src/app/api/users.py` — ensure `/me` returns `is_admin`
|
||||
- `frontend/src/contexts/AuthContext.tsx` — fetch and store admin status
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# nuzlocke-tracker-ce4o
|
||||
title: Auth-aware UI and role-based access control
|
||||
status: todo
|
||||
type: epic
|
||||
created_at: 2026-03-21T10:05:52Z
|
||||
updated_at: 2026-03-21T10:05:52Z
|
||||
---
|
||||
|
||||
The app currently shows the same navigation menu to all users regardless of auth state. Logged-out users can navigate to protected pages (e.g., /runs/new, /admin) even though the backend rejects their requests. The admin interface has no role restriction — any authenticated user can access it.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Auth-aware navigation**: Menu items change based on login state (logged-out users only see public browsing options)
|
||||
2. **Route protection**: Protected routes redirect to login, admin routes require admin role
|
||||
3. **Admin role system**: Define which users are admins via a database field, enforce on both frontend and backend
|
||||
4. **Backend admin enforcement**: Admin API endpoints (games, pokemon, evolutions, bosses, routes) require admin role, not just authentication
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Logged-out users see only: Home, Runs (public list), Genlockes, Stats, Sign In
|
||||
- [ ] Logged-out users cannot navigate to /runs/new, /genlockes/new, or /admin/*
|
||||
- [ ] Logged-in non-admin users see: New Run, My Runs, Genlockes, Stats (no Admin link)
|
||||
- [ ] Admin users see the full menu including Admin
|
||||
- [ ] Backend admin endpoints return 403 for non-admin authenticated users
|
||||
- [ ] Admin role is stored in the `users` table (`is_admin` boolean column)
|
||||
- [ ] Admin status is exposed to the frontend via the user API or auth context
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# nuzlocke-tracker-dwah
|
||||
title: Add is_admin column to users table
|
||||
status: todo
|
||||
type: task
|
||||
created_at: 2026-03-21T10:06:19Z
|
||||
updated_at: 2026-03-21T10:06:19Z
|
||||
parent: nuzlocke-tracker-ce4o
|
||||
---
|
||||
|
||||
Add an `is_admin` boolean column (default `false`) to the `users` table via an Alembic migration.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Create Alembic migration adding `is_admin: Mapped[bool]` column with `server_default="false"`
|
||||
- [ ] Update `User` model in `backend/src/app/models/user.py`
|
||||
- [ ] Run migration and verify column exists
|
||||
- [ ] Seed a test admin user (or document how to set `is_admin=true` via SQL)
|
||||
|
||||
## Files to change
|
||||
|
||||
- `backend/src/app/models/user.py` — add `is_admin` field
|
||||
- `backend/src/app/alembic/versions/` — new migration
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# nuzlocke-tracker-f4d0
|
||||
title: Add require_admin dependency and protect admin endpoints
|
||||
status: todo
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-03-21T10:06:19Z
|
||||
updated_at: 2026-03-21T10:06:24Z
|
||||
parent: nuzlocke-tracker-ce4o
|
||||
blocked_by:
|
||||
- nuzlocke-tracker-dwah
|
||||
---
|
||||
|
||||
Add a `require_admin` FastAPI dependency that checks the `is_admin` column on the `users` table. Apply it to all admin-facing API endpoints (games CRUD, pokemon CRUD, evolutions CRUD, bosses CRUD, route CRUD).
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Add `require_admin` dependency in `backend/src/app/core/auth.py` that:
|
||||
- Requires authentication (reuses `require_auth`)
|
||||
- Looks up the user in the `users` table by `AuthUser.id`
|
||||
- Returns 403 if `is_admin` is not `True`
|
||||
- [ ] Apply `require_admin` to write endpoints in: `games.py`, `pokemon.py`, `evolutions.py`, `bosses.py` (all POST/PUT/PATCH/DELETE)
|
||||
- [ ] Keep read endpoints (GET) accessible to all authenticated users
|
||||
- [ ] Add tests for 403 response when non-admin user hits admin endpoints
|
||||
|
||||
## Files to change
|
||||
|
||||
- `backend/src/app/core/auth.py` — add `require_admin`
|
||||
- `backend/src/app/api/games.py` — replace `require_auth` with `require_admin` on mutations
|
||||
- `backend/src/app/api/pokemon.py` — same
|
||||
- `backend/src/app/api/evolutions.py` — same
|
||||
- `backend/src/app/api/bosses.py` — same
|
||||
27
.beans/nuzlocke-tracker-h205--auth-aware-navigation-menu.md
Normal file
27
.beans/nuzlocke-tracker-h205--auth-aware-navigation-menu.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# nuzlocke-tracker-h205
|
||||
title: Auth-aware navigation menu
|
||||
status: todo
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-03-21T10:06:20Z
|
||||
updated_at: 2026-03-21T10:06:24Z
|
||||
parent: nuzlocke-tracker-ce4o
|
||||
blocked_by:
|
||||
- nuzlocke-tracker-5svj
|
||||
---
|
||||
|
||||
Update the Layout component to show different nav links based on auth state and admin role.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Replace static \`navLinks\` array with dynamic links based on \`useAuth()\` state
|
||||
- [ ] **Logged out**: Home, Runs, Genlockes, Stats (no New Run, no Admin)
|
||||
- [ ] **Logged in (non-admin)**: New Run, My Runs, Genlockes, Stats
|
||||
- [ ] **Logged in (admin)**: New Run, My Runs, Genlockes, Stats, Admin
|
||||
- [ ] Update both desktop and mobile nav (they share the same \`navLinks\` array, so this should be automatic)
|
||||
- [ ] Verify menu updates reactively on login/logout
|
||||
|
||||
## Files to change
|
||||
|
||||
- \`frontend/src/components/Layout.tsx\` — make \`navLinks\` dynamic based on auth state
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
# nuzlocke-tracker-he1n
|
||||
title: Add local GoTrue container for dev auth testing
|
||||
status: in-progress
|
||||
status: todo
|
||||
type: feature
|
||||
priority: normal
|
||||
created_at: 2026-03-20T20:57:04Z
|
||||
updated_at: 2026-03-20T21:08:22Z
|
||||
updated_at: 2026-03-20T21:13:18Z
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -37,7 +37,12 @@ class NuzlockeRun(Base):
|
||||
String(20), index=True
|
||||
) # active, completed, failed
|
||||
visibility: Mapped[RunVisibility] = mapped_column(
|
||||
Enum(RunVisibility, name="run_visibility", create_constraint=False),
|
||||
Enum(
|
||||
RunVisibility,
|
||||
name="run_visibility",
|
||||
create_constraint=False,
|
||||
values_callable=lambda e: [m.value for m in e],
|
||||
),
|
||||
default=RunVisibility.PUBLIC,
|
||||
server_default="public",
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
- GOTRUE_SITE_URL=http://localhost:5173
|
||||
# Database
|
||||
- GOTRUE_DB_DRIVER=postgres
|
||||
- GOTRUE_DB_DATABASE_URL=postgres://postgres:postgres@db:5432/nuzlocke?sslmode=disable
|
||||
- GOTRUE_DB_DATABASE_URL=postgres://postgres:postgres@db:5432/nuzlocke?sslmode=disable&search_path=auth
|
||||
# JWT - must match backend's SUPABASE_JWT_SECRET
|
||||
- GOTRUE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
|
||||
- GOTRUE_JWT_AUD=authenticated
|
||||
|
||||
@@ -2,14 +2,32 @@ import { createClient, type SupabaseClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = import.meta.env['VITE_SUPABASE_URL'] ?? ''
|
||||
const supabaseAnonKey = import.meta.env['VITE_SUPABASE_ANON_KEY'] ?? ''
|
||||
const isLocalDev = supabaseUrl.includes('localhost')
|
||||
|
||||
// supabase-js hardcodes /auth/v1 as the auth path prefix, but GoTrue
|
||||
// serves at the root when accessed directly (no API gateway).
|
||||
// This custom fetch strips the prefix for local dev.
|
||||
function localGoTrueFetch(
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> {
|
||||
const url = input instanceof Request ? input.url : String(input)
|
||||
const rewritten = url.replace('/auth/v1/', '/')
|
||||
if (input instanceof Request) {
|
||||
return fetch(new Request(rewritten, input), init)
|
||||
}
|
||||
return fetch(rewritten, init)
|
||||
}
|
||||
|
||||
function createSupabaseClient(): SupabaseClient {
|
||||
if (!supabaseUrl || !supabaseAnonKey) {
|
||||
// Return a stub client for tests/dev without Supabase configured
|
||||
// Uses port 9999 to match local GoTrue container
|
||||
return createClient('http://localhost:9999', 'stub-key')
|
||||
}
|
||||
return createClient(supabaseUrl, supabaseAnonKey)
|
||||
return createClient(supabaseUrl, supabaseAnonKey, {
|
||||
...(isLocalDev && {
|
||||
global: { fetch: localGoTrueFetch },
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const supabase = createSupabaseClient()
|
||||
|
||||
Reference in New Issue
Block a user