Add a config to create new users on startup

This commit is contained in:
2026-03-01 00:15:09 +01:00
parent 18b5f0f0e1
commit 660c44992e
5 changed files with 134 additions and 46 deletions

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
## Generate new accounts on startup
`--add-user` or `-a` can be used to create an account on startup. It requires a string following htpasswd format `[username]:[bcrypt hashed password]`.
The password can be generated using `htpasswd -nB [username]`.
For example, to create an user account `demo`:
```bash
htpasswd -nBC10 demo
New password:
Re-type new password:
demo:$2y$10$UHR2646SZo2W.Rhna7bn5eWNLXWJZ/Sa3oLd9RlxlXs57Bwp6isOS
```
Then, starting the server:
```
./PersonalLibraryManager -a 'demo:$2y$10$UHR2646SZo2W.Rhna7bn5eWNLXWJZ/Sa3oLd9RlxlXs57Bwp6isOS'
```
This will create on startup a new demo user if it does not exist already. Like every parameter, you can also edit `add-user` in the configuration file.

View File

@@ -4,6 +4,7 @@ import (
"errors"
"log"
"os"
"strings"
"github.com/alecthomas/kong"
kongtoml "github.com/alecthomas/kong-toml"
@@ -20,6 +21,18 @@ type Config struct {
InventaireUrl string `toml:"inventaire-url" default:"https://inventaire.io" comment:"An inventaire.io instance URL."`
DisableRegistration bool `toml:"disable-registration" default:"false" comment:"Disable new account creation."`
DemoMode bool `toml:"demo-mode" default:"false" comment:"Activate demo mode: anyone connecting to the instance will be logged in as user 'demo'"`
AddUser UserListAsStrings `toml:"add-user" short:"a" comment:"Add an user on startup following htpasswd bcrypt format, example: [\"demo:$2y$10$UHR2646SZo2W.Rhna7bn5eWNLXWJZ/Sa3oLd9RlxlXs57Bwp6isOS\",\"user:$2y$10$3WYUp.VDpzJRywtrxO1s/uWfUIKpTE4yh5B1d2RCef3hvczYbEWTC\"]"`
}
type UserListAsStrings []string
func (u UserListAsStrings) Validate() error {
for _, s := range u {
if strings.Count(s, ":") != 1 {
return errors.New("For adding users, please follow the format [username]:[bcrypt hashed password]")
}
}
return nil
}
func defaultConfig() Config {
@@ -33,6 +46,7 @@ func defaultConfig() Config {
InventaireUrl: "https://inventaire.io",
DisableRegistration: false,
DemoMode: false,
AddUser: []string{},
}
}

View File

@@ -0,0 +1,82 @@
package createuser
import (
"errors"
"fmt"
"net/http"
"strings"
"git.artlef.fr/PersonalLibraryManager/internal/config"
"git.artlef.fr/PersonalLibraryManager/internal/model"
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// this method will hash the password
func CreateUser(db *gorm.DB, username string, password string) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
return CreateUserWithHashedPassword(db, username, string(hashedPassword))
}
// only call this method with hashed password
func CreateUserWithHashedPassword(db *gorm.DB, username string, hashedPassword string) error {
var existingUser model.User
err := db.Where("name = ?", username).First(&existingUser).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if err == nil {
return myvalidator.HttpError{
StatusCode: http.StatusInternalServerError,
Err: errors.New("An user with this name already exists."),
}
}
user := model.User{
Name: username,
Password: hashedPassword,
}
return db.Model(&model.User{}).Save(&user).Error
}
func CreateDefaultUsers(db *gorm.DB, config *config.Config) error {
usersPasswordMap := make(map[string]string)
var usernames []string
for _, s := range config.AddUser {
splittedString := strings.Split(s, ":")
if len(splittedString) < 2 {
return fmt.Errorf("Error when creating user from string %s", s)
}
usernames = append(usernames, splittedString[0])
usersPasswordMap[splittedString[0]] = splittedString[1]
}
var existingUsers []model.User
err := db.Where("name IN ?", usernames).Find(&existingUsers).Error
if err != nil {
return err
}
for _, username := range usernames {
if isInExistingUsers(username, existingUsers) {
continue
}
err = CreateUserWithHashedPassword(db, username, usersPasswordMap[username])
if err != nil {
return err
}
}
return nil
}
func isInExistingUsers(username string, existingUsers []model.User) bool {
for _, existingUser := range existingUsers {
if username == existingUser.Name {
return true
}
}
return false
}

View File

@@ -5,11 +5,9 @@ import (
"net/http"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
"git.artlef.fr/PersonalLibraryManager/internal/createuser"
"git.artlef.fr/PersonalLibraryManager/internal/dto"
"git.artlef.fr/PersonalLibraryManager/internal/model"
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
func PostSignupHandler(ac appcontext.AppContext) {
@@ -27,43 +25,10 @@ func PostSignupHandler(ac appcontext.AppContext) {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
userDb, err := userWsToDb(user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
var existingUser model.User
err = ac.Db.Where("name = ?", user.Username).First(&existingUser).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
if err == nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac,
myvalidator.HttpError{
StatusCode: http.StatusInternalServerError,
Err: errors.New("An user with this name already exists."),
})
return
}
err = ac.Db.Model(&model.User{}).Save(&userDb).Error
err = createuser.CreateUser(ac.Db, user.Username, user.Password)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
ac.C.String(200, "Success")
}
func userWsToDb(u dto.UserSignup) (model.User, error) {
user := model.User{
Name: u.Username,
Password: "",
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return user, err
}
user.Password = string(hashedPassword)
return user, nil
}

View File

@@ -11,6 +11,7 @@ import (
"git.artlef.fr/PersonalLibraryManager/front"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
"git.artlef.fr/PersonalLibraryManager/internal/config"
"git.artlef.fr/PersonalLibraryManager/internal/createuser"
"git.artlef.fr/PersonalLibraryManager/internal/db"
i18nresource "git.artlef.fr/PersonalLibraryManager/internal/i18nresource"
"git.artlef.fr/PersonalLibraryManager/internal/jwtauth"
@@ -24,6 +25,10 @@ func Setup(config *config.Config) *gin.Engine {
if err != nil {
panic(err)
}
err = createuser.CreateDefaultUsers(db, config)
if err != nil {
panic(err)
}
r := gin.Default()
bundle := i18nresource.InitializeI18n()