Graphe des révisions
59 Révisions
Auteur SHA1 Message Date
Francois CB 04d1a04bcb Drop build instructions from the README 2026-08-29 22:49:50 +02:00
Francois CB 06281aeb6d Rewrite the README: threat model, design decisions, tighter wording 2026-08-29 22:49:13 +02:00
Francois CB e2ed426622 Refine README.md for clarity and fix typos
Updated language for clarity and corrected minor typos.
2026-07-27 21:29:05 +02:00
Franciskid 4923746610 add 1.0.4.0 to the manifest 2026-07-27 21:00:56 +02:00
Franciskid 34637f52dc fold the device fix into 1.0.4.0
No point shipping 1.0.5.0 as its own version, neither was ever released.
2026-07-27 20:56:17 +02:00
Francois CB 911ab41b0c delete guest devices on teardown (#18)
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.
2026-07-27 20:56:04 +02:00
Francois CB d1ee74677b Block plugin routes for share guests (#16)
* 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
2026-07-27 20:39:21 +02:00
Francois CB 4c48861506 Revise README for clarity and add workflow images
Updated README to clarify guest session settings and added workflow steps with images.
2026-07-27 20:01:16 +02:00
Francois CB 98065fe170 Add new images to README 2026-07-27 19:30:44 +02:00
Franciskid cf297f121e update the 1.0.3.0 manifest entry for the season share fix 2026-07-27 00:36:10 +02:00
Francois CB 917b80fe50 Merge pull request #14 from Franciskid/fix/season-share-leaks-whole-series
Stop a shared season handing over the rest of the series
2026-07-27 00:35:25 +02:00
Franciskid ad17b17ab4 stop a shared season handing over the rest of the series
Sharing one season let the guest see every season of that show. Sharing a whole
series or a single episode was fine, which is what made it look like a season
specific problem.

The cause is that the tag went upwards as well as downwards. BuildTagTreeTargets
added a season's parent series, so the guest's tag landed on the series itself,
and Jellyfin's GetInheritedTags is an item's own tags plus every ancestor's, with
AllowedTags matched against that. Tagging the series therefore made every other
season and episode under it inherit the tag and become visible. Sharing a series
looked correct because the whole show is meant to be visible, and sharing an
episode looked correct because nothing above it was ever tagged.

Tagging now only ever goes down: the shared item, and its descendants when it is a
folder. A guest sent one season gets that season and its episodes, and the series
page is not theirs to open, which is what sharing a season should mean.

Removal deliberately still reaches up to the parent series. Links created by
earlier builds put the tag there, and taking a tag off can only remove access, so
those get cleaned up when the link expires or is revoked instead of being stranded
on the series forever.
2026-07-27 00:34:55 +02:00
Francois CB a811095784 Update ShareLink expiry and image dimensions
Updated the expiry duration for ShareLink from 30 days to 7 days and changed the image dimensions in the README.
2026-07-27 00:16:37 +02:00
Francois CB bedd7bf36a Update README with new image and text corrections
Updated image source and fixed minor text formatting issues.
2026-07-26 23:52:47 +02:00
Franciskid ddc21f292d fold 1.0.3.1 back into 1.0.3.0 in the manifest 2026-07-26 23:08:04 +02:00
Francois CB 3ed982b893 Merge pull request #12 from Franciskid/feature/cleanup-finished-links
Add a button to clear out finished share links, and fold 1.0.3.1 back into 1.0.3.0
2026-07-26 23:06:40 +02:00
Franciskid 4ad6d5b1ed add a button to clear out finished share links
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.
2026-07-26 23:04:51 +02:00
Franciskid 34ed2c8ad9 add 1.0.3.1 to the plugin manifest 2026-07-26 21:49:48 +02:00
Francois CB 2f91845f7e Merge pull request #11 from Franciskid/fix/viewer-ceiling-refusal
Refuse an over-the-ceiling viewer without destroying the link
2026-07-26 21:48:41 +02:00
Franciskid 01e2235c02 refuse an over-the-ceiling viewer without destroying the link
The ceiling shipped in 1.0.3.0 did the opposite of its job. Jellyfin throws
MediaBrowser.Controller.Net.SecurityException when a user is at their session
limit, and the catch that was meant to handle it named System.Security's type of
the same name, so it never matched. The exception fell through to the failure
path, which marks the record failed and runs cleanup, which deletes the guest
account. Live result with the ceiling at 3: the fourth viewer got a dead-link
page, the three already watching were kicked out, and the link was gone.

Rather than swap one exception type for another and trust it, the over-the-ceiling
viewer is now turned away before anything is written at all: no tag work, no
status change, the guest account untouched. The check mirrors what Jellyfin does
when it creates a session, counting sessions for the guest against the ceiling.
The typed catch stays as a backstop for the race between the check and the call,
and it now names the right type.

A link that has never been redeemed has no guest yet, so it can never be at a
ceiling, and 0 still means no limit.
2026-07-26 21:45:48 +02:00
Franciskid fd1e4a8d3c add 1.0.3.0 to the plugin manifest 2026-07-26 21:30:33 +02:00
Francois CB 111bede362 Merge pull request #10 from Franciskid/feature/multi-use-links
Let a link be used by several people instead of dying on first open
2026-07-26 21:29:25 +02:00
Franciskid 224a79f7e5 cap how many people can watch one multi-use link at once
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.
2026-07-26 21:22:20 +02:00
Franciskid f0b9e8351b let a link be used by several people instead of dying on first open
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.
2026-07-26 21:05:54 +02:00
Franciskid ca0ab96ee9 add 1.0.2.0 to the plugin manifest 2026-07-26 20:46:18 +02:00
Francois CB e419cbbce4 Merge pull request #9 from Franciskid/security/harden-redemption-and-expiry
Harden redemption, expiry limits, token storage and guest sign-in
2026-07-26 20:44:42 +02:00
Francois CB b9f57e24aa Merge pull request #8 from Franciskid/fix/hide-share-tags-from-non-admins
Keep the share tag out of the UI for everyone except admins
2026-07-26 20:44:11 +02:00
Francois CB 9607edd9a8 Merge pull request #7 from Franciskid/fix/menu-section-and-media-only
Move the ShareLink action to its own section, and only for media
2026-07-26 20:43:49 +02:00
Franciskid abbb0d448f hide share tags unless the viewer is a confirmed admin
The check skipped hiding when the Users/Me lookup came back empty, which is the
one case where we do not know who is looking. Only a positive administrator
verdict skips it now.
2026-07-26 20:30:40 +02:00
Franciskid aa8317d081 keep the share tag out of the UI for everyone except admins
The tag has to live in the item's real Tags for Jellyfin's tag policy to confine
the guest, so normal users were seeing a "sharelinks-<guid>" chip on any shared
title and an entry for it in the library tag filter. Admins still see them; for
everyone else the chip is taken back out of the tag row, along with the ", "
Jellyfin puts between chips, and the whole row is hidden when the share tag was
the only one. The tag filter entry gets hidden too.

Only tags matching sharelinks- plus 32 hex are touched, so a real tag that happens
to start with "sharelinks" is left alone. Guests benefit as well, since they were
seeing the tag on the title they were sent.

Worth being clear about the limit: this is the web UI. The tag is still in the API
response, so anyone reading the raw API can see it.
2026-07-26 20:30:26 +02:00
Franciskid 85a80b1c5b re-check who the current user is instead of trusting the first answer
The result of Users/Me was cached in a module level promise for the lifetime of
the page. The web client is a single page app, so signing out or switching
accounts never reloads the document and the script kept whichever user it saw
first. Open the page as an admin, switch to a normal user in the same tab, and
isAdministrator() still said yes: the admin-only ShareLink action was injected
into that user's menus.

The cached user and guest state are now keyed to ApiClient.getCurrentUserId(),
so a switch invalidates them, and a non-admin verdict actively removes any action
left in the DOM by the previous session instead of just declining to add one.

Nothing was reachable through this. The create endpoint checks the Administrator
role server side and answers 403 for a non-admin (confirmed against a real
non-admin session), so the button was there but did nothing.
2026-07-26 20:30:00 +02:00
Franciskid f1989f8824 call Jellyfin's APIs directly instead of probing for them at runtime
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.
2026-07-26 16:47:18 +02:00
Franciskid 96299be57c apply the key file mode on load too, so existing keys get fixed 2026-07-26 16:24:14 +02:00
Franciskid 5348b4e78b harden redemption, expiry limits and token storage
Findings from a pass over the plugin, smallest first:

Redemptions now run one at a time behind a gate. The status checks and the status
write that follows them were not atomic, so two requests arriving together with
the same one-use token could both mint a guest session. The spent-link check also
moved above the tagging step, so hammering an already-used link no longer re-tags
a whole series on every hit.

The configured maximum expiry is actually respected. Both the API and the picker
did Math.max(configured, 720), so setting the ceiling to anything under 30 days
was silently ignored. The picker now also hides the quick-pick durations that sit
above the ceiling.

The share URL, which carries the raw token, is dropped from the record when the
link is revoked or expires. Records are never deleted, so dead tokens were
accumulating in the store forever. Live links keep it so the dashboard can still
copy them, and the README claim that no token is ever written to disk is corrected
to say what the code actually does.

The HMAC key file is created 0600 instead of inheriting the default mask.
2026-07-26 16:22:41 +02:00
Franciskid 35631eae46 move the ShareLink action to its own section at the bottom of the item menu
The button used to be cloned in next to "Copy Stream URL", or shoved at the top
of the menu when there was none, which put it in the middle of Jellyfin's own
command groups. Now there is a single injection path: append an actionsheetDivider
plus the action at the end of the sheet's scroller, so it reads as its own
section the way Jellyfin separates its groups.

It also stopped appearing on menus for things that are not media. The old code
resolved the item from the URL, so opening the "..." menu on a cast member inside
a movie page showed ShareLink and would have shared the movie behind it. The item
is now taken from the trigger that opened the menu (card, list row or the detail
page's own button, all of which we can walk up from) and its type is checked
against the server before anything is injected: movie, series, season, episode,
nothing else. The API refuses everything else too, so a hand-rolled request
cannot tag a person or a playlist either.

Clicking the action now dismisses the action sheet instead of leaving it stacked
under the dialog.
2026-07-26 16:21:05 +02:00
Franciskid 36fc574d49 update 1.0.1.0 manifest checksum for the series navigation fixes 2026-07-08 02:10:44 +02:00
Franciskid 6fecdb8902 fix series share navigation and menu placement
Three problems reported on series shares. The ShareLink action landed
at the bottom of the series menu because the fallback injector cloned
the last item and appended; it now clones the first item and inserts
at the top, matching where it sits for movies. Guests could not open
season or episode pages because guest mode disabled pointer events on
all cards; cards are clickable again, since the server-verified route
check and the tag policy already control where a guest can actually
go. The injected item also inherited the template's data-id, which
could shadow a real menu command like Informations; it is stripped
now.
2026-07-08 02:09:49 +02:00
Franciskid dce87e0826 add 1.0.1.0 to the plugin manifest 2026-07-08 01:51:58 +02:00
Francois CB 95813b8e42 Merge pull request #6 from Franciskid/series-season-sharing
Share whole series and seasons
2026-07-08 01:51:15 +02:00
Franciskid bbb999842f share whole series and seasons, not just single titles
Sharing a series or season now tags the entire tree (series, seasons,
episodes) so the guest can browse and play everything inside it, and
strips it all again at cleanup. Redeeming re-tags the tree, so episodes
added after the link was created show up on the next redemption. The
guest lockdown in the web client now asks the server whether a page's
item is visible to the guest instead of hard-coding the single shared
id, so guests can navigate inside the shared tree but nowhere else.
Libraries and collections are still rejected.

Bumps the version to 1.0.1.0.
2026-07-08 01:50:57 +02:00
Francois CB c637558085 Merge pull request #5 from Franciskid/friendly-redeem-page
Friendly page for dead share links, empty selector default
2026-07-08 01:39:59 +02:00
Franciskid 06a49d0bfe show a friendly page for dead share links, drop plugin-specific selector defaults
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.
2026-07-08 01:39:28 +02:00
Franciskid 2fb912e734 update manifest checksum for rebuilt v1.0.0.0 2026-07-08 01:19:41 +02:00
Francois CB b66a88b72c Merge pull request #4 from Franciskid/server-minted-guest-sessions
Stop storing guest passwords, mint sessions server side
2026-07-08 01:16:48 +02:00
Franciskid f2b523be97 stop storing guest passwords, mint sessions server side
The old flow encrypted the guest password on disk next to its own key,
then sent it to the guest in the bootstrap HTML anyway. Now redemption
mints the session with ISessionManager.AuthenticateDirect and the page
only ever carries the session token. The guest account still gets a
random password nobody knows, so blank login stays impossible, but no
password is stored or sent anywhere anymore. Cleanup now defaults to
every 30 minutes instead of daily at 4am so expired guests die fast.
2026-07-08 00:50:20 +02:00
Francois CB 1d7d9b1b85 Add installation section to README
Added installation instructions for the plugin.
2026-07-07 23:40:14 +02:00
Francois CB dc01f87bf6 Delete ShareLinks.sln 2026-07-07 22:31:17 +02:00
Francois CB 58b4fdcbaa Enhance README with images and formatting
Added images and updated the README structure.
2026-07-07 22:29:00 +02:00
Franciskid 2f3c4a8213 ShareLinks: only inject the ShareLink button into real item action sheets
The fallback injector matched almost any dialog with a couple of buttons,
so the button leaked into the Edit images and Add to collection dialogs
(both opened from the item menu). Restrict it to genuine action sheets
(.actionSheet / [role=menu]) whose entries are .actionSheetMenuItem and
include a recognizable item command, which also excludes audio/subtitle/
quality pickers. The primary path (next to Copy Stream URL) is unchanged.
2026-07-07 22:15:26 +02:00
Franciskid 3c19c63d8c ShareLinks: add expiry date/time picker and French localization
Replace the 30-days quick button with a native datetime-local picker
(bounded by the server max) so links can expire on an exact date; the
picked date is converted to whole hours through the existing path.

Localize the creation popup and its toasts to French when the Jellyfin
UI language is French, detected from the html lang attribute like the
sibling plugin does; English is the fallback.
2026-07-07 22:10:11 +02:00