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 à :
+98
-62
@@ -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.
|
||||
|
||||
Référencer dans un nouveau ticket
Bloquer un utilisateur