feat: add JWT validation against Authentik JWKS for protected adapters
Replaces stub 'check Authorization header' auth with real JWT validation: - Extracts Bearer token from Authorization header - Validates signature against Authentik JWKS endpoint - Verifies iss, aud, exp claims - Checks permissions claim for required capability - Handles key rotation with 15min cache TTL - Returns 403 with detailed error on auth failure Protected adapters (memory, iam) now require valid Authentik JWT tokens.
This commit is contained in:
@@ -8,21 +8,29 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"forgejo.riotpiao.com/rock/homelab-frontend/internal/auth"
|
||||
"forgejo.riotpiao.com/rock/homelab-frontend/internal/problem"
|
||||
)
|
||||
|
||||
// Dispatcher handles X-Service based routing to service adapters.
|
||||
type Dispatcher struct {
|
||||
registry *Registry
|
||||
// authValidator would check capabilities if internal/auth exists
|
||||
// For now, we stub it
|
||||
registry *Registry
|
||||
validators map[string]*auth.Validator // Per-service JWT validators
|
||||
}
|
||||
|
||||
// NewDispatcher creates a new service adapter dispatcher.
|
||||
// NewDispatcher creates a new service adapter dispatcher with Authentik JWT validators.
|
||||
func NewDispatcher(registry *Registry) *Dispatcher {
|
||||
return &Dispatcher{
|
||||
registry: registry,
|
||||
dispatcher := &Dispatcher{
|
||||
registry: registry,
|
||||
validators: make(map[string]*auth.Validator),
|
||||
}
|
||||
|
||||
// Create validators for all registered services
|
||||
for _, adapter := range registry.List() {
|
||||
dispatcher.validators[adapter.ServiceName] = auth.NewValidator(adapter.ServiceName)
|
||||
}
|
||||
|
||||
return dispatcher
|
||||
}
|
||||
|
||||
// Matches returns true if the request should be dispatched based on X-Service header.
|
||||
@@ -85,8 +93,7 @@ func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check auth requirements (stub for now — internal/auth integration in 8.3)
|
||||
// Determine required capability
|
||||
// Check auth requirements using JWT validation
|
||||
requiredCapability := ""
|
||||
auth := resource.Auth
|
||||
if auth == nil {
|
||||
@@ -98,11 +105,28 @@ func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if auth != nil && auth.Required && auth.Capability != "" {
|
||||
requiredCapability = auth.Capability
|
||||
// Would validate JWT and capability here (depends on internal/auth)
|
||||
// For now, stub — just log that it would be checked
|
||||
if !d.hasCapability(r, requiredCapability) {
|
||||
|
||||
// Validate JWT token
|
||||
validator := d.validators[serviceName]
|
||||
if validator == nil {
|
||||
p := problem.NewProblem(http.StatusInternalServerError, "about:blank#server-error",
|
||||
"Internal Server Error", fmt.Sprintf("no validator for service '%s'", serviceName))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := validator.ValidateToken(r.Header.Get("Authorization"))
|
||||
if err != nil {
|
||||
p := problem.NewProblem(http.StatusForbidden, "about:blank#forbidden",
|
||||
"Forbidden", fmt.Sprintf("capability '%s' required", requiredCapability))
|
||||
"Forbidden", fmt.Sprintf("authentication failed: %v", err))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Check permission
|
||||
if !validator.HasPermission(claims, requiredCapability) {
|
||||
p := problem.NewProblem(http.StatusForbidden, "about:blank#forbidden",
|
||||
"Forbidden", fmt.Sprintf("permission '%s' required", requiredCapability))
|
||||
_ = p.Write(w)
|
||||
return
|
||||
}
|
||||
@@ -140,13 +164,7 @@ func (d *Dispatcher) Dispatch(w http.ResponseWriter, r *http.Request) {
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// hasCapability checks if the request has the required capability.
|
||||
// Stub implementation — depends on internal/auth JWT validation.
|
||||
func (d *Dispatcher) hasCapability(r *http.Request, capability string) bool {
|
||||
// TODO: Parse JWT from Authorization header and check capabilities
|
||||
// For now, assume all authenticated requests have all capabilities
|
||||
return r.Header.Get("Authorization") != ""
|
||||
}
|
||||
|
||||
|
||||
func (d *Dispatcher) writeError(w http.ResponseWriter, p *problem.Problem) {
|
||||
_ = p.Write(w)
|
||||
|
||||
Reference in New Issue
Block a user