package serve import ( "context" "net/http" "net/netip" "strings" "github.com/google/uuid" "git.sunturtle.xyz/studio/shotgun/player" ) // WithPlayerID is a middleware that adds a player ID to the request context // based on the X-Forwarded-For header. If there is no such header, or the // originator addr otherwise cannot be parsed from it, the request fails with // a 500 error. func WithPlayerID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ff := r.Header.Get("X-Forwarded-For") addr, err := originator(ff) if err != nil { http.Error(w, "missing or invalid X-Forwarded-For header; check server configuration", http.StatusInternalServerError) return } id := player.ID{UUID: uuid.UUID(addr.As16())} ctx := ctxWith(r.Context(), id) next.ServeHTTP(w, r.WithContext(ctx)) }) } // Player returns the player ID set by WithPlayerID in the request context. func PlayerID(ctx context.Context) player.ID { return ctxValue[player.ID](ctx) } // originator parses the IP of the client that originated a request from the // content of its X-Forwarded-For header. func originator(ff string) (netip.Addr, error) { ff, _, _ = strings.Cut(ff, ",") return netip.ParseAddr(ff) } type ctxKey[T any] struct{} func ctxValue[T any](ctx context.Context) T { r, _ := ctx.Value(ctxKey[T]{}).(T) return r } func ctxWith[T any](ctx context.Context, v T) context.Context { return context.WithValue(ctx, ctxKey[T]{}, v) }