package server import ( "encoding/json" "net/http/httptest" "strings" "sync" "testing" "time" "github.com/golang-jwt/jwt/v5" ) func rawAdminSession(t *testing.T, f *fixture, token string) string { t.Helper() c := &claims{} _, err := jwt.ParseWithClaims(token, c, func(*jwt.Token) (any, error) { return []byte(f.server.Config.JWTSecret), nil }) if err != nil { t.Fatal(err) } return c.ID } func TestAdminSessionsSelectiveRevocationAndAudit(t *testing.T) { f := newFixtureWorker(t, false) owner := f.login("admin") target := createTestAccount(t, f, owner, "session-reader", "viewer") first := accountLogin(t, f, "session-reader", testAccountPassword) second := accountLogin(t, f, "session-reader", testAccountPassword) raw := rawAdminSession(t, f, first) key := f.server.administratorSessionKey(raw) code, out := f.request("GET", "/administrators/"+target+"/sessions", owner, nil, "") expect(t, 200, code, out) if out["total"] != float64(2) { t.Fatal(out) } encoded, _ := json.Marshal(out) if strings.Contains(string(encoded), raw) || strings.Contains(string(encoded), first) { t.Fatal("credential material disclosed") } code, out = f.request("DELETE", "/administrators/"+target+"/sessions/"+key, owner, nil, "") expect(t, 204, code, out) code, out = f.request("GET", "/auth/me", first, nil, "") expect(t, 401, code, out) code, out = f.request("GET", "/auth/me", second, nil, "") expect(t, 200, code, out) code, out = f.request("DELETE", "/administrators/"+target+"/sessions/"+key, owner, nil, "") expect(t, 404, code, out) var count int f.db.Get(&count, "SELECT count(*) FROM admin_session_details WHERE session_id=$1", raw) if count != 0 { t.Fatal("metadata not cascaded") } var audit string if err := f.db.Get(&audit, "SELECT target FROM audit_logs WHERE action='revoke_administrator_session'"); err != nil { t.Fatal(err) } if audit != "session-reader:"+key || strings.Contains(audit, raw) { t.Fatal("wrong audit handle") } } func TestAdminSessionsPermissionsMetadataAndLegacy(t *testing.T) { f := newFixtureWorker(t, false) owner := f.login("admin") var target string f.db.Get(&target, "SELECT id FROM administrators WHERE username='admin'") raw := rawAdminSession(t, f, owner) key := f.server.administratorSessionKey(raw) path := "/administrators/" + target + "/sessions" for _, user := range []string{"", "viewer", "operator"} { token := "" want := 401 if user != "" { token = f.login(user) want = 403 } for _, req := range []struct{ method, path string }{{"GET", path}, {"DELETE", path + "/" + key}} { code, out := f.request(req.method, req.path, token, nil, "") expect(t, want, code, out) } } code, out := f.request("GET", path, owner, nil, "") expect(t, 200, code, out) row := out["items"].([]any)[0].(map[string]any) if row["current"] != true || row["createdAt"] == nil { t.Fatal(row) } code, out = f.request("DELETE", path+"/"+key, owner, nil, "") expect(t, 409, code, out) code, out = f.request("DELETE", path+"/"+raw, owner, nil, "") expect(t, 404, code, out) code, out = f.request("GET", "/administrators/missing/sessions", owner, nil, "") expect(t, 404, code, out) // Pre-upgrade sessions remain valid, with unknown source/time rather than invented metadata. f.db.MustExec("DELETE FROM admin_session_details WHERE session_id=$1", raw) code, out = f.request("GET", path, owner, nil, "") expect(t, 200, code, out) row = out["items"].([]any)[0].(map[string]any) if row["createdAt"] != nil || row["clientIp"] != "" || row["userAgent"] != "" { t.Fatal(row) } f.db.MustExec("INSERT INTO admin_sessions VALUES('expired-session',$1,now()-interval '1 second')", target) code, out = f.request("GET", path, owner, nil, "") expect(t, 200, code, out) if out["total"] != float64(1) { t.Fatal(out) } code, out = f.request("DELETE", path+"/"+f.server.administratorSessionKey("expired-session"), owner, nil, "") expect(t, 404, code, out) request := httptest.NewRequest("POST", "/api/v1/auth/login", strings.NewReader(`{"username":"viewer","password":"Demo@2026"}`)) request.RemoteAddr = "192.0.2.8:42000" request.Header.Set("Content-Type", "application/json") request.Header.Set("X-Forwarded-For", "198.51.100.99") request.Header.Set("User-Agent", "Client\n"+strings.Repeat("界", 600)) response := httptest.NewRecorder() f.server.Router.ServeHTTP(response, request) if response.Code != 200 { t.Fatal(response.Body.String()) } var source struct { IP string `db:"client_ip"` Agent string `db:"user_agent"` } if err := f.db.Get(&source, "SELECT client_ip,user_agent FROM admin_session_details d JOIN admin_sessions x ON x.id=d.session_id JOIN administrators a ON a.id=x.admin_id WHERE a.username='viewer' ORDER BY d.created_at DESC LIMIT 1"); err != nil { t.Fatal(err) } if source.IP != "192.0.2.8" || len([]rune(source.Agent)) != 500 || strings.Contains(source.Agent, "\n") { t.Fatal("invalid login source") } } func TestAdminSessionsConcurrentRevocationAndActorRecheck(t *testing.T) { f := newFixtureWorker(t, false) owner := f.login("admin") target := createTestAccount(t, f, owner, "concurrent-sessions", "viewer") token := accountLogin(t, f, "concurrent-sessions", testAccountPassword) path := "/administrators/" + target + "/sessions/" + f.server.administratorSessionKey(rawAdminSession(t, f, token)) codes := make(chan int, 2) var wg sync.WaitGroup for i := 0; i < 2; i++ { wg.Add(1) go func() { defer wg.Done(); code, _ := f.request("DELETE", path, owner, nil, ""); codes <- code }() } wg.Wait() close(codes) sum := 0 for code := range codes { if code != 204 && code != 404 { t.Fatal(code) } sum += code } if sum != 608 { t.Fatal("duplicate successful revocation", sum) } var count int f.db.Get(&count, "SELECT count(*) FROM audit_logs WHERE action='revoke_administrator_session'") if count != 1 { t.Fatal(count) } // Middleware authenticated before revocation must not authorize a write after waiting on the account lock. tx := f.db.MustBegin() defer tx.Rollback() tx.MustExec("SELECT pg_advisory_xact_lock(7651092028)") done := make(chan int, 1) go func() { code, _ := f.request("DELETE", path, owner, nil, ""); done <- code }() deadline := time.Now().Add(3 * time.Second) for { var waiting int if err := f.db.Get(&waiting, "SELECT count(*) FROM pg_stat_activity WHERE datname=current_database() AND wait_event='advisory' AND pid<>pg_backend_pid()"); err != nil { t.Fatal(err) } if waiting > 0 { break } if time.Now().After(deadline) { t.Fatal("request did not reach account transaction") } time.Sleep(10 * time.Millisecond) } tx.MustExec("DELETE FROM admin_sessions WHERE id=$1", rawAdminSession(t, f, owner)) if err := tx.Commit(); err != nil { t.Fatal(err) } select { case code := <-done: if code != 401 { t.Fatal(code) } case <-time.After(3 * time.Second): t.Fatal("blocked request") } }