Jellyfin does not cascade a user delete to the Device rows redemption
creates, and DeviceManager throws for the whole listing when one device's
user is missing, so a single leftover guest 404s the dashboard devices page.
Deletes the guest's devices before the user, and sweeps devices this plugin
created whose user is already gone at startup for links torn down by older
builds.
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.
JellyfinGuestUserService looked up IUserManager methods by reflection, trying
eight candidate signatures for ChangePassword alone, and ItemTagService did the
same for UpdateItemAsync. That fails at runtime on any API drift and only logs a
warning, which is exactly how the DbUpdateConcurrencyException hunt started. We
already pin Jellyfin.Controller 10.11, so these are now plain typed calls and any
future drift is a compile error. 427 lines of shim gone, behaviour unchanged
(UpdateItemAsync still gets ItemUpdateType.None, password still set before the
policy update).
Guest accounts also get their own authentication provider now, which refuses every
interactive sign-in. Redemption is unaffected: AuthenticateDirect passes
enforcePassword false and never consults a provider. If the plugin is disabled the
provider id stops resolving and Jellyfin assigns the account to its own
InvalidAuthProvider, which refuses too, so this fails closed. A random password is
still set as a second line of defence.
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.