// Package auth enforces Authentik group membership from the oauth2-proxy // identity header. oauth2-proxy already gates the route, but mediamark mutates // the on-disk library, so it re-checks the group server-side rather than // trusting the front door alone. package auth import ( "net/http" "git.unkin.net/unkin/mediamark/internal/config" ) // Middleware rejects requests whose group header carries none of the allowed // groups. header is the request header to read; allowed must be non-empty. type Middleware struct { header string allowed map[string]bool } // New builds a Middleware. An empty allowed set denies everything, which is the // correct fail-closed behaviour if config validation is ever bypassed. func New(header string, allowed []string) *Middleware { m := &Middleware{header: header, allowed: make(map[string]bool, len(allowed))} for _, g := range allowed { m.allowed[g] = true } return m } // Permit reports whether the request carries an allowed group. func (m *Middleware) Permit(r *http.Request) bool { if len(m.allowed) == 0 { return false } for _, v := range r.Header.Values(m.header) { for _, g := range config.ParseGroups(v) { if m.allowed[g] { return true } } } return false } // Wrap gates next behind Permit, answering 403 with a plain body that never // echoes the submitted groups back to the caller. func (m *Middleware) Wrap(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !m.Permit(r) { http.Error(w, "forbidden: missing required group", http.StatusForbidden) return } next.ServeHTTP(w, r) }) }