Repository navigation
Player redesign 8: Listening in the house and cast tokens (household, cast capabilities) - #125
Merged
Merged
Conversation
…) only The web player loads cast_sender.js and the Cast framework from gstatic on Chromium to cast (capability cast). No other directive changes; the test pins the whole policy string and checks the console's site-wide CSP stays same-origin.
…ity household)
Migration 0041 household_sharing (user_id PK, CASCADE; no row = off).
GET/PUT /me/household {share_listening} (strict, field required; demo 403)
and GET /household/listening {sharing, listening[]}: catalog.HouseListening
answers [] unless the caller shares, and otherwise one row per other enabled,
non-demo, sharing person: their newest recorded session with listening in it
within 24 h, dropped when its book is outside the caller's scopes. Rows carry
no device, address, app or token. The chapter-at-position lookup is shared
with LiveSessions (chapterCache).
…ability cast)
Migration 0042 cast_tokens (SHA-256 hash only, user CASCADE, library_id +
rel_path, fixed-width ms expiry). POST /cast/token {library_id, path} -> 201
{token, expires_at}: resolved and scope-checked like the media routes (a part
path stores its book's path), 24 h life, expired rows purged and the newest 20
per person kept on mint; demo may mint, an API key may not.
Accepted only as the ?token= of GET /libraries/{id}/cover and /stream
(requireBookMediaAuth), never with an Authorization header and never on any
other route (/meta/cover included), only while unexpired and its user enabled,
and only for the token's book or a file inside it on a segment boundary
(auth.CastGrant.Covers); the handler then checks the person's current scope.
A cast-authenticated answer sets CORP cross-origin so the receiver can load
it; the media limiter keys cast tokens apart; a cast transcode does not mark a
device's session. Backup test for 0041/0042.
… token rules, CSP)
…next) cast tokens catalog's pathAllowedBy moves to its own tiny package so auth can apply the same rule to a cast token's book without importing catalog's whole graph.
0042 (unreleased, rewritten in place) drops the parallel cast_tokens store: a cast token is a tokens row of kind cast (hashed, expires_at 24 h) with a cast_grants row naming its book and its parent, the token that minted it. lookupToken stays the one validity check and also refuses a cast token whose parent is signed out or expired, so logging out the sending device now ends its cast tokens (the old known limit). auth owns the whole cast decision: ResolveCastRequest accepts a cast token only when its grant covers the requested path (relpath.Within, empty guards kept); ResolveRequest never does. authenticate takes the accepted kinds and does one lookup; the cast path only for ?token= with no Authorization header on a book's cover/stream. CORP cross-origin is set in one place from the credential kind, the media limiter keys every credential by its token id (mediaBucket gone), and Credential.DeviceID (the parent for a cast token) lets a cast transcode mark the sender's session without a branch. The wire is unchanged.
…n-sharer's poll loads no scopes
The handler asks HouseholdSharing first and answers {false, []} before any
scope query. catalog.HouseListening is one query from the caller's own sharing
row (still reciprocal by itself) into each other sharer's newest session via
idx_sessions_user_last, joining books only for those winners (CROSS JOIN pins
the order; the verified EXPLAIN QUERY PLAN is in the comment), with NULLIF in
place of the shadow fields. The wire-only envelopes move to the handler, the
HousePlaying/HousePaused aliases go (SessionPlaying/SessionPaused), houseWindow
is unexported, and the catalog tests run on the session fixture so the indexed
book's title, author, length and chapter are covered. The wire is unchanged.
…LAUDE.md names copyFixtures
…he Cast sender's paths; dead cast tokens pruned - A cast token's path must belong to its own indexed book (no nested books in its folder). - script-src lists only the Cast sender's three gstatic paths, never the bare origin. - PruneCastTokens deletes expired, grantless or sender-signed-out cast tokens on mint and in daily retention. - relpath.Clean shared by catalog and auth; ResolveCastRequest cleans the path itself. - lookupToken back to one two-table query; the cast grant is read only for cast tokens. - recordHeartbeat keys transcode marks on DeviceID. - Tests: pairing token refused on media, Basic header beside a cast token refused, cast covers 200 + CORP, a receiver's transcode marks the sender, nested books refused. - CLAUDE.md and comments match the wiring.
… 0041 for series keys)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Player redesign Phase 8 (Household), server side. Two additive capabilities, both mirrored in the player (frontend PR to follow; this merges first).
Listening in the house (
household)GET/PUT /api/v1/me/household{share_listening}(off by default; demo 403; strict body).GET /api/v1/household/listening->{sharing, listening[]}: reciprocal (empty unless the caller shares), other enabled non-demo users who share, one row per person (newest listening session within 24 h;playing/paused/recent), dropped when the book is outside the caller's scopes, never device/IP/app/token fields. One query driven fromhousehold_sharing(plan pinned in a comment).household_sharing; in backups; purged with the user.Cast tokens (
cast)POST /api/v1/cast/token {library_id, path}->201 {token, expires_at}(24 h). A cast token is atokenskind with acast_grantsrow and a parent (sender) token: signing out the sending device ends it. API keys refused (a token cannot create tokens); demo allowed.GET /libraries/{id}/coverand/stream, only as?token=with noAuthorizationheader, only for its own indexed book (not a nested book in its folder; segment-boundary paths via the sharedinternal/relpath). Answers carryCross-Origin-Resource-Policy: cross-origin(the receiver is a gstatic.com app). A receiver's transcode marks the sender's session. Dead tokens pruned on mint and daily. Migration 0042.script-srcadds exactly the Cast sender's three gstatic paths (not the origin: gstatic also serves AngularJS).Tests
Allowed + denied for every rule (other book,
CradlevsCradle 2, nested book, other library, non-media routes incl./meta/cover, bearer header, Basic header beside a cast token, pairing token on media, expired, disabled user, revoked scope, sender signed out); household privacy rules incl. no ip/device keys; backups; CSP pinned exactly.Quality passes
/simplify (cast tokens redesigned from a parallel table into a tokens kind) then /code-review max --fix (13 fixed). Skipped: the per-person cap of 20 can evict a token a receiver still plays from (needs a client re-mint on 401 or a per-device cap: product decision).
Manager: builds/tests clean against this branch (no change).