Conversation
…airing QRs and reset email (ZL154#216)
This was referenced Sep 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the wrong host in every link the plugin hands to someone else, reported by @Heldenkrieger01 while testing #218 on an LG TV: the OIDC
redirect_uricame out as the private LAN address instead of the public name the IdP has registered, and he saw the same thing in the password reset email.The links are all derived from the incoming request. That is correct behind a reverse proxy, where the forwarded headers carry the public name once the peer is in
TrustedProxyCidrs. It cannot work for a client that reaches Jellyfin directly: a smart TV connects to 192.168.x.y, no proxy is in the path, and there is no header to read. The plugin then uses the address it was reached on, which is not the address the outside world uses. No proxy setting can fix that, because the proxy never sees the request; the server has to be told its public address.This adds one place that answers "what is this server's public base URL", and routes the four externally facing links through it.
Type of change
Nothing changes for a server where the request already carries the right host: with neither source configured, the previous derivation runs untouched.
Related issues
Reported in #218 (the QR PR) while testing #216. The two are independent and complementary: #218 shows the sign-in link as a QR, this one makes sure the URL inside it is the right one. The reporter's case needs both.
How was this tested?
Server tested against: Jellyfin 12.0.0, official Docker image, with Dex 2.41.1 as the identity provider, reading the
redirect_uriout of the authorize URL thatOidc/LoginInforeturns.redirect_uriproducedhttp://127.0.0.1:8121/TwoFactorAuth/Oidc/Callback/dexprovPublishedServerUriBySubnet = all=https://jellyfin.example.testhttps://jellyfin.example.test/TwoFactorAuth/Oidc/Callback/dexprovPublicBaseUrl = https://tv.example.test/jellyfinhttps://tv.example.test/jellyfin/TwoFactorAuth/Oidc/Callback/dexprovhttps://jellyfin.example.test/...With #218 merged in locally on top, the QR returned by the same endpoint is a valid PNG that re-renders byte for byte from the authorize URL, and the
redirect_uriinside it is the corrected public one. That is the combination the reporter needs.Local, with the .NET SDK 9.0.316 that CI installs:
dotnet build JellyfinSecurity.sln -c Releasehas 0 errors and 0 warnings,dotnet test -c Releaseis 532 passed and 0 failed (511 before this branch),node --test tests/oidc-bridge.test.mjsis 8 passed, andnode --checkon the admin script passes. With the .NET SDK 10.0.400 the Jellyfin 12 build has 0 warnings and 0 errors.What changed
Services/ExternalUrlResolver.cs(new): resolves the public base once, first hit wins.PublicBaseUrl, for deployments where nothing else is right.externalentry first and thenall. A subnet-specific entry is deliberately ignored, since by definition it is not the address an IdP or a phone on mobile data would use.Normalizeaccepts an absolute http(s) URL and returnsscheme://host[:port][/path]with no trailing slash. A value carrying a query, a fragment, credentials or another scheme is refused rather than trimmed into shape, because a link built from a half-understood value fails at the IdP with no clue why; refusing sends the caller back to the old behaviour, which at least works on a directly reachable server. Reading Jellyfin's configuration is wrapped so it can never break a sign-in.Configuration/PluginConfiguration.cs: thePublicBaseUrlsetting, documented with when to leave it empty.Services/OidcRedirectUriBuilder.cs:Buildtakes an optionalpublicBaseUrlthat short-circuits the derivation. The existing scheme and host logic, including the trusted-proxy gate, is untouched for everyone else.Api/SecurityController.cs: the OIDCredirect_uripasses the resolved base.Api/TwoFactorAuthController.cs: the two pairing QR URLs and the password reset email origin use it too. The reset origin was not even proxy-aware before, it readRequest.Schemedirectly; it now goes through the same resolution.Pages/admin.html,Pages/admin-script.js,Pages/translations/*.json: the setting in the Settings tab with a help line, two new keys in all eight languages.tests/.../ExternalUrlResolverTests.cs(new, 21 cases): which published entry counts as the outside world and the precedence between them, what shape a usable base has, everything that is refused, and that the redirect URI is replaced with the declared base while the no-configuration path stays exactly as it was.Checklist
dotnet formatif unsure)dotnet build+dotnet testgreen)Additional notes