feat: add permanent deletion for revoked keys

DELETE /admin/api/keys/{id}/purge removes the key record; it requires the key to be revoked first and keeps historical logs. The keys page swaps the revoke button for a delete button once a key is revoked.
This commit is contained in:
2026-10-25 18:40:32 +08:00
parent 293f7f524a
commit 578afe509c
4 changed files with 86 additions and 4 deletions
+50
View File
@@ -252,6 +252,56 @@ func TestKeyRevocationAndProxy(t *testing.T) {
if n != 1 {
t.Fatal(n)
}
w = call(s, "DELETE", "/admin/api/keys/"+id+"/purge", "", c)
if w.Code != 200 {
t.Fatal(w.Body.String())
}
s.db.QueryRow("SELECT count(*) FROM keys WHERE id=?", id).Scan(&n)
if n != 0 {
t.Fatal("purged key still stored", n)
}
w = call(s, "DELETE", "/admin/api/keys/"+id+"/purge", "", c)
if w.Code != 404 {
t.Fatal("purged key not reported missing", w.Code)
}
s.db.QueryRow("SELECT count(*) FROM logs").Scan(&n)
if n != 1 {
t.Fatal("purge removed history", n)
}
}
func TestKeyPurgeRequiresRevocation(t *testing.T) {
s := testServer(t)
c := loginCookie(t, s)
w := call(s, "POST", "/admin/api/keys", `{"name":"gone","all_channels":true}`, c)
var key map[string]string
if err := json.Unmarshal(w.Body.Bytes(), &key); err != nil || w.Code != 200 {
t.Fatal(w.Code, w.Body.String(), err)
}
var id string
if err := s.db.QueryRow("SELECT id FROM keys WHERE name='gone'").Scan(&id); err != nil {
t.Fatal(err)
}
if w = call(s, "DELETE", "/admin/api/keys/"+id+"/purge", "", c); w.Code != 409 {
t.Fatal("purged active key", w.Code, w.Body.String())
}
if w = call(s, "DELETE", "/admin/api/keys/"+id, "", c); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call(s, "DELETE", "/admin/api/keys/"+id+"/purge", "", c); w.Code != 200 {
t.Fatal(w.Body.String())
}
w = call(s, "GET", "/admin/api/keys", "", c)
if strings.Contains(w.Body.String(), `"gone"`) {
t.Fatal("purged key still listed", w.Body.String())
}
r := httptest.NewRequest("GET", "/v1/models", nil)
r.Header.Set("Authorization", "Bearer "+key["key"])
w = httptest.NewRecorder()
s.ServeHTTP(w, r)
if w.Code != http.StatusUnauthorized {
t.Fatal("purged key still authenticates", w.Code)
}
}
func TestStatsUnknownAndFilters(t *testing.T) {
s := testServer(t)