Files
Tomas Dvorak 355a97bab4 overhaul
2026-04-14 18:04:48 +02:00

120 lines
2.6 KiB
Go

package auth
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
func RandomToken(bytesLen int) (string, error) {
buf := make([]byte, bytesLen)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func RandomPassword(length int) (string, error) {
token, err := RandomToken(length)
if err != nil {
return "", err
}
if len(token) > length {
return token[:length], nil
}
return token, nil
}
func RandomID(prefix string) (string, error) {
buf := make([]byte, 10)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(buf)), nil
}
func HashToken(value string) string {
sum := sha256.Sum256([]byte(value))
return hex.EncodeToString(sum[:])
}
func HashPassword(password string) (string, error) {
if len(password) < 8 {
return "", errors.New("password must be at least 8 characters")
}
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
iterations := uint32(3)
memory := uint32(64 * 1024)
parallelism := uint8(2)
keyLen := uint32(32)
hash := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, keyLen)
encoded := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
memory,
iterations,
parallelism,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
)
return encoded, nil
}
func VerifyPassword(encodedHash, password string) (bool, error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
return false, errors.New("invalid hash format")
}
if parts[1] != "argon2id" {
return false, errors.New("unsupported hash type")
}
var memory uint32
var iterations uint32
var parallelism uint8
_, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, &parallelism)
if err != nil {
return false, err
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return false, err
}
decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
return false, err
}
calculated := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, uint32(len(decodedHash)))
if len(calculated) != len(decodedHash) {
return false, nil
}
var diff byte
for i := range calculated {
diff |= calculated[i] ^ decodedHash[i]
}
return diff == 0, nil
}
func ParseIntOrDefault(value string, fallback int) int {
parsed, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return parsed
}