Skip to content
Snippets Groups Projects
authprovider.go 2.9 KiB
Newer Older
gbe's avatar
gbe committed
package main

import (
	"context"
	"crypto/sha256"
	"errors"
	"fmt"
	"log"
gbe's avatar
gbe committed
	"math/rand"
	bolt "go.etcd.io/bbolt"

gbe's avatar
gbe committed
	"git.c3pb.de/gbe/invinoveritas/auth"
)

func hashPassword(ctx context.Context, db *bolt.DB, user, pass string) (string, error) {
gbe's avatar
gbe committed
	// 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
	})
gbe's avatar
gbe committed

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

gbe's avatar
gbe committed
type authProvider struct {
	db *bolt.DB
gbe's avatar
gbe committed
}

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
	})
gbe's avatar
gbe committed

	user := &auth.User{
		Name: userName,
	}

	if err == errNoUsers {
		// No users yet in DB, allow everyone
gbe's avatar
gbe committed
		return user, nil
	}

	if err != nil {
		return nil, err
	}
	if len(dbHash) == 0 {
		return nil, errAuthFailed
	}
	hashedPw, err := hashPassword(ctx, a.db, userName, pass)
gbe's avatar
gbe committed
	if err != nil {
		return nil, err
	}

	log.Printf("hashed pw for %s: %s", userName, hashedPw)

	if string(dbHash) != hashedPw {
		return nil, errAuthFailed
	}

gbe's avatar
gbe committed
	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
	})
gbe's avatar
gbe committed

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
}