// Copyright (C) 2022 Marius Schellenberger package db import ( "errors" "regexp" "sort" "strconv" "git.giftfish.de/ston1th/goacc/pkg/core" "git.giftfish.de/ston1th/goacc/pkg/otp" "git.giftfish.de/ston1th/jwt/v3" "golang.org/x/crypto/bcrypt" ) const userPrefix = "user/" var ( errUserLocked = errors.New("db: user locked") errUserLogin = errors.New("db: wrong login") errUserPin = errors.New("db: wrong pin") errUserExists = errors.New("db: user already exist") errNameReserved = errors.New("db: name is reserved") userRe = regexp.MustCompile("[^a-zA-Z0-9]+") ) func (db *DB) GetUsers() (users core.Users, err error) { err = db.store.ForEachPrefix(userPrefix, func(k string, _ []byte) error { u, err := db.GetUser(k) u.Password = "" if err == nil { users = append(users, u) } return nil }) sort.Sort(users) return } func (db *DB) GetUserWithoutSecrets(username string) (u *core.User, err error) { u, err = db.GetUser(username) u.Password = "" if u.Secret != "" { u.Secret = "redacted" } return } func (db *DB) GetUserSecret(username string) (secret string, err error) { var user *core.User user, err = db.GetUser(username) secret = user.Secret return } func (db *DB) GetUser(username string) (u *core.User, err error) { u = new(core.User) err = db.store.Get(userPrefix+username, u) return } func (db *DB) UserExists(username string) error { return db.store.Get(userPrefix+username, nil) } func (db *DB) CreateUser(username, email, password string, admin, disabled bool) error { username = userRe.ReplaceAllString(username, "") if err := db.UserExists(username); err == nil { return errUserExists } hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) if err != nil { return err } return db.store.Set(userPrefix+username, &core.User{ Username: username, Email: email, Password: string(hash), Created: strconv.Itoa(int(jwt.Now())), Admin: admin, Disabled: disabled, }) } func (db *DB) Login(username, password string) (u *core.User, err error) { u, err = db.GetUser(username) if err != nil { return } if u.Disabled { err = errors.New("db: user disabled") return } if u.Locked == 3 { err = errUserLocked return } if err = bcrypt.CompareHashAndPassword([]byte(u.Password), validatePassword(password)); err != nil { u.Locked++ err = db.store.Set(userPrefix+username, u) if err != nil { return } err = errUserLogin return } if u.Locked > 0 { u.Locked = 0 err = db.store.Set(userPrefix+username, u) if err != nil { return } } return } func (db *DB) Totp(username, pin string) (u *core.User, err error) { u, err = db.GetUser(username) if err != nil { return } if u.Locked == 3 { err = errUserLocked return } if !otp.Validate(pin, u.Secret) { u.Locked++ err = db.store.Set(userPrefix+username, u) if err != nil { return } err = errUserPin return } if u.Locked > 0 { u.Locked = 0 err = db.store.Set(userPrefix+username, u) if err != nil { return } } return } func (db *DB) UnlockUser(username string) (err error) { var u *core.User u, err = db.GetUser(username) if err != nil { return } if u.Locked > 0 { u.Locked = 0 err = db.store.Set(userPrefix+username, u) if err != nil { return } } return } func (db *DB) UpdateUserSecret(username, secret string) error { u, err := db.GetUser(username) if err != nil { return err } u.Secret = secret return db.store.Set(userPrefix+username, u) } func (db *DB) UpdateUserPassword(username, password string) error { if password == "" { return errors.New("empty password") } u, err := db.GetUser(username) if err != nil { return err } hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) if err != nil { return err } u.Password = string(hash) return db.store.Set(userPrefix+username, u) } func (db *DB) ResetAdmin() error { return db.UpdateUserPassword(defUser, defPassword) } func (db *DB) AdminUpdateUser(username, password string, admin, disabled bool) error { if username == defUser && !admin { return errors.New("user '" + defUser + "' cannot lose admin privileges") } u, err := db.GetUser(username) if err != nil { return err } u.Admin = admin u.Disabled = disabled if password != "" { hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost) if err != nil { return err } u.Password = string(hash) } return db.store.Set(userPrefix+username, u) } func (db *DB) DeleteUser(username string) error { if username == defUser { return errors.New("user '" + defUser + "' cannot be deleted") } u, err := db.GetUser(username) if err != nil { return err } err = db.store.Delete(userPrefix + username) if err != nil { return err } return db.lockout(username, u.Created) }