File size: 3,766 Bytes
7c4758a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 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)
}
}
}
|