feat(identity): inject X-Forwarded-User headers after JWT validation (#15)
CI / CI (push) Successful in 3m14s

Closes homelab#9 (P3.4)

## Changes

- New `internal/identity` package: header injection + anti-spoofing
- `proxy.go`: strip spoofed headers on all requests, inject identity after JWT validation

## Headers

| Header | Source | When |
|--------|--------|------|
| X-Forwarded-User | sub claim | Always after JWT |
| X-Forwarded-Roles | roles or permissions claim | Always after JWT |
| X-Acting-Service | azp claim | Only when azp != sub |
| X-Auth-Verified | literal "true" | Always after JWT |

## Tests

13 tests, 93.9% coverage. Covers: spoofing, service accounts, human users, empty claims, nil values, wildcard, mixed types, precedence.

---------

Co-authored-by: Poimen <[email protected]>
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
2026-09-08 23:08:39 +00:00
co-authored by poimen
parent 5dd76f3b49
commit 6f7b850193
3 changed files with 324 additions and 0 deletions
+8
View File
@@ -15,6 +15,7 @@ import (
"forgejo.riotpiao.com/rock/homelab-frontend/internal/auth"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/config"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/identity"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/logging"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/tracing"
)
@@ -303,6 +304,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// Strip spoofed identity headers from all inbound requests.
// Must happen before any routing — even unauthenticated paths.
identity.StripIncoming(r)
// JWT Authentication for /v1/* endpoints
if h.jwtValidator != nil && strings.HasPrefix(r.URL.Path, "/v1/") {
authHeader := r.Header.Get("Authorization")
@@ -331,6 +336,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// Inject identity headers for downstream services
identity.Inject(r, claims)
// Check required capability if configured
if h.config.Auth.RequiredCapability != "" {
if !h.jwtValidator.CheckPermissions(claims, h.config.Auth.RequiredCapability, "*") {