v2.4.13
v2.4.13 - Bug fix: "Server not available" after external 2FA login
Promotes the v2.4.12.1 diagnostic prerelease to a stable catalog release. Identical bytes - just version-bumped and added to manifest.json so anyone hitting "Check for Updates" gets the fix automatically.
What broke
Reported by @FsxShader2012 in the #35 thread, after v2.4.12 shipped:
"Using v2.4.12, if i try to access jellyfin from an external IP and sign in (2FA challenge is issued) i can't access the server. Browser displays 'Server not available'. Signing in without a 2FA challenge i get redirected to the home menu of jellyfin as usual. The log doesn't show anything."
Couldn't reproduce on my own Cloudflare + nginx setup, but the symptom was reproducible enough that a second user (@Feinstone) independently hit it and reported the same.
Why it happened
The post-login dashboard short-circuit added in v2.4.12 set sessionStorage.__tfa_pending via inject.js on /web/ to suppress the Jellyfin Web API barrage during the redirect to the challenge page. The challenge page (/TwoFactorAuth/Challenge) is a self-contained HTML page that does NOT load inject.js - it submits to /TwoFactorAuth/Verify directly with its own inline script. So inject.js's clear-on-Verify-success path never fired for users who completed 2FA on the challenge page.
When the user finished 2FA and the challenge page redirected them to /web/index.html, inject.js re-mounted on the dashboard, read the still-set sessionStorage flag, and short-circuited every Jellyfin Web API call with a synthetic 403. Jellyfin Web's apiclient interprets persistent non-2xx as the server being unreachable, surfacing as the "Server not available" screen. No log lines server-side because none of the short-circuited requests left the browser.
The failure window depended on browser timing, Jellyfin Web build version, and whether inject.js won the script-loading race against the page bundle - which is why some users (including me) couldn't reproduce while others hit it every time.
Fix
Single-line client-side fix at the top of challenge.html's inline script:
try { sessionStorage.removeItem('__tfa_pending'); } catch (e) {}By the time the user is on the challenge page, the flag's job - suppressing the dashboard barrage during the post-login redirect window - is done. Clearing it here means the post-verify navigation back to /web/ is unblocked. The existing inject.js-side clear-on-Verify-success stays as belt-and-suspenders for any future code path that wraps Verify through the fetch shim.
Also includes (carried from v2.4.12.1's prerelease)
release.yml 4-part tag truncation fix. Tag derivation was TAG="v${VERSION%.*}" which stripped the last .N segment unconditionally - fine for standard 4-part assembly versions ending in .0 (e.g. 2.4.13.0 → v2.4.13) but mangled 4-part prereleases (e.g. 2.4.10.1 → v2.4.10), causing them to overwrite the wrong stable release. Fixed to TAG="v${VERSION%.0}" - strip trailing .0 only. Verified by v2.4.12.1's artifacts uploading to v2.4.12.1 instead of clobbering v2.4.12.
Confirmation chain that justified the stable promotion
- @Feinstone ( - dropped v2.4.12.1 into their plugin folder, restarted the container, confirmed "Server not available" was gone.
- @FsxShader2012 (original reporter)
- Local home-server smoke-test across all login methods (passkey + normal + OIDC + email + non-2FA + LAN bypass + video playback + Streamyfin + Swiftfin) - all green.
Implementation
challenge.html: +13 lines (theremoveItemplus rationale comment block)release.yml: +9 lines / -1 (the tag derivation fix plus comment)- Versions:
2.4.12.0→2.4.13.0in csproj + meta.json
181/181 tests pass. Build clean with TreatWarningsAsErrors=true. No new test added - the bug was specifically a sessionStorage / cross-page-navigation race that's effectively impossible to exercise from xUnit alone; the cross-user reproduction was the test.
Upgrade
- Users on v2.4.12 hitting "Server not available" externally with 2FA enabled: this is the fix. Update via the catalog and the dashboard will load cleanly after 2FA.
- Users on v2.4.12 not hitting the bug: nothing visible changes — the fix is defensive (clearing a flag that may or may not be set), no-op when the bug isn't manifesting.
- Users on v2.4.10 / v2.4.11 / earlier: this rolls up all v2.4.11 + v2.4.12 + v2.4.12.1 fixes including the SessionStarted LAN-bypass-without-XFF hole closure and the SWAG
nginx-unauthorizedfail2ban 401-burst mitigation.
Verify the release
cosign verify-blob \
--certificate Jellyfin.Plugin.TwoFactorAuthv2.4.13.0.zip.pem \
--signature Jellyfin.Plugin.TwoFactorAuthv2.4.13.0.zip.sig \
--certificate-identity-regexp 'https://github.com/ZL154/JellyfinSecurity/.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
Jellyfin.Plugin.TwoFactorAuthv2.4.13.0.zip
SLSA build provenance: Jellyfin.Plugin.TwoFactorAuthv2.4.13.0.zip.intoto.jsonl — verify with gh attestation verify --owner ZL154 Jellyfin.Plugin.TwoFactorAuthv2.4.13.0.zip.
Thanks
Special thanks to @FsxShader2012 for tireless testing across the entire v2.4.10 → v2.4.13 cycle — multiple rounds of diagnostic prereleases, careful log analysis that surfaced the SEC-H3 LAN-bypass hole as well as this regression, and quick verification of each fix. And to @Feinstone for the independent third-party confirmation that gave me the confidence to ship stable.