brunel/auth/auth.go
2024-07-28 19:59:50 +01:00

127 lines
3.1 KiB
Go

package auth
import (
"brunel/config"
"brunel/domain"
"brunel/helpers"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"golang.org/x/crypto/argon2"
)
type Params struct {
Memory uint32
Iterations uint32
Parallelism uint8
KeyLength uint32
}
func NewPasswordHash(password string) string {
params := &Params{
Memory: 64 * 1024,
Iterations: 3,
Parallelism: 2,
KeyLength: 32,
}
salt := []byte(config.Configs.Salt)
hash := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Iterations, params.Parallelism, base64.RawURLEncoding.EncodeToString(salt), base64.RawURLEncoding.EncodeToString(hash))
}
func VerifyPassword(username, password string) (bool, error) {
var user domain.User
user, err := helpers.DBInst.GetUser(username)
if err != nil {
usr, ok := config.Users[username]
if !ok {
return false, errors.New("user not found")
}
user = usr
}
encodedHash := user.PasswordHash
_, _, _, _, salt, hash, err := decodeHash(encodedHash)
if err != nil {
slog.Error(err.Error())
return false, err
}
params := &Params{
Memory: 64 * 1024,
Iterations: 3,
Parallelism: 2,
KeyLength: 32,
}
newHash := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
if subtle.ConstantTimeCompare(hash, newHash) == 1 {
return true, nil
}
return false, nil
}
func GenerateAndStoreSessionToken(username string) (string, error) {
token := make([]byte, 32)
_, err := rand.Read(token)
if err != nil {
return "", err
}
endcodedToken := base64.RawURLEncoding.EncodeToString(token)
helpers.DBInst.CreateSession(domain.Session{
Token: endcodedToken,
Username: username,
Expiry: time.Now().Add(24 * time.Hour),
})
return endcodedToken, nil
}
func CheckSessionToken(token string) (bool, string) {
session, err := helpers.DBInst.GetSession(token)
if err != nil {
return false, ""
}
return true, session.Username
}
func decodeHash(encodedHash string) (version int, memory, iterations, parallelism uint32, salt, hash []byte, err error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid hash format")
}
if parts[1] != "argon2id" {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("unsupported hash algorithm")
}
var v int
_, err = fmt.Sscanf(parts[2], "v=%d", &v)
if err != nil {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid version: %v", err)
}
version = v
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, &parallelism)
if err != nil {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid parameters: %v", err)
}
salt, err = base64.RawURLEncoding.DecodeString(parts[4])
if err != nil {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid salt: %v", err)
}
hash, err = base64.RawURLEncoding.DecodeString(parts[5])
if err != nil {
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid hash: %v", err)
}
return version, memory, iterations, parallelism, salt, hash, nil
}