165 lignes
8.0 KiB
Markdown
165 lignes
8.0 KiB
Markdown
# ShareLinks for Jellyfin
|
|
|
|
Send someone a single movie, episode, season or whole series, without giving
|
|
them an account, without them seeing the rest of your library.
|
|
|
|
> *"here, watch this one film, the link dies tomorrow"*
|
|
|
|
ShareLinks adds a **ShareLink** button (with a little share icon) to the context
|
|
menu of any movie, episode, series or season in the Jellyfin web client. It sits
|
|
in its own section at the bottom of the menu, and it only shows up for those four
|
|
kinds of item, never for a cast member, a studio, a library or anything else you
|
|
cannot actually watch. Click it, pick how long the link should live, and you get
|
|
a URL you can send to anyone. When they open it, they land straight on that title, already signed in,
|
|
and they cannot wander off into the rest of your server.
|
|
|
|
No account for them to create, no password for you to hand out, no permanent
|
|
guest user piling up. The link is temporary, the guest is temporary, and when it
|
|
expires everything is cleaned up on its own.
|
|
|
|
I built this for my own server (shared with family and a few friends), because I
|
|
kept wanting to show someone *one specific film* without either adding them as a
|
|
real user or handing over a login that sees everything.
|
|
|
|
---
|
|
|
|
<img width="1476" height="784" alt="image" src="https://github.com/user-attachments/assets/841f24f8-8b0f-4fd0-a8f8-890ff80faa2f" />
|
|
|
|
|
|
## How it works
|
|
|
|
1. As an admin you open the context menu on a movie, episode, series or season
|
|
and hit **ShareLink**. You choose an expiry (1 hour up to 30 days) and the
|
|
plugin hands you a link, copied to your clipboard.
|
|
2. Behind the scenes the plugin tags the shared item with a unique, random tag
|
|
and records the share. Share a series or a season and the tag is applied to
|
|
the whole tree underneath it too - series, seasons and episodes - so the
|
|
guest can actually browse from the series page down into a season and an
|
|
episode, not just see a single locked node. Lookups only ever go through a
|
|
keyed HMAC hash of the token, and the link itself is dropped from the record
|
|
once it is revoked or expired.
|
|
3. Whoever opens the link gets a throwaway guest user created on the spot,
|
|
restricted by that tag to the shared item and its tree, and is signed in
|
|
automatically. They land on the title's page.
|
|
4. When the link expires (or you revoke it), a cleanup pass disables and deletes
|
|
the guest user and strips the temporary tag from the whole tree again. A
|
|
scheduled task and a startup pass make sure nothing lingers if the server was
|
|
off at expiry time.
|
|
|
|
## What the guest sees
|
|
|
|
Just the shared title (and, for a series or season, its seasons and episodes),
|
|
and the ability to play them. The confinement is real and it is enforced on the
|
|
server, not only in the browser:
|
|
|
|
- The guest's Jellyfin policy only permits items carrying the share's tag, so
|
|
every other movie, show, library and search comes back empty from the API.
|
|
Even someone poking at the raw API cannot list your other content.
|
|
- On top of that, the web client is locked down for the guest: the home,
|
|
menu and search buttons are hidden, in-page links (cast, studio, genres) are
|
|
made inert, "add to playlist" is removed, and any attempt to navigate
|
|
somewhere outside the shared tree snaps back to the shared title. Navigating
|
|
within the tree - series to season to episode - works normally.
|
|
|
|
Playback works normally, including transcoding and remuxing if you allow it, and
|
|
the player's back button still returns them to the title's page.
|
|
|
|
One honest caveat: if you share a series or season and new episodes get added
|
|
to it later, those episodes only pick up the tag (and become visible to the
|
|
guest) the next time the link is redeemed - not the instant they are added. For
|
|
a one-use link that has already been redeemed, that never happens, so a
|
|
one-use link is a snapshot of the tree as it existed at redemption time.
|
|
|
|
## Managing links
|
|
|
|
The plugin's dashboard page lists every share with its status, the title, a
|
|
copyable link, the temporary guest name, and an expiry, and lets you revoke any
|
|
of them on the spot. Revoking runs the same teardown as expiry: guest gone, tag
|
|
gone.
|
|
|
|
## Hiding other plugins from guests
|
|
|
|
If you run other plugins that inject their own UI into the web client (a search
|
|
bar, a floating button), you probably do not want a guest to see them. I had
|
|
exactly that problem with a different plugin of mine, so the **Guest hidden
|
|
selectors** setting is a comma-separated list of CSS selectors that get hidden
|
|
in guest sessions. It ships with a default that hides that plugin's floating
|
|
button and panel; add any other plugin's selector and it disappears for guests
|
|
too, no code change needed.
|
|
|
|
## Security stance
|
|
|
|
The design goal is simple: a raw share token exists only at the moment it is
|
|
issued, is returned to you once, and is then forgotten. Persistent storage keeps
|
|
only a keyed HMAC hash of the token plus the metadata needed to audit and clean
|
|
up the link. So:
|
|
|
|
1. raw tokens are never logged
|
|
2. only the token's HMAC hash is used to look a link up
|
|
3. the finished share URL is kept on the record while the link is live, so the
|
|
dashboard can re-copy it, and is dropped again the moment the link is revoked
|
|
or expires
|
|
4. token validation is a hash comparison
|
|
5. guest-user creation and teardown live behind explicit service calls
|
|
6. the real access boundary is the server-side tag policy; the web-client
|
|
lockdown is convenience on top of it
|
|
|
|
The same applies to the guest's login. The plugin mints the guest session itself
|
|
on the server, using Jellyfin's own session manager. No password is ever stored
|
|
anywhere, not even encrypted, and no password ever appears in the page sent to
|
|
the guest. The only thing the guest's browser receives is a session token
|
|
scoped to that one guest account, and that token dies the moment the guest
|
|
account is cleaned up. On top of that, the guest account is assigned an authentication provider that
|
|
refuses every interactive sign-in, so the normal login page cannot be used to get
|
|
into a guest account at all, password or not. If the plugin is disabled Jellyfin
|
|
falls back to its own invalid-provider handling, which refuses too.
|
|
|
|
## Configuration
|
|
|
|
All of these live on the plugin's dashboard page:
|
|
|
|
| Setting | What it does |
|
|
|---|---|
|
|
| Default / maximum expiry | The default the menu offers, and the ceiling a link may be set to |
|
|
| Public base URL override | Force the host used when building links (otherwise derived from the request) |
|
|
| Guest username prefix | Prefix for the throwaway guest accounts (default `share-`) |
|
|
| Allow transcoding / remuxing | Whether guest playback may transcode or remux |
|
|
| Cleanup interval | How often the background cleanup runs |
|
|
| One-use default | Whether new links default to single redemption |
|
|
| Guest lockdown | The web-client confinement described above (on by default) |
|
|
| Guest hidden selectors | CSS selectors hidden from guests, to suppress other plugins' UI |
|
|
|
|
## Known limitation: cast and crew
|
|
|
|
Jellyfin has a core bug ([jellyfin/jellyfin#14926](https://github.com/jellyfin/jellyfin/issues/14926))
|
|
where a user restricted by tags loses the Cast & Crew section entirely, because
|
|
the tag filter is applied to people as well as to media. Since a ShareLinks
|
|
guest is tag-restricted, they hit this: the shared title's page shows no
|
|
actors, director or writer. This is a server-side Jellyfin issue, not something
|
|
the plugin can style around. A workaround inside the plugin is possible and on
|
|
the list.
|
|
|
|
## Compatibility
|
|
|
|
- Jellyfin **10.11** (targetAbi `10.11.0.0`), .NET 9. Tested on 10.11.8.
|
|
- The UI injection targets the standard Jellyfin web client, and works with both
|
|
the English and French interface.
|
|
|
|
## Install
|
|
|
|
Dashboard => Plugins => Manage repositories => New repository => https://raw.githubusercontent.com/Franciskid/jellyfin-plugin-sharelinks/main/manifest.json
|
|
|
|
**You may need to hard refresh the page for the button to appear**
|
|
|
|
## Credits and license
|
|
|
|
Developed by [Franciskid](https://github.com/Franciskid).
|
|
|
|
Licensed under the [GPL-3.0](LICENSE), like most Jellyfin plugins.
|
|
|
|
|
|
## Images
|
|
|
|
<img width="590" height="427" alt="image" src="https://github.com/user-attachments/assets/1ae3c28a-644e-4c9f-824a-07b800aa5eff" />
|
|
|