e9fba3bb1da6dcc516b10749cb51649e41a65132
3
Commits
-
Add single-user GitHub OAuth, hardened for internet exposure
muse drives a download client and writes to the media library, so exposing it publicly needs more than a login form. It fails closed. Auth is on unless MUSE_AUTH=off is set explicitly, and when it is on with incomplete config the app refuses to start rather than serving open. The mode worth engineering against is not a broken login page, it is a misconfigured deploy quietly publishing an unauthenticated download client. Sessions are server-side: the cookie carries a 256-bit random token and nothing else, and only its SHA-256 is stored. There is no signed payload to get subtly wrong, a stolen database yields no usable cookies, and logout revokes for real. The allowlist is re-checked on every request so removing a user takes effect immediately rather than at session expiry. OAuth state is compared with secrets.compare_digest, so a callback the user did not initiate is rejected. MUSE_BASE_URL comes from config rather than X-Forwarded-* because those headers are attacker-controllable and this one is a redirect target. CSP is default-src 'self' with no unsafe-inline, which is why the stylesheet and script moved out of index.html into static files -- an injected <script> now cannot execute. Also frame-ancestors 'none', base-uri 'none', nosniff, DENY, no-referrer and HSTS. FastAPI's /docs, /redoc and /openapi.json are disabled so the login page does not hand out a map of every endpoint. /healthz is the only unauthenticated route and returns a bare ok, so the container healthcheck works without leaking anything. Verified: misconfigured auth refuses to boot; unauthenticated / redirects and /api/* returns 401; a forged state is rejected 400; a junk cookie is 401; a valid session for a non-allowlisted user is 401; all headers present; and the UI renders with no CSP violations in the console. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

aryansinghandClaude Opus 5
2026-08-04 12:09:51 -05:00 -
Add bulk import, and a bounded queue to make it safe
Requesting songs one at a time does not scale past a handful, so this adds a paste-a-list import. It accepts "Artist - Title", en dashes, "Title by Artist", numbered lists, bare titles, and Deezer album/playlist URLs, which get expanded into their tracks. The queue is the load-bearing part. Every request previously spawned its own asyncio task, so a 50-track paste would have launched 50 concurrent Soulseek searches -- each 10-40s of polling and real load on slskd -- and drowned the client. Work now goes through an asyncio.Queue drained by MUSE_WORKERS consumers, default 3. Requests return immediately and the batch drains at a civil rate. The queue is in memory, so jobs left 'queued' or 'searching' are re-queued at startup rather than stranded by a restart. Ranking needed two fixes found by testing against the real API: Import was calling deezer_search(limit=1), which fetches two rows, so the ranker was choosing between a demo and a rehearsal take while the studio version -- six times the popularity -- sat unseen further down. It now pulls a real candidate pool. More seriously, the Deezer ranker had no title-relevance term at all: only artist match, popularity and penalties. A different song by the right artist therefore competed on equal footing, and "Oasis - Live Forever" resolved to "Cloudburst" as soon as a variant penalty tipped the balance. Title overlap now dominates, and the variant penalty is small enough only to break ties between versions of the same song. Derivative terms match on word boundaries rather than substrings, so a bare "live" catches "Come As You Are (Live)" without penalising "Deliver Me" or "Olive", and a term is ignored when the user typed it -- "oasis live forever" still works. Verified against live slskd: six tracks pasted, six resolved to the correct song, five imported with correct album/year/track and link count 2, in about two minutes. The sixth returned an empty Soulseek search and is retryable from the UI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

aryansinghandClaude Opus 5
2026-08-04 03:23:17 -05:00 -
muse: request a single song, get it tagged in your library
Lidarr's smallest unit is a MusicBrainz release, so asking for one track means monitoring a whole discography. Soulseek trades individual files but has no concept of a request. muse bridges the two: search, click, and the song lands in the library correctly tagged. The design point is that the user picks the exact track before anything downloads, so muse holds artist, album, year and track number as ground truth and tags deterministically rather than inferring them afterwards from whatever a stranger named the file. Search is Deezer rather than MusicBrainz, decided on measurement: MusicBrainz put eight covers above the real recording for "radiohead creep" and never surfaced Queen for "bohemian rhapsody" across 90 results, because its index matches a word in a cover's title as readily as in the artist field. Re-ranking cannot rescue a result set missing the right row. Imports are os.link(), so downloads keep seeding from the Soulseek share while the library gets a second name for the same bytes.
aryansingh
2026-08-04 03:05:45 -05:00