fix: add configurable minimum password length
parent
089255997a
commit
2f3517e627
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
|
@ -29,15 +28,13 @@ 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)
|
||||
pwd, err := users.RandomPwd(setting.MinimumPasswordLength + 10)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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", 12, "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:")
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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":
|
||||
|
|
13
cmd/root.go
13
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
interface ISettings {
|
||||
signup: boolean;
|
||||
createUserDir: boolean;
|
||||
minimumPasswordLength: number;
|
||||
userHomeBasePath: string;
|
||||
defaults: SettingsDefaults;
|
||||
rules: any[];
|
||||
|
|
|
@ -18,14 +18,26 @@
|
|||
{{ t("settings.createUserDir") }}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p class="small">{{ t("settings.userHomeBasePath") }}</p>
|
||||
<p>
|
||||
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
||||
<input
|
||||
class="input input--block"
|
||||
type="text"
|
||||
v-model="settings.userHomeBasePath"
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="minimumPasswordLength">{{
|
||||
t("settings.minimumPasswordLength")
|
||||
}}</label>
|
||||
<vue-number-input
|
||||
controls
|
||||
v-model.number="settings.minimumPasswordLength"
|
||||
id="minimumPasswordLength"
|
||||
:min="1"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<h3>{{ t("settings.rules") }}</h3>
|
||||
<p class="small">{{ t("settings.globalRules") }}</p>
|
||||
|
@ -229,17 +241,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLayoutStore } from "@/stores/layout";
|
||||
import { settings as api } from "@/api";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import UserForm from "@/components/settings/UserForm.vue";
|
||||
import { StatusError } from "@/api/utils";
|
||||
import Rules from "@/components/settings/Rules.vue";
|
||||
import Themes from "@/components/settings/Themes.vue";
|
||||
import UserForm from "@/components/settings/UserForm.vue";
|
||||
import { useLayoutStore } from "@/stores/layout";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import { getTheme, setTheme } from "@/utils/theme";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import { computed, inject, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { StatusError } from "@/api/utils";
|
||||
import { getTheme, setTheme } from "@/utils/theme";
|
||||
|
||||
const error = ref<StatusError | null>(null);
|
||||
const originalSettings = ref<ISettings | null>(null);
|
||||
|
|
|
@ -151,7 +151,7 @@ var signupHandler = func(_ http.ResponseWriter, r *http.Request, d *data) (int,
|
|||
|
||||
d.settings.Defaults.Apply(user)
|
||||
|
||||
pwd, err := users.HashPwd(info.Password)
|
||||
pwd, err := users.HashAndValidatePwd(info.Password, d.settings.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
|
@ -73,6 +73,9 @@ func handle(fn handleFunc, prefix string, store *storage.Storage, server *settin
|
|||
|
||||
if status != 0 {
|
||||
txt := http.StatusText(status)
|
||||
if status == http.StatusBadRequest && err != nil {
|
||||
txt += " (" + err.Error() + ")"
|
||||
}
|
||||
http.Error(w, strconv.Itoa(status)+" "+txt, status)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,28 +9,30 @@ import (
|
|||
)
|
||||
|
||||
type settingsData struct {
|
||||
Signup bool `json:"signup"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults settings.UserDefaults `json:"defaults"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
Branding settings.Branding `json:"branding"`
|
||||
Tus settings.Tus `json:"tus"`
|
||||
Shell []string `json:"shell"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Signup bool `json:"signup"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults settings.UserDefaults `json:"defaults"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
Branding settings.Branding `json:"branding"`
|
||||
Tus settings.Tus `json:"tus"`
|
||||
Shell []string `json:"shell"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
}
|
||||
|
||||
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
data := &settingsData{
|
||||
Signup: d.settings.Signup,
|
||||
CreateUserDir: d.settings.CreateUserDir,
|
||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||
Defaults: d.settings.Defaults,
|
||||
Rules: d.settings.Rules,
|
||||
Branding: d.settings.Branding,
|
||||
Tus: d.settings.Tus,
|
||||
Shell: d.settings.Shell,
|
||||
Commands: d.settings.Commands,
|
||||
Signup: d.settings.Signup,
|
||||
CreateUserDir: d.settings.CreateUserDir,
|
||||
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||
Defaults: d.settings.Defaults,
|
||||
Rules: d.settings.Rules,
|
||||
Branding: d.settings.Branding,
|
||||
Tus: d.settings.Tus,
|
||||
Shell: d.settings.Shell,
|
||||
Commands: d.settings.Commands,
|
||||
}
|
||||
|
||||
return renderJSON(w, r, data)
|
||||
|
@ -45,6 +47,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
|
|||
|
||||
d.settings.Signup = req.Signup
|
||||
d.settings.CreateUserDir = req.CreateUserDir
|
||||
d.settings.MinimumPasswordLength = req.MinimumPasswordLength
|
||||
d.settings.UserHomeBasePath = req.UserHomeBasePath
|
||||
d.settings.Defaults = req.Defaults
|
||||
d.settings.Rules = req.Rules
|
||||
|
|
|
@ -125,7 +125,11 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *
|
|||
return http.StatusBadRequest, fbErrors.ErrEmptyPassword
|
||||
}
|
||||
|
||||
req.Data.Password, err = users.HashPwd(req.Data.Password)
|
||||
if len(req.Data.Password) < int(d.settings.MinimumPasswordLength) {
|
||||
return http.StatusBadRequest, fbErrors.ErrShortPassword
|
||||
}
|
||||
|
||||
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -163,7 +167,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
|||
}
|
||||
|
||||
if req.Data.Password != "" {
|
||||
req.Data.Password, err = users.HashPwd(req.Data.Password)
|
||||
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength)
|
||||
} else {
|
||||
var suser *users.User
|
||||
suser, err = d.store.Users.Get(d.server.Root, d.raw.(uint))
|
||||
|
@ -186,7 +190,11 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
|||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
req.Data.Password, err = users.HashPwd(req.Data.Password)
|
||||
if len(req.Data.Password) < int(d.settings.MinimumPasswordLength) {
|
||||
return http.StatusBadRequest, fbErrors.ErrShortPassword
|
||||
}
|
||||
|
||||
req.Data.Password, err = users.HashAndValidatePwd(req.Data.Password, d.settings.MinimumPasswordLength)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
|
@ -10,23 +10,25 @@ import (
|
|||
)
|
||||
|
||||
const DefaultUsersHomeBasePath = "/users"
|
||||
const DefaultMinimumPasswordLength = 12
|
||||
|
||||
// AuthMethod describes an authentication method.
|
||||
type AuthMethod string
|
||||
|
||||
// Settings contain the main settings of the application.
|
||||
type Settings struct {
|
||||
Key []byte `json:"key"`
|
||||
Signup bool `json:"signup"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
Branding Branding `json:"branding"`
|
||||
Tus Tus `json:"tus"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Shell []string `json:"shell"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
Key []byte `json:"key"`
|
||||
Signup bool `json:"signup"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
Branding Branding `json:"branding"`
|
||||
Tus Tus `json:"tus"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Shell []string `json:"shell"`
|
||||
Rules []rules.Rule `json:"rules"`
|
||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||
}
|
||||
|
||||
// GetRules implements rules.Provider.
|
||||
|
|
|
@ -33,6 +33,9 @@ func (s *Storage) Get() (*Settings, error) {
|
|||
if set.UserHomeBasePath == "" {
|
||||
set.UserHomeBasePath = DefaultUsersHomeBasePath
|
||||
}
|
||||
if set.MinimumPasswordLength == 0 {
|
||||
set.MinimumPasswordLength = DefaultMinimumPasswordLength
|
||||
}
|
||||
if set.Tus == (Tus{}) {
|
||||
set.Tus = Tus{
|
||||
ChunkSize: DefaultTusChunkSize,
|
||||
|
|
|
@ -4,11 +4,18 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// randomPasswordBytesCount is chosen to fit in a base64 string without padding
|
||||
const randomPasswordBytesCount = 9
|
||||
// HashPwd hashes a password.
|
||||
func HashAndValidatePwd(password string, minimumLength uint) (string, error) {
|
||||
if len(password) < int(minimumLength) {
|
||||
return "", errors.ErrShortPassword
|
||||
}
|
||||
|
||||
return HashPwd(password)
|
||||
}
|
||||
|
||||
// HashPwd hashes a password.
|
||||
func HashPwd(password string) (string, error) {
|
||||
|
@ -22,8 +29,8 @@ func CheckPwd(password, hash string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func RandomPwd() (string, error) {
|
||||
randomPasswordBytes := make([]byte, randomPasswordBytesCount)
|
||||
func RandomPwd(passwordLength uint) (string, error) {
|
||||
randomPasswordBytes := make([]byte, passwordLength)
|
||||
var _, err = rand.Read(randomPasswordBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
Loading…
Reference in New Issue