package httpserver_test import ( "context" "net/http" "net/http/httptest" "net/url" "regexp" "strconv" "strings" "testing" ) func TestAdminAPIKeyFlowRevealsRawKeyOnceAndSupportsRevocation(t *testing.T) { t.Parallel() router, _, store := newTestRouterWithStore(t) sessionCookie := loginAsAdmin(t, router) projectLocation := submitForm(t, router, http.MethodPost, "/admin/projects", url.Values{ "name": {"Desktop App"}, "slug": {"desktop-app"}, }, http.StatusSeeOther, sessionCookie) projectID := extractResourceID(t, projectLocation, "/admin/projects/") submitForm(t, router, http.MethodPost, "/admin/projects", url.Values{ "name": {"Mobile App"}, "slug": {"mobile-app"}, }, http.StatusSeeOther, sessionCookie) tagLocation := submitForm(t, router, http.MethodPost, "/admin/tags", url.Values{ "name": {"Windows"}, "slug": {"windows"}, }, http.StatusSeeOther, sessionCookie) tagID := extractResourceID(t, tagLocation, "/admin/tags/") submitForm(t, router, http.MethodPost, "/admin/projects/"+projectID+"/tags", url.Values{ "tag_id": {tagID}, }, http.StatusSeeOther, sessionCookie) csrfCookie := ensureCSRFCookie(t, router, sessionCookie) recorder := performRequest(t, router, http.MethodPost, "/admin/api-keys", url.Values{ "name": {"Windows Clients"}, "description": {"Download access for Windows builds."}, "scope_mode": {"tag_allow_list"}, "can_download": {"1"}, "can_manage_projects": {"1"}, "tag_id": {tagID}, "csrf_token": {csrfCookie.Value}, }, sessionCookie, csrfCookie) if recorder.Code != http.StatusOK { t.Fatalf("expected create api key page, got %d with body %s", recorder.Code, recorder.Body.String()) } assertHeaderContains(t, recorder, "Cache-Control", "no-store") assertHeaderEquals(t, recorder, "Pragma", "no-cache") assertHeaderEquals(t, recorder, "Expires", "0") body := recorder.Body.String() rawKey := findRawAPIKey(t, body) if !strings.Contains(body, rawKey) { t.Fatal("expected raw api key to be shown on creation response") } assertBodyContains(t, body, `value="Windows Clients"`) assertBodyContains(t, body, "Download access for Windows builds.") assertBodyContains(t, body, `option value="tag_allow_list" selected`) assertBodyContains(t, body, `name="can_download" value="1" checked`) assertBodyContains(t, body, `name="can_manage_projects" value="1" checked`) assertBodyContains(t, body, `name="tag_id" value="`+tagID+`" checked`) assertBodyContains(t, body, "/api/v1/projects") assertBodyContains(t, body, "/api/v1/projects/desktop-app/releases/latest") assertBodyContains(t, body, "Authorization: Bearer") keys, err := store.APIKeys.List(context.Background()) if err != nil { t.Fatalf("list api keys: %v", err) } if len(keys) != 1 { t.Fatalf("expected one api key, got %d", len(keys)) } keyID := keys[0].APIKey.ID storedKey, err := store.APIKeys.GetByID(context.Background(), keyID) if err != nil { t.Fatalf("load stored api key: %v", err) } if storedKey.KeyHash == rawKey { t.Fatal("expected stored api key hash to differ from the raw key") } accessibleProjects, err := store.APIKeys.ListAccessibleProjects(context.Background(), keyID, storedKey.ScopeMode) if err != nil { t.Fatalf("list accessible projects: %v", err) } if len(accessibleProjects) != 1 || accessibleProjects[0].Name != "Desktop App" { t.Fatalf("expected only Desktop App to be accessible, got %+v", accessibleProjects) } recorder = performRequest(t, router, http.MethodGet, "/admin/api-keys/"+itoa64(keyID), nil, sessionCookie) if recorder.Code != http.StatusOK { t.Fatalf("expected api key detail page to load, got %d", recorder.Code) } if strings.Contains(recorder.Body.String(), rawKey) { t.Fatal("expected raw api key to disappear after the creation response") } submitForm(t, router, http.MethodPost, "/admin/api-keys/"+itoa64(keyID)+"/activate", url.Values{ "state": {"revoke"}, }, http.StatusSeeOther, sessionCookie) storedKey, err = store.APIKeys.GetByID(context.Background(), keyID) if err != nil { t.Fatalf("reload revoked api key: %v", err) } if storedKey.IsActive { t.Fatal("expected api key to be revoked") } } func TestAdminAPIKeyCreateResponseShowsPersistedProjectRules(t *testing.T) { t.Parallel() router, _, _ := newTestRouterWithStore(t) sessionCookie := loginAsAdmin(t, router) projectLocation := submitForm(t, router, http.MethodPost, "/admin/projects", url.Values{ "name": {"Desktop App"}, "slug": {"desktop-app"}, }, http.StatusSeeOther, sessionCookie) projectID := extractResourceID(t, projectLocation, "/admin/projects/") submitForm(t, router, http.MethodPost, "/admin/projects", url.Values{ "name": {"Mobile App"}, "slug": {"mobile-app"}, }, http.StatusSeeOther, sessionCookie) csrfCookie := ensureCSRFCookie(t, router, sessionCookie) recorder := performRequest(t, router, http.MethodPost, "/admin/api-keys", url.Values{ "name": {"Desktop Only"}, "description": {"Project allow-list clients."}, "scope_mode": {"project_allow_list"}, "can_download": {"1"}, "project_id": {projectID}, "csrf_token": {csrfCookie.Value}, }, sessionCookie, csrfCookie) if recorder.Code != http.StatusOK { t.Fatalf("expected create api key page, got %d with body %s", recorder.Code, recorder.Body.String()) } body := recorder.Body.String() assertBodyContains(t, body, `value="Desktop Only"`) assertBodyContains(t, body, "Project allow-list clients.") assertBodyContains(t, body, `option value="project_allow_list" selected`) assertBodyContains(t, body, `name="project_id" value="`+projectID+`" checked`) assertBodyContains(t, body, "/api/v1/projects") assertBodyContains(t, body, "/api/v1/projects/desktop-app/releases/latest") assertHeaderContains(t, recorder, "Cache-Control", "no-store") } func findRawAPIKey(t *testing.T, body string) string { t.Helper() re := regexp.MustCompile(`upsk_[A-Za-z0-9_-]+`) matches := re.FindAllString(body, -1) if len(matches) == 0 { t.Fatal("expected raw api key in response body") } longest := matches[0] for _, match := range matches[1:] { if len(match) > len(longest) { longest = match } } return longest } func itoa64(value int64) string { return strconv.FormatInt(value, 10) } func assertBodyContains(t *testing.T, body, fragment string) { t.Helper() if !strings.Contains(body, fragment) { t.Fatalf("expected response body to contain %q", fragment) } } func assertHeaderContains(t *testing.T, recorder *httptest.ResponseRecorder, key, fragment string) { t.Helper() if value := recorder.Header().Get(key); !strings.Contains(value, fragment) { t.Fatalf("expected %s header to contain %q, got %q", key, fragment, value) } } func assertHeaderEquals(t *testing.T, recorder *httptest.ResponseRecorder, key, want string) { t.Helper() if value := recorder.Header().Get(key); value != want { t.Fatalf("expected %s header %q, got %q", key, want, value) } }