ShareLinks: rename menu action to ShareLink with a share icon; rewrite README

Rename the injected context-menu action from 'Create guest link' to
'ShareLink' and swap its cloned copy icon (content_copy) for the share
icon, matching the other menu items' style.

Replace the placeholder scaffold README with a user-facing one describing
what the plugin does, the server-side guest confinement, configuration,
and the known Jellyfin cast/crew tag bug (jellyfin/jellyfin#14926).
Cette révision appartient à :
Franciskid
2026-07-07 00:50:08 +02:00
Parent 0fecbb6697
révision 20160b3cbb
2 fichiers modifiés avec 107 ajouts et 64 suppressions
+98 -62
Voir le fichier
@@ -1,85 +1,121 @@
# ShareLinks
# ShareLinks for Jellyfin
ShareLinks is a Jellyfin 10.11 plugin scaffold for issuing expiring guest-share
links to individual items. This staging tree now includes the core dashboard
and web-client wiring that later workers will build on:
Send someone a single movie or episode, without giving them an account, without
them seeing the rest of your library.
- plugin metadata and DI registration
- JSON-backed record storage
- token generation and hashing
- startup and scheduled cleanup shells
- configuration defaults
- Jellyfin Web script injection and a dashboard config page shell
> *"here, watch this one film, the link dies tomorrow"*
## Current security stance
ShareLinks adds a **ShareLink** button (with a little share icon) to the context
menu of any movie or episode in the Jellyfin web client. 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 one title, already signed in, and they cannot
wander off into the rest of your server.
The design goal is simple: a raw share token should exist only at the moment it
is issued, returned to the caller once, and then forgotten. Persistent storage
keeps only a keyed HMAC hash of the token plus the metadata needed to audit or
clean up the link.
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.
That means later API work must keep a few rules:
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.
1. never log raw tokens
2. never write raw tokens to disk
3. only return the token in the initial creation response
4. treat token validation as hash comparison only
5. keep guest-user creation and teardown behind explicit service calls
---
## Configuration plan
## How it works
The `PluginConfiguration` defaults are deliberately opinionated:
1. As an admin you open the context menu on a movie or episode 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 that one item with a unique, random tag and
records the share. The raw link token is shown to you once and never stored,
only a keyed HMAC hash of it is kept.
3. Whoever opens the link gets a throwaway guest user created on the spot,
restricted by that tag to the single shared item, 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. A scheduled task and a startup
pass make sure nothing lingers if the server was off at expiry time.
- default expiry hours
- maximum allowed expiry hours
- optional public base URL override
- guest username prefix
- transcoding and remuxing toggles
- cleanup interval in minutes
- one-use default
- guest-mode lockdown enabled by default
## What the guest sees
Later workers should wire those settings into the issue / redeem / revoke
pipeline and into the guest-user creation logic.
Just the one title, and the ability to play it. The confinement is real and it
is enforced on the server, not only in the browser:
## Storage layout
- The guest's Jellyfin policy only permits the single shared item, 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 away
snaps back to the shared title.
The staging implementation stores plugin data under Jellyfin's application data
path, in a dedicated `sharelinks` directory. The persistent JSON store keeps
`ShareLinkRecord` entries keyed by id, while the token service stores its secret
key separately in the same directory.
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.
This keeps the plugin portable and avoids any hardcoded filesystem locations.
## Managing links
## Cleanup architecture
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.
There are two cleanup entry points already wired:
## Hiding other plugins from guests
- `Tasks/CleanupShareLinksScheduledTask.cs`
- `Lifecycle/StartupCleanupHostedService.cs`
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. 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 the
[AI Search](https://github.com/Franciskid) button; add any other plugin's
selector and it disappears for guests too, no code change needed.
Both currently call an `IShareLinkCleanupService` implementation that is a
no-op. That gives later workers a stable seam for:
## Security stance
- expiring old links
- removing one-use links after redemption
- tearing down guest accounts and tokens
- recording cleanup attempts and failures
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:
## Endpoint audit requirements
1. raw tokens are never logged
2. raw tokens are never written to disk
3. the token is only returned in the creation response
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
When the API surface is added, it should be audited for:
## Configuration
- authz on every create/list/redeem/revoke endpoint
- exact token handling on create and redeem flows
- rate limiting for token guesses and redemption retries
- whether any response leaks the token hash, raw token, or guest credentials
- whether guest-mode lockdown is enforced consistently
- whether cleanup can safely run while links are being created or redeemed
- whether error messages reveal link existence or status
All of these live on the plugin's dashboard page:
## What is intentionally missing
| 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 |
The remaining work is mostly policy polish and cleanup edge cases. The plugin
already has its controller, web injection hook, and dashboard page entry point
in place.
## 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.
## Credits and license
Developed by [Franciskid](https://github.com/Franciskid).
Licensed under the [GPL-3.0](LICENSE), like most Jellyfin plugins.