Skip to content

Fix Seerr session cookie detection, build.ps1 manifest array bug, and…#184

Open
Ryuiku wants to merge 1 commit into
Moonfin-Client:masterfrom
Ryuiku:fix/seerr-cookie-and-build-script
Open

Fix Seerr session cookie detection, build.ps1 manifest array bug, and…#184
Ryuiku wants to merge 1 commit into
Moonfin-Client:masterfrom
Ryuiku:fix/seerr-cookie-and-build-script

Conversation

@Ryuiku

@Ryuiku Ryuiku commented Jul 8, 2026

Copy link
Copy Markdown

DISCLAIMER: i used claude for this, i am not experienced in coding at all

however, i tested the final product and it resolved my issue.

Three fixes: Seerr session cookie detection, a build.ps1 manifest corruption bug, and missing meta.json generation in build.ps1

This PR bundles three related fixes found while debugging a failed Seerr SSO
link on a self-hosted deployment, then building from source to verify the
fix end-to-end. Each is independent and could be split into separate PRs if
you'd prefer — let me know.

  1. Seerr SSO fails when Seerr issues its session cookie under a non-default name
  2. build.ps1 corrupts manifest.json when there's exactly one plugin entry
  3. build.ps1 never generates meta.json, breaking clean manual installs

1. Seerr SSO fails with "No session cookie received from Seerr" on deployments that rename the session cookie

Summary

SeerrSessionService.ReadSessionCookie() hardcodes the expected session cookie
name as connect.sid. That's the default name Express's express-session
middleware uses, and it's what Jellyseerr/Overseerr ship with — but a Seerr
instance can be configured (or forked/rebranded) to issue its session cookie
under a different name. When that happens, Moonfin never recognizes the
cookie, discards it, and the link flow fails with:

No session cookie received from Seerr

even though the auth request itself succeeded.

Reproduction

Environment: Seerr and Jellyfin/Moonfin both running as Docker containers on
the same host, talking over the internal Docker network via HTTP (no reverse
proxy involved).

  1. Configure the Seerr integration in Moonfin with valid Jellyfin credentials.
  2. Moonfin logs: No session cookie received from Seerr for user "USER".
  3. Seerr's own logs show the auth succeeded: Found matching Jellyfin user; updating user with Jellyfin.
  4. Jellyfin's logs confirm a normal successful auth + new session token for
    the user.
  5. Using bad credentials produces a distinct authentication-failure error
    (confirming connectivity + the auth call itself are both working).

Manually curling the auth endpoint reproduces it directly:

curl -i -X POST http://<seerr-host>:5055/api/v1/auth/jellyfin \
  -H "Content-Type: application/json" \
  -d '{"username":"<user>","password":"<pass>"}'

Response headers include:

Set-Cookie: sb.sid=s%3A3CVHTVpA...; Path=/; Expires=...; HttpOnly; SameSite=Lax

The session cookie is named sb.sid, not connect.sid. (In this case
sb reflects the deployment's own branding/tooling prefix — the exact prefix
will vary by deployment, which is the point: it isn't safe to assume the
cookie is always named connect.sid.)

Because ReadSessionCookie() only looks for a cookie literally named
connect.sid, it silently ignores the sb.sid cookie Seerr returns, and the
link flow reports no session cookie was received — even though one was.

Root cause

In SeerrSessionService.cs:

private static string? ReadSessionCookie(HttpResponseMessage response, CookieContainer jar, string seerrUrl)
{
    if (response.Headers.TryGetValues("Set-Cookie", out var setCookieHeaders))
    {
        foreach (var header in setCookieHeaders)
        {
            if (header.StartsWith("connect.sid=", StringComparison.OrdinalIgnoreCase))
            {
                ...
            }
        }
    }

    var raw = jar.GetCookies(new Uri(seerrUrl))["connect.sid"]?.Value;
    return string.IsNullOrEmpty(raw) ? null : Uri.UnescapeDataString(raw);
}

The cookie name connect.sid is hardcoded in two more places as well
(re-adding the stored cookie to the CookieContainer before replaying it on
/api/v1/auth/me validation calls and on proxied requests), so even patching
detection alone wouldn't be enough — the stored session also needs to
remember which cookie name to send back.

Proposed fix

Attached is a patch (SeerrSessionService.patch, unified diff) with the
following changes:

  1. Dynamic session-cookie detection. ReadSessionCookie() no longer
    assumes a name. It treats any Set-Cookie entry that isn't a known
    Seerr CSRF cookie (XSRF-TOKEN, _csrf) as the session cookie, and
    returns both its name and value.
  2. Persist the cookie name per session. SeerrSession gets a new
    SessionCookieName field (defaulting to connect.sid so existing stored
    sessions on disk keep deserializing and working without a migration).
  3. Use the stored name everywhere the cookie is replayed — session
    validation (/api/v1/auth/me) and the general Seerr proxy path both
    construct their Cookie using session.SessionCookieName instead of a
    hardcoded string.
  4. Track rotation of the name, not just the value, in
    CheckForRotatedCookieAsync, in case a deployment ever changes it
    mid-session (unlikely, but cheap to handle correctly).

This makes Moonfin resilient to any Seerr deployment that issues its session
cookie under a non-default name, without needing to special-case sb.sid
specifically — the fix generalizes to any name.


2. build.ps1 corrupts manifest.json when there's exactly one plugin entry

Symptom

Running build.ps1 against a manifest.json containing a single plugin
entry (i.e. before any releases exist yet, or after manually resetting to
one entry) throws:

Cannot index into a null array.
At build.ps1:77 char:5
+     $Manifest[0].versions[0].version = $Version

If manifest.json already has multiple historical version entries in a
single plugin object, this doesn't reproduce — which is likely why it went
unnoticed.

Root cause

$Manifest = [System.IO.File]::ReadAllText($ManifestFile) | ConvertFrom-Json

When the top-level JSON array has exactly one element, PowerShell's pipeline
enumeration unwraps it: $Manifest ends up as the bare plugin object rather
than a one-item array. $Manifest[0] still "works" here (indexing a scalar
with [0] in PowerShell returns the scalar itself), so the read/modify step
doesn't fail.

The corruption happens on write:

$Json = ConvertTo-Json -InputObject $Manifest -Depth 10

Serializing that not-quite-array value produces
{"value": [...], "Count": 1} instead of a plain [...] array — a known
ConvertTo-Json artifact when it's handed something that isn't a genuine
native array. That gets written back to manifest.json, permanently
corrupting it for every subsequent run, since the next run's
$Manifest[0].versions now resolves to $null (the wrapper object has no
versions property), and indexing $null[0] throws the error above.

Fix

  • Force $Manifest into a real array immediately after parsing:
    $Manifest = @($Manifest), so behavior is identical regardless of entry
    count.
  • On write, guarantee array-shaped output even on PowerShell 5.1 (where
    ConvertTo-Json -AsArray isn't available, since that parameter requires
    PowerShell 6+):
    $Json = ConvertTo-Json -InputObject $Manifest -Depth 10
    if ($Manifest.Count -eq 1 -and -not $Json.TrimStart().StartsWith('[')) {
        $Json = "[$Json]"
    }

3. build.ps1 never generates meta.json

Symptom

Jellyfin requires a meta.json in every plugin's install directory —
it's used to identify the plugin (guid/name/version) and to allowlist which
DLLs are permitted to load (PluginManager cross-references a manifest
assemblies list against what's physically present in the folder). If it's
missing, Jellyfin falls back to a synthetic manifest built from the folder
name, with no declared version or assembly allowlist.

build.ps1 currently packages the compiled DLL and bundled frontend into
the release zip, but never writes a meta.json into it. This doesn't show
up when updating an existing install (the previously-installed meta.json
is just left in place), but breaks a clean manual install into an empty
plugin folder — e.g. exactly the workflow this repo's README documents
under "Building from Source."

Fix

build.ps1 now generates meta.json automatically before zipping, sourcing
the plugin's identity fields (guid, name, description, overview,
owner, category) directly from manifest.json rather than duplicating
them as a second hardcoded copy that could drift out of sync over time. The
current version's changelog is also pulled from manifest.json's
versions[0].changelog, and both files now share one $Timestamp value
computed once, so they're stamped identically for a given build.

If manifest.json is missing, the script prints a warning and continues
rather than failing outright, since not every checkout will have one.


Files changed

  • SeerrSessionService.cs — fix 1
  • build.ps1 — fixes 2 and 3

Happy to split these into separate PRs if that's preferred, or answer
questions about any of the three. All three were verified against a live
Jellyfin + Seerr Docker deployment, including a full from-source build on
Windows following the README's documented build steps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant