goacc/pkg/db/user.go
2022-07-10 01:10:31 +02:00

220 lines
4.7 KiB
Go

// 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) GetUserWithoutPassword(username string) (u *core.User, err error) {
u, err = db.GetUser(username)
u.Password = ""
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 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,
})
}
func (db *DB) Login(username, password string) (u *core.User, err error) {
if username == defUser && !db.admin {
err = errors.New("db: user disabled")
return
}
u, err = db.GetUser(username)
if err != nil {
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 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
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)
}