backend-service/internal/services/jwt_service.go

107 lines
2.7 KiB
Go
Raw Normal View History

package services
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
// JWTClaims represents the JWT token claims
type JWTClaims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
IsAdmin bool `json:"is_admin"`
jwt.RegisteredClaims
}
// JWTService handles JWT token operations
type JWTService interface {
GenerateToken(userID uint, email string, isAdmin bool) (string, error)
ValidateToken(tokenString string) (*JWTClaims, error)
RefreshToken(tokenString string) (string, error)
}
type jwtService struct {
secretKey string
expiration time.Duration
}
// NewJWTService creates a new JWT service instance
func NewJWTService(secretKey string, expiration time.Duration) JWTService {
return &jwtService{
secretKey: secretKey,
expiration: expiration,
}
}
// GenerateToken creates a new JWT token for the given user
func (j *jwtService) GenerateToken(userID uint, email string, isAdmin bool) (string, error) {
if j.secretKey == "" {
return "", errors.New("JWT secret key is not configured")
}
now := time.Now()
claims := &JWTClaims{
UserID: userID,
Email: email,
IsAdmin: isAdmin,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(j.expiration)),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
Issuer: "booking-system",
Subject: email,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(j.secretKey))
if err != nil {
return "", err
}
return tokenString, nil
}
// ValidateToken validates and parses a JWT token
func (j *jwtService) ValidateToken(tokenString string) (*JWTClaims, error) {
if j.secretKey == "" {
return nil, errors.New("JWT secret key is not configured")
}
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("invalid signing method")
}
return []byte(j.secretKey), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token claims")
}
// RefreshToken generates a new token from an existing valid token
func (j *jwtService) RefreshToken(tokenString string) (string, error) {
claims, err := j.ValidateToken(tokenString)
if err != nil {
return "", err
}
// Check if token is close to expiration (within 1 hour)
if time.Until(claims.ExpiresAt.Time) > time.Hour {
return "", errors.New("token is not eligible for refresh yet")
}
// Generate new token with same user information
return j.GenerateToken(claims.UserID, claims.Email, claims.IsAdmin)
}