From 464b644adf22a2178414a6f1e4fa286276de81d2 Mon Sep 17 00:00:00 2001
From: Henrique Dias
Date: Sat, 28 Jun 2025 10:07:34 +0200
Subject: [PATCH] fix: add configurable minimum password length (#5225)
---
README.md | 4 ---
auth/hook.go | 4 +--
auth/proxy.go | 8 ++----
cmd/config.go | 2 ++
cmd/config_init.go | 13 +++++----
cmd/config_set.go | 2 ++
cmd/root.go | 13 +++++----
cmd/users_add.go | 2 +-
cmd/users_update.go | 6 ++--
docs/configuration.md | 13 +++++----
errors/errors.go | 1 +
frontend/src/i18n/en.json | 1 +
frontend/src/types/settings.d.ts | 1 +
frontend/src/views/settings/Global.vue | 28 ++++++++++++------
http/auth.go | 2 +-
http/data.go | 3 ++
http/settings.go | 39 ++++++++++++++------------
http/users.go | 14 +++++++--
settings/settings.go | 24 ++++++++--------
settings/storage.go | 3 ++
users/password.go | 16 ++++++++---
21 files changed, 122 insertions(+), 77 deletions(-)
diff --git a/README.md b/README.md
index f8331e21..ab8a120c 100644
--- a/README.md
+++ b/README.md
@@ -38,10 +38,6 @@ File Browser is a **create-your-own-cloud-kind** of software where you can insta
| :----------------------: | :----------------------: | :----------------------: |
|  |  |  |
-> [!CAUTION]
->
-> The **command execution** functionality has been disabled for all existent and new installations by default from version v2.33.8 and onwards, due to continuous and known security vulnerabilities. You should only use this feature if you are aware of all of the security risks involved. For more up to date information, consult issue [#5199](https://github.com/filebrowser/filebrowser/issues/5199).
-
## Install
For information on how to install File Browser, please check [docs/installation.md](./docs/installation.md).
diff --git a/auth/hook.go b/auth/hook.go
index c659e57b..9ccbd2fe 100644
--- a/auth/hook.go
+++ b/auth/hook.go
@@ -150,7 +150,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
}
if u == nil {
- pass, err := users.HashPwd(a.Cred.Password)
+ pass, err := users.HashAndValidatePwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
if err != nil {
return nil, err
}
@@ -186,7 +186,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
// update the password when it doesn't match the current
if p {
- pass, err := users.HashPwd(a.Cred.Password)
+ pass, err := users.HashAndValidatePwd(a.Cred.Password, a.Settings.MinimumPasswordLength)
if err != nil {
return nil, err
}
diff --git a/auth/proxy.go b/auth/proxy.go
index 0e954309..61312f1a 100644
--- a/auth/proxy.go
+++ b/auth/proxy.go
@@ -1,7 +1,6 @@
package auth
import (
- "crypto/rand"
"errors"
"net/http"
@@ -29,15 +28,14 @@ func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Sett
}
func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *settings.Server, username string) (*users.User, error) {
- const passwordSize = 32
- randomPasswordBytes := make([]byte, passwordSize)
- _, err := rand.Read(randomPasswordBytes)
+ const randomPasswordLength = settings.DefaultMinimumPasswordLength + 10
+ pwd, err := users.RandomPwd(randomPasswordLength)
if err != nil {
return nil, err
}
var hashedRandomPassword string
- hashedRandomPassword, err = users.HashPwd(string(randomPasswordBytes))
+ hashedRandomPassword, err = users.HashAndValidatePwd(pwd, setting.MinimumPasswordLength)
if err != nil {
return nil, err
}
diff --git a/cmd/config.go b/cmd/config.go
index de55c28e..3ee8dab8 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -32,6 +32,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
addUserFlags(flags)
flags.BoolP("signup", "s", false, "allow users to signup")
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
+ flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
flags.String("shell", "", "shell command to which other commands should be appended")
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
@@ -144,6 +145,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
+ fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
fmt.Fprintln(w, "\nBranding:")
diff --git a/cmd/config_init.go b/cmd/config_init.go
index 60a0f37b..d9710514 100644
--- a/cmd/config_init.go
+++ b/cmd/config_init.go
@@ -29,12 +29,13 @@ override the options.`,
authMethod, auther := getAuthentication(flags)
s := &settings.Settings{
- Key: generateKey(),
- Signup: mustGetBool(flags, "signup"),
- CreateUserDir: mustGetBool(flags, "create-user-dir"),
- Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
- AuthMethod: authMethod,
- Defaults: defaults,
+ Key: generateKey(),
+ Signup: mustGetBool(flags, "signup"),
+ CreateUserDir: mustGetBool(flags, "create-user-dir"),
+ MinimumPasswordLength: mustGetUint(flags, "minimum-password-length"),
+ Shell: convertCmdStrToCmdArray(mustGetString(flags, "shell")),
+ AuthMethod: authMethod,
+ Defaults: defaults,
Branding: settings.Branding{
Name: mustGetString(flags, "branding.name"),
DisableExternal: mustGetBool(flags, "branding.disableExternal"),
diff --git a/cmd/config_set.go b/cmd/config_set.go
index 23ff7e1b..05816795 100644
--- a/cmd/config_set.go
+++ b/cmd/config_set.go
@@ -51,6 +51,8 @@ you want to change. Other options will remain unchanged.`,
set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name))
case "create-user-dir":
set.CreateUserDir = mustGetBool(flags, flag.Name)
+ case "minimum-password-length":
+ set.MinimumPasswordLength = mustGetUint(flags, flag.Name)
case "branding.name":
set.Branding.Name = mustGetString(flags, flag.Name)
case "branding.color":
diff --git a/cmd/root.go b/cmd/root.go
index d9f37889..c2ee7c73 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -365,10 +365,11 @@ func setupLog(logMethod string) {
func quickSetup(flags *pflag.FlagSet, d pythonData) {
set := &settings.Settings{
- Key: generateKey(),
- Signup: false,
- CreateUserDir: false,
- UserHomeBasePath: settings.DefaultUsersHomeBasePath,
+ Key: generateKey(),
+ Signup: false,
+ CreateUserDir: false,
+ MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
+ UserHomeBasePath: settings.DefaultUsersHomeBasePath,
Defaults: settings.UserDefaults{
Scope: ".",
Locale: "en",
@@ -426,12 +427,12 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
if password == "" {
var pwd string
- pwd, err = users.RandomPwd()
+ pwd, err = users.RandomPwd(set.MinimumPasswordLength)
checkErr(err)
log.Println("Randomly generated password for user 'admin':", pwd)
- password, err = users.HashPwd(pwd)
+ password, err = users.HashAndValidatePwd(pwd, set.MinimumPasswordLength)
checkErr(err)
}
diff --git a/cmd/users_add.go b/cmd/users_add.go
index e7f132ed..4b344107 100644
--- a/cmd/users_add.go
+++ b/cmd/users_add.go
@@ -21,7 +21,7 @@ var usersAddCmd = &cobra.Command{
checkErr(err)
getUserDefaults(cmd.Flags(), &s.Defaults, false)
- password, err := users.HashPwd(args[1])
+ password, err := users.HashAndValidatePwd(args[1], s.MinimumPasswordLength)
checkErr(err)
user := &users.User{
diff --git a/cmd/users_update.go b/cmd/users_update.go
index 822bb6dc..aa06abee 100644
--- a/cmd/users_update.go
+++ b/cmd/users_update.go
@@ -27,8 +27,10 @@ options you want to change.`,
password := mustGetString(flags, "password")
newUsername := mustGetString(flags, "username")
+ s, err := d.store.Settings.Get()
+ checkErr(err)
+
var (
- err error
user *users.User
)
@@ -64,7 +66,7 @@ options you want to change.`,
}
if password != "" {
- user.Password, err = users.HashPwd(password)
+ user.Password, err = users.HashAndValidatePwd(password, s.MinimumPasswordLength)
checkErr(err)
}
diff --git a/docs/configuration.md b/docs/configuration.md
index e38744d4..2add31e7 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -123,20 +123,21 @@ filebrowser cmds ls
Or you can use the web interface to manage them via **Settings** → **Global Settings**.
+## Command Execution
-## Shell commands
+> [!CAUTION]
+>
+> The **command execution** functionality has been disabled for all existent and new installations by default from version v2.33.8 and onwards, due to continuous and known security vulnerabilities. You should only use this feature if you are aware of all of the security risks involved. For more up to date information, consult issue [#5199](https://github.com/filebrowser/filebrowser/issues/5199).
-Within Filebrowser you can toggle the shell (`< >` icon at the top right) and this will open a shell command window at the bottom of the screen.
+Within File Browser you can toggle the shell (`< >` icon at the top right) and this will open a shell command window at the bottom of the screen. This functionality can be turned on using the environment variable `FB_DISABLE_EXEC=false` or the flag `--disable-exec=false`.
-**By default no commands are available as the command list is empty**
-
-To enable commands these need to either be done on a per-user basis (including for the Admin user).
+By default no commands are available as the command list is empty. To enable commands these need to either be done on a per-user basis (including for the Admin user).
You can do this by adding them in Settings > User Management > (edit user) > Commands or to *apply to all new users created from that point forward* they can be set in Settings > Global Settings
> [!NOTE]
>
-> If using a proxy manager then remember to enable websockets support for the Filebrowser proxy
+> If using a proxy manager then remember to enable websockets support for the File Browser proxy
> [!NOTE]
>
diff --git a/errors/errors.go b/errors/errors.go
index 5ec364c0..7bb10e81 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -7,6 +7,7 @@ var (
ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty")
+ ErrShortPassword = errors.New("password is too short")
ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path")
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
index 1360bbec..7de128ed 100644
--- a/frontend/src/i18n/en.json
+++ b/frontend/src/i18n/en.json
@@ -170,6 +170,7 @@
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commands updated!",
"createUserDir": "Auto create user home dir while adding new user",
+ "minimumPasswordLength": "Minimum password length",
"tusUploads": "Chunked Uploads",
"tusUploadsHelp": "File Browser supports chunked file uploads, allowing for the creation of efficient, reliable, resumable and chunked file uploads even on unreliable networks.",
"tusUploadsChunkSize": "Indicates to maximum size of a request (direct uploads will be used for smaller uploads). You may input a plain integer denoting byte size input or a string like 10MB, 1GB etc.",
diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts
index a2c19f76..90ca6ae7 100644
--- a/frontend/src/types/settings.d.ts
+++ b/frontend/src/types/settings.d.ts
@@ -1,6 +1,7 @@
interface ISettings {
signup: boolean;
createUserDir: boolean;
+ minimumPasswordLength: number;
userHomeBasePath: string;
defaults: SettingsDefaults;
rules: any[];
diff --git a/frontend/src/views/settings/Global.vue b/frontend/src/views/settings/Global.vue
index 1997576e..331dd438 100644
--- a/frontend/src/views/settings/Global.vue
+++ b/frontend/src/views/settings/Global.vue
@@ -18,14 +18,26 @@
{{ t("settings.createUserDir") }}