package service import ( "bugulma/backend/internal/domain" "fmt" "time" "github.com/golang-jwt/jwt/v5" ) // JWTClaims represents the JWT claims structure type JWTClaims struct { UserID string `json:"user_id"` Email string `json:"email"` Role string `json:"role"` OrgID string `json:"org_id,omitempty"` jwt.RegisteredClaims } // JWTService handles JWT token operations type JWTService struct { secretKey []byte } // NewJWTService creates a new JWT service func NewJWTService(secretKey string) *JWTService { return &JWTService{ secretKey: []byte(secretKey), } } // GenerateToken generates a JWT token for a user func (j *JWTService) GenerateToken(user *domain.User, orgID string) (string, error) { claims := JWTClaims{ UserID: user.ID, Email: user.Email, Role: string(user.Role), OrgID: orgID, RegisteredClaims: jwt.RegisteredClaims{ Issuer: "bugulma-backend", Subject: user.ID, ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 24 hours IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(j.secretKey) } // ValidateToken validates and parses a JWT token func (j *JWTService) ValidateToken(tokenString string) (*JWTClaims, error) { 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, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return j.secretKey, nil }) if err != nil { return nil, fmt.Errorf("failed to parse token: %w", err) } if !token.Valid { return nil, fmt.Errorf("invalid token") } claims, ok := token.Claims.(*JWTClaims) if !ok { return nil, fmt.Errorf("invalid token claims") } // Check if token is expired if claims.ExpiresAt != nil && claims.ExpiresAt.Before(time.Now()) { return nil, fmt.Errorf("token expired") } return claims, nil } // ExtractClaims extracts claims from a validated token func (j *JWTService) ExtractClaims(tokenString string) (*JWTClaims, error) { return j.ValidateToken(tokenString) } // RefreshToken generates a new token with updated expiration func (j *JWTService) RefreshToken(tokenString string) (string, error) { claims, err := j.ValidateToken(tokenString) if err != nil { return "", fmt.Errorf("cannot refresh invalid token: %w", err) } // Create new claims with updated expiration newClaims := JWTClaims{ UserID: claims.UserID, Email: claims.Email, Role: claims.Role, OrgID: claims.OrgID, RegisteredClaims: jwt.RegisteredClaims{ Issuer: claims.Issuer, Subject: claims.Subject, ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims) return token.SignedString(j.secretKey) }