diff --git a/conf/configuration.go b/conf/configuration.go
index 44bb10d7f..e2a467e7a 100644
--- a/conf/configuration.go
+++ b/conf/configuration.go
@@ -26,6 +26,7 @@ type configOptions struct {
BaseURL string
UILoginBackgroundURL string
EnableTranscodingConfig bool
+ EnableDownloads bool
TranscodingCacheSize string
ImageCacheSize string
AutoImportPlaylists bool
@@ -113,6 +114,7 @@ func init() {
viper.SetDefault("transcodingcachesize", "100MB")
viper.SetDefault("imagecachesize", "100MB")
viper.SetDefault("autoimportplaylists", true)
+ viper.SetDefault("enabledownloads", true)
// Config options only valid for file/env configuration
viper.SetDefault("searchfullstring", false)
diff --git a/server/app/serve_index.go b/server/app/serve_index.go
index 1cce76bfd..a143a5b2b 100644
--- a/server/app/serve_index.go
+++ b/server/app/serve_index.go
@@ -34,6 +34,7 @@ func serveIndex(ds model.DataStore, fs http.FileSystem) http.HandlerFunc {
"welcomeMessage": policy.Sanitize(conf.Server.UIWelcomeMessage),
"enableTranscodingConfig": conf.Server.EnableTranscodingConfig,
"gaTrackingId": conf.Server.GATrackingID,
+ "enableDownloads": conf.Server.EnableDownloads,
"devActivityMenu": conf.Server.DevActivityMenu,
}
j, err := json.Marshal(appConfig)
diff --git a/server/app/serve_index_test.go b/server/app/serve_index_test.go
index bf0621c44..8c9b0d3ee 100644
--- a/server/app/serve_index_test.go
+++ b/server/app/serve_index_test.go
@@ -103,6 +103,17 @@ var _ = Describe("serveIndex", func() {
Expect(config).To(HaveKeyWithValue("enableTranscodingConfig", true))
})
+ It("sets the enableDownloads", func() {
+ conf.Server.EnableDownloads = true
+ r := httptest.NewRequest("GET", "/index.html", nil)
+ w := httptest.NewRecorder()
+
+ serveIndex(ds, fs)(w, r)
+
+ config := extractAppConfig(w.Body.String())
+ Expect(config).To(HaveKeyWithValue("enableDownloads", true))
+ })
+
It("sets the gaTrackingId", func() {
conf.Server.GATrackingID = "UA-12345"
r := httptest.NewRequest("GET", "/index.html", nil)
diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go
index d6131ca33..33bf12866 100644
--- a/server/subsonic/stream.go
+++ b/server/subsonic/stream.go
@@ -8,9 +8,11 @@ import (
"strconv"
"strings"
+ "github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
+ "github.com/deluan/navidrome/model/request"
"github.com/deluan/navidrome/server/subsonic/responses"
"github.com/deluan/navidrome/utils"
)
@@ -80,11 +82,17 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
+ username, _ := request.UsernameFrom(ctx)
id, err := requiredParamString(r, "id")
if err != nil {
return nil, err
}
+ if !conf.Server.EnableDownloads {
+ log.Warn(ctx, "Downloads are disabled", "user", username, "id", id)
+ return nil, newError(responses.ErrorAuthorizationFail, "downloads are disabled")
+ }
+
entity, err := core.GetEntityByID(ctx, c.ds, id)
if err != nil {
return nil, err
diff --git a/ui/src/album/AlbumActions.js b/ui/src/album/AlbumActions.js
index 52f482008..b13284758 100644
--- a/ui/src/album/AlbumActions.js
+++ b/ui/src/album/AlbumActions.js
@@ -15,6 +15,7 @@ import { playNext, addTracks, playTracks, shuffleTracks } from '../actions'
import subsonic from '../subsonic'
import { formatBytes } from '../common/SizeField'
import { useMediaQuery } from '@material-ui/core'
+import config from '../config'
const AlbumActions = ({ className, ids, data, record, ...rest }) => {
const dispatch = useDispatch()
@@ -67,15 +68,17 @@ const AlbumActions = ({ className, ids, data, record, ...rest }) => {
>
-
+ {config.enableDownloads && (
+
+ )}
)
}
diff --git a/ui/src/common/ContextMenus.js b/ui/src/common/ContextMenus.js
index 3082ab71b..88f8d36e7 100644
--- a/ui/src/common/ContextMenus.js
+++ b/ui/src/common/ContextMenus.js
@@ -17,6 +17,7 @@ import {
import subsonic from '../subsonic'
import StarButton from './StarButton'
import { formatBytes } from './SizeField'
+import config from '../config'
const useStyles = makeStyles({
noWrap: {
@@ -45,31 +46,37 @@ const ContextMenu = ({
const options = {
play: {
+ enabled: true,
needData: true,
label: translate('resources.album.actions.playAll'),
action: (data, ids) => dispatch(playTracks(data, ids)),
},
playNext: {
+ enabled: true,
needData: true,
label: translate('resources.album.actions.playNext'),
action: (data, ids) => dispatch(playNext(data, ids)),
},
addToQueue: {
+ enabled: true,
needData: true,
label: translate('resources.album.actions.addToQueue'),
action: (data, ids) => dispatch(addTracks(data, ids)),
},
shuffle: {
+ enabled: true,
needData: true,
label: translate('resources.album.actions.shuffle'),
action: (data, ids) => dispatch(shuffleTracks(data, ids)),
},
addToPlaylist: {
+ enabled: true,
needData: true,
label: translate('resources.album.actions.addToPlaylist'),
action: (data, ids) => dispatch(openAddToPlaylist({ selectedIds: ids })),
},
download: {
+ enabled: config.enableDownloads,
needData: false,
label: `${translate('resources.album.actions.download')} (${formatBytes(
record.size
@@ -146,11 +153,14 @@ const ContextMenu = ({
open={open}
onClose={handleOnClose}
>
- {Object.keys(options).map((key) => (
-
- ))}
+ {Object.keys(options).map(
+ (key) =>
+ options[key].enabled && (
+
+ )
+ )}
)
diff --git a/ui/src/common/SongContextMenu.js b/ui/src/common/SongContextMenu.js
index 133ef4e50..a8e603162 100644
--- a/ui/src/common/SongContextMenu.js
+++ b/ui/src/common/SongContextMenu.js
@@ -9,6 +9,7 @@ import { playNext, addTracks, setTrack, openAddToPlaylist } from '../actions'
import subsonic from '../subsonic'
import StarButton from './StarButton'
import { formatBytes } from './SizeField'
+import config from '../config'
const useStyles = makeStyles({
noWrap: {
@@ -32,18 +33,22 @@ const SongContextMenu = ({
const [anchorEl, setAnchorEl] = useState(null)
const options = {
playNow: {
+ enabled: true,
label: translate('resources.song.actions.playNow'),
action: (record) => dispatch(setTrack(record)),
},
playNext: {
+ enabled: true,
label: translate('resources.song.actions.playNext'),
action: (record) => dispatch(playNext({ [record.id]: record })),
},
addToQueue: {
+ enabled: true,
label: translate('resources.song.actions.addToQueue'),
action: (record) => dispatch(addTracks({ [record.id]: record })),
},
addToPlaylist: {
+ enabled: true,
label: translate('resources.song.actions.addToPlaylist'),
action: (record) =>
dispatch(
@@ -54,6 +59,7 @@ const SongContextMenu = ({
),
},
download: {
+ enabled: config.enableDownloads,
label: `${translate('resources.song.actions.download')} (${formatBytes(
record.size
)})`,
@@ -95,11 +101,14 @@ const SongContextMenu = ({
open={open}
onClose={handleClose}
>
- {Object.keys(options).map((key) => (
-
- ))}
+ {Object.keys(options).map(
+ (key) =>
+ options[key].enabled && (
+
+ )
+ )}
)
diff --git a/ui/src/config.js b/ui/src/config.js
index 8277cd3ce..fcb4de3c2 100644
--- a/ui/src/config.js
+++ b/ui/src/config.js
@@ -7,6 +7,7 @@ const defaultConfig = {
baseURL: '',
loginBackgroundURL: 'https://source.unsplash.com/random/1600x900?music',
enableTranscodingConfig: true,
+ enableDownloads: true,
welcomeMessage: '',
gaTrackingId: '',
devActivityMenu: true,
diff --git a/ui/src/playlist/PlaylistActions.js b/ui/src/playlist/PlaylistActions.js
index e8dc3d893..0586514e9 100644
--- a/ui/src/playlist/PlaylistActions.js
+++ b/ui/src/playlist/PlaylistActions.js
@@ -18,6 +18,7 @@ import subsonic from '../subsonic'
import PropTypes from 'prop-types'
import { formatBytes } from '../common/SizeField'
import { useMediaQuery } from '@material-ui/core'
+import config from '../config'
const PlaylistActions = ({ className, ids, data, record, ...rest }) => {
const dispatch = useDispatch()
@@ -88,15 +89,17 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => {
>
-
+ {config.enableDownloads && (
+
+ )}