Help us shape the future of Go! Take the 2026 Go Developer Survey

identity

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: GPL-3.0 Imports: 34 Imported by: 0

Documentation

Overview

Package identity contains protocol-neutral household identity services. A credential must always be resolved for the surface that issued it.

Index

Constants

View Source
const MinPasswordLength = 12

Variables

View Source
var ErrPasswordTooShort = errors.New("identity: password must be at least 12 characters")
View Source
var ErrQuickConnectExpired = errors.New("identity: quick connect expired")

ErrQuickConnectExpired means the code was real and its window has closed.

View Source
var ErrQuickConnectPending = errors.New("identity: quick connect pending")

Poll consumes an approved request and creates its grant in one transaction. ErrQuickConnectPending means that approval has not yet happened.

View Source
var ErrQuickConnectUsed = errors.New("identity: quick connect already used")

ErrQuickConnectUsed means the code was already approved or redeemed.

Functions

func ClientNet

func ClientNet(r *http.Request, trusted ...netip.Prefix) (netip.Addr, bool)

ClientNet classifies the peer and returns its client address. Forwarding headers are trusted only from loopback or explicitly configured proxies. Malformed chains fail closed. A public hop cannot be hidden by a preceding client-supplied private address.

func IsGrantCredential

func IsGrantCredential(credential string) bool

IsGrantCredential distinguishes Ether grants from credentials belonging to external providers. Plex authentication must reject this prefix outright.

func LAN

func LAN(ip netip.Addr) bool

LAN reports whether an address belongs to a LAN or overlay network.

func ProtocolSetupComplete

func ProtocolSetupComplete() bool

ProtocolSetupComplete always reports that protocol-side setup is complete. Household setup and Plex claim are available only from the owner console.

func ProxyPrefixes

func ProxyPrefixes(cidrs []string) []netip.Prefix

ProxyPrefixes parses configured peer networks. Invalid entries grant no trust.

func PruneImageGrantShard

func PruneImageGrantShard(ctx context.Context, db *sql.DB, now time.Time, shard int64) (int64, error)

PruneImageGrantShard performs one bounded maintenance transaction for one shard. Scheduling and rotation across shards belong to its caller. It takes only that shard's issue/revoke lock, and rechecks expiry and shard in each outer delete. The caller supplies the same trusted clock as image issuance. Issued scope and expiry are immutable after cutover.

func PruneImageGrants

func PruneImageGrants(ctx context.Context, db *sql.DB, now time.Time) (int64, error)

PruneImageGrants removes expired legacy image grants and expired image headers. Callers hold every image shard lock in ascending order before either delete, and never take those locks in another order. Header deletion uses PruneHeadersInTx, which is namespace-global and capped at 40 rows.

func VerifyOwnerCredential

func VerifyOwnerCredential(cred OwnerCredential, username, password string) bool

VerifyOwnerCredential checks username and password against cred with the same Argon2id verification and constant-time comparison as Authenticate.

Types

type ActiveViewer

type ActiveViewer interface {
	ActiveUser(context.Context, int64) (*core.User, error)
}

ActiveViewer must return only currently active household members.

type ApprovedTarget

type ApprovedTarget struct {
	DeviceName string
	Client     string
	Product    string
	Surface    string
}

ApprovedTarget is the device a successful approval was bound to.

type GrantInfo

type GrantInfo struct {
	Hash     string `json:"hash"`
	Surface  string `json:"surface"`
	DeviceID string `json:"deviceId"`
	Client   string `json:"client"`
	Product  string `json:"product"`
	Version  string `json:"version"`
	LastUsed int64  `json:"lastUsed"`
}

type Grants

type Grants struct {
	// contains filtered or unexported fields
}

Grants stores only hashes of credentials in the shared database. Each grant is scoped to one surface and expires after a sliding period of inactivity.

func NewGrants

func NewGrants(db *sql.DB, viewers ActiveViewer, ttl time.Duration, now func() time.Time) (*Grants, error)

func (*Grants) Generation

func (g *Grants) Generation() uint64

Generation changes when a credential is revoked.

func (*Grants) GrantReference

func (g *Grants) GrantReference(ctx context.Context, surface, credential string) (string, *core.Viewer, error)

GrantReference validates a surface credential and returns an internal, non-secret reference plus its active viewer. The reference is not itself an authentication credential and must never be exposed to a client.

func (*Grants) GrantReferenceExpiry

func (g *Grants) GrantReferenceExpiry(ctx context.Context, surface, reference string, viewerID int64) (time.Time, error)

GrantReferenceExpiry validates a reference without extending the underlying grant and returns its current absolute expiry.

func (*Grants) Issue

func (g *Grants) Issue(ctx context.Context, viewerID int64, identityID *int64, surface string, device core.Device) (string, error)

Issue creates a new independent credential for a device and surface.

func (*Grants) IssueLogin

func (g *Grants) IssueLogin(ctx context.Context, viewerID int64, identityID *int64, surface string, device core.Device) (string, error)

IssueLogin atomically issues a grant and records a successful explicit sign-in.

func (*Grants) IssueOwnerLogin

func (g *Grants) IssueOwnerLogin(ctx context.Context, viewerID int64, identityID *int64, surface string, device core.Device) (string, error)

IssueOwnerLogin admits only an active owner and records the sign-in atomically.

func (*Grants) IssueRefresh

func (g *Grants) IssueRefresh(ctx context.Context, surface, access string, device core.Device) (string, error)

IssueRefresh binds a refresh family to an existing active access grant.

func (*Grants) List

func (g *Grants) List(ctx context.Context, viewerID int64) ([]GrantInfo, error)

func (*Grants) Refresh

func (g *Grants) Refresh(ctx context.Context, surface, token string, device core.Device) (string, string, error)

Refresh rotates a family atomically. Replaying a consumed token revokes the family and its current access grant. Clients must serialize refreshes and must not retry an already successful rotation with the previous token.

func (*Grants) Resolve

func (g *Grants) Resolve(ctx context.Context, surface, credential string) (*core.Viewer, error)

Resolve rejects malformed, expired, revoked, inactive and cross-surface grants.

func (*Grants) ResolveDevice

func (g *Grants) ResolveDevice(ctx context.Context, surface, credential string, device core.Device) (*core.Viewer, time.Time, error)

ResolveDevice validates the stored grant binding without extending expiry or changing last-used/activity data. It returns the actual current expiry.

func (*Grants) ResolveGrantDevice

func (g *Grants) ResolveGrantDevice(ctx context.Context, surface, credential, claim string) (*core.Viewer, core.Device, time.Time, error)

ResolveGrantDevice returns the verified stored device binding. An omitted claim is allowed; a supplied claim must match. No grant or activity writes occur, and missing device bindings fail closed.

func (*Grants) RevokeDevice

func (g *Grants) RevokeDevice(ctx context.Context, viewerID int64, deviceID string) error

func (*Grants) RevokeGrant

func (g *Grants) RevokeGrant(ctx context.Context, viewerID int64, hash string) error

RevokeGrant, RevokeDevice and RevokeViewer invalidate credentials without deleting audit metadata. Only a credential's hash is needed for revocation.

func (*Grants) RevokeViewer

func (g *Grants) RevokeViewer(ctx context.Context, viewerID int64) error

func (*Grants) SignInWithGrant

func (g *Grants) SignInWithGrant(ctx context.Context, surface, username, credential string, device core.Device) (string, error)

SignInWithGrant authenticates a client sign-in request that supplies an app-password grant credential instead of a local password. It verifies the grant is active for surface, checks that username matches the grant's viewer, and binds the client device to the grant.

func (*Grants) SignOutViewer added in v0.3.2

func (g *Grants) SignOutViewer(ctx context.Context, viewerID int64) (int, error)

SignOutViewer revokes every live device grant (including the grants other apps use as app passwords) and refresh family the viewer holds, returning how many credentials were revoked.

func (*Grants) ValidateGrantReference

func (g *Grants) ValidateGrantReference(ctx context.Context, surface, reference string, viewerID int64) error

ValidateGrantReference rechecks the shared grant and active membership for a scoped capability. Unlike Resolve, it does not extend the grant lifetime.

func (*Grants) ViewerTimes

func (g *Grants) ViewerTimes() *ViewerTimes

ViewerTimes returns the database-backed event observer owned by this grant service.

type ImageCapacityError

type ImageCapacityError struct{ Scope string }

ImageCapacityError means this viewer's or shard's image-header admission is full. Every image payload must have a charged reservation.

func (*ImageCapacityError) Error

func (e *ImageCapacityError) Error() string

type ImageGrantService

type ImageGrantService struct {
	// contains filtered or unexported fields
}

func NewArtworkImageGrantService

func NewArtworkImageGrantService(db *sql.DB, surface frontend.Protocol, grants *Grants, experience func() frontend.Experience, artwork frontend.Artwork, ttl time.Duration, now func() time.Time) (*ImageGrantService, error)

NewArtworkImageGrantService serves descriptors through the authorized artwork source. The source is consulted again for every fetch, after the session, viewer and Experience policy checks performed by Serve.

func NewImageGrantService

func NewImageGrantService(db *sql.DB, surface frontend.Protocol, grants *Grants, experience func() frontend.Experience, images ImageSource, ttl time.Duration, now func() time.Time) (*ImageGrantService, error)

func (*ImageGrantService) Generation

func (s *ImageGrantService) Generation() uint64

Generation changes when an image grant is revoked, so a cached page stops repeating that grant.

func (*ImageGrantService) Issue

func (*ImageGrantService) IssueBatch

IssueBatch returns at most one primary descriptor of each requested kind per visible item. Source membership and each item's policy are rechecked before inserting independent capabilities in a single transaction.

func (*ImageGrantService) Revoke

func (s *ImageGrantService) Revoke(ctx context.Context, credential, secret string) error

func (*ImageGrantService) Serve

func (s *ImageGrantService) Serve(w http.ResponseWriter, r *http.Request, secret string)

func (*ImageGrantService) SetImageGeneration

func (s *ImageGrantService) SetImageGeneration(gen func() uint64)

SetImageGeneration invalidates cached artwork identities when the catalogue or config generation changes. A nil function disables that cache.

func (*ImageGrantService) SetImageKey

func (s *ImageGrantService) SetImageKey(key []byte)

SetImageKey installs the node key for stateless page grants. An empty key keeps the process-local key created on first use.

func (*ImageGrantService) SignPage

SignPage seals artwork the caller already listed, with one key derivation for the page and no grant write. Serve rechecks the credential, expiry, revocation and current artwork policy before returning bytes.

type ImageSource

type ImageSource interface {
	Get(context.Context, string, int) ([]byte, string, error)
}

type Invite

type Invite struct {
	// contains filtered or unexported fields
}

Invite issues one-use links to an existing active viewer.

func NewInvite

func NewInvite(db *sql.DB, ttl, grantTTL time.Duration, now func() time.Time) (*Invite, error)

func (*Invite) Create

func (i *Invite) Create(ctx context.Context, viewerID int64) (string, error)

func (*Invite) CreateViewer

func (i *Invite) CreateViewer(ctx context.Context, display string) (int64, string, error)

CreateViewer creates an owner-managed passwordless viewer and its first invite atomically, so a failed invite cannot leave an unreachable account.

func (*Invite) Redeem

func (i *Invite) Redeem(ctx context.Context, secret string) (string, error)

type LinkedIdentity

type LinkedIdentity struct {
	ID       int64  `json:"id"`
	Provider string `json:"provider"`
	Display  string `json:"display"`
}

type Local

type Local struct {
	// contains filtered or unexported fields
}

Local authenticates unique local usernames, independent of display names. Membership is checked at authentication time against the viewer record.

func NewLocal

func NewLocal(db *sql.DB) (*Local, error)

func (*Local) ActivateDLNAGuest

func (l *Local) ActivateDLNAGuest(ctx context.Context, id int64) error

ActivateDLNAGuest fails closed until an explicit safety policy exists.

func (*Local) Authenticate

func (l *Local) Authenticate(ctx context.Context, username, password string) (viewerID, identityID int64, err error)

Authenticate returns the viewer and identity IDs only for active members.

func (*Local) Create

func (l *Local) Create(ctx context.Context, viewerID int64, username, password string) error

Create links a username and password to an existing viewer. Database uniqueness prevents two identities from claiming the same username.

func (*Local) CreateDLNAGuest

func (l *Local) CreateDLNAGuest(ctx context.Context, display, maxRating string) (int64, error)

CreateDLNAGuest creates an inactive, passwordless local guest with the given content rating limit. Callers must install its safety policy before activating it.

func (*Local) CreateOwner

func (l *Local) CreateOwner(ctx context.Context, username, password string) (int64, error)

CreateOwner atomically creates the first local owner and their credential. The database's single-owner constraint arbitrates concurrent setup requests. Callers must refresh auth's viewer cache after successful creation.

func (*Local) CreateViewer

func (l *Local) CreateViewer(ctx context.Context, username, password string) (int64, error)

CreateViewer atomically creates an owner-managed local viewer.

func (*Local) HasPassword

func (l *Local) HasPassword(ctx context.Context, viewerID int64) (bool, error)

HasPassword reports whether an active viewer has a local password identity.

func (*Local) LinkPlex

func (l *Local) LinkPlex(ctx context.Context, viewerID, plexID int64, display string) error

LinkPlex records an already verified Plex identity. A provider subject may belong to only one viewer; conflicts never move it between viewers.

func (l *Local) Links(ctx context.Context, viewerID int64) ([]LinkedIdentity, error)

func (*Local) Name

func (*Local) Name() string

func (*Local) PublicViewers

func (l *Local) PublicViewers(ctx context.Context) ([]frontend.PublicViewer, error)

PublicViewers returns only active local sign-in identities, including Plex-linked viewers with a local password. The caller enforces picker opt-in.

func (*Local) SetPassword

func (l *Local) SetPassword(ctx context.Context, viewerID int64, username, password string) error

SetPassword resets a linked local credential by viewer and username.

func (*Local) UpsertPassword

func (l *Local) UpsertPassword(ctx context.Context, viewerID int64, username, password string) error

func (*Local) Verify

func (l *Local) Verify(ctx context.Context, proof Proof) (VerifiedIdentity, error)

type LoginLimiter

type LoginLimiter struct {
	// contains filtered or unexported fields
}

LoginLimiter counts attempts against both the client IP and the combination of identity and IP. Database updates serialize concurrent cluster nodes. Success does not reset either bucket. Each call represents one attempt.

func NewLoginLimiter

func NewLoginLimiter(db *sql.DB, window time.Duration, perIP, perIdentityIP int, now func() time.Time) (*LoginLimiter, error)

func NewPollLimiter

func NewPollLimiter(db *sql.DB, window time.Duration, perIP, perHandleIP int, now func() time.Time) (*LoginLimiter, error)

NewPollLimiter uses a separate persistent bucket namespace from login attempts. Use it for PIN and Quick Connect polling, keyed by the opaque handle or polling secret. Multiple nodes share the same limits.

func (*LoginLimiter) Allow

func (l *LoginLimiter) Allow(ctx context.Context, identity string, ip netip.Addr) (bool, error)

Allow records an attempt before verification. It fails closed on database errors; callers must not skip this step for valid passwords or PINs.

func (*LoginLimiter) Cleanup

func (l *LoginLimiter) Cleanup(ctx context.Context, limit int) (int64, error)

Cleanup removes at most limit expired buckets. The predicate is repeated outside the selection so a bucket refreshed by another node is retained.

type OwnerCredential

type OwnerCredential struct {
	ViewerID   int64  `json:"viewerId"`
	IdentityID int64  `json:"identityId"`
	Username   string `json:"username"`
	Hash       string `json:"hash"`
}

OwnerCredential is the owner's local sign-in: what a failed start's recovery page verifies against while the database cannot be used.

func OwnerLocalCredential

func OwnerLocalCredential(ctx context.Context, q interface {
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}) (cred OwnerCredential, ok bool, err error)

OwnerLocalCredential reads the active owner's local credential from q (a database, possibly opened read-only). ok is false when the owner has no local password.

type PlexAuthenticator

type PlexAuthenticator interface {
	Authenticate(context.Context, string) (*core.User, error)
}

PlexAuthenticator validates only Plex-surface credentials.

type PlexProvider

type PlexProvider struct{ Auth PlexAuthenticator }

PlexProvider delegates Plex proofs to the existing Plex authenticator, which validates membership against the current server.

func (PlexProvider) Name

func (PlexProvider) Name() string

func (PlexProvider) Verify

func (p PlexProvider) Verify(ctx context.Context, proof Proof) (VerifiedIdentity, error)

type Preferences

type Preferences struct {
	// contains filtered or unexported fields
}

func NewPreferences

func NewPreferences(db *sql.DB, surface frontend.Protocol) *Preferences

func (Preferences) Delete

func (s Preferences) Delete(ctx context.Context, viewer *core.Viewer, client, key string) error

func (Preferences) Get

func (s Preferences) Get(ctx context.Context, viewer *core.Viewer, client, key string) (json.RawMessage, error)

func (Preferences) GetVersioned

func (s Preferences) GetVersioned(ctx context.Context, viewer *core.Viewer, client, key string) (json.RawMessage, int64, time.Time, error)

func (Preferences) Put

func (s Preferences) Put(ctx context.Context, viewer *core.Viewer, client, key string, raw json.RawMessage) error

func (Preferences) PutVersioned

func (s Preferences) PutVersioned(ctx context.Context, viewer *core.Viewer, client, key string, raw json.RawMessage) (int64, time.Time, error)

type Proof

type Proof struct {
	Username, Password, Token string
	Parameters                map[string]string
}

Proof carries provider-specific credentials. Providers interpret only the fields they support; future providers can add parameters without changing identity_links or grants.

type Provider

type Provider interface {
	Name() string
	Verify(context.Context, Proof) (VerifiedIdentity, error)
}

Provider verifies a proof and resolves it to a household viewer. Grant issuance and surface authorization are deliberately separate.

type QuickConnect

type QuickConnect struct {
	// contains filtered or unexported fields
}

QuickConnect is a shared, one-use approval broker. HTTP handlers must rate limit code approval and secret polling; codes are intentionally short.

func NewQuickConnect

func NewQuickConnect(db *sql.DB, ttl, grantTTL time.Duration, now func() time.Time) (*QuickConnect, error)

func (*QuickConnect) Approve

func (q *QuickConnect) Approve(ctx context.Context, code string, viewerID int64) error

Approve binds a pending, unexpired code to an active viewer exactly once.

func (*QuickConnect) ApproveSurface

func (q *QuickConnect) ApproveSurface(ctx context.Context, surface, code string, viewerID int64) error

ApproveSurface restricts an approval to a request for the given surface.

func (*QuickConnect) ApprovedTarget

func (q *QuickConnect) ApprovedTarget(ctx context.Context, code string, viewerID int64) (ApprovedTarget, error)

ApprovedTarget reports the device bound to a code this viewer just approved.

func (*QuickConnect) DetailsSurface

func (q *QuickConnect) DetailsSurface(ctx context.Context, surface, secret string) (QuickConnectDetails, error)

DetailsSurface reads metadata without consuming a valid surface-bound secret.

func (*QuickConnect) Initiate

func (q *QuickConnect) Initiate(ctx context.Context, surface string, device core.Device) (code, secret string, err error)

Initiate creates a code to be shown on the device and a separate secret for polling.

func (*QuickConnect) Poll

func (q *QuickConnect) Poll(ctx context.Context, secret string) (string, error)

func (*QuickConnect) PollSurface

func (q *QuickConnect) PollSurface(ctx context.Context, surface, secret string) (string, error)

PollSurface verifies the surface atomically before consuming the request.

func (*QuickConnect) PollSurfaceDevice

func (q *QuickConnect) PollSurfaceDevice(ctx context.Context, surface, secret string, device core.Device) (string, error)

PollSurfaceDevice redeems a secret, or repeats the same token for a short window when the device and client match the one that started the code.

func (*QuickConnect) StatusSurface

func (q *QuickConnect) StatusSurface(ctx context.Context, surface, secret string) (bool, error)

StatusSurface reports approval without consuming the secret or issuing a grant.

type QuickConnectDetails

type QuickConnectDetails struct {
	Approved  bool
	Code      string
	Device    core.Device
	CreatedAt time.Time
	ExpiresAt time.Time
}

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver routes credentials to their surface-specific validator. Plex tokens never fall back to grant validation and grants never reach plex.tv.

func NewResolver

func NewResolver(plex PlexAuthenticator, grants *Grants) (*Resolver, error)

func (*Resolver) Resolve

func (r *Resolver) Resolve(ctx context.Context, surface, credential string) (*core.Viewer, error)

type TicketService

type TicketService struct {
	// contains filtered or unexported fields
}

func NewTicketService

func NewTicketService(db *sql.DB, surface frontend.Protocol, grants *Grants, now func() time.Time) (*TicketService, error)

func (*TicketService) Active

func (s *TicketService) Active(ctx context.Context, t frontend.Ticket) (bool, error)

Active rechecks the issuing grant and membership for a live socket.

func (*TicketService) Consume

func (s *TicketService) Consume(ctx context.Context, token, purpose string) (frontend.Ticket, error)

func (*TicketService) Issue

func (s *TicketService) Issue(ctx context.Context, credential string, device frontend.Device, purpose string) (string, error)

type VerifiedIdentity

type VerifiedIdentity struct {
	ViewerID, IdentityID int64
	Provider, Subject    string
}

VerifiedIdentity is proof of a linked identity, not a surface grant.

type ViewerTimes

type ViewerTimes struct {
	// contains filtered or unexported fields
}

ViewerTimes stores observed events, rather than inferring them from grants. Its activity throttle is local; the database update is monotonic across nodes.

func NewViewerTimes

func NewViewerTimes(db *sql.DB, now func() time.Time) (*ViewerTimes, error)

func (*ViewerTimes) Get

func (s *ViewerTimes) Get(ctx context.Context, actorID, viewerID int64) (frontend.ViewerTimes, error)

Get authorizes the active actor and active target using shared database state, so a stale node cannot assert owner access. Plex's durable last_seen records real authenticated events and can contribute to the activity time.

func (*ViewerTimes) Observe

func (s *ViewerTimes) Observe(ctx context.Context, viewerID int64) error

Observe records an authenticated request at most once per viewer per node per interval. Failed writes are not cached; an older node cannot regress data.

func (*ViewerTimes) RecordOwnerLogin

func (s *ViewerTimes) RecordOwnerLogin(ctx context.Context, viewerID int64) error

RecordOwnerLogin records a completed external owner sign-in after the external provider has authenticated it. Active owner status is rechecked.

func (*ViewerTimes) RecordSignIn

func (s *ViewerTimes) RecordSignIn(ctx context.Context, viewerID int64) error

RecordSignIn notes a completed password or Quick Connect sign-in. Callers that must answer the client first invoke it after the response is written.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL