package jwt import ( "crypto/rand" "encoding/hex" "fmt" "time" "github.com/golang-jwt/jwt/v5" ) // Claims содержит данные JWT токена type Claims struct { UserID int64 `json:"user_id"` Email string `json:"email"` jwt.RegisteredClaims } // Manager управляет JWT токенами type Manager struct { secretKey string accessTokenDuration time.Duration refreshTokenDuration time.Duration } // NewManager создает новый JWT менеджер func NewManager(secretKey string, accessDuration, refreshDuration time.Duration) *Manager { return &Manager{ secretKey: secretKey, accessTokenDuration: accessDuration, refreshTokenDuration: refreshDuration, } } // GenerateAccessToken генерирует access токен func (m *Manager) GenerateAccessToken(userID int64, email string) (string, error) { claims := Claims{ UserID: userID, Email: email, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.accessTokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(m.secretKey)) } // GenerateRefreshToken генерирует refresh токен func (m *Manager) GenerateRefreshToken(userID int64, email string) (string, error) { // Генерируем уникальный ID для токена jti, err := generateJTI() if err != nil { return "", fmt.Errorf("failed to generate jti: %w", err) } claims := Claims{ UserID: userID, Email: email, RegisteredClaims: jwt.RegisteredClaims{ ID: jti, ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.refreshTokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(m.secretKey)) } // generateJTI генерирует уникальный ID для JWT токена func generateJTI() (string, error) { bytes := make([]byte, 16) if _, err := rand.Read(bytes); err != nil { return "", err } return hex.EncodeToString(bytes), nil } // ValidateToken валидирует токен и возвращает claims func (m *Manager) ValidateToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { // Проверяем метод подписи if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(m.secretKey), nil }) if err != nil { return nil, fmt.Errorf("failed to parse token: %w", err) } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token claims") } return claims, nil } // GetExpirationTime возвращает время истечения access токена func (m *Manager) GetAccessTokenDuration() time.Duration { return m.accessTokenDuration } // GetRefreshTokenDuration возвращает время истечения refresh токена func (m *Manager) GetRefreshTokenDuration() time.Duration { return m.refreshTokenDuration }