diff --git a/serve/session.go b/serve/session.go
index 92d1b23..04df2f9 100644
--- a/serve/session.go
+++ b/serve/session.go
@@ -34,9 +34,22 @@ func SetSession(w http.ResponseWriter, s player.Session) {
})
}
-// WithSession 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 401 error.
+// 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.
func WithSession(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) {
@@ -55,7 +68,7 @@ func WithSession(sessions player.RowQuerier) func(http.Handler) http.Handler {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
- ctx := with(r.Context(), p)
+ ctx := with(with(r.Context(), id), p)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
@@ -65,3 +78,8 @@ func WithSession(sessions player.RowQuerier) func(http.Handler) http.Handler {
func ReqPlayer(ctx context.Context) player.ID {
return value[player.ID](ctx)
}
+
+// ReqSession returns the session ID set by WithSession in the request context.
+func ReqSession(ctx context.Context) player.Session {
+ return value[player.Session](ctx)
+}
diff --git a/server.go b/server.go
index dcbf82b..5490cec 100644
--- a/server.go
+++ b/server.go
@@ -147,6 +147,30 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
slog.InfoContext(ctx, "logged in", "player", p, "id", id)
}
+func (s *Server) Logout(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ slog := slog.With(
+ slog.String("remote", r.RemoteAddr),
+ slog.String("forwarded-for", r.Header.Get("X-Forwarded-For")),
+ slog.String("user-agent", r.UserAgent()),
+ )
+ id := serve.ReqSession(ctx)
+ if id == (player.Session{}) {
+ slog.WarnContext(ctx, "no session on logout")
+ http.Error(w, "what", http.StatusUnauthorized)
+ panic("unreachable")
+ }
+ err := player.Logout(ctx, s.sessions, id)
+ if err != nil {
+ slog.ErrorContext(ctx, "logout failed", "err", err.Error())
+ http.Error(w, "something went wrong", http.StatusInternalServerError)
+ return
+ }
+ serve.RemoveSession(w)
+ slog.InfoContext(ctx, "logged out", "session", id)
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
func (s *Server) Me(w http.ResponseWriter, r *http.Request) {
id := serve.ReqPlayer(r.Context())
if id == (player.ID{}) {
diff --git a/site/src/App.vue b/site/src/App.vue
index fefd170..5ea7efc 100644
--- a/site/src/App.vue
+++ b/site/src/App.vue
@@ -13,7 +13,8 @@
- Play
+ Play
+ Log Out
@@ -83,5 +84,6 @@ onMounted(async () => {
} catch {
me.value = null;
}
+ me.value = 'a';
});