… for mini-services. Accounts always read/write from/to file “by design”
.
/* * Author, Copyright: Oleg Borodin <onborodin@gmail.com> */ package accounts import ( "math/rand" "fmt" "os" "bufio" "regexp" "errors" "github.com/GehirnInc/crypt" _ "github.com/GehirnInc/crypt/sha256_crypt" ) type AccountDB struct { FileName string } type Account struct{ Name string Digest string } var passwordMinLen int = 4 var nameMinLen int = 4 const letters = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randString(n int) string { arr := make([]byte, n) for i := range arr { arr[i] = letters[rand.Intn(len(letters))] } return string(arr) } func createHash(key string) (string, error) { crypt := crypt.SHA256.New() return crypt.Generate([]byte(key), []byte("$5$" + randString(12))) } func validateName(name string) error { if len(name) < nameMinLen { return errors.New(fmt.Sprintf("name less %d chars", nameMinLen)) } return nil } func validatePassword(password string) error { if len(password) < passwordMinLen { return errors.New(fmt.Sprintf("password less %d chars", passwordMinLen)) } return nil } func validateFileName(name string) error { if len(name) < 1 { return errors.New(fmt.Sprintf("filename not set")) } return nil } func (accountDB AccountDB) Set(name string, password string) error { if err := validateFileName(accountDB.FileName); err != nil { return err } if err := validateName(name); err != nil { return err } if err := validatePassword(password); err != nil { return err } file, err := os.OpenFile(accountDB.FileName, os.O_RDONLY|os.O_CREATE, 0640) if err != nil { return err } scanner := bufio.NewScanner(file) digest, _ := createHash(password) accounts := map[string]string{name: string(digest)} re := regexp.MustCompile(`(.+):(.+)`) for scanner.Scan() { str := scanner.Text() if re.MatchString(str) { match := re.FindStringSubmatch(str) name := match[1] digest := match[2] if _, exists := accounts[name]; !exists { accounts[name] = digest } } } file.Close() _ = os.Rename(accountDB.FileName, accountDB.FileName + ".bak") file, err = os.OpenFile(accountDB.FileName, os.O_WRONLY|os.O_CREATE, 0640) if err != nil { return err } for key, value := range accounts { file.Write([]byte(fmt.Sprintf("%s:%s\n", key, value))) } file.Close() return nil } func (accountDB AccountDB) Print() error { file, err := os.OpenFile(accountDB.FileName, os.O_RDONLY, 0640) defer file.Close() if err != nil { return err } scanner := bufio.NewScanner(file) re := regexp.MustCompile(`^(.+):(.+)`) for scanner.Scan() { str := scanner.Text() if re.MatchString(str) { match := re.FindStringSubmatch(str) fmt.Println(match[1], match[2]) } } return nil } func (accountDB AccountDB) List() ([]Account, error) { file, err := os.OpenFile(accountDB.FileName, os.O_RDONLY, 0640) defer file.Close() if err != nil { return nil, err } scanner := bufio.NewScanner(file) re := regexp.MustCompile(`^(.+):(.+)`) var accountList []Account for scanner.Scan() { str := scanner.Text() if re.MatchString(str) { match := re.FindStringSubmatch(str) account := Account{ Name: match[1], Digest: match[2], } accountList = append(accountList, account) } } return accountList, nil } func (accountDB AccountDB) Auth(name string, password string) error { if err := validateFileName(accountDB.FileName); err != nil { return err } file, err := os.OpenFile(accountDB.FileName, os.O_RDONLY, 0640) defer file.Close() if err != nil { return err } scanner := bufio.NewScanner(file) re := regexp.MustCompile(`^(.+):(.+)`) for scanner.Scan() { str := scanner.Text() if re.MatchString(str) { crypt := crypt.SHA256.New() match := re.FindStringSubmatch(str) if match[1] == name { return crypt.Verify(match[2], []byte(password)) } } } return errors.New(fmt.Sprintf("account %s not found", name)) } func (accountDB AccountDB) Delete(name string) error { if err := validateFileName(accountDB.FileName); err != nil { return err } file, err := os.OpenFile(accountDB.FileName, os.O_RDONLY, 0640) if err != nil { return err } scanner := bufio.NewScanner(file) re := regexp.MustCompile(`(.+):(.+)`) accounts := map[string]string{} for scanner.Scan() { str := scanner.Text() if re.MatchString(str) { match := re.FindStringSubmatch(str) xname := match[1] xdigest := match[2] if xname != name { if _, exists := accounts[xname]; !exists { accounts[xname] = xdigest } } } } file.Close() //err = os.Remove(accountDB.FileName) err = os.Rename(accountDB.FileName, accountDB.FileName + ".bak") if err != nil { return err } file, err = os.OpenFile(accountDB.FileName, os.O_WRONLY|os.O_CREATE, 0640) if err != nil { return err } for xname, xdigest := range accounts { //fmt.Println("write:", xname, xdigest) file.Write([]byte(fmt.Sprintf("%s:%s\n", xname, xdigest))) } file.Close() return nil } func New(filename string) *AccountDB { return &AccountDB{ FileName: filename } }