Add token rotation

This commit is contained in:
2026-09-13 01:09:46 +02:00
parent 48229f15a2
commit aad1b931ba
6 changed files with 461 additions and 89 deletions
+62
View File
@@ -1729,3 +1729,65 @@ func TestPassphraseSessionsAreMemoised(t *testing.T) {
t.Error("the session was not memoised, so every request would derive again")
}
}
// Rotating a secret has to end the sessions that were using it, or rotation
// would not actually revoke anything.
func TestRotationEndsLiveSessions(t *testing.T) {
h := newHarness(t, nil)
// A logged-in browser, and a page render proving the session works.
session := &http.Cookie{Name: tokenCookie, Value: h.token}
req, _ := http.NewRequest("GET", h.ts.URL+"/", nil)
req.AddCookie(session)
resp, err := h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
raw, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(raw), ">friend<") {
t.Fatal("setup: the session is not logged in")
}
var replacement string
if err := h.tokens.Update("friend", func(tok *auth.Token) error {
s, err := tok.SetGenerated()
replacement = s
return err
}); err != nil {
t.Fatal(err)
}
// The old cookie is now just a string.
req, _ = http.NewRequest("GET", h.ts.URL+"/", nil)
req.AddCookie(session)
resp, err = h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
raw, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if strings.Contains(string(raw), ">friend<") {
t.Error("a rotated-away secret still authenticates a session")
}
if !strings.Contains(string(raw), "no longer valid") {
t.Error("the page does not explain that the session ended")
}
// Uploading with the old secret is refused; the new one works.
old := h.upload(t, []byte("x"), map[string]string{"Authorization": "Bearer " + h.token})
old.Body.Close()
if old.StatusCode != http.StatusUnauthorized {
t.Errorf("upload with the old secret => %s, want 401", old.Status)
}
fresh := h.upload(t, []byte("x"), map[string]string{
"Authorization": "Bearer " + replacement,
"Vanity": "after-rotation",
})
if fresh.StatusCode != http.StatusCreated {
t.Fatalf("upload with the rotated secret => %s", fresh.Status)
}
if res := decode[uploadResult](t, fresh); res.ID != "after-rotation" {
t.Errorf("id = %q: the rotated token lost its vanity permission", res.ID)
}
}