diff --git a/client/src/components/files.vue b/client/src/components/files.vue
index e8c3005e..b5f263f9 100644
--- a/client/src/components/files.vue
+++ b/client/src/components/files.vue
@@ -9,7 +9,7 @@
{{ item.name }}
{{ fileSize(item.size) }}
-
+
@@ -73,6 +73,7 @@
{
/////////////////////////////
// File Transfer Events
/////////////////////////////
- protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) {
+ protected [EVENT.FILETRANSFER.LIST]({ cwd, user_download, user_upload, files }: FileTransferListPayload) {
this.$accessor.files.setCwd(cwd)
this.$accessor.files.setFileList(files)
+ this.$accessor.files.setUserDownload(user_download)
+ this.$accessor.files.setUserUpload(user_upload)
}
/////////////////////////////
diff --git a/client/src/neko/messages.ts b/client/src/neko/messages.ts
index ac12ef23..1c3546d4 100644
--- a/client/src/neko/messages.ts
+++ b/client/src/neko/messages.ts
@@ -204,6 +204,8 @@ export interface FileTransferListMessage extends WebSocketMessage, FileTransferL
export interface FileTransferListPayload {
cwd: string
+ user_download: boolean
+ user_upload: boolean
files: FileListItem[]
}
diff --git a/client/src/store/files.ts b/client/src/store/files.ts
index 1a4ca842..c45a4ac4 100644
--- a/client/src/store/files.ts
+++ b/client/src/store/files.ts
@@ -7,6 +7,8 @@ export const state = () => ({
cwd: '',
files: [] as FileListItem[],
transfers: [] as FileTransfer[],
+ userDownload: false,
+ userUpload: false,
})
export const getters = getterTree(state, {
@@ -22,6 +24,14 @@ export const mutations = mutationTree(state, {
state.files = files
},
+ _setUserDownload(state, val: boolean) {
+ state.userDownload = val
+ },
+
+ _setUserUpload(state, val: boolean) {
+ state.userUpload = val
+ },
+
_addTransfer(state, transfer: FileTransfer) {
state.transfers = [...state.transfers, transfer]
},
@@ -42,6 +52,14 @@ export const actions = actionTree(
accessor.files._setFileList(files)
},
+ setUserDownload(store, val: boolean) {
+ accessor.files._setUserDownload(val)
+ },
+
+ setUserUpload(store, val: boolean) {
+ accessor.files._setUserUpload(val)
+ },
+
addTransfer(store, transfer: FileTransfer) {
if (transfer.status !== 'pending') {
return
diff --git a/server/internal/http/legacy/message/messages.go b/server/internal/http/legacy/message/messages.go
index 89cf4e4c..17d527bd 100644
--- a/server/internal/http/legacy/message/messages.go
+++ b/server/internal/http/legacy/message/messages.go
@@ -109,9 +109,11 @@ type EmoteSend struct {
}
type FileTransferList struct {
- Event string `json:"event"`
- Cwd string `json:"cwd"`
- Files []types.FileListItem `json:"files"`
+ Event string `json:"event"`
+ Cwd string `json:"cwd"`
+ UserDownload bool `json:"user_download"`
+ UserUpload bool `json:"user_upload"`
+ Files []types.FileListItem `json:"files"`
}
type Admin struct {
diff --git a/server/internal/http/legacy/wstoclient.go b/server/internal/http/legacy/wstoclient.go
index 8304acfe..d5609d6c 100644
--- a/server/internal/http/legacy/wstoclient.go
+++ b/server/internal/http/legacy/wstoclient.go
@@ -607,9 +607,11 @@ func (s *session) wsToClient(msg []byte) error {
}
return s.toClient(&oldMessage.FileTransferList{
- Event: oldEvent.FILETRANSFER_LIST,
- Cwd: request.RootDir,
- Files: files,
+ Event: oldEvent.FILETRANSFER_LIST,
+ Cwd: request.RootDir,
+ UserDownload: request.UserDownload,
+ UserUpload: request.UserUpload,
+ Files: files,
})
// Screen Events
diff --git a/server/internal/plugins/filetransfer/config.go b/server/internal/plugins/filetransfer/config.go
index 04ed8109..b84f8b51 100644
--- a/server/internal/plugins/filetransfer/config.go
+++ b/server/internal/plugins/filetransfer/config.go
@@ -12,6 +12,8 @@ type Config struct {
Enabled bool
RootDir string
RefreshInterval time.Duration
+ UserDownload bool
+ UserUpload bool
}
func (Config) Init(cmd *cobra.Command) error {
@@ -30,6 +32,16 @@ func (Config) Init(cmd *cobra.Command) error {
return err
}
+ cmd.PersistentFlags().Bool("filetransfer.user_download", false, "allow non-admin users to download files")
+ if err := viper.BindPFlag("filetransfer.user_download", cmd.PersistentFlags().Lookup("filetransfer.user_download")); err != nil {
+ return err
+ }
+
+ cmd.PersistentFlags().Bool("filetransfer.user_upload", false, "allow non-admin users to upload files")
+ if err := viper.BindPFlag("filetransfer.user_upload", cmd.PersistentFlags().Lookup("filetransfer.user_upload")); err != nil {
+ return err
+ }
+
// v2 config
cmd.PersistentFlags().Bool("file_transfer_enabled", false, "enable file transfer feature")
@@ -50,6 +62,8 @@ func (s *Config) Set() {
rootDir := viper.GetString("filetransfer.dir")
s.RootDir = filepath.Clean(rootDir)
s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval")
+ s.UserDownload = viper.GetBool("filetransfer.user_download")
+ s.UserUpload = viper.GetBool("filetransfer.user_upload")
// v2 config
diff --git a/server/internal/plugins/filetransfer/manager.go b/server/internal/plugins/filetransfer/manager.go
index e805b35a..28e2919a 100644
--- a/server/internal/plugins/filetransfer/manager.go
+++ b/server/internal/plugins/filetransfer/manager.go
@@ -103,9 +103,11 @@ func (m *Manager) broadcastUpdate() {
m.mu.RUnlock()
m.sessions.Broadcast(FILETRANSFER_UPDATE, Message{
- Enabled: m.config.Enabled,
- RootDir: m.config.RootDir,
- Files: fileList,
+ Enabled: m.config.Enabled,
+ RootDir: m.config.RootDir,
+ UserDownload: m.config.UserDownload,
+ UserUpload: m.config.UserUpload,
+ Files: fileList,
})
}
@@ -115,9 +117,11 @@ func (m *Manager) sendUpdate(session types.Session) {
m.mu.RUnlock()
session.Send(FILETRANSFER_UPDATE, Message{
- Enabled: m.config.Enabled,
- RootDir: m.config.RootDir,
- Files: fileList,
+ Enabled: m.config.Enabled,
+ RootDir: m.config.RootDir,
+ UserDownload: m.config.UserDownload,
+ UserUpload: m.config.UserUpload,
+ Files: fileList,
})
}
@@ -208,8 +212,8 @@ func (m *Manager) Shutdown() error {
}
func (m *Manager) Route(r types.Router) {
- r.With(auth.AdminsOnly).Get("/", m.downloadFileHandler)
- r.With(auth.AdminsOnly).Post("/", m.uploadFileHandler)
+ r.Get("/", m.downloadFileHandler)
+ r.Post("/", m.uploadFileHandler)
}
func (m *Manager) WebSocketHandler(session types.Session, msg types.WebSocketMessage) bool {
@@ -251,6 +255,10 @@ func (m *Manager) downloadFileHandler(w http.ResponseWriter, r *http.Request) er
return utils.HttpForbidden("file transfer is disabled")
}
+ if !session.Profile().IsAdmin && !m.config.UserDownload {
+ return utils.HttpForbidden("file download is not allowed for non-admin users")
+ }
+
filename := r.URL.Query().Get("filename")
badChars, err := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
if filename == "" || badChars || err != nil {
@@ -285,6 +293,10 @@ func (m *Manager) uploadFileHandler(w http.ResponseWriter, r *http.Request) erro
return utils.HttpForbidden("file transfer is disabled")
}
+ if !session.Profile().IsAdmin && !m.config.UserUpload {
+ return utils.HttpForbidden("file upload is not allowed for non-admin users")
+ }
+
err = r.ParseMultipartForm(multipartFormMaxMemory)
if err != nil || r.MultipartForm == nil {
return utils.HttpBadRequest().
diff --git a/server/internal/plugins/filetransfer/types.go b/server/internal/plugins/filetransfer/types.go
index 32748d46..dcb6ec50 100644
--- a/server/internal/plugins/filetransfer/types.go
+++ b/server/internal/plugins/filetransfer/types.go
@@ -11,9 +11,11 @@ const (
)
type Message struct {
- Enabled bool `json:"enabled"`
- RootDir string `json:"root_dir"`
- Files []Item `json:"files"`
+ Enabled bool `json:"enabled"`
+ RootDir string `json:"root_dir"`
+ UserDownload bool `json:"user_download"`
+ UserUpload bool `json:"user_upload"`
+ Files []Item `json:"files"`
}
type ItemType string
diff --git a/webpage/docs/configuration/plugins.md b/webpage/docs/configuration/plugins.md
index 5f6d5e38..b511f21b 100644
--- a/webpage/docs/configuration/plugins.md
+++ b/webpage/docs/configuration/plugins.md
@@ -59,11 +59,15 @@ The file transfer plugin is a simple pre-loaded internal plugin that allows you
type: 'duration',
defaultValue: '30s',
},
+ 'filetransfer.user_download': false,
+ 'filetransfer.user_upload': false,
}} />
- enables the file transfer support. If set to `false`, the file transfer is disabled.
- refers to the directory where the files are stored.
- refers to the interval at which the file list is refreshed.
+- allows non-admin users to download files. If set to `false` (default), only admins can download files.
+- allows non-admin users to upload files. If set to `false` (default), only admins can upload files.
The file transfer plugin extends user profile and room settings by adding the following fields: