Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions frontend/internal/handlers/admin_api_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,29 @@ func (h *PageHandler) revokeAPIKey(r *http.Request, id string) error {
return nil
}

// forwardAuth copies auth-related headers from the incoming request to the
// outgoing backend request. This ensures Pangolin SSO tokens and API keys
// are forwarded to the backend for authentication.
// forwardAuth copies auth-related headers from the incoming browser request
// to the outgoing backend request, so that the backend's auth middleware sees
// the same credentials the Pangolin-edge handed us.
//
// Forwarded headers (must stay in sync with api.HubClient.WithAuthFrom):
// - X-Label-Hub-Key — App-side API key
// - X-Pangolin-User — Legacy Pangolin SSO identity header
// - X-Pangolin-Token — Pangolin Resource custom upstream trust token
// - Remote-User — Standard Pangolin SSO identity header
// - Authorization — Pangolin Basic-Auth bypass (claude-automation)
//
// The X-Pangolin-Token / Remote-User pair is what makes the SSO-trust path
// work for browser users without an API key. Forgetting one of them means
// every /admin/* route returns 503 because the backend rejects the call as
// unauthenticated.
func (h *PageHandler) forwardAuth(from *http.Request, to *http.Request) {
for _, hdr := range []string{"X-Label-Hub-Key", "X-Pangolin-User", "Authorization"} {
for _, hdr := range []string{
"X-Label-Hub-Key",
"X-Pangolin-User",
"X-Pangolin-Token",
"Remote-User",
"Authorization",
} {
Comment on lines +255 to +261

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Die Liste der weiterzuleitenden Header ist identisch mit der Liste in api.HubClient.WithAuthFrom (in frontend/internal/api/client.go). Um zukünftige Abweichungen (Drift) zu vermeiden und die Wartbarkeit zu verbessern, wäre es ratsam, diese Header-Liste an einer zentralen Stelle (z. B. als exportierte Variable api.AuthHeaders im api-Paket) zu definieren und an beiden Stellen wiederzuverwenden.

if v := from.Header.Get(hdr); v != "" {
to.Header.Set(hdr, v)
}
Expand Down
41 changes: 41 additions & 0 deletions frontend/internal/handlers/admin_printers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,44 @@ func TestEnablePrinter_CallsBackendUndRedirectZuDetail(t *testing.T) {
t.Errorf("EnablePrinter Redirect: %q enthält nicht 'lager-sued'", loc)
}
}

// TestListPrintersPage_ForwardetPangolinSSOHeaders prüft dass forwardAuth
// X-Pangolin-Token + Remote-User an das Backend weitergibt — sonst kann der
// SSO-Trust-Pfad nicht greifen und das Backend lehnt mit 401 ab, was zu 503
// im Frontend führt.
//
// Regression-Test für die Lücke die PR #130/#132 (WithAuthFrom) zwar im
// oapi-Client gefixt haben, aber forwardAuth blieb auf der alten 3-Header-
// Liste hängen.
func TestListPrintersPage_ForwardetPangolinSSOHeaders(t *testing.T) {
t.Parallel()
var receivedToken, receivedRemoteUser string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v1/admin/printers" {
receivedToken = r.Header.Get("X-Pangolin-Token")
receivedRemoteUser = r.Header.Get("Remote-User")
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `[]`)
return
}
http.NotFound(w, r)
}))
t.Cleanup(srv.Close)

ph := handlers.NewPageHandlerFromURL(t, srv.URL)
req := httptest.NewRequest(http.MethodGet, "/admin/printers", nil)
req.Header.Set("X-Pangolin-Token", "trust-token-abc123")
req.Header.Set("Remote-User", "strausmann")
w := httptest.NewRecorder()
ph.ListPrintersPage(w, req)

if w.Code != http.StatusOK {
t.Errorf("ListPrintersPage: Status %d, erwartet 200", w.Code)
}
if receivedToken != "trust-token-abc123" {
t.Errorf("X-Pangolin-Token forwarded als %q, erwartet %q", receivedToken, "trust-token-abc123")
}
if receivedRemoteUser != "strausmann" {
t.Errorf("Remote-User forwarded als %q, erwartet %q", receivedRemoteUser, "strausmann")
}
}
Loading