package auth import ( "context" "errors" "net/http" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) type ctxKey int const userCtxKey ctxKey = 1 // RequireAdmin gates a handler on X-API-Token matching an admin user. This is // the Minstrel-native token path (`/api/*`); Subsonic-compatible auth under // `/rest/*` lands with the Subsonic server. func RequireAdmin(pool *pgxpool.Pool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-API-Token") if token == "" { http.Error(w, "missing X-API-Token", http.StatusUnauthorized) return } q := dbq.New(pool) user, err := q.GetUserByAPIToken(r.Context(), token) if err != nil { if errors.Is(err, pgx.ErrNoRows) { http.Error(w, "invalid token", http.StatusUnauthorized) return } http.Error(w, "auth lookup failed", http.StatusInternalServerError) return } if !user.IsAdmin { http.Error(w, "admin required", http.StatusForbidden) return } ctx := context.WithValue(r.Context(), userCtxKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // UserFromContext returns the authenticated user when the request passed // through RequireAdmin (or a future RequireUser middleware). func UserFromContext(ctx context.Context) (dbq.User, bool) { u, ok := ctx.Value(userCtxKey).(dbq.User) return u, ok }