package auth import ( "errors" "fmt" "os" "strconv" "time" "github.com/golang-jwt/jwt/v5" ) var ( ErrInvalidToken = errors.New("invalid token") ErrExpiredToken = errors.New("token has expired") ErrInvalidSignature = errors.New("invalid token signature") ErrMissingClaims = errors.New("missing required claims") ) // Claims represents the JWT claims structure type Claims struct { Email string `json:"email"` jwt.RegisteredClaims } type JWTManager struct { secretKey []byte tokenDuration time.Duration } func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager { return &JWTManager{ secretKey: []byte(secretKey), tokenDuration: tokenDuration, } } // GenerateToken creates a new JWT token for a user func (m *JWTManager) GenerateToken(userID int64, email string) (string, error) { claims := Claims{ Email: email, RegisteredClaims: jwt.RegisteredClaims{ Subject: strconv.FormatInt(userID, 10), ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: os.Getenv("JWT_ISSUER"), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(m.secretKey) if err != nil { return "", fmt.Errorf("failed to sign token: %w", err) } return tokenString, nil } // ValidateToken validates the JWT token and returns the claims func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims( tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { // Verify signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return m.secretKey, nil }, ) if err != nil { if errors.Is(err, jwt.ErrTokenExpired) { return nil, ErrExpiredToken } if errors.Is(err, jwt.ErrSignatureInvalid) { return nil, ErrInvalidSignature } return nil, ErrInvalidToken } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, ErrInvalidToken } return claims, nil } // RefreshToken generates a new token from an existing valid token func (m *JWTManager) RefreshToken(tokenString string) (string, error) { claims, err := m.ValidateToken(tokenString) if err != nil { return "", err } userId, err := strconv.ParseInt(claims.Subject, 10, 64) if err != nil { return "", err } return m.GenerateToken(userId, claims.Email) }