Files

76 lines
2.2 KiB
Go

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)
}