auth: replace copy-paste JWT checks with real middleware#194
auth: replace copy-paste JWT checks with real middleware#194sumnerevans wants to merge 7 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors admin/volunteer authentication to use shared JWT parsing and dedicated middleware, removing duplicated cookie/token checks from individual handlers and consolidating protected route registration.
Changes:
- Introduces
parseTokenByIssuerand updates admin/volunteer login handlers to use it. - Adds
VolunteerAuthMiddlewareand applies auth middleware to volunteer scan/checkin routes. - Consolidates all protected admin pages + API endpoints under a single
/admin/subrouter wrapped withAdminAuthMiddleware, and simplifies template extra-data merging viamaps.Copy.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/volunteer.go | Removes inline volunteer auth checks; uses shared token parsing for volunteer/admin token validation. |
| internal/middleware.go | Adds shared JWT parsing helper; updates admin middleware redirect target; adds volunteer auth middleware. |
| internal/application.go | Consolidates protected admin routing under one subrouter + middleware; wraps volunteer routes with middleware; uses maps.Copy. |
| internal/admin.go | Removes inline admin auth checks; uses shared token parsing for admin login flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -179,7 +127,7 @@ func (a *Application) HandleAdminEmailLogin(w http.ResponseWriter, r *http.Reque | |||
| tok := r.URL.Query().Get("tok") | |||
| log := zerolog.Ctx(r.Context()) | |||
| log.Info().Str("token", tok).Msg("got token") | |||
There was a problem hiding this comment.
This logs the raw login JWT from the query string. JWTs are bearer credentials and should not be written to logs (they can be replayed if leaked via log aggregation/support tooling). Please remove this field from logs or replace it with a safe surrogate (e.g., token hash / last few chars) and rely on request_id for correlation.
| log.Info().Str("token", tok).Msg("got token") | |
| log.Info().Msg("got admin login token") |
|
|
||
| func (a *Application) HandleVolunteerEmailLogin(w http.ResponseWriter, r *http.Request) { | ||
| tok := r.URL.Query().Get("tok") | ||
| zerolog.Ctx(r.Context()).Info().Str("token", tok).Msg("got token") |
There was a problem hiding this comment.
This logs the raw login JWT from the query string. JWTs are bearer credentials and should not be written to logs (they can be replayed if leaked via log aggregation/support tooling). Please remove this field from logs or replace it with a safe surrogate (e.g., token hash / last few chars) and rely on request_id for correlation.
| zerolog.Ctx(r.Context()).Info().Str("token", tok).Msg("got token") | |
| zerolog.Ctx(r.Context()).Info().Msg("received volunteer login token") |
f210558 to
5603ce4
Compare
Add a shared parseTokenByIssuer helper and VolunteerAuthMiddleware, consolidate all protected admin routes into a single subrouter wrapped with AdminAuthMiddleware, and remove the duplicated cookie+token checks that were scattered across handler and template functions. Closes #73 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests for parseTokenByIssuer (empty, correct issuer, wrong issuer, expired, wrong signing key) and both AdminAuthMiddleware and VolunteerAuthMiddleware (no cookie, invalid token, wrong issuer, valid token). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract BuildRouter from Start so tests can exercise the full router without starting a real server. Add routing_test.go covering all protected admin and volunteer routes (no cookie, wrong-issuer cookie, valid cookie) and key unprotected routes. Uses an in-memory SQLite DB with MaxOpenConns/MaxIdleConns=1 so migrations run correctly on the shared connection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
middleware_test.go now only covers parseTokenByIssuer (JWT parsing logic in isolation). Routing tests cover the full middleware+routing behavior: no cookie, malformed token, wrong-issuer token, and valid token — with all protected routes checked in table-driven loops. 32 tests → 15 tests, no loss of coverage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The renderFn loop was only redirecting to /register/teacher/login for pages marked RedirectIfLoggedIn:true (login/confirm-email pages). Pages with RedirectIfLoggedIn:false (schoolinfo, teams, team/edit, addmember) would render with nil data when accessed without a session. Now any unauthenticated GET on a /register/teacher/* protected page redirects to /register/teacher/login with 303. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Sumner Evans <me@sumnerevans.com>
22b0f49 to
9fe993a
Compare
Summary
parseTokenByIssuerhelper that replaces the nearly-identicalisAdminByTokenandisVolunteerByTokenfunctionsVolunteerAuthMiddlewaremirroring the existingAdminAuthMiddlewareAdminAuthMiddlewareviaStripPrefix("/admin", ...), consistent with the existing patternVolunteerAuthMiddlewareGetAdminTeamsTemplate,GetAdminDietaryRestrictionsTemplate,GetVolunteerScanTemplate, andHandleVolunteerCheckInCloses #73
Test plan
GET /admin/teamswithout cookie → redirects to/admin/loginGET /admin/dietaryrestrictionswithout cookie → redirects to/admin/loginGET /admin/loginwithout cookie → renders login page (unprotected)GET /volunteer/scanwithout cookie → redirects to/volunteer/loginGET /volunteer/checkinwithout cookie → redirects to/volunteer/login🤖 Generated with Claude Code