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, ¶llelism) 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 }