Foundation: config, schema/migrations, auth, stable IDs, metadata probing
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user