fix: revoke sessions after updates

This commit is contained in:
MengMengCode
2026-08-09 20:23:00 +08:00
parent 1fc6ea9b6c
commit 0e68dc6893
2 changed files with 44 additions and 4 deletions
+30 -1
View File
@@ -3,11 +3,13 @@ package server
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"vocat/internal/device"
"vocat/internal/modem"
@@ -354,7 +356,22 @@ func TestHandleUpdateCheckUsesTrustedRepository(t *testing.T) {
}
func TestHandleUpdateApplyInstallsFromTrustedRepository(t *testing.T) {
database, err := store.Open(context.Background(), ":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.Close() })
if err := database.SetAdmin(context.Background(), "admin", []byte("hash")); err != nil {
t.Fatal(err)
}
tokenHash := []byte("active-session")
if err := database.CreateSession(
context.Background(), 1, tokenHash, []byte("csrf"), time.Now().Add(time.Hour),
); err != nil {
t.Fatal(err)
}
server := &Server{
store: database,
logger: regionTestLogger(),
updateRepository: update.DefaultRepository,
updateApply: func(_ context.Context, _ *slog.Logger, options update.Options, restart bool) (update.CheckResult, error) {
@@ -370,9 +387,21 @@ func TestHandleUpdateApplyInstallsFromTrustedRepository(t *testing.T) {
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body)
}
data := decodeData(t, recorder)
if data["applied"] != true || data["version"] != "9.9.9" {
if data["applied"] != true || data["version"] != "9.9.9" || data["reauthentication_required"] != true {
t.Fatalf("apply data = %#v", data)
}
if _, err := database.SessionByTokenHash(context.Background(), tokenHash); !errors.Is(err, store.ErrNotFound) {
t.Fatalf("session must be revoked after update, got %v", err)
}
expired := map[string]bool{}
for _, cookie := range recorder.Result().Cookies() {
if cookie.MaxAge < 0 {
expired[cookie.Name] = true
}
}
if !expired[sessionCookieName] || !expired[csrfCookieName] {
t.Fatalf("auth cookies were not expired: %#v", recorder.Result().Cookies())
}
}
func TestE911WebsheetFlow(t *testing.T) {
+14 -3
View File
@@ -421,11 +421,22 @@ func (s *Server) handleUpdateApply(w http.ResponseWriter, r *http.Request) {
})
return
}
// A binary update changes the trusted server code underneath every active
// browser/API session. Revoke every durable token before scheduling the
// restart and expire this client's cookies so all users must authenticate
// against the newly installed version.
if err := s.store.DeleteAllSessions(r.Context()); err != nil {
s.logger.Error("revoke sessions after update failed", "error", err)
writeError(w, http.StatusInternalServerError, "update_session_revocation_failed", "The update was installed, but active sessions could not be revoked; restart the service and sign in again.")
return
}
s.clearAuthCookies(w)
writeJSON(w, http.StatusOK, map[string]any{
"data": map[string]any{
"applied": true,
"version": result.Latest,
"message": "Update verified and installed; the service is restarting.",
"applied": true,
"version": result.Latest,
"reauthentication_required": true,
"message": "Update verified and installed; all sessions were revoked and the service is restarting.",
},
})
if flusher, ok := w.(http.Flusher); ok {