madipo2611 95bdb56e70 v0.0.4
2025-05-05 14:15:18 +03:00

157 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package auth
import (
"errors"
"fmt"
"log"
"strconv"
"tailly_back_v2/internal/domain"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidToken = errors.New("invalid token")
)
type TokenAuth struct {
accessTokenSecret string
refreshTokenSecret string
accessTokenExpiry time.Duration
refreshTokenExpiry time.Duration
}
func NewTokenAuth(accessSecret, refreshSecret string, accessExpiry, refreshExpiry time.Duration) *TokenAuth {
return &TokenAuth{
accessTokenSecret: accessSecret,
refreshTokenSecret: refreshSecret,
accessTokenExpiry: accessExpiry,
refreshTokenExpiry: refreshExpiry,
}
}
// GenerateTokens создает пару access и refresh токенов
func (a *TokenAuth) GenerateTokens(userID int, existingRefreshToken ...string) (*domain.Tokens, error) {
accessExpires := time.Now().UTC().Add(a.accessTokenExpiry)
refreshExpires := time.Now().UTC().Add(a.refreshTokenExpiry)
// Генерируем новый access token
accessToken, err := a.generateAccessToken(userID)
if err != nil {
return nil, err
}
// Используем существующий refresh token, если он передан и валиден
var refreshToken string
if len(existingRefreshToken) > 0 && existingRefreshToken[0] != "" {
// Проверяем, что существующий токен еще действителен
if _, err := a.ValidateRefreshToken(existingRefreshToken[0]); err == nil {
refreshToken = existingRefreshToken[0]
} else {
log.Printf("Existing refresh token is invalid, generating new one: %v", err)
refreshToken, err = a.generateRefreshToken(userID)
if err != nil {
return nil, err
}
}
} else {
// Генерируем новый refresh token
refreshToken, err = a.generateRefreshToken(userID)
if err != nil {
return nil, err
}
}
return &domain.Tokens{
AccessToken: accessToken,
RefreshToken: refreshToken,
AccessTokenExpires: accessExpires,
RefreshTokenExpires: refreshExpires,
}, nil
}
// generateAccessToken создает access токен (короткоживущий)
func (a *TokenAuth) generateAccessToken(userID int) (string, error) {
claims := jwt.RegisteredClaims{
Subject: fmt.Sprintf("%d", userID),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(a.accessTokenExpiry)),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(a.accessTokenSecret))
}
// generateRefreshToken создает refresh токен (долгоживущий)
func (a *TokenAuth) generateRefreshToken(userID int) (string, error) {
claims := jwt.RegisteredClaims{
Subject: fmt.Sprintf("%d", userID),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(a.refreshTokenExpiry)),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(a.refreshTokenSecret))
}
// ValidateAccessToken проверяет access токен и возвращает userID
func (a *TokenAuth) ValidateAccessToken(tokenString string) (int, error) {
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, 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(a.accessTokenSecret), nil
})
if err != nil {
return 0, err
}
if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
var userID int
_, err := fmt.Sscanf(claims.Subject, "%d", &userID)
if err != nil {
return 0, ErrInvalidToken
}
return userID, nil
}
return 0, ErrInvalidToken
}
// ValidateRefreshToken проверяет refresh токен и возвращает userID
func (a *TokenAuth) ValidateRefreshToken(tokenString string) (int, error) {
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, 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(a.refreshTokenSecret), nil
})
log.Printf("ValidateRefreshToken валидация токена: %v", token)
if err != nil {
return 0, fmt.Errorf("token validation failed: %w", err)
}
if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
userID, err := strconv.Atoi(claims.Subject)
if err != nil {
return 0, errors.New("invalid user ID in token")
}
return userID, nil
}
return 0, errors.New("invalid token claims")
}
// HashPassword хеширует пароль
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// CheckPasswordHash проверяет пароль с хешем
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}