238 lines
5 KiB
Go
238 lines
5 KiB
Go
// Copyright (C) 2018 Marius Schellenberger
|
|
|
|
package db
|
|
|
|
import (
|
|
"errors"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
|
"git.giftfish.de/ston1th/gowiki/pkg/otp"
|
|
"git.giftfish.de/ston1th/jwt/v3"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const userPrefix = "user/"
|
|
|
|
var (
|
|
errUserNotFound = errors.New("db: user not found")
|
|
errUserExists = errors.New("db: user already exist")
|
|
errUserReserved = errors.New("db: username is reserved")
|
|
|
|
userRe = regexp.MustCompile("[^a-zA-Z0-9]+")
|
|
)
|
|
|
|
func (db *DB) GetUsers() (users core.Users, err error) {
|
|
err = db.store.ForEach(func(k string, _ []byte) error {
|
|
if trim, ok := hasTrimPrefix(k, userPrefix); ok {
|
|
u, err := db.GetUser(trim)
|
|
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) {
|
|
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, password string, admin bool) error {
|
|
username = userRe.ReplaceAllString(username, "")
|
|
if reservedUser(username) {
|
|
return errUserReserved
|
|
}
|
|
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,
|
|
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 = errors.New("db: user locked")
|
|
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 = errors.New("db: wrong login")
|
|
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 = errors.New("db: user locked")
|
|
return
|
|
}
|
|
if !otp.Validate(pin, u.Secret) {
|
|
u.Locked++
|
|
err = db.store.Set(userPrefix+username, u)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = errors.New("db: wrong pin")
|
|
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) 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
|
|
}
|
|
err = db.lockout(username, u.Created)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
username += "/"
|
|
var pages []string
|
|
db.store.ForEach(func(k string, _ []byte) error {
|
|
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
|
|
if strings.HasPrefix(trim, username) {
|
|
pages = append(pages, trim)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
for _, p := range pages {
|
|
err := db.deletePage(p)
|
|
if err != nil {
|
|
log.Debugf("DeleteUser %s, %s", username, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|