2024-02-01 09:26:27 -05:00
|
|
|
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"
|
|
|
|
|
2024-02-01 21:40:59 -05:00
|
|
|
// SetSession sets a cookie carrying a session token on a response.
|
|
|
|
func SetSession(w http.ResponseWriter, s player.Session) {
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
|
|
Name: sessionCookie,
|
|
|
|
Value: s.String(),
|
|
|
|
Path: "/",
|
|
|
|
Expires: time.Now().Add(365 * 24 * time.Hour),
|
|
|
|
Secure: true,
|
|
|
|
HttpOnly: true,
|
|
|
|
SameSite: http.SameSiteLaxMode,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-02 18:30:35 -05:00
|
|
|
// RemoveSession clears the session token cookie on a response.
|
|
|
|
func RemoveSession(w http.ResponseWriter) {
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
|
|
Name: sessionCookie,
|
|
|
|
Value: "",
|
|
|
|
Path: "/",
|
|
|
|
Expires: time.Unix(0, 0),
|
|
|
|
Secure: true,
|
|
|
|
HttpOnly: true,
|
|
|
|
SameSite: http.SameSiteLaxMode,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithSession is a middleware that adds a player ID and session 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 401 error.
|
2024-02-01 22:38:44 -05:00
|
|
|
func WithSession(sessions player.RowQuerier) func(http.Handler) http.Handler {
|
2024-02-01 09:26:27 -05:00
|
|
|
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 {
|
2024-02-01 22:38:44 -05:00
|
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
2024-02-01 09:26:27 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
id, err := player.ParseSession(c.Value)
|
|
|
|
if err != nil {
|
2024-02-01 22:38:44 -05:00
|
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
2024-02-01 09:26:27 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
p, err := player.FromSession(r.Context(), sessions, id, time.Now())
|
|
|
|
if err != nil {
|
2024-02-01 22:38:44 -05:00
|
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
2024-02-01 09:26:27 -05:00
|
|
|
return
|
|
|
|
}
|
2024-02-02 18:30:35 -05:00
|
|
|
ctx := with(with(r.Context(), id), p)
|
2024-02-01 09:26:27 -05:00
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-02 14:03:05 -05:00
|
|
|
// ReqPlayer returns the session ID set by WithSession in the request context.
|
|
|
|
func ReqPlayer(ctx context.Context) player.ID {
|
|
|
|
return value[player.ID](ctx)
|
2024-02-01 09:26:27 -05:00
|
|
|
}
|
2024-02-02 18:30:35 -05:00
|
|
|
|
|
|
|
// ReqSession returns the session ID set by WithSession in the request context.
|
|
|
|
func ReqSession(ctx context.Context) player.Session {
|
|
|
|
return value[player.Session](ctx)
|
|
|
|
}
|