initial commit

This commit is contained in:
ston1th 2022-07-10 01:10:31 +02:00
commit f48fa210bb
49 changed files with 4058 additions and 0 deletions

33
pkg/db/blacklist.go Normal file
View file

@ -0,0 +1,33 @@
// Copyright (C) 2022 Marius Schellenberger
package db
import "git.giftfish.de/ston1th/jwt/v3"
const blacklistPrefix = "blacklist/"
func (db *DB) Add(sig string, exp int64) error {
return db.store.Set(blacklistPrefix+sig, exp)
}
func (db *DB) Remove(sig string) error {
return db.store.Delete(blacklistPrefix + sig)
}
func (db *DB) Check(sig string) (ok bool) {
return db.store.Get(blacklistPrefix+sig, nil) == nil
}
func (db *DB) Map() (list jwt.BlacklistMap, err error) {
list = make(jwt.BlacklistMap)
err = db.store.ForEachPrefix(blacklistPrefix, func(k string, v []byte) error {
var exp int64
err := db.store.Unmarshal(v, &exp)
if err != nil {
return err
}
list[k] = exp
return nil
})
return
}

69
pkg/db/db.go Normal file
View file

@ -0,0 +1,69 @@
// Copyright (C) 2022 Marius Schellenberger
package db
import (
"io"
"path/filepath"
"git.giftfish.de/ston1th/goacc/pkg/core"
"git.giftfish.de/ston1th/goacc/pkg/store"
"github.com/go-logr/logr"
)
const (
defUser = "admin"
defEmail = "admin@goacc"
defPassword = "goacc"
storeFile = "store.db"
bcryptCost = 13
)
type DB struct {
log logr.Logger
admin bool
store store.Store
}
func NewPlain(log logr.Logger, cfg *core.Config) (db *DB, err error) {
db = &DB{log: log, admin: cfg.Admin}
dbFile := filepath.Join(cfg.DataDir, storeFile)
//err = godrop.Unveil(dbFile, "rwc")
//if err != nil {
// return
//}
db.store, err = store.NewBoltStore(dbFile, nil)
return
}
func New(log logr.Logger, cfg *core.Config) (db *DB, err error) {
db, err = NewPlain(log, cfg)
if err != nil {
return
}
if db.UserExists(defUser) != nil {
err = db.CreateUser(defUser, defEmail, defPassword, true)
if err != nil {
return
}
}
err = db.UnlockUser(defUser)
if err != nil {
return
}
err = db.RunMigrations()
return
}
func (db *DB) Dump(w io.Writer) error {
return db.store.Dump(w)
}
func (db *DB) Restore(r io.Reader) error {
return db.store.Restore(r)
}
func (db *DB) Close() error {
return db.store.Close()
}

21
pkg/db/helper.go Normal file
View file

@ -0,0 +1,21 @@
// Copyright (C) 2022 Marius Schellenberger
package db
import (
"crypto/sha256"
"time"
)
func validatePassword(pw string) (b []byte) {
hash := sha256.New()
hash.Write(b)
b = hash.Sum(nil)
return
}
const timeFmt = "2006-01-02 15:04:05"
func now() string {
return time.Now().Format(timeFmt)
}

13
pkg/db/lockout.go Normal file
View file

@ -0,0 +1,13 @@
// Copyright (C) 2022 Marius Schellenberger
package db
const lockoutPrefix = "lockout/"
func (db *DB) lockout(username, created string) error {
return db.store.Set(lockoutPrefix+username+created, nil)
}
func (db *DB) LockedOut(username, created string) bool {
return db.store.Get(lockoutPrefix+username+created, nil) == nil
}

60
pkg/db/migration.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright (C) 2022 Marius Schellenberger
package db
import (
"errors"
"git.giftfish.de/ston1th/goacc/pkg/store"
)
const (
versionKey = "version/version"
currentVersion = 0
)
type migrator func(*DB, int) error
var migrators = []migrator{
func(_ *DB, _ int) error {
return nil
},
}
func (db *DB) RunMigrations() (err error) {
log := db.log.WithName("migration")
var version int
err = db.store.Get(versionKey, &version)
if err == store.ErrKeyNotFound {
err = db.store.Set(versionKey, currentVersion)
if err != nil {
return
}
err = db.store.Get(versionKey, &version)
if err != nil {
return
}
}
log.Info("db versions", "current", version, "new", currentVersion)
if currentVersion < version {
return errors.New("incompatible versions")
}
if currentVersion == version {
log.Info("nothing to do")
}
for version < currentVersion {
v := version + 1
log.Info("running", "version", v)
err = migrators[v](db, v)
if err != nil {
return
}
err = db.store.Set(versionKey, v)
if err != nil {
return
}
version = v
log.Info("finished", "version", v)
}
return
}

220
pkg/db/user.go Normal file
View file

@ -0,0 +1,220 @@
// 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)
}