package auth import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) type Claims struct { UserID string `json:"uid"` Email string `json:"email"` jwt.RegisteredClaims } type TokenManager struct { secret []byte accessTTL time.Duration refreshTTL time.Duration } func NewTokenManager(secret string, accessTTL, refreshTTL time.Duration) *TokenManager { return &TokenManager{secret: []byte(secret), accessTTL: accessTTL, refreshTTL: refreshTTL} } func (m *TokenManager) AccessTTL() time.Duration { return m.accessTTL } func (m *TokenManager) RefreshTTL() time.Duration { return m.refreshTTL } func (m *TokenManager) GenerateAccess(userID, email string) (string, error) { now := time.Now() claims := Claims{ UserID: userID, Email: email, RegisteredClaims: jwt.RegisteredClaims{ Subject: userID, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(m.accessTTL)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(m.secret) } func (m *TokenManager) ParseAccess(tokenStr string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } return m.secret, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token") } return claims, nil } func NewRefreshToken() (raw string, hash string, err error) { buf := make([]byte, 32) if _, err = rand.Read(buf); err != nil { return "", "", err } raw = base64.RawURLEncoding.EncodeToString(buf) return raw, HashRefreshToken(raw), nil } func HashRefreshToken(raw string) string { sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) } func NewID() string { return uuid.NewString() }