Documentation
¶
Overview ¶
Package auth implements core.Auth: it maps the X-Plex-Token a Plex app presents to an Ether user, and keeps the users table with per-user settings (stream limit, content rating cap, languages, sections).
A token is recognised, in order, as
- the server's own token (the owner),
- a server access token synced from plex.tv for the owner or a friend,
- any other plex.tv token, validated against plex.tv (GET /api/v2/user) and checked for access to this server (owner id, synced friend, or the server in that token's resources list).
Results of (3) are cached (positive and negative) so plex.tv is asked at most once per token per TTL; during a plex.tv outage a previously valid token keeps working for StaleTTL.
Index ¶
- Constants
- Variables
- func HashToken(token string) string
- func IsOwnerSession(token string) bool
- func StoredIdentity(ctx context.Context, db *sql.DB, id int64) (*core.User, error)
- type Acceptance
- type Options
- type PlexTVValidator
- type PlexViewerDefaults
- type Service
- func (s *Service) Accept(ctx context.Context, token string) (*core.User, Acceptance, error)
- func (s *Service) ActiveUser(ctx context.Context, id int64) (*core.User, error)
- func (s *Service) AllUsers(ctx context.Context) ([]UserInfo, error)
- func (s *Service) Authenticate(ctx context.Context, token string) (*core.User, error)
- func (s *Service) BootstrapOwner(ctx context.Context, p config.Plex) error
- func (s *Service) Close()
- func (s *Service) FlushSeen(ctx context.Context) error
- func (s *Service) Generation() uint64
- func (s *Service) Invalidate(token string)
- func (s *Service) IssueDelegation(ctx context.Context, userID int64) (string, error)
- func (s *Service) IssueOwnerSession(ctx context.Context, credential string) (string, time.Time, error)
- func (s *Service) LinkPlex(ctx context.Context, viewerID int64, token string) error
- func (s *Service) MergeViewers(ctx context.Context, fromID, intoID int64) error
- func (s *Service) Owner(ctx context.Context) (*core.User, error)
- func (s *Service) OwnerSession(ctx context.Context, token string) (*core.User, error)
- func (s *Service) PauseViewer(ctx context.Context, viewerID int64) error
- func (s *Service) PlexDefaults(userID int64) (PlexViewerDefaults, bool)
- func (s *Service) PlexDefaultsHandler() http.Handler
- func (s *Service) PolicyInTx(ctx context.Context, tx *sql.Tx, id int64) (*core.User, error)
- func (s *Service) PolicyUser(ctx context.Context, id int64) (*core.User, error)
- func (s *Service) ReceiptCurrent(ctx context.Context, a Acceptance) error
- func (s *Service) RefreshViewers(ctx context.Context) error
- func (s *Service) RemoveViewer(ctx context.Context, viewerID int64) error
- func (s *Service) ResumeViewer(ctx context.Context, viewerID int64) error
- func (s *Service) RevokeOwnerSession(ctx context.Context, token string) error
- func (s *Service) RevokeViewerSessions(ctx context.Context, viewerID int64) (int, error)
- func (s *Service) SetBus(bus clusterbus.Bus)
- func (s *Service) SetOwner(ctx context.Context, a plextv.Account, serverToken string) error
- func (s *Service) SetOwnerEnabled(ctx context.Context, viewerID int64, enabled bool) error
- func (s *Service) SetUnknownTokenRefresher(fn func(context.Context) error)
- func (s *Service) SetViewerMaxRating(ctx context.Context, id int64, rating string) error
- func (s *Service) SyncFriends(ctx context.Context, friends []plextv.Friend) error
- func (s *Service) UpdateUser(ctx context.Context, u core.User) error
- func (s *Service) User(ctx context.Context, id int64) (*core.User, error)
- func (s *Service) Users(ctx context.Context) ([]core.User, error)
- type UserInfo
- type Validator
Constants ¶
const ( // OwnerSessionTTL is how long an unused session stays valid. Use // extends it, at most once a day, up to OwnerSessionMaxAge. OwnerSessionTTL = 30 * 24 * time.Hour // OwnerSessionMaxAge bounds a session regardless of use. OwnerSessionMaxAge = 90 * 24 * time.Hour )
Owner console sessions. Signing in to the admin console used to hand the browser the owner's plex.tv account token, which then sat in localStorage where any script on the page could read it and take over the Plex account. The console now exchanges that credential for a server-local session: it only opens the admin API, is never accepted as a Plex or playback credential, and can be revoked. Only hashes persist, and every check reads the shared database, so a session issued on one cluster node works on all of them and dies everywhere the moment it is revoked or ownership changes.
Variables ¶
var ( ErrViewerIsOwner = errors.New("auth: viewer is the owner") ErrViewerPlexManaged = errors.New("auth: viewer is managed on plex.tv") ErrMergeSameViewer = errors.New("auth: cannot merge a viewer into itself") )
Viewer lifecycle refusals. Plex viewers are managed on plex.tv, full stop: Ether never pauses, removes or merges them. The owner can never be paused, removed or merged either.
Functions ¶
func IsOwnerSession ¶ added in v0.2.48
IsOwnerSession reports whether token has the shape of an owner console session. It says nothing about validity.
Types ¶
type Acceptance ¶ added in v0.3.0
type Acceptance struct {
// contains filtered or unexported fields
}
Acceptance is minted only by Service.Accept. The zero value is invalid. Formatting, JSON and logs show a fixed label so a receipt cannot leak a credential digest.
func (Acceptance) GoString ¶ added in v0.3.0
func (a Acceptance) GoString() string
func (Acceptance) MarshalJSON ¶ added in v0.3.0
func (a Acceptance) MarshalJSON() ([]byte, error)
func (Acceptance) String ¶ added in v0.3.0
func (a Acceptance) String() string
func (Acceptance) Valid ¶ added in v0.3.0
func (a Acceptance) Valid() bool
Valid reports whether Accept minted this value. It does not recheck revocation.
func (Acceptance) ViewerID ¶ added in v0.3.0
func (a Acceptance) ViewerID() int64
ViewerID is the viewer named by the acceptance. ReceiptCurrent still rechecks that identity against the live credential.
type Options ¶
type Options struct {
// Validator checks unknown tokens; nil means only server and synced
// tokens are accepted.
Validator Validator
// RefreshUnknownToken, when set, runs a rate-limited friend/access-token
// sync when an unrecognised token arrives, so a newly granted server
// access token is accepted without waiting for the periodic sync.
RefreshUnknownToken func(context.Context) error
// CacheTTL is how long a plex.tv-validated token is trusted (15m).
CacheTTL time.Duration
// NegativeTTL is how long a rejected token is remembered (2m).
NegativeTTL time.Duration
// StaleTTL lets a validated token keep working this long past CacheTTL
// while plex.tv is unreachable (24h).
StaleTTL time.Duration
// MaxCacheEntries bounds the token caches (10000).
MaxCacheEntries int
// MaxConcurrentValidations bounds distinct unknown tokens being checked
// with plex.tv (16). Excess requests fail fast; cached tokens still work.
MaxConcurrentValidations int
Logger *slog.Logger
Now func() time.Time
}
Options configures the Service.
type PlexTVValidator ¶
PlexTVValidator is the production Validator.
type PlexViewerDefaults ¶ added in v0.3.0
type PlexViewerDefaults struct {
AudioLanguage string `json:"audioLanguage,omitempty"`
AutoSelectAudio bool `json:"autoSelectAudio,omitempty"`
SubtitleLanguage string `json:"subtitleLanguage,omitempty"`
SubtitleMode int `json:"subtitleMode,omitempty"`
ForcedOnly bool `json:"forcedOnly,omitempty"`
HasProfile bool `json:"hasProfile,omitempty"`
FilterMovies string `json:"filterMovies,omitempty"`
FilterTelevision string `json:"filterTelevision,omitempty"`
AllowSync *bool `json:"allowSync,omitempty"`
HasSettings bool `json:"hasSettings,omitempty"`
}
PlexViewerDefaults is the cached plex.tv grant for one Ether viewer. It is a default layer only: owner Ether policy and the viewer's own picks always win.
func (PlexViewerDefaults) EtherMode ¶ added in v0.3.0
func (d PlexViewerDefaults) EtherMode() string
EtherMode is the Ether subtitle mode for this cached plex.tv profile.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements core.Auth and plextv.UserSync.
func (*Service) Accept ¶ added in v0.3.0
Accept authenticates token and returns the receipt minted by that acceptance. Callers must keep the receipt from this call. Reconstructing one from the request does not establish which branch accepted it.
func (*Service) ActiveUser ¶
ActiveUser returns a current access snapshot, including for stream grants.
func (*Service) Authenticate ¶
Authenticate implements core.Auth. It keeps the historical user and error result; Accept is the same decision plus the receipt minted inside it.
func (*Service) BootstrapOwner ¶
BootstrapOwner creates the owner from config without network access (on startup, before plex.tv is reachable) and registers the server token.
func (*Service) FlushSeen ¶
FlushSeen persists last-seen times (call periodically and at shutdown). Every node flushes its own observations; the update only ever moves last_seen_at forward, so nodes flushing in any order keep the latest.
func (*Service) Generation ¶ added in v0.3.0
Generation changes when a household member loses access.
func (*Service) Invalidate ¶
Invalidate forgets cached validations of token.
func (*Service) IssueDelegation ¶ added in v0.2.7
IssueDelegation creates a server-local credential, never a plex.tv account token. Only hashes persist; expiry and membership are checked on every use, including when another cluster node receives the credential.
func (*Service) IssueOwnerSession ¶ added in v0.2.48
func (s *Service) IssueOwnerSession(ctx context.Context, credential string) (string, time.Time, error)
IssueOwnerSession exchanges an owner credential (a plex.tv token Ether accepts as the owner's) for a console session. Sessions and delegated credentials cannot be exchanged, so a stolen session cannot extend itself past its absolute lifetime.
func (*Service) LinkPlex ¶ added in v0.3.0
LinkPlex verifies a Plex account and its access to this server before attaching it to an existing active viewer. Existing viewers are never merged.
func (*Service) MergeViewers ¶ added in v0.3.2
MergeViewers moves everything from the source viewer into the target (store.MergeViewer: the target wins conflicts, watch history and plays union), then removes the source, in one transaction. Neither side may be the owner or a Plex viewer.
func (*Service) OwnerSession ¶ added in v0.2.48
OwnerSession returns the owner a console session belongs to. It fails with core.ErrUnauthorized once the session expired, was revoked, or its user is no longer the active owner.
func (*Service) PauseViewer ¶ added in v0.3.2
PauseViewer pauses a viewer's access: everything of theirs is kept, but they can no longer sign in or watch. The caller signs them out everywhere as part of pausing (admin does, via identity grants).
func (*Service) PlexDefaults ¶ added in v0.3.0
func (s *Service) PlexDefaults(userID int64) (PlexViewerDefaults, bool)
PlexDefaults returns the cached plex.tv layer for a local viewer id.
func (*Service) PlexDefaultsHandler ¶ added in v0.3.0
PlexDefaultsHandler serves GET /{id} as the read-only plex.tv layer shown in the owner console.
func (*Service) PolicyInTx ¶ added in v0.3.0
PolicyInTx reads durable membership on an open transaction so a caller can fence it with the rest of an authorization snapshot.
func (*Service) PolicyUser ¶ added in v0.3.0
PolicyUser reads durable membership and restrictions without the process cache.
func (*Service) ReceiptCurrent ¶ added in v0.3.0
func (s *Service) ReceiptCurrent(ctx context.Context, a Acceptance) error
ReceiptCurrent reports whether a still names the credential Accept minted and that credential's current local revocation state. It does not ask plex.tv.
func (*Service) RefreshViewers ¶ added in v0.3.0
RefreshViewers reloads viewer state after an identity service has made an atomic membership change. Cluster peers receive the same invalidation.
func (*Service) RemoveViewer ¶ added in v0.3.2
RemoveViewer deletes a viewer and all of their data in one transaction: every registered per-viewer purge (store.PurgeViewer), then the auth_users row itself, whose ON DELETE CASCADE children are a backstop.
func (*Service) ResumeViewer ¶ added in v0.3.2
ResumeViewer ends a pause.
func (*Service) RevokeOwnerSession ¶ added in v0.2.48
RevokeOwnerSession ends a console session. Unknown tokens are not an error: signing out twice is harmless.
func (*Service) RevokeViewerSessions ¶ added in v0.3.2
RevokeViewerSessions deletes the viewer's console sessions and delegated credentials, returning how many were revoked. Device grants and app passwords live in identity and are revoked there; together they make "sign out everywhere".
func (*Service) SetBus ¶
func (s *Service) SetBus(bus clusterbus.Bus)
SetBus connects the cluster bus (nil disconnects): after every write this service tells the other nodes to reload users and tokens, and it reloads when they write. A standalone server never calls it.
func (*Service) SetOwner ¶
SetOwner implements plextv.UserSync: it makes a the owner and registers serverToken as the owner's token. A unique index admits one owner row, so when another node changes the owner concurrently the losing transaction is retried against the winner's committed state.
func (*Service) SetOwnerEnabled ¶ added in v0.3.0
SetOwnerEnabled changes the owner's membership decision. Plex sharing remains independently authoritative and can keep a viewer inactive.
func (*Service) SetUnknownTokenRefresher ¶ added in v0.3.0
SetUnknownTokenRefresher installs or replaces the callback invoked when an unrecognised token arrives before falling back to plex.tv validation.
func (*Service) SetViewerMaxRating ¶ added in v0.3.0
SetViewerMaxRating changes only the content ceiling without overwriting other viewer settings.
func (*Service) SyncFriends ¶
SyncFriends implements plextv.UserSync: it replaces the synced access tokens, upserts friends' profiles (never their Ether settings) and deactivates every non-owner absent from the authoritative membership list, including viewers first discovered through personal-token validation.
type UserInfo ¶
type UserInfo struct {
core.User
Active bool `json:"active"`
Origin string `json:"origin"`
Home bool `json:"home"`
Restricted bool `json:"restricted"`
PlexSections []int `json:"plexSections"`
LastSeen time.Time `json:"lastSeen,omitempty"`
// OwnerEnabled is false when the owner paused this viewer;
// PlexShared is whether plex.tv still shares the server with them;
// HasPlex is whether the viewer carries a Plex account id.
OwnerEnabled bool `json:"ownerEnabled"`
HasPlex bool `json:"hasPlex"`
}
UserInfo is a user plus bookkeeping for the admin UI.
type Validator ¶
type Validator interface {
// Account returns the account behind token. An error matching
// core.ErrUnauthorized means the token is invalid; other errors mean
// plex.tv could not answer.
Account(ctx context.Context, token string) (*plextv.Account, error)
// HasServer reports whether token's resources list includes this
// server (i.e. the account was granted access).
HasServer(ctx context.Context, token string) (bool, error)
}
Validator checks tokens Ether does not already know against plex.tv.