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) 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 }