2019-11-11 00:26:36 +00:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
2020-03-15 12:29:17 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"time"
|
|
|
|
|
2019-11-11 00:26:36 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2020-03-15 12:29:17 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2019-11-11 00:26:36 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2020-03-15 12:29:17 +00:00
|
|
|
|
|
|
|
"code.chrissexton.org/cws/cabinet/config"
|
|
|
|
"code.chrissexton.org/cws/cabinet/db"
|
2019-11-11 00:26:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type User struct {
|
|
|
|
*db.Database
|
|
|
|
|
2020-03-15 12:29:17 +00:00
|
|
|
ID int64
|
|
|
|
Name string
|
|
|
|
Hash []byte
|
|
|
|
AuthKey string `db:"auth_key"`
|
|
|
|
Invalidate time.Time
|
2019-11-11 00:26:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func PrepareTable(tx *sqlx.Tx) error {
|
|
|
|
q := `create table if not exists users (
|
|
|
|
id integer primary key,
|
2020-03-15 12:29:17 +00:00
|
|
|
name text unique not null,
|
|
|
|
hash text not null,
|
|
|
|
auth_key text,
|
|
|
|
invalidate datetime
|
2019-11-11 00:26:36 +00:00
|
|
|
)`
|
|
|
|
_, err := tx.Exec(q)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-15 12:29:17 +00:00
|
|
|
func makeKey() (string, error) {
|
|
|
|
keySize := config.GetInt("key.size", 10)
|
|
|
|
buf := make([]byte, keySize)
|
|
|
|
_, err := rand.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
key := hex.EncodeToString(buf)
|
|
|
|
log.Debug().Msgf("Encoded secret key %s as %s", string(buf), key)
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2019-11-11 00:26:36 +00:00
|
|
|
func New(db *db.Database, name, password string) (*User, error) {
|
2020-03-15 12:29:17 +00:00
|
|
|
q := `insert into users values (null, ?, ?, ?, ?)`
|
|
|
|
|
|
|
|
key, err := makeKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
invalidate := time.Now().Add(time.Duration(config.GetInt("invalidate.hours", 7*24)) * time.Hour)
|
2020-03-16 20:28:23 +00:00
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), config.GetInt("hash.cost", 10))
|
2020-03-15 13:40:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-15 12:29:17 +00:00
|
|
|
|
2020-03-15 13:40:47 +00:00
|
|
|
res, err := db.Exec(q, name, hash, key, invalidate)
|
2019-11-11 00:26:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
id, err := res.LastInsertId()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
u := &User{
|
2020-03-15 12:29:17 +00:00
|
|
|
ID: id,
|
|
|
|
Name: name,
|
|
|
|
AuthKey: key,
|
|
|
|
Invalidate: invalidate,
|
2019-11-11 00:26:36 +00:00
|
|
|
}
|
|
|
|
u.Set(password)
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Get(db *db.Database, name string) (*User, error) {
|
|
|
|
q := `select * from users where name = ?`
|
|
|
|
u := &User{}
|
|
|
|
if err := db.Get(u, q, name); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *User) Set(newPassword string) error {
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
u.Hash = hash
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *User) Validate(password string) bool {
|
|
|
|
err := bcrypt.CompareHashAndPassword(u.Hash, []byte(password))
|
|
|
|
if err != nil {
|
2020-03-15 13:40:47 +00:00
|
|
|
log.Debug().Err(err).Msg("incorrect credentials")
|
2019-11-11 00:26:36 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2020-03-15 12:29:17 +00:00
|
|
|
|
|
|
|
func GetByKey(db *db.Database, key string) (*User, error) {
|
|
|
|
q := `select * from users where auth_key = ?`
|
|
|
|
u := &User{}
|
|
|
|
invalid := errors.New("invalid key")
|
|
|
|
if err := db.Get(u, q, key); err != nil {
|
|
|
|
if err.Error() == "sql: no rows in result set" {
|
|
|
|
return nil, invalid
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if u.Invalidate.Before(time.Now()) {
|
|
|
|
return nil, invalid
|
|
|
|
}
|
|
|
|
return u, nil
|
|
|
|
}
|