You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

89 lines
3.0 KiB

package handlers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"regexp"
"strings"
"testing"
"teraclone/internal/service"
)
func TestWorkspaceIdentityIsPerLoginAndNotAnAuthenticationToken(t *testing.T) {
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v1/auth/logout" {
w.WriteHeader(http.StatusOK)
return
}
if r.URL.Path != "/api/v1/auth/login" {
http.NotFound(w, r)
return
}
var credentials map[string]string
_ = json.NewDecoder(r.Body).Decode(&credentials)
role := "administrator"
if credentials["username"] == "operator" {
role = "port_operator"
}
_ = json.NewEncoder(w).Encode(map[string]any{"session_token": "private-backend-token", "actor": "test-" + credentials["username"], "role": role})
}))
defer backend.Close()
app, err := service.NewAppService(filepath.Join(t.TempDir(), "frontend.db"))
if err != nil {
t.Fatal(err)
}
defer app.Close()
handler, err := NewDeviceHandler(app, backend.URL)
if err != nil {
t.Fatal(err)
}
mux := http.NewServeMux()
handler.RegisterRoutes(mux)
identity := regexp.MustCompile(`data-workspace-id="([a-f0-9]{64})"`)
seen := make(map[string]bool)
for _, username := range []string{"admin", "operator", "operator"} {
request := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(url.Values{"username": {username}, "password": {"test"}}.Encode()))
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
response := httptest.NewRecorder()
mux.ServeHTTP(response, request)
if response.Code != http.StatusSeeOther || len(response.Result().Cookies()) != 1 {
t.Fatalf("login failed: %d", response.Code)
}
cookie := response.Result().Cookies()[0]
var previousID string
for _, path := range []string{"/", "/port-status", "/"} {
pageRequest := httptest.NewRequest(http.MethodGet, path, nil)
pageRequest.AddCookie(cookie)
page := httptest.NewRecorder()
mux.ServeHTTP(page, pageRequest)
body := page.Body.String()
match := identity.FindStringSubmatch(body)
if page.Code != http.StatusOK || len(match) != 2 {
t.Fatalf("missing public workspace identity: status=%d", page.Code)
}
id := match[1]
if previousID != "" && id != previousID {
t.Fatal("workspace identity changed during page navigation/refresh")
}
if id == cookie.Value || strings.Contains(body, cookie.Value) || strings.Contains(body, "private-backend-token") {
t.Fatal("authentication token leaked into workspace HTML")
}
if !strings.Contains(body, `data-current-user="`+username+`"`) || page.Header().Get("Cache-Control") != "no-store" {
t.Fatal("missing workspace user or cache prevention")
}
previousID = id
}
if seen[previousID] {
t.Fatal("workspace identity reused after a new login")
}
seen[previousID] = true
logoutRequest := httptest.NewRequest(http.MethodPost, "/logout", nil)
logoutRequest.AddCookie(cookie)
mux.ServeHTTP(httptest.NewRecorder(), logoutRequest)
}
}