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) }