* block plugin routes for share guests
Adds a global MVC filter that refuses share-guest accounts on any plugin
controller. Jellyfin's own API stays open, the share tag policy already
bounds it and playback needs it.
Guests are identified by the auth provider marker on the account, so this
covers a leaked token used from curl or a native client, not just the web
client where the CSS lockdown runs.
* add plugin exception list and honest wording
Config page lists installed plugins with a checkbox each, for the ones that
need to answer guests during playback. Default is unticked.
Renames the hidden selectors setting to say it is cosmetic, and stops the
readme implying the web client lockdown confines anything.
* bump to 1.0.4.0
The dashboard listed every share ever made, including ones revoked or expired
months ago, and nothing ever pruned them. There is now a "Clean up finished links"
button next to Refresh that runs a normal cleanup pass and then drops the records
that are done with, reporting how many went.
Only revoked, expired and failed records are removed. A link that can still be
used is left alone, including a spent one-use link whose guest is still watching
until it expires, since that record is Redeemed rather than finished.
Folded into 1.0.3.0 rather than shipped as 1.0.3.1: the ceiling fix and this are
going out as one replacement of that version.
New setting, ten by default, zero for no limit. Single-use links are unaffected,
they are one viewer by definition.
The catch is what happens at the ceiling. Jellyfin throws SecurityException once a
user is at MaxActiveSessions, and that was landing in the generic handler, which
marks the record failed and runs cleanup, which deletes the guest account. So
without care, adding a ceiling would mean the eleventh person to open a link kicks
out the ten already watching and destroys the link. Capacity is caught separately
now: the record goes back to the state it was in, nothing is torn down, and the
new arrival gets a 503 page inviting them to try again.
Worth being honest that this caps how many people can start watching at once, not
how many ever get in: each redemption issues its own session token that keeps
working until the link is revoked or expires. Revoke is still the hard stop.
README picks up the multi-use option, the new setting, and a section on what a
multi-use link does and does not protect, plus the known limits around the token
in the query string, the unthrottled redeem endpoint, and the tag being hidden in
the web UI only.
The create popup now has a "Let several people use this link" box next to the
expiry controls. Tick it and the link stays redeemable by everyone you send it to
until it expires; leave it and you get the old behaviour, where the first person
to open it is the only one who gets in. The plugin setting that used to be the
only control is now just what the box starts out as, and its label on the config
page says so, because "Default one-use links" explained nothing.
Multi-use did not actually work before this. Two things in Jellyfin stopped it,
and both had to change:
Guests were given MaxActiveSessions = 1, and AuthenticateNewSessionInternal throws
SecurityException once a user is at that limit. The second viewer's redemption
would fail, the record would go to Failed, and cleanup would then delete the guest
account, kicking the first viewer out too. Multi-use links now get 0, which is how
Jellyfin spells "no limit" in that check.
The device id was generated once and reused for every redemption, and
GetAuthorizationToken logs out every existing session for the same user and device
before issuing a token. So even under a raised session cap, each new viewer would
have revoked the previous one's token. Multi-use links now mint a device id per
redemption.
Both viewers of a multi-use link share one temporary account, so they also share
playback position and watched state on the shared title.
Opening an expired or invalid link used to dump raw problem-details
JSON at the guest. Now they get a small page matching the sign-in
look, with the same neutral wording whether the link expired or never
existed (so tokens cannot be probed by outsiders), and an automatic
redirect to the Jellyfin home page. Fixes#1.
GuestHiddenSelectors now defaults to empty instead of shipping CSS
selectors for a plugin nobody else runs. Existing installs keep their
saved value. Fixes#3.
The movie detail page's Tags section lives in its own .itemTags container,
not inside .detailsGroupItem like genres/studios/cast, so tag chips stayed
clickable for guests. Disable those links too.
The add-to-playlist and add-to-collection entries in the item's "..."
menu render with data-id="playlist"/data-id="addtocollection", not
data-action="addtoplaylist"/"addtocollection" as previously assumed —
that selector matched an unrelated list-row button and never hid the
actual menu items. Target the correct attribute so both are hidden.
Also drop the specific plugin name from the guest-hidden-selectors
description in favor of generic wording; the default selector value
is unchanged.
Hiding .headerBackButton for guests also removed the video player's back
button (same class), so a guest could not return from playback to the
movie page. Stop hiding that class; keep home/drawer/search hidden.
Cast, studio, genre and tag links on the detail page now have
pointer-events disabled for guests so they stay visible but inert
(clicking Studio no longer spins on a page the guest cannot load), and
the add-to-playlist/add-to-collection actions are hidden.
Add a GuestHiddenSelectors config field (comma-separated CSS selectors,
surfaced through GuestState since guests cannot read plugin config) so
other plugins injected UI can be suppressed for guest sessions without
code changes; defaults to hiding the AI Search elements.
The sign-in bootstrap sent the auth request body as a JS object, so it
was coerced to [object Object] and AuthenticateByName returned 400. Send
JSON.stringify(...) instead.
Guest credentials were written to localStorage as a flat object under
made-up keys. jellyfin-web reads jellyfin_credentials as
{Servers:[{Id,AccessToken,UserId,...}]}, so the guest was treated as
logged out. Write that shape, pulling server Id/name from
System/Info/Public, and redirect with the 10.11 hash route
(#/details?id=...&serverId=...) instead of the legacy #!/ form that
rendered a blank page.
Creating a link now rejects folders and libraries (only movies and
episodes are shareable) so a guest cannot land on an empty tag-filtered
library. Item ids from the menu action are validated as GUIDs client
side, rejected creates are logged server side, malformed redeem tokens
return 404 instead of 500, and the admin table shows the item name and a
copyable link instead of the raw item id.
Includes fix for redemption failing with DbUpdateConcurrencyException:
change the guest password before applying the user policy, since
UpdatePolicyAsync bumps the user's EF concurrency token and a stale
instance then breaks ChangePassword.