package main import ( "context" "crypto/sha256" "errors" "fmt" "log" "math/rand" bolt "go.etcd.io/bbolt" "git.c3pb.de/gbe/invinoveritas/auth" ) func hashPassword(ctx context.Context, db *bolt.DB, user, pass string) (string, error) { // Look up password salt from DB and hash password with it var salt []byte err := db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte("meta")) if bucket == nil { return errors.New("no meta bucket") } salt = bucket.Get([]byte("pwsalt")) return nil }) h := sha256.New() _, err = h.Write(salt) if err != nil { return "", err } fmt.Fprint(h, ":", user, ":", pass) return fmt.Sprintf("%02x", h.Sum(nil)), nil } var ( errAuthFailed = errors.New("authentication failed") errNoUsers = errors.New("no users") ) type authProvider struct { db *bolt.DB } func (a authProvider) Valid(ctx context.Context, userName, pass string) (*auth.User, error) { var dbHash []byte err := a.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte("users")) if bucket == nil { return errNoUsers } if bucket.Stats().KeyN == 0 { return errNoUsers } dbHash = bucket.Get([]byte(userName)) return nil }) user := &auth.User{ Name: userName, } if err == errNoUsers { // No users yet in DB, allow everyone return user, nil } if err != nil { return nil, err } if len(dbHash) == 0 { return nil, errAuthFailed } hashedPw, err := hashPassword(ctx, a.db, userName, pass) if err != nil { return nil, err } log.Printf("hashed pw for %s: %s", userName, hashedPw) if string(dbHash) != hashedPw { return nil, errAuthFailed } return user, nil } func (a authProvider) updatePassword(ctx context.Context, userName, passOld, passNew string) error { hashedOld, err := hashPassword(ctx, a.db, userName, passOld) if err != nil { return err } hashedNew, err := hashPassword(ctx, a.db, userName, passNew) if err != nil { return err } return a.db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists([]byte("users")) if err != nil { return err } pwHash := bucket.Get([]byte(userName)) if len(pwHash) != 0 && string(pwHash) != hashedOld { // A password exists already and it's not the one the user gave as their 'old' password. return errAuthFailed } // Either user doesn't exist yet (no entry) or the password they gave as their old matched. We // can update. err = bucket.Put([]byte(userName), []byte(hashedNew)) if err != nil { return err } return nil }) } func (a authProvider) createUser(ctx context.Context, userName string) (string, error) { // generate random password const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var genPw string for idx := 0; idx < 16; idx++ { r := chars[rand.Intn(len(chars))] genPw += string(r) } err := a.updatePassword(ctx, userName, "", genPw) if err != nil { return "", err } return genPw, nil }