/* * Author, Copyright: Oleg Borodin <onborodin@gmail.com> */ package accountdb import ( "strings" "fmt" "io/ioutil" "path/filepath" "math/rand" "errors" "os" "github.com/GehirnInc/crypt" _ "github.com/GehirnInc/crypt/sha256_crypt" ) type Account struct { Username string Digest string } type AccountDB struct { FileName string } const ( DigestMinLen = 56 UsernameMinLen = 4 PasswordMinLen = 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(password string) (string, error) { crypt := crypt.SHA256.New() return crypt.Generate([]byte(password), []byte("$5$" + randString(12))) } func checkHash(digest string, password string) error { crypt := crypt.SHA256.New() return crypt.Verify(digest, []byte(password)) } func (accountDB AccountDB) readFile() ([]Account, error) { var data []byte var err error if data, err = ioutil.ReadFile(accountDB.FileName); err != nil { return nil, err } records := strings.Split(string(data), "\n") var accountArray []Account for _, recordString := range records { if len(recordString) == 0 { continue } recordArray := strings.SplitN(recordString, ":", 2) username, digest := recordArray[0], recordArray[1] if len(username) < UsernameMinLen { continue } if len(digest) < DigestMinLen { continue } accountArray = append(accountArray, Account{ Username: username, Digest: digest, }) } return accountArray, nil } func (accountDB AccountDB) writeFile(accountArray []Account) error { var data string for _, account := range accountArray { record := account.Username + ":" + account.Digest + "\n" data = data + record } fileName, _ := filepath.Abs(accountDB.FileName) os.Rename(fileName, fileName + "~") return ioutil.WriteFile(fileName, []byte(data), 0640) } func (accountDB AccountDB) uniqAccounts(accountArray []Account) ([]Account, error) { accountMap := make(map[string]string) for _, account := range accountArray { if _, has := accountMap[account.Username]; !has { accountMap[account.Username] = account.Digest } } var uniqArray []Account for username, digest := range accountMap { record := Account{ Username: username, Digest: digest, } uniqArray = append(uniqArray, record) } return uniqArray, nil } func (accountDB AccountDB) Auth(username string, password string) error { accountArray, err := accountDB.readFile() if err != nil { return err } accountArray, _= accountDB.uniqAccounts(accountArray) for _, account := range accountArray { if username == account.Username { return checkHash(account.Digest, password) } } return errors.New("username not found") } func (accountDB AccountDB) Set(username string, password string) error { if len(password) < PasswordMinLen { return errors.New(fmt.Sprintf("password less %d chars", PasswordMinLen)) } hasAccount := false var accountArray []Account var err error accountArray, err = accountDB.readFile() if err == nil && accountArray != nil { accountArray, _= accountDB.uniqAccounts(accountArray) for i, account := range accountArray { if username == account.Username { accountArray[i].Digest, _ = createHash(password) hasAccount = true } } } if !hasAccount { digest, _ := createHash(password) accountArray = append(accountArray, Account{ Username: username, Digest: digest, }) } return accountDB.writeFile(accountArray) } func (accountDB AccountDB) Delete(username string) error { var accountArray []Account var reducedArray []Account var err error accountArray, err = accountDB.readFile() hasAccount := false if err == nil && accountArray != nil { accountArray, _= accountDB.uniqAccounts(accountArray) for _, account := range accountArray { if username == account.Username { hasAccount = true continue } reducedArray = append(reducedArray, account) } } if hasAccount { return accountDB.writeFile(reducedArray) } return nil } func New(fileName string) *AccountDB { fileName, _ = filepath.Abs(fileName) return &AccountDB{ FileName: fileName, } }