registar / internal /utils /utils.go
wilooper's picture
V3
7c4758a
Raw History Blame Contribute Delete
3.77 kB
package utils
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"os"
"strings"
"sync"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
// ── UID ───────────────────────────────────────────────────────────────────────
func NewUID() string {
raw := strings.ReplaceAll(uuid.New().String(), "-", "")
return "cos_" + raw[:16]
}
// ── Password ──────────────────────────────────────────────────────────────────
func HashPassword(pw string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(pw), 12)
return string(b), err
}
func CheckPassword(hash, pw string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) == nil
}
// ── JWT ───────────────────────────────────────────────────────────────────────
const jwtTTL = 7 * 24 * time.Hour
type Claims struct {
UID string `json:"uid"`
Email string `json:"email"`
Provider string `json:"provider"`
jwt.RegisteredClaims
}
func SignJWT(uid, email, provider string) (string, error) {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return "", errors.New("JWT_SECRET not set")
}
c := Claims{
UID: uid, Email: email, Provider: provider,
RegisteredClaims: jwt.RegisteredClaims{
Subject: uid,
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
},
}
return jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString([]byte(secret))
}
func VerifyJWT(tokenStr string) (*Claims, error) {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return nil, errors.New("JWT_SECRET not set")
}
t, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
c, ok := t.Claims.(*Claims)
if !ok || !t.Valid {
return nil, errors.New("invalid token")
}
return c, nil
}
// ── Constant-time key comparison ──────────────────────────────────────────────
// KeyMatch compares two API keys in constant time to prevent timing attacks.
func KeyMatch(a, b string) bool {
if len(a) == 0 || len(b) == 0 {
return false
}
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
// ── Nonce store (auth pipe challenge) ─────────────────────────────────────────
const nonceTTL = 15 * time.Minute // full pipeline window
type nonceEntry struct{ exp time.Time }
var (
nonceMu sync.Mutex
nonceStore = map[string]nonceEntry{}
)
func NewNonce() string {
b := make([]byte, 24)
_, _ = rand.Read(b)
n := hex.EncodeToString(b)
nonceMu.Lock()
nonceStore[n] = nonceEntry{exp: time.Now().Add(nonceTTL)}
nonceMu.Unlock()
go sweepNonces()
return n
}
func ClaimNonce(n string) bool {
nonceMu.Lock()
defer nonceMu.Unlock()
e, ok := nonceStore[n]
if !ok {
return false
}
delete(nonceStore, n)
return time.Now().Before(e.exp)
}
func sweepNonces() {
nonceMu.Lock()
defer nonceMu.Unlock()
now := time.Now()
for k, v := range nonceStore {
if now.After(v.exp) {
delete(nonceStore, k)
}
}
}