C-1. Session JWT signature verification (authZ bypass fix) - Add SUPABASE_JWT_SECRET env var; fail-fast at startup if unset. - auth.Client.VerifyToken uses github.com/golang-jwt/jwt/v5 to verify HS256 signatures, reject alg=none, enforce exp/nbf/iat. - Middleware stores verified claims in request context; WithUserID reads only verified claims (no more raw-cookie sub decoding). - API requests get 401 on missing/invalid token (was 302 redirect). - Refresh flow only runs on expiry; other signature failures reject outright and clear cookies. C-2. Dashboard Termine cross-user privacy leak - dashboard_service.loadUpcomingAppointments now mirrors TerminService.canSee: personal Termine (akte_id IS NULL) are creator-only; admins do NOT see other users' personal Termine. C-3. Role gate on Parteien + Termine mutations - ParteienService.Delete now partner/admin only (matches FristService). - TerminService.Update / Delete on Akte-linked Termine now require partner/admin (or the original creator). Personal Termine stay creator-only. C-4. Email gate → ALLOWED_EMAIL_DOMAINS whitelist - isHoganLovellsEmail → isAllowedEmailDomain reading the env var (default: hoganlovells.com,hlc.com,hlc.de). Case-insensitive, whitespace-tolerant. - login.tsx placeholder: name@hoganlovells.com → name@hlc.com - Error strings + login.hint (de/en) rewritten for HLC branding. C-5. Docker compose env wiring - docker-compose.yml gains SUPABASE_JWT_SECRET, CALDAV_ENCRYPTION_KEY, and ALLOWED_EMAIL_DOMAINS passthrough; commented-out ANTHROPIC_API_KEY line for Phase H readiness. Tests - auth_test.go: valid/wrong-secret/expired/alg-none/missing-sub/garbage token cases for VerifyToken. - handlers/auth_test.go: default + env-override cases for the email whitelist. - go build ./..., go vet ./..., go test ./... all clean.
61 lines
1.8 KiB
Go
61 lines
1.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
const (
|
|
userIDContextKey contextKey = "paliad.userID"
|
|
claimsContextKey contextKey = "paliad.claims"
|
|
)
|
|
|
|
// UserIDFromContext returns the authenticated user's UUID, populated by
|
|
// WithUserID middleware (which runs after the session middleware).
|
|
// Returns (uuid.Nil, false) if no user is in context.
|
|
func UserIDFromContext(ctx context.Context) (uuid.UUID, bool) {
|
|
v, ok := ctx.Value(userIDContextKey).(uuid.UUID)
|
|
if !ok {
|
|
return uuid.Nil, false
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
// withVerifiedClaims stores signature-verified JWT claims in the request
|
|
// context. Called only from Client.Middleware after VerifyToken succeeds.
|
|
func withVerifiedClaims(ctx context.Context, claims *VerifiedClaims) context.Context {
|
|
return context.WithValue(ctx, claimsContextKey, claims)
|
|
}
|
|
|
|
// verifiedClaimsFromContext returns the signature-verified JWT claims
|
|
// attached by Client.Middleware.
|
|
func verifiedClaimsFromContext(ctx context.Context) (*VerifiedClaims, bool) {
|
|
v, ok := ctx.Value(claimsContextKey).(*VerifiedClaims)
|
|
return v, ok
|
|
}
|
|
|
|
// WithUserID reads the `sub` claim from verified JWT claims attached by
|
|
// Client.Middleware and injects the user's UUID into the request context.
|
|
// Must run after Client.Middleware — the claims are only set there, after
|
|
// signature verification succeeds.
|
|
func (c *Client) WithUserID(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
claims, ok := verifiedClaimsFromContext(r.Context())
|
|
if !ok {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
uid, err := uuid.Parse(claims.Sub)
|
|
if err != nil {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
ctx := context.WithValue(r.Context(), userIDContextKey, uid)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|