From 4b0233cf6ffd62cadb5bda3f58bfe6804264341d Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Thu, 6 Aug 2026 22:26:15 +0800 Subject: [PATCH] Allow run lifecycle reports through auth gate --- platform/api/authorization.go | 1 + platform/api/authorization_test.go | 32 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/platform/api/authorization.go b/platform/api/authorization.go index 1d771f3..ed52dce 100644 --- a/platform/api/authorization.go +++ b/platform/api/authorization.go @@ -44,6 +44,7 @@ func publicAPIRequest(r *http.Request) bool { func runServiceRequest(r *http.Request) bool { return strings.HasPrefix(r.URL.Path, "/api/v1/run/control/") || + strings.HasPrefix(r.URL.Path, "/api/v1/run/lifecycle/") || strings.HasPrefix(r.URL.Path, "/api/v1/run/jobs/") || strings.HasPrefix(r.URL.Path, "/api/v1/run/logs/") || strings.HasPrefix(r.URL.Path, "/api/v1/run/artifacts/") || diff --git a/platform/api/authorization_test.go b/platform/api/authorization_test.go index f7e134e..020dd41 100644 --- a/platform/api/authorization_test.go +++ b/platform/api/authorization_test.go @@ -166,6 +166,38 @@ func TestRunHTTPEnvelopeRequiresValidSignatureAndRejectsReplay(t *testing.T) { } } +func TestAuthorizedRouterAllowsRunLifecycleReportWithoutBearer(t *testing.T) { + store := repo.NewMemoryStore() + core := service.NewCoreService(store) + if _, err := core.CreateGamePlugin(validGamePluginRequest().ToDomain()); err != nil { + t.Fatalf("create plugin: %v", err) + } + if _, err := core.CreateRunEndpoint(validRunEndpointRequest().ToDomain()); err != nil { + t.Fatalf("create endpoint: %v", err) + } + if _, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-authorized-lifecycle", PluginID: "server.scum", RunEndpointID: "run-local", Name: "Authorized Lifecycle", State: domain.ServerInstanceStateFailed, ConfigVersion: 1}); err != nil { + t.Fatalf("create server: %v", err) + } + router := NewAuthorizedRouterWithCore(core) + hello := decodeBody[dto.RunControlHelloResponse](t, performRunControlHello(t, router, validRunControlHelloRequest())) + if hello.SessionToken == "" { + t.Fatalf("expected run session token") + } + body, err := json.Marshal(dto.RunLifecycleReportRequest{RunEndpointID: "run-local", SessionToken: hello.SessionToken, ServerInstanceID: "server-authorized-lifecycle", Capability: domain.LifecycleCapabilityStart, State: domain.JobStateSucceeded, Progress: dto.JobProgressBody{Percent: 100}, ExecutionResult: dto.RunJobExecutionResultBody{Kind: "process", ProcessState: "running"}}) + if err != nil { + t.Fatalf("marshal lifecycle report: %v", err) + } + signed := signedRunRequest(t, router, "/api/v1/run/lifecycle/report", body, hello.SessionToken, "nonce-api-lifecycle-authorized", time.Now().UTC()) + assertStatus(t, signed, http.StatusOK) + updated, err := core.GetServerInstance("server-authorized-lifecycle") + if err != nil { + t.Fatalf("get server: %v", err) + } + if updated.State != domain.ServerInstanceStateRunning { + t.Fatalf("expected run lifecycle report to project running, got %s", updated.State) + } +} + func signedRunRequest(t *testing.T, router http.Handler, path string, body []byte, token string, nonce string, stamp time.Time) *httptest.ResponseRecorder { t.Helper() timestamp := strconv.FormatInt(stamp.Unix(), 10)