2025-11-19 17:34:42 +08:00

75 lines
1.8 KiB
Go

package auth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
)
// HashPassword returns a bcrypt hash for the given plain text password.
func HashPassword(password string) (string, error) {
if password == "" {
return "", errors.New("empty password")
}
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(b), nil
}
// CheckPassword verifies a bcrypt hash against the given plain text password.
func CheckPassword(hash, password string) bool {
if hash == "" || password == "" {
return false
}
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
// MakeSessionToken builds a simple HMAC-signed token: username|expUnix|sigHex
func MakeSessionToken(username string, ttl time.Duration, secret []byte) (string, time.Time) {
if ttl <= 0 {
ttl = 24 * time.Hour
}
exp := time.Now().Add(ttl).UTC()
payload := username + "|" + strconv.FormatInt(exp.Unix(), 10)
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(payload))
sig := hex.EncodeToString(mac.Sum(nil))
return payload + "|" + sig, exp
}
// ParseSessionToken validates token and returns username if valid.
func ParseSessionToken(token string, secret []byte) (string, bool) {
parts := strings.Split(token, "|")
if len(parts) != 3 {
return "", false
}
username := parts[0]
expStr := parts[1]
sigHex := parts[2]
// recompute
payload := fmt.Sprintf("%s|%s", username, expStr)
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(payload))
if hex.EncodeToString(mac.Sum(nil)) != sigHex {
return "", false
}
// expiry check
expUnix, err := strconv.ParseInt(expStr, 10, 64)
if err != nil {
return "", false
}
if time.Now().UTC().After(time.Unix(expUnix, 0)) {
return "", false
}
return username, true
}