Files
observatory/internal/httpserver/ui_test.go
T
gamertan 92a66db3df docs: publish Preview 19 dogfood evidence
Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence.

AI-Assisted: OpenAI Codex
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
2026-08-18 21:47:08 -04:00

1123 lines
65 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only
package httpserver
import (
"bytes"
"context"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"time"
"gamertan.com/observatory/internal/identity"
"gamertan.com/observatory/internal/model"
"gamertan.com/observatory/internal/query"
"gamertan.com/observatory/internal/schema"
"gamertan.com/observatory/internal/site"
"gamertan.com/observatory/internal/storage"
"gamertan.com/web/authhttp"
)
func TestHandlerAssignsFreshBoundedRequestIDs(t *testing.T) {
server, store, identities, _ := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
var previous string
for _, test := range []struct {
method string
target string
}{
{http.MethodGet, "https://observatory.example/healthz"},
{http.MethodHead, "https://observatory.example/readyz"},
{http.MethodGet, "https://observatory.example/"},
{http.MethodGet, "https://observatory.example/not-found"},
} {
header := http.Header{"X-Request-ID": []string{"attacker-selected"}}
response := perform(handler, test.method, test.target, nil, nil, header)
requestID := response.Header().Get("X-Request-ID")
decoded, err := hex.DecodeString(requestID)
if err != nil || len(decoded) != 16 || requestID == "attacker-selected" || requestID == previous {
t.Fatalf("%s %s request_id=%q decoded=%d err=%v previous=%q", test.method, test.target, requestID, len(decoded), err, previous)
}
previous = requestID
}
}
func TestHTMLInterfaceAuthenticationAssetsAndOverview(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
landing := perform(handler, http.MethodGet, "https://observatory.example/", nil, nil)
if landing.Code != http.StatusOK || landing.Header().Get("Content-Type") != "text/html; charset=utf-8" || !strings.Contains(landing.Body.String(), "Keep the evidence close.") {
t.Fatalf("landing status=%d body=%s", landing.Code, landing.Body.String())
}
csp := landing.Header().Get("Content-Security-Policy")
if strings.Contains(csp, "unsafe-inline") || !strings.Contains(csp, "script-src 'self'") || !strings.Contains(csp, "style-src 'self'") || !strings.Contains(csp, "manifest-src 'self'") || !strings.Contains(csp, "worker-src 'self'") || strings.Contains(landing.Body.String(), "<style") {
t.Fatalf("CSP=%q body=%s", csp, landing.Body.String())
}
if !strings.Contains(landing.Body.String(), `<link rel="manifest" href="/manifest.webmanifest">`) {
t.Fatalf("landing omitted manifest discovery: %s", landing.Body.String())
}
head := perform(handler, http.MethodHead, "https://observatory.example/", nil, nil)
if head.Code != http.StatusOK || head.Body.Len() != 0 || head.Header().Get("Content-Length") != landing.Header().Get("Content-Length") {
t.Fatalf("HEAD status=%d length=%q body=%d", head.Code, head.Header().Get("Content-Length"), head.Body.Len())
}
for _, path := range []string{site.AssetPaths().StylePath, site.AssetPaths().ScriptPath, site.AssetPaths().IconPath} {
asset := perform(handler, http.MethodGet, "https://observatory.example"+path, nil, nil)
if asset.Code != http.StatusOK || asset.Body.Len() == 0 || asset.Header().Get("Cache-Control") != "public, max-age=31536000, immutable" {
t.Fatalf("asset %s status=%d cache=%q", path, asset.Code, asset.Header().Get("Cache-Control"))
}
assetHead := perform(handler, http.MethodHead, "https://observatory.example"+path, nil, nil)
if assetHead.Code != http.StatusOK || assetHead.Body.Len() != 0 || assetHead.Header().Get("Content-Length") != asset.Header().Get("Content-Length") {
t.Fatalf("asset HEAD %s status=%d", path, assetHead.Code)
}
}
manifest := perform(handler, http.MethodGet, "https://observatory.example/manifest.webmanifest", nil, nil)
if manifest.Code != http.StatusOK || manifest.Header().Get("Content-Type") != "application/manifest+json" || !strings.Contains(manifest.Body.String(), site.AssetPaths().IconPath) {
t.Fatalf("manifest status=%d headers=%v body=%s", manifest.Code, manifest.Header(), manifest.Body.String())
}
manifestHead := perform(handler, http.MethodHead, "https://observatory.example/manifest.webmanifest", nil, nil)
if manifestHead.Code != http.StatusOK || manifestHead.Body.Len() != 0 || manifestHead.Header().Get("Content-Length") != manifest.Header().Get("Content-Length") {
t.Fatalf("manifest HEAD status=%d body=%d", manifestHead.Code, manifestHead.Body.Len())
}
worker := perform(handler, http.MethodGet, "https://observatory.example/service-worker.js", nil, nil)
if worker.Code != http.StatusOK || worker.Header().Get("Content-Type") != "text/javascript; charset=utf-8" || worker.Header().Get("Cache-Control") != "no-cache" || worker.Header().Get("Service-Worker-Allowed") != "/" || !strings.Contains(worker.Body.String(), "cache-inbox") {
t.Fatalf("worker status=%d headers=%v", worker.Code, worker.Header())
}
offline := perform(handler, http.MethodGet, "https://observatory.example/offline/", nil, nil)
if offline.Code != http.StatusOK || !strings.Contains(offline.Body.String(), "The evidence is still safe.") {
t.Fatalf("offline status=%d body=%s", offline.Code, offline.Body.String())
}
disabledPush := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", strings.NewReader(`{}`), nil)
if disabledPush.Code != http.StatusNotFound {
t.Fatalf("disabled push status=%d body=%s", disabledPush.Code, disabledPush.Body.String())
}
unauthenticated := perform(handler, http.MethodGet, "https://observatory.example/app/", nil, nil)
if unauthenticated.Code != http.StatusSeeOther || unauthenticated.Header().Get("Location") != "/login/" {
t.Fatalf("unauthenticated status=%d location=%q", unauthenticated.Code, unauthenticated.Header().Get("Location"))
}
extraQuery := perform(handler, http.MethodGet, "https://observatory.example/app/?unexpected=true", nil, []*http.Cookie{loginHTML(t, handler)})
if extraQuery.Code != http.StatusBadRequest {
t.Fatalf("unexpected query status=%d", extraQuery.Code)
}
loginCookie := loginHTML(t, handler)
now := server.now()
token, err := store.CreateSource(context.Background(), "ui-source", model.Scope{OrganizationID: bootstrap.Organization.ID, ProjectID: "project-a", EnvironmentID: "production", ServiceID: "service-a"})
if err != nil {
t.Fatal(err)
}
batch := model.Batch{Version: model.BatchVersion, SourceID: "ui-source", StreamID: "requests", Sequence: 1, ObservedAt: now, Signal: model.SignalLogs, Records: []model.Observation{{Timestamp: now, Name: "application.http.request", Attributes: map[string]string{"http.status_code": "200", "http.route": "/"}}}}
body, _ := json.Marshal(batch)
updates, remove, err := server.refresh.subscribe(bootstrap.Organization.ID)
if err != nil {
t.Fatal(err)
}
defer remove()
ingestHeaders := http.Header{"Authorization": []string{"Bearer " + token}, "Content-Type": []string{"application/json"}}
ingest := perform(handler, http.MethodPost, "https://observatory.example/api/v1/ingest/native", bytes.NewReader(body), nil, ingestHeaders)
if ingest.Code != http.StatusAccepted {
t.Fatalf("ingest status=%d body=%s", ingest.Code, ingest.Body.String())
}
pending := perform(handler, http.MethodGet, "https://observatory.example/app/", nil, []*http.Cookie{loginCookie})
if pending.Code != http.StatusOK || !strings.Contains(pending.Body.String(), "Durable evidence is still being indexed.") || !strings.Contains(pending.Body.String(), "Accepted batches safely stored: 1.") || !strings.Contains(pending.Body.String(), `role="status"`) {
t.Fatalf("pending app status=%d body=%s", pending.Code, pending.Body.String())
}
if err = store.Recover(context.Background()); err != nil {
t.Fatal(err)
}
select {
case <-updates:
default:
t.Fatal("successful ingest did not publish organization refresh")
}
app := perform(handler, http.MethodGet, "https://observatory.example/app/", nil, []*http.Cookie{loginCookie})
if app.Code != http.StatusOK || !strings.Contains(app.Body.String(), bootstrap.Organization.Name) || !strings.Contains(app.Body.String(), "application.http.request") || !strings.Contains(app.Body.String(), "Recent metrics") || !strings.Contains(app.Body.String(), "Recent traces") || !strings.Contains(app.Body.String(), "Recent deployments") || !strings.Contains(app.Body.String(), `action="/app/queries/builder/"`) || !strings.Contains(app.Body.String(), "Build a query") {
t.Fatalf("app status=%d body=%s", app.Code, app.Body.String())
}
if strings.Contains(app.Body.String(), "Durable evidence is still being indexed.") {
t.Fatalf("app retained indexing status after projection completed: %s", app.Body.String())
}
for _, forbidden := range []string{"Render #", "request number", "position:sticky", "unsafe-inline"} {
if strings.Contains(app.Body.String(), forbidden) {
t.Fatalf("app exposed forbidden marker %q", forbidden)
}
}
appHead := perform(handler, http.MethodHead, "https://observatory.example/app/", nil, []*http.Cookie{loginCookie})
if appHead.Code != http.StatusOK || appHead.Body.Len() != 0 || appHead.Header().Get("Content-Length") != app.Header().Get("Content-Length") {
t.Fatalf("app HEAD status=%d body=%d", appHead.Code, appHead.Body.Len())
}
sessionToken := loginCookie.Value
csrf, err := authhttp.CSRFToken(sessionToken, "session:delete")
if err != nil {
t.Fatal(err)
}
logout := url.Values{"csrf_token": []string{csrf}}.Encode()
logoutHeaders := http.Header{"Origin": []string{"null"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
loggedOut := perform(handler, http.MethodPost, "https://observatory.example/logout/", strings.NewReader(logout), []*http.Cookie{loginCookie}, logoutHeaders)
if loggedOut.Code != http.StatusSeeOther || loggedOut.Header().Get("Location") != "/login/" {
t.Fatalf("logout status=%d location=%q", loggedOut.Code, loggedOut.Header().Get("Location"))
}
}
func TestExploreWorkbenchUsesBoundedServerRenderedQueries(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
unauthenticated := perform(handler, http.MethodGet, "https://observatory.example/app/explore/?organization="+url.QueryEscape(bootstrap.Organization.ID), nil, nil)
if unauthenticated.Code != http.StatusSeeOther || unauthenticated.Header().Get("Location") != "/login/" {
t.Fatalf("unauthenticated status=%d location=%q", unauthenticated.Code, unauthenticated.Header().Get("Location"))
}
cookie := loginHTML(t, handler)
missingOrganization := perform(handler, http.MethodGet, "https://observatory.example/app/explore/", nil, []*http.Cookie{cookie})
if missingOrganization.Code != http.StatusBadRequest {
t.Fatalf("missing organization status=%d body=%s", missingOrganization.Code, missingOrganization.Body.String())
}
target := "https://observatory.example/app/explore/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
page := perform(handler, http.MethodGet, target, nil, []*http.Cookie{cookie})
pageBody := page.Body.String()
currentLink := `<a aria-current="page" href="/app/explore/?organization=` + bootstrap.Organization.ID + `">Explore</a>`
if page.Code != http.StatusOK || !strings.Contains(pageBody, "Follow the evidence.") || !strings.Contains(pageBody, currentLink) || !strings.Contains(pageBody, defaultExploreQuery) || strings.Contains(pageBody, "Authorized query results") {
t.Fatalf("explore status=%d body=%s", page.Code, pageBody)
}
if !strings.Contains(pageBody, `method="post" action="/app/explore/?organization=`+bootstrap.Organization.ID+`"`) || strings.Contains(pageBody, "?query=") {
t.Fatalf("explore form did not keep query in POST body: %s", pageBody)
}
head := perform(handler, http.MethodHead, target, nil, []*http.Cookie{cookie})
if head.Code != http.StatusOK || head.Body.Len() != 0 || head.Header().Get("Content-Length") != page.Header().Get("Content-Length") {
t.Fatalf("explore HEAD status=%d length=%q body=%d", head.Code, head.Header().Get("Content-Length"), head.Body.Len())
}
now := server.now()
sourceToken, err := store.CreateSource(context.Background(), "explore-source", model.Scope{OrganizationID: bootstrap.Organization.ID, ProjectID: "project-a", EnvironmentID: "production", ServiceID: "service-a"})
if err != nil {
t.Fatal(err)
}
batch := model.Batch{Version: model.BatchVersion, SourceID: "explore-source", StreamID: "requests", Sequence: 1, ObservedAt: now, Signal: model.SignalLogs, Records: []model.Observation{{Timestamp: now, Name: "application.http.request", Attributes: map[string]string{"http.status_code": "200", "http.route": "/explore-proof"}}}}
if _, err = store.Ingest(context.Background(), sourceToken, batch, now); err != nil {
t.Fatal(err)
}
if err = store.Recover(context.Background()); err != nil {
t.Fatal(err)
}
csrf, err := authhttp.CSRFToken(cookie.Value, "query:execute")
if err != nil {
t.Fatal(err)
}
queryText := `logs | where route == "/explore-proof" | window 1h | limit 10`
form := url.Values{"csrf_token": []string{csrf}, "query": []string{queryText}}.Encode()
headers := http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}}
result := perform(handler, http.MethodPost, target, strings.NewReader(form), []*http.Cookie{cookie}, headers)
resultBody := result.Body.String()
for _, required := range []string{"Authorized query results", "/explore-proof", "Scanned rows", "Matched rows", "Scanned bytes", "Execution", html.EscapeString(queryText)} {
if !strings.Contains(resultBody, required) {
t.Fatalf("result omitted %q: status=%d body=%s", required, result.Code, resultBody)
}
}
if result.Code != http.StatusOK || strings.Contains(result.Header().Get("Content-Type"), "application/json") {
t.Fatalf("query status=%d type=%q body=%s", result.Code, result.Header().Get("Content-Type"), resultBody)
}
invalidForm := url.Values{"csrf_token": []string{"invalid"}, "query": []string{"logs | limit 10"}}.Encode()
invalid := perform(handler, http.MethodPost, target, strings.NewReader(invalidForm), []*http.Cookie{cookie}, headers)
if invalid.Code != http.StatusForbidden || !strings.Contains(invalid.Body.String(), "query form expired") || strings.Contains(invalid.Header().Get("Content-Type"), "application/json") {
t.Fatalf("invalid CSRF status=%d body=%s", invalid.Code, invalid.Body.String())
}
badQuery := url.Values{"csrf_token": []string{csrf}, "query": []string{"logs | become unbounded"}}.Encode()
rejected := perform(handler, http.MethodPost, target, strings.NewReader(badQuery), []*http.Cookie{cookie}, headers)
if rejected.Code != http.StatusUnprocessableEntity || !strings.Contains(rejected.Body.String(), "could not be parsed") || !strings.Contains(rejected.Body.String(), "logs | become unbounded") {
t.Fatalf("rejected query status=%d body=%s", rejected.Code, rejected.Body.String())
}
}
func TestHTMLLoginFailsClosed(t *testing.T) {
server, store, identities, _ := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
wrongOrigin := http.Header{"Origin": []string{"https://attacker.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
result := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader("identifier=operator&password=do-not-echo"), nil, wrongOrigin)
if result.Code != http.StatusForbidden || !strings.Contains(result.Body.String(), loginFormFailure) || strings.Contains(result.Body.String(), "do-not-echo") || strings.Contains(result.Header().Get("Content-Type"), "application/json") {
t.Fatalf("wrong origin status=%d body=%s", result.Code, result.Body.String())
}
csrfCookie, csrfToken := loginFormCSRF(t, handler)
extraField := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
extraForm := url.Values{"csrf_token": []string{csrfToken}, "identifier": []string{"operator"}, "password": []string{"wrong"}, "next": []string{"https://attacker.example"}}.Encode()
result = perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(extraForm), []*http.Cookie{csrfCookie}, extraField)
if result.Code != http.StatusForbidden || !strings.Contains(result.Body.String(), loginFormFailure) || strings.Contains(result.Body.String(), "attacker.example") || result.Header().Get("Location") != "" {
t.Fatalf("extra field status=%d location=%q body=%s", result.Code, result.Header().Get("Location"), result.Body.String())
}
}
func TestHTMLLoginUsesTokenWhenBrowserOmitsOrObscuresOriginMetadata(t *testing.T) {
server, store, identities, _ := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
page := perform(handler, http.MethodGet, "https://observatory.example/login/", nil, nil)
if page.Code != http.StatusOK {
t.Fatalf("login page status=%d body=%s", page.Code, page.Body.String())
}
var csrfCookie *http.Cookie
for _, cookie := range page.Result().Cookies() {
if cookie.Name == loginCSRFCookieName {
csrfCookie = cookie
}
}
if csrfCookie == nil || !csrfCookie.Secure || !csrfCookie.HttpOnly || csrfCookie.SameSite != http.SameSiteStrictMode || csrfCookie.MaxAge != 600 {
t.Fatalf("login CSRF cookie=%+v", csrfCookie)
}
const marker = `name="csrf_token" value="`
start := strings.Index(page.Body.String(), marker)
if start < 0 {
t.Fatalf("login page omitted CSRF token: %s", page.Body.String())
}
start += len(marker)
end := strings.IndexByte(page.Body.String()[start:], '"')
if end < 0 {
t.Fatal("login page CSRF token is unterminated")
}
token := page.Body.String()[start : start+end]
if token == "" || token != csrfCookie.Value {
t.Fatal("login form and cookie CSRF tokens differ")
}
form := url.Values{"csrf_token": []string{token}, "identifier": []string{"not-a-user"}, "password": []string{"not-a-password"}}.Encode()
contentType := http.Header{"Content-Type": []string{"application/x-www-form-urlencoded"}}
omittedMetadata := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(form), []*http.Cookie{csrfCookie}, contentType)
if omittedMetadata.Code != http.StatusUnauthorized || strings.Contains(omittedMetadata.Body.String(), "not-a-password") {
t.Fatalf("origin-metadata fallback status=%d body=%s", omittedMetadata.Code, omittedMetadata.Body.String())
}
opaqueSameOriginHeaders := contentType.Clone()
opaqueSameOriginHeaders.Set("Origin", "null")
opaqueSameOriginHeaders.Set("Sec-Fetch-Site", "same-origin")
opaqueSameOrigin := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(form), []*http.Cookie{csrfCookie}, opaqueSameOriginHeaders)
if opaqueSameOrigin.Code != http.StatusUnauthorized || strings.Contains(opaqueSameOrigin.Body.String(), "not-a-password") {
t.Fatalf("opaque same-origin fallback status=%d body=%s", opaqueSameOrigin.Code, opaqueSameOrigin.Body.String())
}
withoutToken := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader("identifier=not-a-user&password=not-a-password"), nil, contentType)
if withoutToken.Code != http.StatusForbidden {
t.Fatalf("originless tokenless status=%d body=%s", withoutToken.Code, withoutToken.Body.String())
}
opaqueWithoutToken := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader("identifier=not-a-user&password=not-a-password"), nil, opaqueSameOriginHeaders)
if opaqueWithoutToken.Code != http.StatusForbidden {
t.Fatalf("opaque tokenless status=%d body=%s", opaqueWithoutToken.Code, opaqueWithoutToken.Body.String())
}
opaqueWithoutFetchMetadata := contentType.Clone()
opaqueWithoutFetchMetadata.Set("Origin", "null")
opaqueTokenOnly := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(form), []*http.Cookie{csrfCookie}, opaqueWithoutFetchMetadata)
if opaqueTokenOnly.Code != http.StatusUnauthorized || !strings.Contains(opaqueTokenOnly.Body.String(), loginCredentialFailure) {
t.Fatalf("opaque origin without same-origin fetch metadata status=%d body=%s", opaqueTokenOnly.Code, opaqueTokenOnly.Body.String())
}
wrongOrigin := contentType.Clone()
wrongOrigin.Set("Origin", "https://attacker.example")
crossSite := perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(form), []*http.Cookie{csrfCookie}, wrongOrigin)
if crossSite.Code != http.StatusForbidden {
t.Fatalf("cross-site token replay status=%d body=%s", crossSite.Code, crossSite.Body.String())
}
}
func TestTemporaryOperatorMustRotatePasswordBeforeUsingApplication(t *testing.T) {
server, store, identities, _ := newRotationTestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
const temporary = "temporary correct horse battery staple"
const replacement = "permanent correct horse battery staple"
headers := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
login := loginHTMLResponse(t, handler, "operator", temporary)
if login.Code != http.StatusSeeOther || login.Header().Get("Location") != "/account/password/" || strings.Contains(login.Body.String(), temporary) {
t.Fatalf("login status=%d location=%q body=%s", login.Code, login.Header().Get("Location"), login.Body.String())
}
var cookie *http.Cookie
for _, candidate := range login.Result().Cookies() {
if candidate.Name == "__Host-observatory_session" {
cookie = candidate
break
}
}
if cookie == nil {
t.Fatalf("login cookies=%+v", login.Result().Cookies())
}
blocked := perform(handler, http.MethodGet, "https://observatory.example/app/", nil, []*http.Cookie{cookie})
if blocked.Code != http.StatusSeeOther || blocked.Header().Get("Location") != "/account/password/" {
t.Fatalf("blocked app status=%d location=%q", blocked.Code, blocked.Header().Get("Location"))
}
blockedWrite := perform(handler, http.MethodPost, "https://observatory.example/api/v1/query", strings.NewReader(`{}`), []*http.Cookie{cookie}, headers)
if blockedWrite.Code != http.StatusForbidden {
t.Fatalf("blocked write status=%d body=%s", blockedWrite.Code, blockedWrite.Body.String())
}
page := perform(handler, http.MethodGet, "https://observatory.example/account/password/", nil, []*http.Cookie{cookie})
if page.Code != http.StatusOK || !strings.Contains(page.Body.String(), "Choose your password") || strings.Contains(page.Body.String(), temporary) {
t.Fatalf("password page status=%d body=%s", page.Code, page.Body.String())
}
head := perform(handler, http.MethodHead, "https://observatory.example/account/password/", nil, []*http.Cookie{cookie})
if head.Code != http.StatusOK || head.Body.Len() != 0 || head.Header().Get("Content-Length") != page.Header().Get("Content-Length") {
t.Fatalf("password HEAD status=%d length=%q body=%d", head.Code, head.Header().Get("Content-Length"), head.Body.Len())
}
csrf, err := authhttp.CSRFToken(cookie.Value, "account:password:change")
if err != nil {
t.Fatal(err)
}
crossSiteHeaders := http.Header{"Origin": []string{"https://attacker.example"}, "Sec-Fetch-Site": []string{"cross-site"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
crossSiteForm := url.Values{"csrf_token": []string{csrf}, "current_password": []string{temporary}, "new_password": []string{replacement}, "confirm_password": []string{replacement}}.Encode()
crossSite := perform(handler, http.MethodPost, "https://observatory.example/account/password/", strings.NewReader(crossSiteForm), []*http.Cookie{cookie}, crossSiteHeaders)
if crossSite.Code != http.StatusForbidden || !strings.Contains(crossSite.Body.String(), passwordFormFailure) || strings.Contains(crossSite.Body.String(), temporary) || strings.Contains(crossSite.Body.String(), replacement) || strings.Contains(crossSite.Header().Get("Content-Type"), "application/json") {
t.Fatalf("cross-site password status=%d body=%s", crossSite.Code, crossSite.Body.String())
}
invalidTokenForm := url.Values{"csrf_token": []string{"invalid"}, "current_password": []string{temporary}, "new_password": []string{replacement}, "confirm_password": []string{replacement}}.Encode()
invalidToken := perform(handler, http.MethodPost, "https://observatory.example/account/password/", strings.NewReader(invalidTokenForm), []*http.Cookie{cookie}, headers)
if invalidToken.Code != http.StatusForbidden || !strings.Contains(invalidToken.Body.String(), passwordFormFailure) || strings.Contains(invalidToken.Body.String(), temporary) || strings.Contains(invalidToken.Body.String(), replacement) || strings.Contains(invalidToken.Header().Get("Content-Type"), "application/json") {
t.Fatalf("invalid-token password status=%d body=%s", invalidToken.Code, invalidToken.Body.String())
}
mismatch := url.Values{"csrf_token": []string{csrf}, "current_password": []string{temporary}, "new_password": []string{replacement}, "confirm_password": []string{"different password value"}}.Encode()
rejected := perform(handler, http.MethodPost, "https://observatory.example/account/password/", strings.NewReader(mismatch), []*http.Cookie{cookie}, headers)
if rejected.Code != http.StatusUnprocessableEntity || !strings.Contains(rejected.Body.String(), passwordMatchFailure) || strings.Contains(rejected.Body.String(), temporary) || strings.Contains(rejected.Body.String(), replacement) {
t.Fatalf("mismatch status=%d body=%s", rejected.Code, rejected.Body.String())
}
change := url.Values{"csrf_token": []string{csrf}, "current_password": []string{temporary}, "new_password": []string{replacement}, "confirm_password": []string{replacement}}.Encode()
privacyHeaders := http.Header{"Origin": []string{"null"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
changed := perform(handler, http.MethodPost, "https://observatory.example/account/password/", strings.NewReader(change), []*http.Cookie{cookie}, privacyHeaders)
if changed.Code != http.StatusSeeOther || changed.Header().Get("Location") != "/login/?password=changed" {
t.Fatalf("change status=%d location=%q body=%s", changed.Code, changed.Header().Get("Location"), changed.Body.String())
}
oldSession := perform(handler, http.MethodGet, "https://observatory.example/app/", nil, []*http.Cookie{cookie})
if oldSession.Code != http.StatusSeeOther || oldSession.Header().Get("Location") != "/login/" {
t.Fatalf("old session status=%d location=%q", oldSession.Code, oldSession.Header().Get("Location"))
}
if _, _, err = identities.Auth.Authenticate(t.Context(), "operator", temporary, time.Hour); err == nil {
t.Fatal("temporary password remained valid")
}
_, principal, err := identities.Auth.Authenticate(t.Context(), "operator", replacement, time.Hour)
if err != nil || principal.User.PasswordChangeRequired {
t.Fatalf("replacement principal=%+v err=%v", principal, err)
}
newLogin := loginHTMLResponse(t, handler, "operator", replacement)
if newLogin.Code != http.StatusSeeOther || newLogin.Header().Get("Location") != "/app/" {
t.Fatalf("new login status=%d location=%q", newLogin.Code, newLogin.Header().Get("Location"))
}
}
func TestAPITemporaryOperatorReceivesScopedRotationToken(t *testing.T) {
server, store, identities, _ := newRotationTestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
loginHeaders := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/json"}}
login := perform(handler, http.MethodPost, "https://observatory.example/api/v1/session", strings.NewReader(`{"identifier":"operator","password":"temporary correct horse battery staple"}`), nil, loginHeaders)
var session struct {
PasswordChangeRequired bool `json:"password_change_required"`
PasswordChangeCSRF string `json:"password_change_csrf"`
}
if login.Code != http.StatusOK || json.Unmarshal(login.Body.Bytes(), &session) != nil || !session.PasswordChangeRequired || session.PasswordChangeCSRF == "" {
t.Fatalf("login status=%d session=%+v body=%s", login.Code, session, login.Body.String())
}
cookies := login.Result().Cookies()
changeHeaders := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/json"}, "X-CSRF-Token": []string{session.PasswordChangeCSRF}}
changed := perform(handler, http.MethodPost, "https://observatory.example/api/v1/account/password", strings.NewReader(`{"current_password":"temporary correct horse battery staple","new_password":"API replacement password value"}`), cookies, changeHeaders)
if changed.Code != http.StatusNoContent || changed.Body.Len() != 0 {
t.Fatalf("change status=%d body=%s", changed.Code, changed.Body.String())
}
_, principal, err := identities.Auth.Authenticate(t.Context(), "operator", "API replacement password value", time.Hour)
if err != nil || principal.User.PasswordChangeRequired {
t.Fatalf("principal=%+v err=%v", principal, err)
}
}
func TestLiveRefreshStreamIsAuthorizedAndCarriesNoTelemetry(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
unauthorized := perform(handler, http.MethodGet, "https://observatory.example/app/events?organization="+url.QueryEscape(bootstrap.Organization.ID), nil, nil)
if unauthorized.Code != http.StatusUnauthorized {
t.Fatalf("unauthorized stream status=%d", unauthorized.Code)
}
cookie := loginHTML(t, handler)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
request := httptest.NewRequest(http.MethodGet, "https://observatory.example/app/events?organization="+url.QueryEscape(bootstrap.Organization.ID), nil).WithContext(ctx)
request.AddCookie(cookie)
stream := newStreamRecorder()
done := make(chan struct{})
go func() {
handler.ServeHTTP(stream, request)
close(done)
}()
waitFlush := func() {
t.Helper()
select {
case <-stream.flushed:
case <-ctx.Done():
t.Fatal("stream did not flush before timeout")
}
}
waitFlush()
if stream.statusCode() != http.StatusOK || stream.Header().Get("Content-Type") != "text/event-stream; charset=utf-8" || stream.Header().Get("X-Accel-Buffering") != "no" || stream.bodyString() != "event: ready\ndata: {}\n\n" {
t.Fatalf("stream status=%d headers=%v body=%q", stream.statusCode(), stream.Header(), stream.bodyString())
}
server.refresh.publish(bootstrap.Organization.ID)
waitFlush()
streamBody := stream.bodyString()
if streamBody != "event: ready\ndata: {}\n\nevent: refresh\ndata: {}\n\n" || strings.Contains(streamBody, bootstrap.Organization.ID) || strings.Contains(streamBody, "service") {
t.Fatalf("stream body=%q", streamBody)
}
cancel()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("stream did not stop after cancellation")
}
}
func TestDashboardManagementIsScopedCSRFProtectedAndExportable(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
cookie := loginHTML(t, handler)
csrf, err := authhttp.CSRFToken(cookie.Value, "dashboards:manage")
if err != nil {
t.Fatal(err)
}
headers := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
invalid := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{"invalid"},
"name": []string{"Recent errors"}, "description": []string{"A bounded recent error view."},
"query": []string{"logs | where status >= 500 | window 1h | limit 50"},
}.Encode()
denied := perform(handler, http.MethodPost, "https://observatory.example/app/queries/", strings.NewReader(invalid), []*http.Cookie{cookie}, headers)
if denied.Code != http.StatusForbidden {
t.Fatalf("invalid CSRF status=%d", denied.Code)
}
queryForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"name": []string{"Recent errors"}, "description": []string{"A bounded recent error view."},
"query": []string{"logs | where status >= 500 | window 1h | limit 50"},
}.Encode()
privacyHeaders := http.Header{"Origin": []string{"null"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
createdQuery := perform(handler, http.MethodPost, "https://observatory.example/app/queries/", strings.NewReader(queryForm), []*http.Cookie{cookie}, privacyHeaders)
if createdQuery.Code != http.StatusSeeOther || !strings.HasPrefix(createdQuery.Header().Get("Location"), "/app/?organization=") {
t.Fatalf("query status=%d location=%q body=%s", createdQuery.Code, createdQuery.Header().Get("Location"), createdQuery.Body.String())
}
queries, err := store.SavedQueries(context.Background(), bootstrap.Organization.ID)
if err != nil || len(queries) != 1 {
t.Fatalf("queries=%+v err=%v", queries, err)
}
mismatchedDashboard := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"slug": []string{"invalid-stat"}, "name": []string{"Invalid stat"},
"description": []string{"A non-summary query cannot become a statistic."}, "panel_title": []string{"Invalid"},
"saved_query_id": []string{queries[0].ID}, "visualization": []string{"stat"},
}.Encode()
mismatched := perform(handler, http.MethodPost, "https://observatory.example/app/dashboards/", strings.NewReader(mismatchedDashboard), []*http.Cookie{cookie}, headers)
if mismatched.Code != http.StatusUnprocessableEntity {
t.Fatalf("mismatched presentation status=%d body=%s", mismatched.Code, mismatched.Body.String())
}
dashboardForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"slug": []string{"recent-errors"}, "name": []string{"Recent errors"},
"description": []string{"An accessible bounded error dashboard."}, "panel_title": []string{"Errors"},
"saved_query_id": []string{queries[0].ID}, "visualization": []string{"table"},
}.Encode()
createdDashboard := perform(handler, http.MethodPost, "https://observatory.example/app/dashboards/", strings.NewReader(dashboardForm), []*http.Cookie{cookie}, headers)
if createdDashboard.Code != http.StatusSeeOther || !strings.HasPrefix(createdDashboard.Header().Get("Location"), "/app/dashboards/recent-errors/") {
t.Fatalf("dashboard status=%d location=%q body=%s", createdDashboard.Code, createdDashboard.Header().Get("Location"), createdDashboard.Body.String())
}
target := "https://observatory.example/app/dashboards/recent-errors/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
dashboard := perform(handler, http.MethodGet, target, nil, []*http.Cookie{cookie})
if dashboard.Code != http.StatusOK || !strings.Contains(dashboard.Body.String(), "An accessible bounded error dashboard.") || !strings.Contains(dashboard.Body.String(), "Recent errors") || !strings.Contains(dashboard.Body.String(), ">Errors</h2>") {
t.Fatalf("dashboard status=%d body=%s", dashboard.Code, dashboard.Body.String())
}
dashboardHead := perform(handler, http.MethodHead, target, nil, []*http.Cookie{cookie})
if dashboardHead.Code != http.StatusOK || dashboardHead.Body.Len() != 0 || dashboardHead.Header().Get("Content-Length") != dashboard.Header().Get("Content-Length") {
t.Fatalf("dashboard HEAD status=%d length=%q", dashboardHead.Code, dashboardHead.Header().Get("Content-Length"))
}
storedDashboard, err := store.Dashboard(context.Background(), bootstrap.Organization.ID, "recent-errors")
if err != nil || storedDashboard.Revision != 1 || !strings.Contains(dashboard.Body.String(), "Update dashboard details") || !strings.Contains(dashboard.Body.String(), `name="expected_revision" value="1"`) {
t.Fatalf("stored dashboard=%+v err=%v body=%s", storedDashboard, err, dashboard.Body.String())
}
revisionForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"dashboard_id": []string{storedDashboard.ID}, "expected_revision": []string{strconv.Itoa(storedDashboard.Revision)},
"slug": []string{storedDashboard.Slug}, "name": []string{"Current errors"},
"description": []string{"A revision-safe bounded error dashboard."},
}.Encode()
revised := perform(handler, http.MethodPost, target, strings.NewReader(revisionForm), []*http.Cookie{cookie}, headers)
if revised.Code != http.StatusSeeOther {
t.Fatalf("revision status=%d body=%s", revised.Code, revised.Body.String())
}
stale := perform(handler, http.MethodPost, target, strings.NewReader(revisionForm), []*http.Cookie{cookie}, headers)
if stale.Code != http.StatusConflict {
t.Fatalf("stale revision status=%d body=%s", stale.Code, stale.Body.String())
}
storedDashboard, err = store.Dashboard(context.Background(), bootstrap.Organization.ID, "recent-errors")
if err != nil || storedDashboard.Revision != 2 || storedDashboard.Name != "Current errors" {
t.Fatalf("revised dashboard=%+v err=%v", storedDashboard, err)
}
addPanelForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"dashboard_id": []string{storedDashboard.ID}, "expected_revision": []string{strconv.Itoa(storedDashboard.Revision)},
"panel_title": []string{"Recent failures"}, "saved_query_id": []string{queries[0].ID}, "visualization": []string{"table"},
}.Encode()
addTarget := "https://observatory.example/app/dashboards/recent-errors/panels/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
added := perform(handler, http.MethodPost, addTarget, strings.NewReader(addPanelForm), []*http.Cookie{cookie}, headers)
if added.Code != http.StatusSeeOther {
t.Fatalf("add panel status=%d body=%s", added.Code, added.Body.String())
}
storedDashboard, err = store.Dashboard(context.Background(), bootstrap.Organization.ID, "recent-errors")
if err != nil || storedDashboard.Revision != 3 || len(storedDashboard.Panels) != 2 {
t.Fatalf("dashboard after add=%+v err=%v", storedDashboard, err)
}
addedPanel := storedDashboard.Panels[1]
updatePanelForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"dashboard_id": []string{storedDashboard.ID}, "expected_revision": []string{strconv.Itoa(storedDashboard.Revision)},
"panel_title": []string{"Renamed failures"}, "saved_query_id": []string{queries[0].ID}, "visualization": []string{"table"},
}.Encode()
updatePanelTarget := "https://observatory.example/app/dashboards/recent-errors/panels/" + url.PathEscape(addedPanel.ID) + "/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
updatedPanel := perform(handler, http.MethodPost, updatePanelTarget, strings.NewReader(updatePanelForm), []*http.Cookie{cookie}, headers)
if updatedPanel.Code != http.StatusSeeOther {
t.Fatalf("update panel status=%d body=%s", updatedPanel.Code, updatedPanel.Body.String())
}
storedDashboard, err = store.Dashboard(context.Background(), bootstrap.Organization.ID, "recent-errors")
if err != nil || storedDashboard.Revision != 4 || storedDashboard.Panels[1].Title != "Renamed failures" {
t.Fatalf("dashboard after panel update=%+v err=%v", storedDashboard, err)
}
mismatchedRevision := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"dashboard_id": []string{storedDashboard.ID}, "expected_revision": []string{strconv.Itoa(storedDashboard.Revision)},
"panel_title": []string{"Invalid chart"}, "saved_query_id": []string{queries[0].ID}, "visualization": []string{"timeseries"},
}.Encode()
mismatchedPanel := perform(handler, http.MethodPost, updatePanelTarget, strings.NewReader(mismatchedRevision), []*http.Cookie{cookie}, headers)
if mismatchedPanel.Code != http.StatusUnprocessableEntity {
t.Fatalf("mismatched panel status=%d body=%s", mismatchedPanel.Code, mismatchedPanel.Body.String())
}
removePanelForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"dashboard_id": []string{storedDashboard.ID}, "expected_revision": []string{strconv.Itoa(storedDashboard.Revision)},
}.Encode()
removeTarget := "https://observatory.example/app/dashboards/recent-errors/panels/" + url.PathEscape(addedPanel.ID) + "/remove/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
removed := perform(handler, http.MethodPost, removeTarget, strings.NewReader(removePanelForm), []*http.Cookie{cookie}, headers)
if removed.Code != http.StatusSeeOther {
t.Fatalf("remove panel status=%d body=%s", removed.Code, removed.Body.String())
}
storedDashboard, err = store.Dashboard(context.Background(), bootstrap.Organization.ID, "recent-errors")
if err != nil || storedDashboard.Revision != 5 || len(storedDashboard.Panels) != 1 {
t.Fatalf("dashboard after remove=%+v err=%v", storedDashboard, err)
}
exportTarget := "https://observatory.example/app/dashboards/recent-errors/export.json?organization=" + url.QueryEscape(bootstrap.Organization.ID)
exported := perform(handler, http.MethodGet, exportTarget, nil, []*http.Cookie{cookie})
if exported.Code != http.StatusOK || exported.Header().Get("Content-Type") != "application/json" || !strings.Contains(exported.Body.String(), `"version": 1`) || strings.Contains(exported.Body.String(), bootstrap.Organization.ID) || strings.Contains(exported.Body.String(), bootstrap.User.ID) {
t.Fatalf("export status=%d headers=%v body=%s", exported.Code, exported.Header(), exported.Body.String())
}
exportedHead := perform(handler, http.MethodHead, exportTarget, nil, []*http.Cookie{cookie})
if exportedHead.Code != http.StatusOK || exportedHead.Body.Len() != 0 || exportedHead.Header().Get("Content-Length") != exported.Header().Get("Content-Length") {
t.Fatalf("export HEAD status=%d length=%q", exportedHead.Code, exportedHead.Header().Get("Content-Length"))
}
unauthorized := perform(handler, http.MethodGet, exportTarget, nil, nil)
if unauthorized.Code != http.StatusUnauthorized || unauthorized.Body.String() == exported.Body.String() {
t.Fatalf("unauthorized export status=%d", unauthorized.Code)
}
}
func TestIncidentRulesEvaluationInboxAndResponseAreScoped(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
pushes := &recordingPushDispatcher{}
server.options.PushDispatcher = pushes
server.options.PushPublicKey = base64.RawURLEncoding.EncodeToString(append([]byte{4}, make([]byte, 64)...))
handler := server.Handler()
cookie := loginHTML(t, handler)
now := server.now()
saved, err := store.SaveQuery(context.Background(), storage.SavedQueryInput{
OrganizationID: bootstrap.Organization.ID, ActorUserID: bootstrap.User.ID, MaxRows: 100,
Name: "Recent failures", Description: "Recent HTTP failures.", Query: "logs | where status >= 500 | window 1h | limit 50",
}, now)
if err != nil {
t.Fatal(err)
}
token, err := store.CreateSource(context.Background(), "incident-source", model.Scope{OrganizationID: bootstrap.Organization.ID, ProjectID: "project-a", EnvironmentID: "production", ServiceID: "service-a"})
if err != nil {
t.Fatal(err)
}
batch := model.Batch{Version: model.BatchVersion, SourceID: "incident-source", StreamID: "requests", Sequence: 1, ObservedAt: now, Signal: model.SignalLogs, Records: []model.Observation{{Timestamp: now, Name: "http.request", Attributes: map[string]string{"http.status_code": "503", "http.route": "/failed"}}}}
if _, err = store.Ingest(context.Background(), token, batch, now); err != nil {
t.Fatal(err)
}
if err = store.Recover(context.Background()); err != nil {
t.Fatal(err)
}
csrf, err := authhttp.CSRFToken(cookie.Value, "incidents:manage")
if err != nil {
t.Fatal(err)
}
headers := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
form := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"name": []string{"HTTP failures"}, "description": []string{"Open when a bounded saved query finds a failure."},
"saved_query_id": []string{saved.ID}, "severity": []string{"critical"}, "minimum_matches": []string{"1"},
"required_consecutive": []string{"1"}, "evaluation_interval": []string{"15s"},
}
invalid := cloneValues(form)
invalid.Set("csrf_token", "invalid")
denied := perform(handler, http.MethodPost, "https://observatory.example/app/alert-rules/", strings.NewReader(invalid.Encode()), []*http.Cookie{cookie}, headers)
if denied.Code != http.StatusForbidden {
t.Fatalf("invalid CSRF status=%d body=%s", denied.Code, denied.Body.String())
}
privacyHeaders := http.Header{"Origin": []string{"null"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
created := perform(handler, http.MethodPost, "https://observatory.example/app/alert-rules/", strings.NewReader(form.Encode()), []*http.Cookie{cookie}, privacyHeaders)
if created.Code != http.StatusSeeOther || !strings.HasPrefix(created.Header().Get("Location"), "/app/incidents/?organization=") {
t.Fatalf("create status=%d location=%q body=%s", created.Code, created.Header().Get("Location"), created.Body.String())
}
updates, remove, err := server.refresh.subscribe(bootstrap.Organization.ID)
if err != nil {
t.Fatal(err)
}
defer remove()
if evaluated, evaluationErr := server.EvaluateAlerts(context.Background()); evaluationErr != nil || evaluated != 1 {
t.Fatalf("evaluated=%d err=%v", evaluated, evaluationErr)
}
if len(pushes.organizations) != 1 || pushes.organizations[0] != bootstrap.Organization.ID {
t.Fatalf("push organizations=%v", pushes.organizations)
}
select {
case <-updates:
default:
t.Fatal("incident change did not publish a generic refresh")
}
incidents, err := store.Incidents(context.Background(), bootstrap.Organization.ID, false, 10)
if err != nil || len(incidents) != 1 || incidents[0].State != "firing" {
t.Fatalf("incidents=%+v err=%v", incidents, err)
}
path := "https://observatory.example/app/incidents/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
inbox := perform(handler, http.MethodGet, path, nil, []*http.Cookie{cookie})
if inbox.Code != http.StatusOK || !strings.Contains(inbox.Body.String(), "What needs attention?") || !strings.Contains(inbox.Body.String(), "HTTP failures") || !strings.Contains(inbox.Body.String(), "critical · firing") || !strings.Contains(inbox.Body.String(), "data-cache-inbox") || !strings.Contains(inbox.Body.String(), "data-push-toggle") || !strings.Contains(inbox.Body.String(), `data-open-incident-count="1"`) || strings.Contains(inbox.Body.String(), "/failed") {
t.Fatalf("inbox status=%d body=%s", inbox.Code, inbox.Body.String())
}
pushCSRF, err := authhttp.CSRFToken(cookie.Value, "push:manage")
if err != nil {
t.Fatal(err)
}
clientKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
authSecret := make([]byte, 16)
if _, err = rand.Read(authSecret); err != nil {
t.Fatal(err)
}
pushBody, _ := json.Marshal(map[string]any{"organization_id": bootstrap.Organization.ID, "endpoint": "https://push.example.test/send/browser", "keys": map[string]string{"p256dh": base64.RawURLEncoding.EncodeToString(clientKey.PublicKey().Bytes()), "auth": base64.RawURLEncoding.EncodeToString(authSecret)}})
pushHeaders := make(http.Header)
pushHeaders.Set("Origin", "https://observatory.example")
pushHeaders.Set("Content-Type", "application/json")
pushHeaders.Set("X-CSRF-Token", pushCSRF)
invalidPushHeaders := pushHeaders.Clone()
invalidPushHeaders.Set("X-CSRF-Token", "invalid")
invalidPush := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(pushBody), []*http.Cookie{cookie}, invalidPushHeaders)
if invalidPush.Code != http.StatusForbidden {
t.Fatalf("invalid push CSRF status=%d body=%s", invalidPush.Code, invalidPush.Body.String())
}
crossOriginPushHeaders := pushHeaders.Clone()
crossOriginPushHeaders.Set("Origin", "https://attacker.example")
crossOriginPush := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(pushBody), []*http.Cookie{cookie}, crossOriginPushHeaders)
if crossOriginPush.Code != http.StatusForbidden {
t.Fatalf("cross-origin push status=%d body=%s", crossOriginPush.Code, crossOriginPush.Body.String())
}
registered := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(pushBody), []*http.Cookie{cookie}, pushHeaders)
if registered.Code != http.StatusCreated || !strings.Contains(registered.Body.String(), `"id":"push_`) {
t.Fatalf("push registration status=%d body=%s", registered.Code, registered.Body.String())
}
if subscriptions, listErr := store.PushSubscriptions(context.Background(), bootstrap.Organization.ID); listErr != nil || len(subscriptions) != 1 || subscriptions[0].UserID != bootstrap.User.ID {
t.Fatalf("push subscriptions=%+v err=%v", subscriptions, listErr)
}
statusBody, _ := json.Marshal(map[string]any{"organization_id": bootstrap.Organization.ID, "endpoint": "https://push.example.test/send/browser", "keys": map[string]string{"p256dh": "", "auth": ""}})
pushStatus := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription/status", bytes.NewReader(statusBody), []*http.Cookie{cookie}, pushHeaders)
if pushStatus.Code != http.StatusOK || !strings.Contains(pushStatus.Body.String(), `"subscribed":true`) {
t.Fatalf("push status=%d body=%s", pushStatus.Code, pushStatus.Body.String())
}
privateEndpointBody := bytes.Replace(pushBody, []byte("https://push.example.test/send/browser"), []byte("https://127.0.0.1/send/browser"), 1)
rejected := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(privateEndpointBody), []*http.Cookie{cookie}, pushHeaders)
if rejected.Code != http.StatusUnprocessableEntity || strings.Contains(rejected.Body.String(), "127.0.0.1") {
t.Fatalf("private endpoint status=%d body=%s", rejected.Code, rejected.Body.String())
}
invalidCurveBody, _ := json.Marshal(map[string]any{"organization_id": bootstrap.Organization.ID, "endpoint": "https://push.example.test/send/invalid-curve", "keys": map[string]string{"p256dh": base64.RawURLEncoding.EncodeToString(append([]byte{4}, make([]byte, 64)...)), "auth": base64.RawURLEncoding.EncodeToString(authSecret)}})
invalidCurve := perform(handler, http.MethodPost, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(invalidCurveBody), []*http.Cookie{cookie}, pushHeaders)
if invalidCurve.Code != http.StatusUnprocessableEntity {
t.Fatalf("invalid curve status=%d body=%s", invalidCurve.Code, invalidCurve.Body.String())
}
deleteBody, _ := json.Marshal(map[string]any{"organization_id": bootstrap.Organization.ID, "endpoint": "https://push.example.test/send/browser", "keys": map[string]string{"p256dh": "", "auth": ""}})
deleted := perform(handler, http.MethodDelete, "https://observatory.example/api/v1/push/subscription", bytes.NewReader(deleteBody), []*http.Cookie{cookie}, pushHeaders)
if deleted.Code != http.StatusOK || !strings.Contains(deleted.Body.String(), `"remaining":false`) {
t.Fatalf("push deletion status=%d body=%s", deleted.Code, deleted.Body.String())
}
offlineInbox := perform(handler, http.MethodGet, "https://observatory.example/app/incidents/offline/?organization="+url.QueryEscape(bootstrap.Organization.ID), nil, []*http.Cookie{cookie})
if offlineInbox.Code != http.StatusOK || !strings.Contains(offlineInbox.Body.String(), "Saved incident inbox") || !strings.Contains(offlineInbox.Body.String(), "HTTP failures") {
t.Fatalf("offline inbox status=%d body=%s", offlineInbox.Code, offlineInbox.Body.String())
}
for _, forbidden := range []string{incidents[0].ID, saved.Query, bootstrap.User.ID, "csrf_token", "/failed", "Acknowledge", "Resolve"} {
if strings.Contains(offlineInbox.Body.String(), forbidden) {
t.Fatalf("offline inbox exposed %q: %s", forbidden, offlineInbox.Body.String())
}
}
unauthenticatedOffline := perform(handler, http.MethodGet, "https://observatory.example/app/incidents/offline/?organization="+url.QueryEscape(bootstrap.Organization.ID), nil, nil)
if unauthenticatedOffline.Code != http.StatusSeeOther || unauthenticatedOffline.Header().Get("Location") != "/login/" {
t.Fatalf("unauthenticated offline status=%d", unauthenticatedOffline.Code)
}
inboxHead := perform(handler, http.MethodHead, path, nil, []*http.Cookie{cookie})
if inboxHead.Code != http.StatusOK || inboxHead.Body.Len() != 0 || inboxHead.Header().Get("Content-Length") != inbox.Header().Get("Content-Length") {
t.Fatalf("inbox HEAD status=%d length=%q body=%d", inboxHead.Code, inboxHead.Header().Get("Content-Length"), inboxHead.Body.Len())
}
missingOrganization := perform(handler, http.MethodGet, "https://observatory.example/app/incidents/", nil, []*http.Cookie{cookie})
if missingOrganization.Code != http.StatusBadRequest {
t.Fatalf("missing organization status=%d", missingOrganization.Code)
}
action := url.Values{"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf}, "action": []string{"acknowledge"}, "silence_duration": []string{""}}.Encode()
acknowledged := perform(handler, http.MethodPost, "https://observatory.example/app/incidents/"+incidents[0].ID+"/", strings.NewReader(action), []*http.Cookie{cookie}, headers)
if acknowledged.Code != http.StatusSeeOther {
t.Fatalf("acknowledge status=%d body=%s", acknowledged.Code, acknowledged.Body.String())
}
current, err := store.Incidents(context.Background(), bootstrap.Organization.ID, false, 10)
if err != nil || len(current) != 1 || current[0].State != "acknowledged" || current[0].AcknowledgedBy != bootstrap.User.ID {
t.Fatalf("current=%+v err=%v", current, err)
}
}
type recordingPushDispatcher struct{ organizations []string }
func (dispatcher *recordingPushDispatcher) Enqueue(organizationID string) bool {
dispatcher.organizations = append(dispatcher.organizations, organizationID)
return true
}
func cloneValues(input url.Values) url.Values {
result := make(url.Values, len(input))
for key, values := range input {
result[key] = append([]string(nil), values...)
}
return result
}
func TestAssistedQueryBuilderCreatesTypedTimeSeriesWithTableAlternative(t *testing.T) {
server, store, identities, bootstrap := newUITestServer(t)
defer store.Close()
defer identities.Close()
handler := server.Handler()
cookie := loginHTML(t, handler)
csrf, err := authhttp.CSRFToken(cookie.Value, "dashboards:manage")
if err != nil {
t.Fatal(err)
}
now := server.now()
token, err := store.CreateSource(context.Background(), "builder-source", model.Scope{OrganizationID: bootstrap.Organization.ID, ProjectID: "project-a", EnvironmentID: "production", ServiceID: "service-a"})
if err != nil {
t.Fatal(err)
}
batch := model.Batch{Version: model.BatchVersion, SourceID: "builder-source", StreamID: "requests", Sequence: 1, ObservedAt: now, Signal: model.SignalLogs, Records: []model.Observation{
{Timestamp: now.Add(-6 * time.Minute), Name: "application.http.request", Attributes: map[string]string{"http.status_code": "503", "http.route": "/failed"}},
{Timestamp: now.Add(-1 * time.Minute), Name: "application.http.request", Attributes: map[string]string{"http.status_code": "500", "http.route": "/failed"}},
}}
if _, err = store.Ingest(context.Background(), token, batch, now); err != nil {
t.Fatal(err)
}
if err = store.Recover(context.Background()); err != nil {
t.Fatal(err)
}
headers := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded"}}
builderForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"name": []string{"Errors over time"}, "description": []string{"Five-minute error counts."},
"signal": []string{"logs"}, "filter_field": []string{"status"}, "filter_operator": []string{">="}, "filter_value": []string{"500"},
"window": []string{"1h"}, "aggregate": []string{"count"}, "aggregate_field": []string{""}, "group_by": []string{"route"}, "bucket": []string{"5m"}, "limit": []string{"50"},
}.Encode()
createdQuery := perform(handler, http.MethodPost, "https://observatory.example/app/queries/builder/", strings.NewReader(builderForm), []*http.Cookie{cookie}, headers)
if createdQuery.Code != http.StatusSeeOther {
t.Fatalf("builder status=%d body=%s", createdQuery.Code, createdQuery.Body.String())
}
queries, err := store.SavedQueries(context.Background(), bootstrap.Organization.ID)
if err != nil || len(queries) != 1 {
t.Fatalf("queries=%+v err=%v", queries, err)
}
expectedText := `logs | where status >= "500" | window 1h | summarize count() by route, window(5m) | limit 50`
if queries[0].Query != expectedText || queries[0].AST.Signal != model.SignalLogs || len(queries[0].AST.Filters) != 1 || queries[0].AST.Filters[0].Value != "500" || queries[0].AST.Summary == nil || queries[0].AST.Bucket != 5*time.Minute {
t.Fatalf("saved query=%+v", queries[0])
}
dashboardForm := url.Values{
"organization_id": []string{bootstrap.Organization.ID}, "csrf_token": []string{csrf},
"slug": []string{"error-rate"}, "name": []string{"Error rate"}, "description": []string{"A bounded error trend."},
"panel_title": []string{"Errors by route"}, "saved_query_id": []string{queries[0].ID}, "visualization": []string{"timeseries"},
}.Encode()
createdDashboard := perform(handler, http.MethodPost, "https://observatory.example/app/dashboards/", strings.NewReader(dashboardForm), []*http.Cookie{cookie}, headers)
if createdDashboard.Code != http.StatusSeeOther {
t.Fatalf("dashboard status=%d body=%s", createdDashboard.Code, createdDashboard.Body.String())
}
target := "https://observatory.example/app/dashboards/error-rate/?organization=" + url.QueryEscape(bootstrap.Organization.ID)
dashboard := perform(handler, http.MethodGet, target, nil, []*http.Cookie{cookie})
body := dashboard.Body.String()
if dashboard.Code != http.StatusOK || !strings.Contains(body, "Errors by route visual summary") || strings.Count(body, "<meter ") != 2 || !strings.Contains(body, "<table>") || !strings.Contains(body, "<caption>Errors by route</caption>") {
t.Fatalf("dashboard status=%d body=%s", dashboard.Code, body)
}
}
func TestAssistedQueryBuilderEscapesStageSeparatorsAndRejectsInvalidCombinations(t *testing.T) {
values := url.Values{
"signal": []string{"logs"}, "filter_field": []string{"name"}, "filter_operator": []string{"=="}, "filter_value": []string{"worker | limit 250"},
"window": []string{"1h"}, "aggregate": []string{"none"}, "aggregate_field": []string{""}, "group_by": []string{""}, "bucket": []string{""}, "limit": []string{"50"},
}
text, err := buildAssistedQuery(values, 1000)
if err != nil || strings.Contains(text, `"worker | limit 250"`) || !strings.Contains(text, `\u007c`) {
t.Fatalf("text=%q err=%v", text, err)
}
ast, err := query.Parse(text, 1000)
if err != nil || len(ast.Filters) != 1 || ast.Filters[0].Value != "worker | limit 250" || ast.Limit != 50 {
t.Fatalf("AST=%+v err=%v", ast, err)
}
values.Set("bucket", "5m")
if _, err = buildAssistedQuery(values, 1000); err == nil {
t.Fatal("time bucket without an aggregate was accepted")
}
}
func TestResultChartIsBoundedAndFailsClosedForNegativeValues(t *testing.T) {
result := query.Result{Columns: []query.Column{{Field: "window_start", Type: schema.TypeTime}, {Field: "count", Type: schema.TypeInteger}}}
for index := range 60 {
label := fmt.Sprintf("2026-08-17T07:%02d:00Z", index)
value := strconv.Itoa(index)
result.Rows = append(result.Rows, query.Row{Values: []*string{&label, &value}})
}
chart := resultChart("Requests", result)
if len(chart.Points) != 48 || chart.Points[47].Maximum != "47" || chart.Points[47].Value != "47" {
t.Fatalf("chart=%+v", chart)
}
negative := "-1"
result.Rows[0].Values[1] = &negative
if chart = resultChart("Requests", result); len(chart.Points) != 0 {
t.Fatalf("negative chart=%+v", chart)
}
}
type streamRecorder struct {
mu sync.Mutex
header http.Header
status int
body bytes.Buffer
flushed chan struct{}
}
func newStreamRecorder() *streamRecorder {
return &streamRecorder{header: make(http.Header), flushed: make(chan struct{}, 4)}
}
func (recorder *streamRecorder) Header() http.Header { return recorder.header }
func (recorder *streamRecorder) WriteHeader(status int) {
recorder.mu.Lock()
defer recorder.mu.Unlock()
if recorder.status == 0 {
recorder.status = status
}
}
func (recorder *streamRecorder) Write(body []byte) (int, error) {
recorder.mu.Lock()
defer recorder.mu.Unlock()
if recorder.status == 0 {
recorder.status = http.StatusOK
}
return recorder.body.Write(body)
}
func (recorder *streamRecorder) Flush() {
select {
case recorder.flushed <- struct{}{}:
default:
}
}
func (recorder *streamRecorder) statusCode() int {
recorder.mu.Lock()
defer recorder.mu.Unlock()
return recorder.status
}
func (recorder *streamRecorder) bodyString() string {
recorder.mu.Lock()
defer recorder.mu.Unlock()
return recorder.body.String()
}
func newUITestServer(t *testing.T) (*Server, *storage.Store, *identity.Services, identity.BootstrapResult) {
t.Helper()
root := filepath.Join(t.TempDir(), "data")
if err := os.Mkdir(root, 0o700); err != nil {
t.Fatal(err)
}
store, err := storage.Open(root)
if err != nil {
t.Fatal(err)
}
identities, err := identity.Open(root)
if err != nil {
store.Close()
t.Fatal(err)
}
bootstrap, err := identities.Bootstrap(context.Background(), identity.BootstrapInput{Username: "operator", Email: "operator@example.test", DisplayName: "Operator", Password: "correct horse battery staple"})
if err != nil {
identities.Close()
store.Close()
t.Fatal(err)
}
server, err := New(store, identities, testOptions())
if err != nil {
identities.Close()
store.Close()
t.Fatal(err)
}
server.now = func() time.Time { return time.Date(2026, 8, 17, 7, 30, 0, 0, time.UTC) }
return server, store, identities, bootstrap
}
func newRotationTestServer(t *testing.T) (*Server, *storage.Store, *identity.Services, identity.BootstrapResult) {
t.Helper()
root := filepath.Join(t.TempDir(), "data")
if err := os.Mkdir(root, 0o700); err != nil {
t.Fatal(err)
}
store, err := storage.Open(root)
if err != nil {
t.Fatal(err)
}
identities, err := identity.Open(root)
if err != nil {
store.Close()
t.Fatal(err)
}
bootstrap, err := identities.Bootstrap(t.Context(), identity.BootstrapInput{Username: "operator", Email: "operator@example.test", DisplayName: "Operator", Password: "temporary correct horse battery staple", RequirePasswordChange: true})
if err != nil {
identities.Close()
store.Close()
t.Fatal(err)
}
server, err := New(store, identities, testOptions())
if err != nil {
identities.Close()
store.Close()
t.Fatal(err)
}
server.now = func() time.Time { return time.Date(2026, 8, 18, 5, 30, 0, 0, time.UTC) }
return server, store, identities, bootstrap
}
func loginHTML(t *testing.T, handler http.Handler) *http.Cookie {
t.Helper()
result := loginHTMLResponse(t, handler, "operator", "correct horse battery staple")
if result.Code != http.StatusSeeOther || result.Header().Get("Location") != "/app/" {
t.Fatalf("login status=%d location=%q body=%s", result.Code, result.Header().Get("Location"), result.Body.String())
}
for _, cookie := range result.Result().Cookies() {
if cookie.Name == "__Host-observatory_session" && cookie.Secure && cookie.HttpOnly && cookie.SameSite == http.SameSiteStrictMode {
return cookie
}
}
t.Fatalf("login cookies=%+v", result.Result().Cookies())
return nil
}
func loginHTMLResponse(t *testing.T, handler http.Handler, identifier, password string) *httptest.ResponseRecorder {
t.Helper()
csrfCookie, csrfToken := loginFormCSRF(t, handler)
form := url.Values{"csrf_token": []string{csrfToken}, "identifier": []string{identifier}, "password": []string{password}}.Encode()
headers := http.Header{"Origin": []string{"https://observatory.example"}, "Content-Type": []string{"application/x-www-form-urlencoded; charset=utf-8"}}
return perform(handler, http.MethodPost, "https://observatory.example/login/", strings.NewReader(form), []*http.Cookie{csrfCookie}, headers)
}
func loginFormCSRF(t *testing.T, handler http.Handler) (*http.Cookie, string) {
t.Helper()
page := perform(handler, http.MethodGet, "https://observatory.example/login/", nil, nil)
if page.Code != http.StatusOK {
t.Fatalf("login page status=%d body=%s", page.Code, page.Body.String())
}
var csrfCookie *http.Cookie
for _, cookie := range page.Result().Cookies() {
if cookie.Name == loginCSRFCookieName {
csrfCookie = cookie
break
}
}
if csrfCookie == nil || !csrfCookie.Secure || !csrfCookie.HttpOnly || csrfCookie.SameSite != http.SameSiteStrictMode {
t.Fatalf("login CSRF cookie=%+v", csrfCookie)
}
const marker = `name="csrf_token" value="`
start := strings.Index(page.Body.String(), marker)
if start < 0 {
t.Fatalf("login page omitted CSRF token: %s", page.Body.String())
}
start += len(marker)
end := strings.IndexByte(page.Body.String()[start:], '"')
if end < 0 {
t.Fatal("login page CSRF token is unterminated")
}
token := page.Body.String()[start : start+end]
if token == "" || token != csrfCookie.Value {
t.Fatal("login form and cookie CSRF tokens differ")
}
return csrfCookie, token
}
func perform(handler http.Handler, method, target string, body io.Reader, cookies []*http.Cookie, headerSets ...http.Header) *httptest.ResponseRecorder {
request := httptest.NewRequest(method, target, body)
for _, cookie := range cookies {
request.AddCookie(cookie)
}
for _, headers := range headerSets {
for name, values := range headers {
for _, value := range values {
request.Header.Add(name, value)
}
}
}
result := httptest.NewRecorder()
handler.ServeHTTP(result, request)
return result
}