100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
// BCrypt cost - 12 is a good balance between security and performance
|
|
bcryptCost = 12
|
|
// Session token length in bytes (32 bytes = 256 bits)
|
|
sessionTokenLength = 32
|
|
// Session expiry duration (7 days)
|
|
sessionExpiry = 7 * 24 * time.Hour
|
|
)
|
|
|
|
var (
|
|
ErrInvalidCredentials = errors.New("invalid email or password")
|
|
ErrUserExists = errors.New("user already exists")
|
|
ErrInvalidSession = errors.New("invalid or expired session")
|
|
ErrWeakPassword = errors.New("password must be at least 8 characters")
|
|
ErrInvalidEmail = errors.New("invalid email format")
|
|
)
|
|
|
|
// HashPassword securely hashes a password using bcrypt
|
|
func HashPassword(password string) (string, error) {
|
|
if len(password) < 8 {
|
|
return "", ErrWeakPassword
|
|
}
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(hash), nil
|
|
}
|
|
|
|
// CheckPassword verifies a password against a hash
|
|
func CheckPassword(password, hash string) bool {
|
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
return err == nil
|
|
}
|
|
|
|
// GenerateSessionToken creates a cryptographically secure random token
|
|
func GenerateSessionToken() (string, error) {
|
|
bytes := make([]byte, sessionTokenLength)
|
|
_, err := rand.Read(bytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Use URL-safe base64 encoding
|
|
token := base64.URLEncoding.EncodeToString(bytes)
|
|
return token, nil
|
|
}
|
|
|
|
// GetSessionExpiry returns the expiry time for a new session
|
|
func GetSessionExpiry() time.Time {
|
|
return time.Now().Add(sessionExpiry)
|
|
}
|
|
|
|
// ValidateEmail performs basic email validation
|
|
func ValidateEmail(email string) bool {
|
|
if len(email) < 3 || len(email) > 254 {
|
|
return false
|
|
}
|
|
|
|
// Basic check for @ symbol and domain
|
|
atCount := 0
|
|
dotAfterAt := false
|
|
atPos := -1
|
|
|
|
for i, c := range email {
|
|
if c == '@' {
|
|
atCount++
|
|
atPos = i
|
|
}
|
|
if atPos > 0 && i > atPos && c == '.' {
|
|
dotAfterAt = true
|
|
}
|
|
}
|
|
|
|
return atCount == 1 && dotAfterAt && atPos > 0 && atPos < len(email)-2
|
|
}
|
|
|
|
// SanitizeInput removes dangerous characters for basic XSS prevention
|
|
// Note: Go templates auto-escape, but this adds an extra layer
|
|
func SanitizeInput(input string) string {
|
|
// Templates handle escaping, but we can enforce length limits
|
|
if len(input) > 1000 {
|
|
return input[:1000]
|
|
}
|
|
return input
|
|
}
|