get player ids from sessions, not ips
This commit is contained in:
parent
73b5ac7960
commit
570fc6a298
14
main.go
14
main.go
@ -1,19 +1,31 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"gitlab.com/zephyrtronium/sq"
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/lobby"
|
"git.sunturtle.xyz/studio/shotgun/lobby"
|
||||||
"git.sunturtle.xyz/studio/shotgun/serve"
|
"git.sunturtle.xyz/studio/shotgun/serve"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
s := Server{
|
s := Server{
|
||||||
l: lobby.New(),
|
l: lobby.New(),
|
||||||
}
|
}
|
||||||
|
sessiondb, err := sq.Open("sqlite", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sessions, err := sessiondb.Conn(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.With(serve.WithPlayerID).Get("/queue", s.Queue)
|
r.With(serve.WithPlayerID(sessions)).Get("/queue", s.Queue)
|
||||||
http.ListenAndServe(":8080", r)
|
http.ListenAndServe(":8080", r)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,15 @@ type Session struct {
|
|||||||
uuid.UUID
|
uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseSession parses a session ID.
|
||||||
|
func ParseSession(s string) (Session, error) {
|
||||||
|
id, err := uuid.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return Session{}, fmt.Errorf("couldn't parse session ID: %w", err)
|
||||||
|
}
|
||||||
|
return Session{id}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// InitSessions initializes an SQLite table relating player IDs to sessions.
|
// InitSessions initializes an SQLite table relating player IDs to sessions.
|
||||||
func InitSessions(ctx context.Context, db Execer) error {
|
func InitSessions(ctx context.Context, db Execer) error {
|
||||||
_, err := db.Exec(ctx, initSessions)
|
_, err := db.Exec(ctx, initSessions)
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
54
serve/session.go
Normal file
54
serve/session.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package serve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/studio/shotgun/player"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxKey[T any] struct{}
|
||||||
|
|
||||||
|
func value[T any](ctx context.Context) T {
|
||||||
|
r, _ := ctx.Value(ctxKey[T]{}).(T)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func with[T any](ctx context.Context, v T) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxKey[T]{}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionCookie = "__Host-id-v1"
|
||||||
|
|
||||||
|
// WithPlayerID is a middleware that adds a player ID to the request context
|
||||||
|
// based on the session cookie content. If there is no such cookie, or its
|
||||||
|
// value is invalid, the request fails with a 403 error.
|
||||||
|
func WithPlayerID(sessions player.RowQuerier) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c, err := r.Cookie(sessionCookie)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := player.ParseSession(c.Value)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := player.FromSession(r.Context(), sessions, id, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := with(r.Context(), p)
|
||||||
|
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 value[player.ID](ctx)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user