Added env variable to set a minium password length (#312)

Co-authored-by: Marc Ole Bulling <Marc-Ole@gmx.de>
This commit is contained in:
Paul Draeger
2025-10-20 17:48:46 +02:00
committed by GitHub
parent 8c375e9031
commit a1efcdf2cf
13 changed files with 73 additions and 51 deletions
+2
View File
@@ -80,6 +80,8 @@ Available environment variables
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| GOKAPI_MAX_PARALLEL_UPLOADS | Set the amount of chunks that are uploaded in parallel for a single file | Yes | 4 |
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| GOKAPI_MIN_LENGTH_PASSWORD | Sets the minium password length - must be at least 6 characters | No | 8 |
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
+-------------------------------+-------------------------------------------------------------------------------------+-----------------+--------------------------------------+
| GOKAPI_DISABLE_CORS_CHECK | Disables the CORS check on startup and during setup, if set to "true" | No | false |
+3 -5
View File
@@ -25,9 +25,6 @@ import (
"github.com/forceu/gokapi/internal/storage/filesystem"
)
// MinLengthPassword is the required length of admin password in characters
const MinLengthPassword = 8
// Environment is an object containing the environment variables
var Environment environment.Environment
@@ -94,6 +91,7 @@ func Load() {
if serverSettings.ChunkSize == 0 {
serverSettings.ChunkSize = 45
}
serverSettings.MinLengthPassword = Environment.MinLengthPassword
serverSettings.LengthId = Environment.LengthId
serverSettings.LengthHotlinkId = Environment.LengthHotlinkId
helper.CreateDir(serverSettings.DataDir)
@@ -187,8 +185,8 @@ func deleteAllEncryptedStorage() {
// SetDeploymentPassword sets a new password. This should only be used for non-interactive deployment, but is not enforced
func SetDeploymentPassword(newPassword string) {
if len(newPassword) < MinLengthPassword {
fmt.Printf("Password needs to be at least %d characters long\n", MinLengthPassword)
if len(newPassword) < serverSettings.MinLengthPassword {
fmt.Printf("Password needs to be at least %d characters long\n", serverSettings.MinLengthPassword)
os.Exit(1)
}
serverSettings.Authentication.SaltAdmin = helper.GenerateRandomString(30)
+3 -2
View File
@@ -1,13 +1,14 @@
package configuration
import (
"os"
"testing"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/configupgrade"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
"github.com/forceu/gokapi/internal/test/testconfiguration"
"os"
"testing"
)
func TestMain(m *testing.M) {
+20 -15
View File
@@ -7,19 +7,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/configupgrade"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/configuration/database/dbabstraction"
"github.com/forceu/gokapi/internal/encryption"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage/filesystem/s3filesystem/aws"
"github.com/forceu/gokapi/internal/webserver/authentication"
"html/template"
"io"
"io/fs"
@@ -33,6 +20,20 @@ import (
"strings"
"syscall"
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/configupgrade"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/configuration/database/dbabstraction"
"github.com/forceu/gokapi/internal/encryption"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage/filesystem/s3filesystem/aws"
"github.com/forceu/gokapi/internal/webserver/authentication"
)
// webserverDir is the embedded version of the "static" folder
@@ -244,6 +245,7 @@ func toConfiguration(formObjects *[]jsonFormObject) (models.Configuration, *clou
MaxMemory: parsedEnv.MaxMemory,
DataDir: parsedEnv.DataDir,
MaxParallelUploads: parsedEnv.MaxParallelUploads,
MinLengthPassword: parsedEnv.MinLengthPassword,
ChunkSize: parsedEnv.ChunkSizeMB,
ConfigVersion: configupgrade.CurrentConfigVersion,
Authentication: models.AuthenticationConfig{},
@@ -644,8 +646,9 @@ func parseEncryptionAndDelete(result *models.Configuration, formObjects *[]jsonF
if encLevel == encryption.LocalEncryptionInput || encLevel == encryption.FullEncryptionInput {
result.Encryption.Salt = helper.GenerateRandomString(30)
result.Encryption.ChecksumSalt = helper.GenerateRandomString(30)
if len(masterPw) < configuration.MinLengthPassword {
return configuration.End2EndReconfigParameters{}, errors.New("password is less than " + strconv.Itoa(configuration.MinLengthPassword) + " characters long")
minLength := environment.New().MinLengthPassword
if len(masterPw) < minLength {
return configuration.End2EndReconfigParameters{}, errors.New("password is less than " + strconv.Itoa(minLength) + " characters long")
}
result.Encryption.Checksum = encryption.PasswordChecksum(masterPw, result.Encryption.ChecksumSalt)
}
@@ -705,6 +708,7 @@ type setupView struct {
CloudSettings cloudconfig.CloudConfig
DatabaseSettings models.DbConnection
ProtectedUrls []string
MinPasswordLength int
}
func (v *setupView) loadFromConfig() {
@@ -717,6 +721,7 @@ func (v *setupView) loadFromConfig() {
v.HasAwsFeature = aws.IsIncludedInBuild
v.ProtectedUrls = protectedUrls
if isInitialSetup {
v.MinPasswordLength = environment.New().MinLengthPassword
return
}
configuration.Load()
+8 -7
View File
@@ -6,13 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
"github.com/forceu/gokapi/internal/test/testconfiguration"
"log"
"net"
"net/http"
@@ -22,6 +15,14 @@ import (
"strings"
"testing"
"time"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
"github.com/forceu/gokapi/internal/test/testconfiguration"
)
var jsonForms []jsonFormObject
@@ -261,10 +261,10 @@
<input type="text" class="form-control" id="auth_username" name="auth_username" placeholder="Username" data-min="3" required data-validate="validateMinLength">
</div><br><br>
<div class="col-sm-8">
<input type="password" autocomplete="new-password" class="form-control" id="auth_pw" name="auth_pw" placeholder="Password" data-min="8" required data-validate="validatePassword">
<input type="password" autocomplete="new-password" class="form-control" id="auth_pw" name="auth_pw" placeholder="Password" data-min={{ .MinPasswordLength }} required data-validate="validatePassword">
</div><br><br>
<div class="col-sm-8">
<input type="password" autocomplete="new-password" class="form-control" id="auth_pw2" name="auth_pw2" placeholder="Password (repeat)" data-min="8" required>
<input type="password" autocomplete="new-password" class="form-control" id="auth_pw2" name="auth_pw2" placeholder="Password (repeat)" data-min={{ .MinPasswordLength }} required>
</div>
</div>
+8 -3
View File
@@ -2,11 +2,12 @@ package environment
import (
"fmt"
"os"
"path"
envParser "github.com/caarlos0/env/v6"
"github.com/forceu/gokapi/internal/environment/flagparser"
"github.com/forceu/gokapi/internal/helper"
"os"
"path"
)
// DefaultPort for the webserver
@@ -14,17 +15,18 @@ const DefaultPort = 53842
// Environment is a struct containing available env variables
type Environment struct {
ChunkSizeMB int `env:"CHUNK_SIZE_MB" envDefault:"45"`
ConfigDir string `env:"CONFIG_DIR" envDefault:"config"`
ConfigFile string `env:"CONFIG_FILE" envDefault:"config.json"`
ConfigPath string
DataDir string `env:"DATA_DIR" envDefault:"data"`
ChunkSizeMB int `env:"CHUNK_SIZE_MB" envDefault:"45"`
LengthId int `env:"LENGTH_ID" envDefault:"15"`
LengthHotlinkId int `env:"LENGTH_HOTLINK_ID" envDefault:"40"`
MaxFileSize int `env:"MAX_FILESIZE" envDefault:"102400"` // 102400==100GB
MaxMemory int `env:"MAX_MEMORY_UPLOAD" envDefault:"50"`
MaxParallelUploads int `env:"MAX_PARALLEL_UPLOADS" envDefault:"3"`
WebserverPort int `env:"PORT" envDefault:"53842"`
MinLengthPassword int `env:"MIN_LENGTH_PASSWORD" envDefault:"8"`
DisableCorsCheck bool `env:"DISABLE_CORS_CHECK" envDefault:"false"`
LogToStdout bool `env:"LOG_STDOUT" envDefault:"false"`
HotlinkVideos bool `env:"ENABLE_HOTLINK_VIDEOS" envDefault:"false"`
@@ -81,6 +83,9 @@ func New() Environment {
if result.MaxFileSize < 1 {
result.MaxFileSize = 5
}
if result.MinLengthPassword < 6 {
result.MinLengthPassword = 6
}
return result
}
+4
View File
@@ -45,6 +45,10 @@ func TestEnvLoad(t *testing.T) {
env = New()
test.IsEqualInt(t, env.LengthId, 86)
os.Unsetenv("GOKAPI_LENGTH_ID")
os.Setenv("GOKAPI_MIN_LENGTH_PASSWORD", "12")
env = New()
test.IsEqualInt(t, env.MinLengthPassword, 12)
os.Unsetenv("GOKAPI_MIN_LENGTH_PASSWORD")
env = New()
os.Setenv("GOKAPI_LENGTH_ID", "15")
os.Setenv("GOKAPI_MAX_MEMORY_UPLOAD", "0")
+1
View File
@@ -21,6 +21,7 @@ type Configuration struct {
MaxParallelUploads int `json:"MaxParallelUploads"`
LengthId int `json:"-"`
LengthHotlinkId int `json:"-"`
MinLengthPassword int `json:"-"`
Encryption Encryption `json:"Encryption"`
UseSsl bool `json:"UseSsl"`
PicturesAlwaysLocal bool `json:"PicturesAlwaysLocal"`
+15 -13
View File
@@ -1,9 +1,10 @@
package models
import (
"github.com/forceu/gokapi/internal/test"
"strings"
"testing"
"github.com/forceu/gokapi/internal/test"
)
var testConfig = Configuration{
@@ -17,18 +18,19 @@ var testConfig = Configuration{
OAuthClientId: "",
OAuthClientSecret: "",
},
Port: ":12345",
ServerUrl: "https://testserver.com/",
RedirectUrl: "https://test.com",
DatabaseUrl: "sqlite://./test/gokapitest.sqlite",
ConfigVersion: 14,
LengthId: 5,
LengthHotlinkId: 10,
DataDir: "test",
MaxMemory: 50,
UseSsl: true,
MaxFileSizeMB: 20,
PublicName: "public-name",
Port: ":12345",
ServerUrl: "https://testserver.com/",
RedirectUrl: "https://test.com",
DatabaseUrl: "sqlite://./test/gokapitest.sqlite",
ConfigVersion: 14,
LengthId: 5,
LengthHotlinkId: 10,
DataDir: "test",
MaxMemory: 50,
UseSsl: true,
MaxFileSizeMB: 20,
MinLengthPassword: 8,
PublicName: "public-name",
Encryption: Encryption{
Level: 1,
Cipher: []byte{0x00},
@@ -475,7 +475,8 @@ var configTestFile = []byte(`{
"UseSsl": false,
"PicturesAlwaysLocal": false,
"SaveIp": false,
"IncludeFilename": false
"IncludeFilename": false,
"MinLengthPassword": 8
}`)
var sslCertValid = []byte(`-----BEGIN CERTIFICATE-----
+4 -2
View File
@@ -310,7 +310,7 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
}
err = templateFolder.ExecuteTemplate(w, "changepw",
genericView{PublicName: configuration.Get().PublicName,
MinPasswordLength: configuration.MinLengthPassword,
MinPasswordLength: configuration.Environment.MinLengthPassword,
ErrorMessage: errMessage,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
@@ -320,7 +320,7 @@ func validateNewPassword(newPassword string, user models.User) (string, string,
if len(newPassword) == 0 {
return "", user.Password, false
}
if len(newPassword) < configuration.MinLengthPassword {
if len(newPassword) < configuration.Environment.MinLengthPassword {
return "Password is too short", user.Password, false
}
newPasswordHash := configuration.HashPassword(newPassword, false)
@@ -677,6 +677,7 @@ type AdminView struct {
ActiveView int
ChunkSize int
MaxParallelUploads int
MinLengthPassword int
TimeNow int64
CustomContent customStatic
}
@@ -771,6 +772,7 @@ func (u *AdminView) convertGlobalConfig(view int, user models.User) *AdminView {
u.IsUserTabAvailable = config.Authentication.Method != models.AuthenticationDisabled
u.EndToEndEncryption = config.Encryption.Level == encryption.EndToEndEncryption
u.MaxParallelUploads = config.MaxParallelUploads
u.MinLengthPassword = config.MinLengthPassword
u.ChunkSize = config.ChunkSize
u.IncludeFilename = config.IncludeFilename
u.SystemKey = api.GetSystemKey(user.Id)
+1 -1
View File
@@ -715,7 +715,7 @@ func apiResetPassword(w http.ResponseWriter, r requestParser, user models.User)
userToEdit.ResetPassword = true
password := ""
if request.NewPassword {
password = helper.GenerateRandomString(configuration.MinLengthPassword + 2)
password = helper.GenerateRandomString(configuration.Environment.MinLengthPassword + 2)
userToEdit.Password = configuration.HashPassword(password, false)
}
database.DeleteAllSessionsByUser(userToEdit.Id)