Users, sharing, and access
Ister does not own its user accounts — the OIDC provider does (Introduction). What Ister does own is what each authenticated user is allowed to see and do: an admin flag derived from the token, per-library visibility, and per-user playback-session sharing.
A user only becomes known to Ister after logging in at least once — the server learns that a user
exists from their first token. That is when they start appearing in the users and shareableUsers
lists.
Admins
Admin status comes entirely from the OIDC token, not from a setting inside Ister. The JWT's roles
claim is mapped to Spring authorities with a ROLE_ prefix, so a realm (or client) role named
admin becomes ROLE_admin. The me query surfaces it as isAdmin; the database keeps a
snapshot (user_entity.is_admin, refreshed on each request) but the token is authoritative.
To make someone an admin: in Keycloak (or your OIDC provider) create a role named admin and
assign it to the user, making sure it lands in the roles claim of the access token. No server
restart is needed — it takes effect on that user's next login.
Everything is available to any authenticated user except these admin-only operations (enforced
with @PreAuthorize("hasRole('admin')")):
- Library scanning and analysis —
scanLibrary,analyzeLibrary, and theanalyzeData…mutations - Search maintenance —
reindexSearch - Podcast subscriptions —
subscribePodcast,unsubscribePodcast - Library access management —
setLibraryVisibleToAll,setUserLibraryAccess - The full
userslisting
Library visibility
Each library is either visible to all or restricted:
- New and existing libraries default to visible to all (
library_entity.visible_to_all, migration V27), so installs from before this feature keep working unchanged. setLibraryVisibleToAll(libraryId, visibleToAll)(admin) flips a library between the two states.- For a restricted library,
setUserLibraryAccess(userId, libraryId, granted)(admin) grants or revokes one user's access. It is idempotent — granting twice, or revoking a grant that does not exist, is fine.
Admins always see every library, restricted or not. A non-admin sees the visible-to-all
libraries plus the restricted ones explicitly granted to them. Access is enforced everywhere media
is served (LibraryAccessService, MediaAccessEnforcementFilter), and a denial reads as not
found, never a 403 — a restricted library is invisible, not merely forbidden.
Playback-session sharing
Whether other users can see what you are playing (now-playing) and control it (remote control / party mode) is owner-controlled, per account, with an optional per-session override. Users set this from the client; as an operator you mainly need to know the defaults and the model.
Two independent scopes make up a user's preferences (playbackSharingSettings, saved with
updatePlaybackSharingSettings):
- Now-playing visibility — default
EVERYONE(the original behaviour: every session is visible to everyone). Can be tightened toPRIVATEor anALLOWLISTof users. - Remote control — default
PRIVATE(owner only). This is a deliberate tightening of the old "any user controls any session" party mode. Can beEVERYONE, anALLOWLIST, orSAME_AS_NOW_PLAYING(reuse the now-playing audience).
An allowlist holds user ids; shareableUsers gives a normal user a name-only list to pick from
(no admin rights required). A single session can override its control scope with setSessionSharing
— for example opening one movie night up to everyone without changing the account default.
Enforcement is again deny-as-not-found, and the owner always passes both checks.
The internals — where the scopes are stored, how they are enforced without a database session on the now-playing stream — are in the architecture guide, Continue watching and live status.