quartzgun/indentalUserDB/indentalUserDB.go

253 lines
5.8 KiB
Go

package indentalUserDB
import (
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
"nilfm.cc/git/quartzgun/auth"
"nilfm.cc/git/quartzgun/cookie"
"os"
"strings"
"time"
)
type IndentalUserDB struct {
Users map[string]*auth.User
Basis string
}
func CreateIndentalUserDB(filePath string) *IndentalUserDB {
u, err := readDB(filePath)
if err == nil {
uMap := map[string]*auth.User{}
for _, usr := range u {
uMap[usr.Name] = usr
}
return &IndentalUserDB{
Users: uMap,
Basis: filePath,
}
} else {
return &IndentalUserDB{
Users: map[string]*auth.User{},
Basis: filePath,
}
}
}
func (self *IndentalUserDB) InitiateSession(user string, password string) (string, error) {
if _, exists := self.Users[user]; !exists {
return "", errors.New("User not in DB")
}
if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(password)) != nil {
return "", errors.New("Incorrect password")
}
sessionId := cookie.GenToken(64)
self.Users[user].Session = sessionId
self.Users[user].LoginTime = time.Now()
self.Users[user].LastSeen = time.Now()
writeDB(self.Basis, self.Users)
return sessionId, nil
}
func (self *IndentalUserDB) ValidateUser(user string, sessionId string) (bool, error) {
if _, exists := self.Users[user]; !exists {
return false, errors.New("User not in DB")
}
validated := self.Users[user].Session == sessionId
if validated {
self.Users[user].LastSeen = time.Now()
writeDB(self.Basis, self.Users)
}
return validated, nil
}
func (self *IndentalUserDB) EndSession(user string) error {
if _, exists := self.Users[user]; !exists {
return errors.New("User not in DB")
}
self.Users[user].Session = ""
self.Users[user].LastSeen = time.Now()
writeDB(self.Basis, self.Users)
return nil
}
func (self *IndentalUserDB) DeleteUser(user string) error {
if _, exists := self.Users[user]; !exists {
return errors.New("User not in DB")
}
delete(self.Users, user)
writeDB(self.Basis, self.Users)
return nil
}
func (self *IndentalUserDB) ChangePassword(user string, password string, oldPassword string) error {
if _, exists := self.Users[user]; !exists {
return errors.New("User not in DB")
}
if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(oldPassword)) != nil {
return errors.New("Incorrect password")
}
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 10)
self.Users[user].Pass = string(hash[:])
writeDB(self.Basis, self.Users)
return nil
}
func (self *IndentalUserDB) AddUser(user string, password string) error {
if _, exists := self.Users[user]; exists {
return errors.New("User already in DB")
}
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 10)
self.Users[user] = &auth.User{
Name: user,
Pass: string(hash[:]),
LastSeen: time.UnixMicro(0),
LoginTime: time.UnixMicro(0),
Session: "",
}
writeDB(self.Basis, self.Users)
return nil
}
func (self *IndentalUserDB) GetLastLoginTime(user string) (time.Time, error) {
if usr, exists := self.Users[user]; exists {
return usr.LoginTime, nil
}
return time.UnixMicro(0), errors.New("User not in DB")
}
func (self *IndentalUserDB) GetLastTimeSeen(user string) (time.Time, error) {
if usr, exists := self.Users[user]; exists {
return usr.LastSeen, nil
}
return time.UnixMicro(0), errors.New("User not in DB")
}
func (self *IndentalUserDB) SetData(user string, key string, value interface{}) error {
if _, exists := self.Users[user]; !exists {
return errors.New("User not in DB")
}
self.Users[user].Data[key] = value
return nil
}
func (self *IndentalUserDB) GetData(user string, key string) (interface{}, error) {
if _, usrExists := self.Users[user]; !usrExists {
return nil, errors.New("User not in DB")
}
data, exists := self.Users[user].Data[key]
if !exists {
return nil, errors.New("No data key for user")
}
return data, nil
}
const timeFmt = "2006-01-02T15:04Z"
func readDB(filePath string) (map[string]*auth.User, error) {
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
fileData := string(f[:])
users := map[string]*auth.User{}
lines := strings.Split(fileData, "\n")
indentLevel := ""
var name string
var pass string
var session string
var loginTime time.Time
var lastSeen time.Time
var data map[string]interface{}
for _, l := range lines {
if strings.HasPrefix(l, indentLevel) {
switch indentLevel {
case "":
name = l
indentLevel = "\t"
case "\t":
if strings.Contains(l, ":") {
kvp := strings.Split(l, ":")
k := strings.TrimSpace(kvp[0])
v := strings.TrimSpace(kvp[1])
switch k {
case "pass":
pass = v
case "session":
session = v
case "loginTime":
loginTime, _ = time.Parse(timeFmt, v)
case "lastSeen":
lastSeen, _ = time.Parse(timeFmt, v)
}
} else {
data = map[string]interface{}{}
indentLevel = "\t\t"
}
case "\t\t":
if strings.Contains(l, ":") {
kvp := strings.Split(l, ":")
k := strings.TrimSpace(kvp[0])
v := strings.TrimSpace(kvp[1])
data[k] = v
}
}
} else {
if indentLevel != "\t\t" {
panic("Malformed indental file")
} else {
users[name] = &auth.User{
Name: name,
Pass: pass,
Session: session,
LoginTime: loginTime,
LastSeen: lastSeen,
Data: data,
}
indentLevel = ""
}
}
}
return users, nil
}
func writeDB(filePath string, users map[string]*auth.User) error {
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
for _, user := range users {
f.WriteString(fmt.Sprintf("%s\n\tpass: %s\n\tsession: %s\n\tloginTime: %s\n\tlastSeen: %s\n\tdata\n",
user.Name,
user.Pass,
user.Session,
user.LoginTime,
user.LastSeen))
for k, v := range user.Data {
f.WriteString(fmt.Sprintf("\t\t%s: %s\n", k, v))
}
f.WriteString("\n")
}
f.Sync()
return nil
}