Skip to content

fix(frontend): Auth-Header-Weiterleitung an Backend behebt Dashboard 503#95

Merged
strausmann merged 1 commit into
mainfrom
fix/frontend-auth-header-forwarding
Jun 1, 2026
Merged

fix(frontend): Auth-Header-Weiterleitung an Backend behebt Dashboard 503#95
strausmann merged 1 commit into
mainfrom
fix/frontend-auth-header-forwarding

Conversation

@strausmann
Copy link
Copy Markdown
Owner

Symptom

https://labels.strausmann.cloud/ liefert seit Phase 7c (PR #88) systematisch 503 Service Unavailable. /healthz und /api/printers via Pangolin antworten mit 200.

Root Cause (Phase 1 Diagnose)

docker exec label-printer-hub-frontend wget -qO- http://label-printer-hub-backend:8000/api/printers
→ HTTP/1.1 401 Unauthorized

docker exec label-printer-hub-frontend wget -qO- \
  --header "X-Pangolin-User: test-frontend" \
  http://label-printer-hub-backend:8000/api/printers
→ HTTP/1.1 200 OK  [{"id":"...","slug":"brother-p750w","name":"PT-P750W",...}]

Phase 7c fügte Backend-Authentifizierung ein (drei Pfade: X-Label-Hub-Key, X-Pangolin-User, Authorization Basic). Browser-Requests kommen über Pangolin → Backend erhält X-Pangolin-User. Aber HubClient baut einen eigenen internen Docker-Netzwerk-Call auf — ohne Pangolin in der Mitte. Die Auth-Headers fehlen → Backend gibt 401 zurück → resp.JSON200 == nildashboard.go:25 rendert 503.

Die ursprüngliche Hypothese (fehlende slug-Feld im oapi-codegen führt zu Decode-Error) war falsch: Go json.Unmarshal ignoriert unbekannte Felder, kein Decode-Error. Der echte Crash war immer die 401.

Fix

HubClient.WithAuthFrom(r *http.Request) gibt einen neuen *HubClient zurück, der via RequestEditorFn die drei Auth-Header aus dem eingehenden Browser-Request in jeden ausgehenden Backend-Call einfügt.

Alle Page-Handler nutzen nun h.client.WithAuthFrom(r).MethodXxx(...):

  • Dashboard (ListPrinters)
  • PrinterDetail (GetPrinterDetail, GetPrinterStatus, GetPrinterTape, GetPrinterQueue)
  • JobsList, JobDetail, JobRetry
  • TemplatesList, TemplateDetail (ListTemplates, RenderPreview)
  • LookupDisplay (LookupEntity)

forwardAuth() in admin_api_keys.go bleibt als separater Mechanismus für die Raw-HTTP-Calls an /api/admin/api-keys/* erhalten — dort gibt es kein HubClient.

Tests

3 neue Regressionstests in frontend/internal/api/client_test.go:

  • TestListPrintersForwards401AsError — 401 vom Backend → ListPrinters gibt Fehler zurück
  • TestWithAuthFromForwardsPangolinHeader — X-Pangolin-User wird an Backend durchgereicht
  • TestWithAuthFromForwardsAPIKeyHeader — X-Label-Hub-Key wird an Backend durchgereicht
ok  github.com/strausmann/label-printer-hub/frontend/cmd/server    0.109s
ok  github.com/strausmann/label-printer-hub/frontend/internal/api  0.021s
ok  github.com/strausmann/label-printer-hub/frontend/internal/handlers  0.087s
ok  github.com/strausmann/label-printer-hub/frontend/internal/proxy  0.022s

Verifikation nach Merge

Watchtower deployed das neue :dev-Image automatisch (Polling ~10 min). Danach:

curl -sI https://labels.strausmann.cloud/ | head -1
# → HTTP/2 200

# Frontend-Logs zeigen dann:
# GET / status=200 (statt 503)

Nach dem Merge von Phase 7c (PR #88) erfordert das Backend eine Authentifizierung
via X-Label-Hub-Key, X-Pangolin-User oder Authorization-Header. Der HubClient
sendete diese Headers beim internen Docker-Netzwerk-Call nicht mit, da kein
Pangolin-Proxy zwischen Frontend und Backend sitzt. Folge: Backend antwortete
mit 401 → JSON200 == nil → Dashboard lieferte 503 Service Unavailable.

Fix: HubClient.WithAuthFrom(*http.Request) kopiert die drei Auth-Header aus dem
eingehenden Browser-Request in alle ausgehenden Backend-Calls via RequestEditorFn.
Alle Page-Handler (Dashboard, Printer, Jobs, Templates, Lookup) wurden entsprechend
angepasst. forwardAuth() in admin_api_keys.go bleibt als separater Mechanismus
für die Raw-HTTP-Calls an /api/admin/api-keys/* erhalten.

Reproduktion: docker exec frontend wget -qO- http://backend:8000/api/printers
→ 401 ohne Header, 200 mit X-Pangolin-User.
Copilot AI review requested due to automatic review settings June 1, 2026 12:59
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.87%. Comparing base (ef8fefd) to head (84776fe).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #95      +/-   ##
==========================================
+ Coverage   89.84%   89.87%   +0.02%     
==========================================
  Files          89       89              
  Lines        4008     4008              
  Branches      343      343              
==========================================
+ Hits         3601     3602       +1     
  Misses        317      317              
+ Partials       90       89       -1     
Components Coverage Δ
Printer Backends (transport) 87.50% <ø> (ø)
Printer Models (drivers) 91.42% <ø> (ø)
Services 91.23% <ø> (+0.07%) ⬆️
REST API 85.08% <ø> (ø)
Pydantic Schemas 100.00% <ø> (ø)
Integration Plugins 100.00% <ø> (ø)
see 1 file with indirect coverage changes
Flag Coverage Δ
backend 89.87% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d4fe576...84776fe. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical authentication issue introduced in a previous phase where internal frontend-to-backend requests were failing due to missing authentication headers. By modifying the 'HubClient' to inject these headers from the original user request, the system now correctly authenticates internal calls, restoring full functionality to the dashboard and other dependent services.

Highlights

  • Authentication Header Forwarding: Implemented a new 'WithAuthFrom' method in 'HubClient' to capture and propagate critical authentication headers (X-Label-Hub-Key, X-Pangolin-User, Authorization) from incoming browser requests to internal backend API calls.
  • Regression Fix: Resolved the 503 Service Unavailable errors occurring on the dashboard and other pages by ensuring the backend receives the necessary credentials to authorize internal Docker-network requests.
  • Regression Testing: Added three new test cases in 'client_test.go' to verify that 401 errors are correctly handled and that authentication headers are properly forwarded to the backend.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the frontend dashboard (and other pages) returning 503 after Phase 7c backend auth by forwarding incoming auth headers on all frontend→backend API calls made through the typed HubClient.

Changes:

  • Added HubClient.WithAuthFrom(*http.Request) to forward X-Label-Hub-Key, X-Pangolin-User, and Authorization via RequestEditorFn on generated API calls.
  • Updated all affected page handlers to call backend APIs through h.client.WithAuthFrom(r) (or a shared authClient).
  • Added regression tests ensuring 401 handling and header forwarding behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
frontend/internal/handlers/templates.go Uses auth-scoped client for template listing.
frontend/internal/handlers/template.go Uses auth-scoped client for template list fetch and preview rendering call path.
frontend/internal/handlers/printer.go Creates authClient once and uses it for parallel backend calls.
frontend/internal/handlers/lookup.go Uses auth-scoped client for lookup calls.
frontend/internal/handlers/jobs.go Uses auth-scoped client for job listing calls.
frontend/internal/handlers/job.go Uses auth-scoped client for job detail and retry calls.
frontend/internal/handlers/dashboard.go Uses auth-scoped client for printer listing on dashboard.
frontend/internal/api/client.go Adds auth header forwarding support and applies request editors to generated client calls.
frontend/internal/api/client_test.go Adds regression tests for 401 behavior and auth header forwarding.

Comment on lines +69 to +73
// WithAuthFrom returns a new HubClient that copies auth-related headers from
// the incoming browser request into every outgoing backend call.
//
// The headers forwarded are:
// - X-Label-Hub-Key — API-key auth (Phase 7c)
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements authentication header forwarding in the frontend's HubClient using a new WithAuthFrom method to propagate X-Label-Hub-Key, X-Pangolin-User, and Authorization headers to backend calls. The review feedback identifies two critical issues: first, RenderPreview bypasses the generated client and does not apply the auth headers, causing preview generation to fail under authentication; second, WithAuthFrom lacks a nil-check for the incoming request r, which can lead to a nil-pointer dereference panic.

previewCtx, previewCancel := context.WithTimeout(r.Context(), 2*time.Second)
defer previewCancel()
previewBytes, previewErr := h.client.RenderPreview(previewCtx, key)
previewBytes, previewErr := h.client.WithAuthFrom(r).RenderPreview(previewCtx, key)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Obwohl hier WithAuthFrom(r) aufgerufen wird, werden die Authentifizierungs-Header bei RenderPreview nicht an das Backend weitergeleitet. Die Methode RenderPreview in frontend/internal/api/client.go verwendet c.hc.Do(req) direkt und wendet weder c.editors() noch die c.authHeaders auf den ausgehenden Request an. Dies führt dazu, dass die Vorschau-Generierung fehlschlägt (401 Unauthorized), wenn das Backend eine Authentifizierung erfordert.

Empfohlene Behebung in frontend/internal/api/client.go:

func (c *HubClient) RenderPreview(ctx context.Context, templateKey string) ([]byte, error) {
    start := time.Now()
    previewURL := c.baseURL + "/api/render/preview?key=" + url.QueryEscape(templateKey)
    req, err := http.NewRequestWithContext(ctx, http.MethodPost, previewURL, nil)
    if err != nil {
        return nil, err
    }
    // Auth-Header manuell hinzufügen:
    for k, v := range c.authHeaders {
        req.Header.Set(k, v)
    }
    resp, err := c.hc.Do(req)
    // ...
}

Comment on lines +84 to +97
func (c *HubClient) WithAuthFrom(r *http.Request) *HubClient {
headers := make(map[string]string, 3)
for _, h := range []string{"X-Label-Hub-Key", "X-Pangolin-User", "Authorization"} {
if v := r.Header.Get(h); v != "" {
headers[h] = v
}
}
return &HubClient{
gen: c.gen,
hc: c.hc,
baseURL: c.baseURL,
authHeaders: headers,
}
}
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

Wenn r nil ist (beispielsweise in bestimmten Testfällen oder Hintergrund-Tasks), führt der Aufruf von r.Header.Get(h) zu einer Nil-Pointer-Dereferenzierung und damit zu einem Absturz (Panic). Es sollte eine defensive Überprüfung hinzugefügt werden, um dies zu verhindern.

Suggested change
func (c *HubClient) WithAuthFrom(r *http.Request) *HubClient {
headers := make(map[string]string, 3)
for _, h := range []string{"X-Label-Hub-Key", "X-Pangolin-User", "Authorization"} {
if v := r.Header.Get(h); v != "" {
headers[h] = v
}
}
return &HubClient{
gen: c.gen,
hc: c.hc,
baseURL: c.baseURL,
authHeaders: headers,
}
}
func (c *HubClient) WithAuthFrom(r *http.Request) *HubClient {
if r == nil {
return c
}
headers := make(map[string]string, 3)
for _, h := range []string{"X-Label-Hub-Key", "X-Pangolin-User", "Authorization"} {
if v := r.Header.Get(h); v != "" {
headers[h] = v
}
}
return &HubClient{
gen: c.gen,
hc: c.hc,
baseURL: c.baseURL,
authHeaders: headers,
}
}

@strausmann strausmann merged commit 0d2de27 into main Jun 1, 2026
20 checks passed
@strausmann strausmann deleted the fix/frontend-auth-header-forwarding branch June 1, 2026 13:06
strausmann added a commit that referenced this pull request Jun 1, 2026
…n) als SSO-Pfad (#96)

Backend akzeptiert Pangolin-Standard-Header (Remote-User + X-Pangolin-Token Trust-Validation) analog zu Hangar. Legacy X-Pangolin-User bleibt für Frontend-internal-Calls (PR #95) erhalten.

PRINTER_HUB_SSO_TRUST_TOKEN muss in Backend-Env gesetzt werden, sonst ist der Pfad sicherheitshalber deaktiviert (leerer Default).

6 neue Tests, 876 Tests grün insgesamt.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants