diff --git a/Dockerfile b/Dockerfile index 032e65b..855c49d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,6 @@ COPY static ./static EXPOSE 5040 HEALTHCHECK --interval=60s --timeout=10s --start-period=15s --retries=3 \ - CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5040/api/health',timeout=8).status==200 else 1)" + CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5040/healthz',timeout=8).status==200 else 1)" CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5040", "--no-access-log"] diff --git a/README.md b/README.md index af4dedd..fe0f0db 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,59 @@ can actually serve you (free upload slot, short queue), then filename plausibility, then bitrate. A marginally better file behind 200 queued users loses to a good-enough one available now. +## Authentication + +Single-user GitHub OAuth, built so this can face the internet. + +**It fails closed.** Auth is on unless `MUSE_AUTH=off` is set explicitly, and +if it is on with incomplete config the app *refuses to start*. The failure mode +worth engineering against isn't a broken login page — it's a misconfigured +deploy quietly serving an unauthenticated download client to the internet. + +### Setting it up + +1. → **New OAuth App** + - Homepage URL: `https://muse.yourdomain.com` + - Authorization callback URL: `https://muse.yourdomain.com/auth/callback` +2. Generate a client secret. +3. Set `MUSE_GITHUB_CLIENT_ID`, `MUSE_GITHUB_CLIENT_SECRET`, + `MUSE_ALLOWED_USERS` (your GitHub login), and `MUSE_BASE_URL`. + +| variable | default | meaning | +|---|---|---| +| `MUSE_AUTH` | `github` | `github` or `off` | +| `MUSE_GITHUB_CLIENT_ID` | — | OAuth app client id | +| `MUSE_GITHUB_CLIENT_SECRET` | — | OAuth app secret | +| `MUSE_ALLOWED_USERS` | — | comma-separated GitHub logins; **nobody else gets in** | +| `MUSE_BASE_URL` | — | external URL, e.g. `https://muse.yourdomain.com` | +| `MUSE_COOKIE_SECURE` | `true` | set `false` only for plain-http LAN testing | +| `MUSE_SESSION_DAYS` | `7` | session lifetime | + +### What it does + +- **Server-side sessions.** The cookie carries a 256-bit random token and + nothing else; the database stores only its SHA-256. No signed payload to get + wrong, a stolen database yields no usable cookies, and logout genuinely + revokes. Cookie is `HttpOnly`, `Secure`, `SameSite=Lax`. +- **Allowlist re-checked every request**, so removing someone locks them out + immediately rather than whenever their session expires. +- **OAuth `state`** is generated per login and compared with + `secrets.compare_digest`, so a callback you didn't initiate is rejected. +- **Strict CSP** — `default-src 'self'`, no `unsafe-inline`. This is why the + stylesheet and script are separate files instead of inline in the page: an + injected `
-

muse search a song → it lands in Navidrome

+

muse search a song → it lands in Navidrome +

@@ -81,7 +24,7 @@ Juice WRLD - Lucid Dreams Tory Lanez - The Color Violet Or paste a Deezer album / playlist URL to expand it."> -
+
Top Deezer match is taken automatically — check the list below.
@@ -92,129 +35,9 @@ Or paste a Deezer album / playlist URL to expand it.">

Requests

- +
nothing requested yet
-