package auth import ( "context" "errors" "fmt" "time" "github.com/golang-jwt/jwt/v4" "autostore/internal/application/interfaces" "autostore/internal/domain/entities" ) var ( ErrInvalidCredentials = errors.New("invalid credentials") ErrInvalidToken = errors.New("invalid token") ErrTokenExpired = errors.New("token expired") ) type JWTAuthService struct { userRepo interfaces.IUserRepository secretKey string logger interfaces.ILogger } func NewJWTAuthService( userRepo interfaces.IUserRepository, secretKey string, logger interfaces.ILogger, ) *JWTAuthService { return &JWTAuthService{ userRepo: userRepo, secretKey: secretKey, logger: logger, } } func (s *JWTAuthService) Authenticate(ctx context.Context, username string, password string) (string, error) { user, err := s.userRepo.FindByUsername(ctx, username) if err != nil { s.logger.Error(ctx, "Failed to find user by username", "error", err, "username", username) return "", ErrInvalidCredentials } if user == nil { s.logger.Warn(ctx, "User not found", "username", username) return "", ErrInvalidCredentials } if !user.ValidatePassword(password) { s.logger.Warn(ctx, "Invalid password", "username", username) return "", ErrInvalidCredentials } token, err := s.generateToken(user) if err != nil { s.logger.Error(ctx, "Failed to generate token", "error", err, "username", username) return "", err } s.logger.Info(ctx, "User authenticated successfully", "username", username, "userID", user.GetID().String()) return token, nil } func (s *JWTAuthService) ValidateToken(ctx context.Context, tokenString string) (bool, error) { token, err := jwt.Parse(tokenString, 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(s.secretKey), nil }) if err != nil { s.logger.Warn(ctx, "Token validation failed", "error", err) return false, nil } return token.Valid, nil } func (s *JWTAuthService) GetUserIDFromToken(ctx context.Context, tokenString string) (string, error) { token, err := jwt.Parse(tokenString, 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(s.secretKey), nil }) if err != nil { s.logger.Warn(ctx, "Failed to parse token", "error", err) return "", ErrInvalidToken } if !token.Valid { s.logger.Warn(ctx, "Invalid token") return "", ErrInvalidToken } claims, ok := token.Claims.(jwt.MapClaims) if !ok { s.logger.Warn(ctx, "Invalid token claims") return "", ErrInvalidToken } userID, ok := claims["sub"].(string) if !ok { s.logger.Warn(ctx, "User ID not found in token claims") return "", ErrInvalidToken } return userID, nil } func (s *JWTAuthService) generateToken(user *entities.UserEntity) (string, error) { claims := jwt.MapClaims{ "sub": user.GetID().String(), "username": user.GetUsername(), "iss": "autostore", "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour * 24).Unix(), // 24 hours expiration } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(s.secretKey)) }