package auth import ( "database/sql" "net" "net/http" "net/netip" "git.noroutes.net/NoRoutes/vibetunes/internal/config" "git.noroutes.net/NoRoutes/vibetunes/internal/database" ) // ExtAuth resolves users from a trusted reverse proxy's identity header, // Navidrome-style. It never talks to an identity provider itself: the // proxy is assumed to have already authenticated the browser user. // // Security-critical: the identity header is only trusted when the actual // TCP peer address (r.RemoteAddr) matches a configured trusted CIDR. // Headers like X-Forwarded-For/X-Real-IP/Forwarded are never consulted // here, because they are attacker-controlled unless a dedicated trusted- // proxy chain abstraction validates them — an arbitrary client could // otherwise set the identity header directly and spoof any user. type ExtAuth struct { DB *sql.DB Config config.ExtAuthConfig } // IdentityFromRequest returns the trusted username asserted by the // reverse proxy for this request, or "" if external auth is disabled, no // header was sent, or the connection did not originate from a trusted // peer. func (e *ExtAuth) IdentityFromRequest(r *http.Request) string { if !e.Config.Enabled { return "" } if !e.peerTrusted(r) { return "" } return r.Header.Get(e.Config.UserHeader) } func (e *ExtAuth) peerTrusted(r *http.Request) bool { host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { host = r.RemoteAddr } addr, err := netip.ParseAddr(host) if err != nil { return false } for _, prefix := range e.Config.TrustedSources { if prefix.Contains(addr) { return true } } return false } // ResolveUser returns the VibeSonic user for a trusted external identity, // creating one if AutoCreateUsers is set and no such user exists yet. It // never creates a second, separate "external" user type: external users // are ordinary users with ExternalAuth set. func (e *ExtAuth) ResolveUser(username string) (*database.User, error) { user, err := database.GetUserByUsername(e.DB, username) if err == nil { return user, nil } if err != database.ErrNotFound { return nil, err } if !e.Config.AutoCreateUsers { return nil, database.ErrNotFound } return database.CreateExternalUser(e.DB, username) }