[PR #5164] Add canStream user permission to control streaming access #4452

Open
opened 2026-04-25 00:50:47 +02:00 by adam · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/advplyr/audiobookshelf/pull/5164
Author: @ZRiddle
Created: 4/2/2026
Status: 🔄 Open

Base: masterHead: feat/can-stream-permission


📝 Commits (1)

  • 464b720 Add canStream user permission to control streaming access

📊 Changes

11 files changed (+236 additions, -1 deletions)

View changed files

📝 client/components/cards/LazyBookCard.vue (+4 -1)
📝 client/components/modals/AccountModal.vue (+10 -0)
📝 client/cypress/tests/components/cards/LazyBookCard.cy.js (+10 -0)
📝 client/pages/item/_id/index.vue (+13 -0)
📝 client/store/user.js (+3 -0)
📝 client/strings/en-us.json (+2 -0)
📝 server/controllers/LibraryItemController.js (+8 -0)
📝 server/models/ApiKey.js (+2 -0)
📝 server/models/User.js (+5 -0)
test/server/controllers/LibraryItemController.canStream.test.js (+101 -0)
test/server/models/User.canStream.test.js (+78 -0)

📄 Description

Summary

Adds a per-user "Can Stream" permission that mirrors the existing "Can Download" pattern. Server admins can disable streaming for specific users, encouraging local downloads instead of sustained server-side playback.

Addresses #2572.

Changes

Backend:

  • server/models/User.js: stream permission added to permissionMapping, getDefaultPermissionsForUserType(), and new canStream getter
  • server/models/ApiKey.js: stream permission added to API key defaults
  • server/controllers/LibraryItemController.js: canStream check added to startPlaybackSession() and startEpisodePlaybackSession(), returning 403 if denied

Frontend:

  • client/store/user.js: getUserCanStream getter
  • client/components/modals/AccountModal.vue: "Can Stream" toggle in admin user management, defaults to true for all user types
  • client/pages/item/_id/index.vue: Play button hidden when canStream: false; Download button shown instead when canDownload: true; "Contact your server admin for access" message when neither is allowed
  • client/components/cards/LazyBookCard.vue: Play overlay hidden when canStream: false
  • client/strings/en-us.json: LabelPermissionsStream and MessageNoStreamOrDownloadAccess strings (English only; other locales fall back)

Tests:

  • 11 new Mocha tests: User model getter (permission true/false/missing/inactive, defaults, mapping) and controller enforcement (403 on both playback endpoints)
  • 1 new Cypress test: play button hidden on book card when canStream: false

Design decisions

Enforcement at session creation, not stream segments. Stream segment routes (/session/:id/track/:index, /hls/:stream/:file) are intentionally unauthenticated (session cookies for performance). Gating at startPlaybackSession / startEpisodePlaybackSession is the clean chokepoint: if you can't create a session, you can't stream.

!== false getter instead of !!. Every other permission getter uses !!, but those permissions existed at launch. canStream is new, so existing users won't have stream in their permissions JSON. Using !! would silently break streaming for all existing users on upgrade. !== false means undefined (existing users) and true both resolve to "allowed"; only an explicit false blocks streaming. The client-side getter in store/user.js uses the same pattern for consistency.

Default stream: true for all user types. This is a non-breaking, opt-in restriction. Admins choose to disable streaming for specific users. Differs from canDownload which defaults false for guests, but streaming is the current default behavior for all users, so changing it would be a regression.

Download button promoted to primary action. When a user can't stream but can download, the item detail page shows a Download button where Play would normally be. This is a new UI pattern (download is normally in the context menu), but it makes the primary action discoverable when streaming is disabled.

Open questions for maintainers

  • "Contact your server admin for access" wording (shown when both streaming and download are disabled): open to suggestions on copy or whether to show a message at all
  • Mobile app impact: The ABS mobile apps (advplyr/audiobookshelf-app) will receive a 403 on stream attempts for restricted users, but the app UI won't adapt gracefully without a companion PR there. The apps already handle 403 for download permission, so it won't crash, but there's no "download instead" prompt. Happy to open a follow-up issue/PR for the app repo
  • Share link streaming is unaffected by this permission since share links bypass user auth entirely. This is intentional and consistent with how share links work today

How I tested this

  • All 346 Mocha tests pass (11 new), including migration safety (missing permission key defaults to allowed)
  • All 106 Cypress component tests pass (1 new), including play button visibility with canStream: false
  • Manual code review of all canDownload references in the client to confirm no missed enforcement points

Screenshots

N/A -- toggle follows the exact same UI pattern as existing permission toggles in the account modal. Happy to add screenshots if needed.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/advplyr/audiobookshelf/pull/5164 **Author:** [@ZRiddle](https://github.com/ZRiddle) **Created:** 4/2/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feat/can-stream-permission` --- ### 📝 Commits (1) - [`464b720`](https://github.com/advplyr/audiobookshelf/commit/464b720d9ecfc4b6c79c5d19eb83bedc88ff715f) Add canStream user permission to control streaming access ### 📊 Changes **11 files changed** (+236 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `client/components/cards/LazyBookCard.vue` (+4 -1) 📝 `client/components/modals/AccountModal.vue` (+10 -0) 📝 `client/cypress/tests/components/cards/LazyBookCard.cy.js` (+10 -0) 📝 `client/pages/item/_id/index.vue` (+13 -0) 📝 `client/store/user.js` (+3 -0) 📝 `client/strings/en-us.json` (+2 -0) 📝 `server/controllers/LibraryItemController.js` (+8 -0) 📝 `server/models/ApiKey.js` (+2 -0) 📝 `server/models/User.js` (+5 -0) ➕ `test/server/controllers/LibraryItemController.canStream.test.js` (+101 -0) ➕ `test/server/models/User.canStream.test.js` (+78 -0) </details> ### 📄 Description ## Summary Adds a per-user **"Can Stream"** permission that mirrors the existing "Can Download" pattern. Server admins can disable streaming for specific users, encouraging local downloads instead of sustained server-side playback. Addresses #2572. ## Changes **Backend:** - `server/models/User.js`: `stream` permission added to `permissionMapping`, `getDefaultPermissionsForUserType()`, and new `canStream` getter - `server/models/ApiKey.js`: `stream` permission added to API key defaults - `server/controllers/LibraryItemController.js`: `canStream` check added to `startPlaybackSession()` and `startEpisodePlaybackSession()`, returning 403 if denied **Frontend:** - `client/store/user.js`: `getUserCanStream` getter - `client/components/modals/AccountModal.vue`: "Can Stream" toggle in admin user management, defaults to `true` for all user types - `client/pages/item/_id/index.vue`: Play button hidden when `canStream: false`; Download button shown instead when `canDownload: true`; "Contact your server admin for access" message when neither is allowed - `client/components/cards/LazyBookCard.vue`: Play overlay hidden when `canStream: false` - `client/strings/en-us.json`: `LabelPermissionsStream` and `MessageNoStreamOrDownloadAccess` strings (English only; other locales fall back) **Tests:** - 11 new Mocha tests: User model getter (permission true/false/missing/inactive, defaults, mapping) and controller enforcement (403 on both playback endpoints) - 1 new Cypress test: play button hidden on book card when `canStream: false` ## Design decisions **Enforcement at session creation, not stream segments.** Stream segment routes (`/session/:id/track/:index`, `/hls/:stream/:file`) are intentionally unauthenticated (session cookies for performance). Gating at `startPlaybackSession` / `startEpisodePlaybackSession` is the clean chokepoint: if you can't create a session, you can't stream. **`!== false` getter instead of `!!`.** Every other permission getter uses `!!`, but those permissions existed at launch. `canStream` is new, so existing users won't have `stream` in their permissions JSON. Using `!!` would silently break streaming for all existing users on upgrade. `!== false` means `undefined` (existing users) and `true` both resolve to "allowed"; only an explicit `false` blocks streaming. The client-side getter in `store/user.js` uses the same pattern for consistency. **Default `stream: true` for all user types.** This is a non-breaking, opt-in restriction. Admins choose to disable streaming for specific users. Differs from `canDownload` which defaults `false` for guests, but streaming is the current default behavior for all users, so changing it would be a regression. **Download button promoted to primary action.** When a user can't stream but can download, the item detail page shows a Download button where Play would normally be. This is a new UI pattern (download is normally in the context menu), but it makes the primary action discoverable when streaming is disabled. ## Open questions for maintainers - **"Contact your server admin for access" wording** (shown when both streaming and download are disabled): open to suggestions on copy or whether to show a message at all - **Mobile app impact**: The ABS mobile apps (`advplyr/audiobookshelf-app`) will receive a 403 on stream attempts for restricted users, but the app UI won't adapt gracefully without a companion PR there. The apps already handle 403 for download permission, so it won't crash, but there's no "download instead" prompt. Happy to open a follow-up issue/PR for the app repo - **Share link streaming** is unaffected by this permission since share links bypass user auth entirely. This is intentional and consistent with how share links work today ## How I tested this - All 346 Mocha tests pass (11 new), including migration safety (missing permission key defaults to allowed) - All 106 Cypress component tests pass (1 new), including play button visibility with `canStream: false` - Manual code review of all `canDownload` references in the client to confirm no missed enforcement points ## Screenshots N/A -- toggle follows the exact same UI pattern as existing permission toggles in the account modal. Happy to add screenshots if needed. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
adam added the pull-request label 2026-04-25 00:50:47 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#4452