mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-16 08:21:29 +00:00
Add canStream user permission to control streaming access
Adds a per-user "Can Stream" permission mirroring the existing "Can Download" pattern. Server admins can now disable streaming for specific users, encouraging local downloads instead. Addresses #2572. Changes: - User model: stream permission in mapping, defaults, and getter - ApiKey model: stream permission in defaults - Controller: 403 enforcement on playback session creation endpoints - Frontend: permission toggle in admin UI, play button gated by canStream, download button shown when streaming disabled, message when neither allowed - Tests: 11 Mocha tests (model + controller), 1 Cypress test (card UI) - Localization: English strings for toggle label and fallback message The getter uses !== false (rather than !!) so existing users without the stream key in their permissions JSON default to allowed on upgrade. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
64cbf59609
commit
464b720d9e
11 changed files with 236 additions and 1 deletions
|
|
@ -436,7 +436,7 @@ export default {
|
|||
return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat
|
||||
},
|
||||
showPlayButton() {
|
||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
|
||||
return this.userCanStream && !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
|
||||
},
|
||||
showSmallEBookIcon() {
|
||||
return !this.isSelectionMode && this.ebookFormat
|
||||
|
|
@ -476,6 +476,9 @@ export default {
|
|||
userCanDownload() {
|
||||
return this.store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanStream() {
|
||||
return this.store.getters['user/getUserCanStream']
|
||||
},
|
||||
userIsAdminOrUp() {
|
||||
return this.store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-2 max-w-md">
|
||||
<div class="w-1/2">
|
||||
<p id="stream-permissions-toggle">{{ $strings.LabelPermissionsStream }}</p>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<ui-toggle-switch labeledBy="stream-permissions-toggle" v-model="newUser.permissions.stream" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-2 max-w-md">
|
||||
<div class="w-1/2">
|
||||
<p id="update-permissions-toggle">{{ $strings.LabelPermissionsUpdate }}</p>
|
||||
|
|
@ -354,6 +363,7 @@ export default {
|
|||
userTypeUpdated(type) {
|
||||
this.newUser.permissions = {
|
||||
download: type !== 'guest',
|
||||
stream: true,
|
||||
update: type === 'admin',
|
||||
delete: type === 'admin',
|
||||
upload: type === 'admin',
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ function createMountOptions() {
|
|||
'user/getUserCanUpdate': true,
|
||||
'user/getUserCanDelete': true,
|
||||
'user/getUserCanDownload': true,
|
||||
'user/getUserCanStream': true,
|
||||
'user/getIsAdminOrUp': true,
|
||||
'user/getUserMediaProgress': (id) => null,
|
||||
'user/getUserSetting': (settingName) => false,
|
||||
|
|
@ -163,6 +164,15 @@ describe('LazyBookCard', () => {
|
|||
cy.get('&ebookFormat').should('not.exist')
|
||||
})
|
||||
|
||||
it('hides play button on mouseover when user cannot stream', () => {
|
||||
mountOptions.mocks.$store.getters['user/getUserCanStream'] = false
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.get('#book-card-0').trigger('mouseover')
|
||||
|
||||
cy.get('&overlay').should('be.visible')
|
||||
cy.get('&playButton').should('be.hidden')
|
||||
})
|
||||
|
||||
it('routes to item page when clicked', () => {
|
||||
mountOptions.mocks.$router = { push: cy.stub().as('routerPush') }
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,15 @@
|
|||
{{ isStreaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-btn v-else-if="!userCanStream && userCanDownload && tracks.length" color="bg" :padding-x="4" small class="flex items-center h-9 mr-2" @click="downloadLibraryItem">
|
||||
<span class="material-symbols text-2xl -ml-2 pr-1 text-white">download</span>
|
||||
{{ $strings.LabelDownload }}
|
||||
</ui-btn>
|
||||
|
||||
<p v-else-if="!userCanStream && !userCanDownload && tracks.length" class="text-sm text-gray-400 mr-2">
|
||||
{{ $strings.MessageNoStreamOrDownloadAccess }}
|
||||
</p>
|
||||
|
||||
<ui-btn v-else-if="isMissing || isInvalid" color="bg-error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
||||
<span class="material-symbols text-2xl -ml-2 pr-1 text-white">error</span>
|
||||
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
||||
|
|
@ -229,6 +238,7 @@ export default {
|
|||
return !!this.mediaMetadata.abridged
|
||||
},
|
||||
showPlayButton() {
|
||||
if (!this.userCanStream) return false
|
||||
if (this.isMissing || this.isInvalid) return false
|
||||
if (this.isPodcast) return this.podcastEpisodes.length
|
||||
return this.tracks.length
|
||||
|
|
@ -352,6 +362,9 @@ export default {
|
|||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanStream() {
|
||||
return this.$store.getters['user/getUserCanStream']
|
||||
},
|
||||
showRssFeedBtn() {
|
||||
if (!this.rssFeed && !this.podcastEpisodes.length && !this.tracks.length) return false // Cannot open RSS feed with no episodes/tracks
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ export const getters = {
|
|||
getUserCanDownload: (state) => {
|
||||
return !!state.user?.permissions?.download
|
||||
},
|
||||
getUserCanStream: (state) => {
|
||||
return state.user?.permissions?.stream !== false
|
||||
},
|
||||
getUserCanUpload: (state) => {
|
||||
return !!state.user?.permissions?.upload
|
||||
},
|
||||
|
|
|
|||
|
|
@ -512,6 +512,7 @@
|
|||
"LabelPermissionsCreateEreader": "Can Create Ereader",
|
||||
"LabelPermissionsDelete": "Can Delete",
|
||||
"LabelPermissionsDownload": "Can Download",
|
||||
"LabelPermissionsStream": "Can Stream",
|
||||
"LabelPermissionsUpdate": "Can Update",
|
||||
"LabelPermissionsUpload": "Can Upload",
|
||||
"LabelPersonalYearReview": "Your Year in Review ({0})",
|
||||
|
|
@ -851,6 +852,7 @@
|
|||
"MessageNoFoldersAvailable": "No Folders Available",
|
||||
"MessageNoGenres": "No Genres",
|
||||
"MessageNoIssues": "No Issues",
|
||||
"MessageNoStreamOrDownloadAccess": "Contact your server admin for access",
|
||||
"MessageNoItems": "No Items",
|
||||
"MessageNoItemsFound": "No items found",
|
||||
"MessageNoListeningSessions": "No Listening Sessions",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue