feat: require auth on tryout and upload endpoints #92
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/lock-down-public-endpoints"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Lock down all previously-public endpoints that expose parent PII (email, phone, registration tokens). Tryouts are over, all 50 users have Keycloak accounts, and there is no longer a reason to serve roster data without authentication. Every endpoint now requires a valid JWT via
get_current_user(any authenticated role).Changes
src/basketball_api/routes/tryouts.py-- Addedget_current_userdependency to 6 endpoints:GET /api/roster/{tenant},GET /tryouts/roster/{tenant},GET /tryouts/roster/{tenant}/print,GET /tryouts/admin/{tenant},POST /api/roster/{tenant}/check-in/{player_id},POST /api/roster/{tenant}/assign-number/{player_id}src/basketball_api/routes/upload.py-- Addedget_current_userdependency toPOST /upload/phototests/test_tryouts.py-- Addedauth_clientfixture (overridesget_current_user), updatedadmin_clientto also overrideget_current_user, switched all functional tests frompublic_clienttoauth_client, addedtest_requires_authtests for roster, print roster, admin dashboard, check-in, and assign-number endpointstests/test_upload.py-- Rewrote to useauth_client/public_clientfixtures with proper auth mocking, addedtest_requires_authtestTest Plan
pytest tests/ -vtest_requires_authtests)ruff formatandruff checkpassReview Checklist
event.locals.accessTokenRelated
plan-2026-03-08-tryout-prepPhase 11Lock down all previously-public endpoints that expose parent PII. Tryouts are over and all users have Keycloak accounts, so there is no longer a reason to serve roster data without authentication. Endpoints secured: - GET /api/roster/{tenant} (JSON roster with parent info) - GET /tryouts/roster/{tenant} (HTML coach roster) - GET /tryouts/roster/{tenant}/print (print view) - GET /tryouts/admin/{tenant} (admin dashboard) - POST /api/roster/{tenant}/check-in/{player_id} - POST /api/roster/{tenant}/assign-number/{player_id} - POST /upload/photo All endpoints use get_current_user (any authenticated user, not role-restricted). Tests updated to use auth_client fixture and new tests verify 401 on unauthenticated access. Closes #91 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>PR #92 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Keycloak JWT auth (Pydantic models, dependency injection).
Auth pattern analysis: The
get_current_userdependency (inauth.py) correctly returns 401 when no Bearer token is present (HTTPBearer(auto_error=False)+ explicitcredentials is Nonecheck). Therequire_rolefactory chains on top ofget_current_user, adding 403 for insufficient permissions. This is sound --get_current_useris the correct dependency for "any authenticated user" gating.Endpoints modified (6 in tryouts.py, 1 in upload.py):
GET /tryouts/roster/{tenant}get_current_userGET /tryouts/roster/{tenant}/printget_current_userGET /api/roster/{tenant}get_current_userPOST /api/roster/{tenant}/check-in/{id}get_current_userPOST /api/roster/{tenant}/assign-number/{id}get_current_userGET /tryouts/admin/{tenant}get_current_userPOST /upload/photoget_current_userEndpoints correctly left unchanged:
GET /payPOST /tryouts/admin/{tenant}/assign-numbersrequire_admin_rolePOST /tryouts/admin/{tenant}/checkin/{id}require_admin_rolePOST /tryouts/admin/{tenant}/uncheckin/{id}require_admin_roleThe
admin_clientfixture correctly overrides BOTHrequire_admin_roleANDget_current_usernow (line 123). This is necessary because the admin dashboard endpoint moved from no auth toget_current_user, while checkin/uncheckin still userequire_admin_role. Without overridingget_current_userin the admin fixture, admin tests hitting the dashboard would fail. Good catch by the dev.Docstrings: All updated from "Public-but-unlisted" to "Requires authentication." -- accurate and consistent.
BLOCKERS
None. This PR is clean.
test_requires_authnegative tests verifying 401/403 on unauthenticated access./payredirect correctly remains public (no PII, just a Stripe redirect).checkin_playeranduncheckin_playerendpoints retain theirrequire_admin_roledependency and are unaffected by this change.NITS
DRY: Duplicated
auth_client/public_clientfixtures. Theauth_clientandpublic_clientfixtures intests/test_upload.py(lines 16-52) are identical copies of the fixtures intests/test_tryouts.py(lines 128-164). These should be extracted totests/conftest.pyas shared fixtures. Not blocking because the duplication is in test code (not auth/security path), but it will drift.Admin dashboard auth level. The admin dashboard (
GET /tryouts/admin/{tenant}) is now gated byget_current_user(any authenticated user), but the page renders parent emails, payment status, registration tokens, and check-in buttons. The check-in buttons call endpoints gated byrequire_admin_role, so non-admin users would see data but get 403 on actions. Consider whether this should berequire_role("admin")instead ofget_current_userfor defense-in-depth. The current choice is defensible (view vs. action separation), but worth a deliberate decision.assert resp.status_code in (401, 403)pattern. Alltest_requires_authtests accept both 401 and 403. Sinceget_current_userraises exactly 401 (andrequire_admin_roleraises 403 after 401), the non-admin endpoints should always return 401. Tightening toassert resp.status_code == 401forget_current_user-only endpoints would catch regressions where someone accidentally changes the auth dependency. Minor -- the current pattern is safe, just less precise.Comment on line 432 still says "playground." The comment
# --- JSON roster API (for SvelteKit playground dashboard) ----------------------on tryouts.py line 432 still references "playground" even though the docstring was correctly updated to remove it. Cosmetic only.SOP COMPLIANCE
feature/lock-down-public-endpoints, should beissue-91or similar per convention. Non-blocking but noted.plan-2026-03-08-tryout-prepPhase 11.Closes #91.PROCESS OBSERVATIONS
get_current_userdependency is battle-tested across the codebase. The westside-app frontend already passes tokens server-side, so no frontend breakage expected.VERDICT: APPROVED