parent
8c35ea1bcd
commit
6406997282
51
serve/proxy.go
Normal file
51
serve/proxy.go
Normal file
@ -0,0 +1,51 @@
|
||||
package serve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"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(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)
|
||||
}
|
12
serve/proxy_test.go
Normal file
12
serve/proxy_test.go
Normal file
@ -0,0 +1,12 @@
|
||||
package serve
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOriginator(t *testing.T) {
|
||||
// We could do plenty of tests here, but the one I really care about is
|
||||
// that we get an error on an empty string.
|
||||
_, err := originator("")
|
||||
if err == nil {
|
||||
t.Error("originator should have returned an error on an empty string")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user