Files
James Griffin 4989ff1061 Scaffold full-stack volunteer scheduling application
Go backend with domain-based packages (volunteer, schedule, timeoff,
checkin, notification), SQLite storage, JWT auth, and chi router.
React TypeScript frontend with routing, auth context, and pages for
all core features. Multi-stage Dockerfile and docker-compose included.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:25:02 -04:00

50 lines
1.3 KiB
Go

package middleware
import (
"context"
"net/http"
"strings"
"git.unsupervised.ca/walkies/internal/auth"
"git.unsupervised.ca/walkies/internal/respond"
)
type contextKey string
const claimsKey contextKey = "claims"
func Authenticate(authSvc *auth.Service) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
respond.Error(w, http.StatusUnauthorized, "missing or invalid authorization header")
return
}
claims, err := authSvc.Parse(strings.TrimPrefix(header, "Bearer "))
if err != nil {
respond.Error(w, http.StatusUnauthorized, "invalid token")
return
}
ctx := context.WithValue(r.Context(), claimsKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func RequireAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := ClaimsFromContext(r.Context())
if claims == nil || claims.Role != "admin" {
respond.Error(w, http.StatusForbidden, "admin access required")
return
}
next.ServeHTTP(w, r)
})
}
func ClaimsFromContext(ctx context.Context) *auth.Claims {
claims, _ := ctx.Value(claimsKey).(*auth.Claims)
return claims
}