Class: LogStruct::Integrations::RackErrorHandler::Middleware

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/log_struct/integrations/rack_error_handler/middleware.rb

Overview

Custom middleware to enhance Rails error logging with JSON format and request details

Constant Summary collapse

IP_SPOOF_HTML =

IP Spoofing error response

T.let(
  "<html><head><title>IP Spoofing Detected</title></head><body>" \
  "<h1>Forbidden</h1>" \
  "<p>IP spoofing detected. This request has been blocked for security reasons.</p>" \
  "</body></html>",
  String
)
CSRF_HTML =

CSRF error response

T.let(
  "<html><head><title>CSRF Error</title></head><body>" \
  "<h1>Forbidden</h1>" \
  "<p>Invalid authenticity token. This request has been blocked to prevent cross-site request forgery.</p>" \
  "</body></html>",
  String
)
IP_SPOOF_HEADERS =

Response headers calculated at load time

T.let(
  {
    "Content-Type" => "text/html",
    "Content-Length" => IP_SPOOF_HTML.bytesize.to_s
  }.freeze,
  T::Hash[String, String]
)
CSRF_HEADERS =
T.let(
  {
    "Content-Type" => "text/html",
    "Content-Length" => CSRF_HTML.bytesize.to_s
  }.freeze,
  T::Hash[String, String]
)
FORBIDDEN_STATUS =

HTTP status code for forbidden responses

T.let(403, Integer)

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Middleware

Returns a new instance of Middleware.



50
51
52
# File 'lib/log_struct/integrations/rack_error_handler/middleware.rb', line 50

def initialize(app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/log_struct/integrations/rack_error_handler/middleware.rb', line 55

def call(env)
  return @app.call(env) unless LogStruct.enabled?

  request = ::ActionDispatch::Request.new(env)

  begin
    # Trigger the same spoofing checks that ActionDispatch::RemoteIp performs after
    # it is initialized in the middleware stack. We run this manually because we
    # execute before that middleware and still want spoofing attacks to surface here.
    perform_remote_ip_check!(request)

    @app.call(env)
  rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError => ip_spoof_error
    # Create a security log for IP spoofing
    security_log = Log::Security::IPSpoof.new(
      path: env["PATH_INFO"],
      http_method: env["REQUEST_METHOD"],
      user_agent: env["HTTP_USER_AGENT"],
      referer: env["HTTP_REFERER"],
      message: ip_spoof_error.message,
      client_ip: env["HTTP_CLIENT_IP"],
      x_forwarded_for: env["HTTP_X_FORWARDED_FOR"],
      timestamp: Time.now
    )

    ::Rails.logger.warn(security_log)

    [FORBIDDEN_STATUS, IP_SPOOF_HEADERS.dup, [IP_SPOOF_HTML]]
  rescue => error
    if csrf_error?(error)
      # Create a security log for CSRF error
      security_log = Log::Security::CSRFViolation.new(
        path: request.path,
        http_method: request.method,
        source_ip: request.remote_ip,
        user_agent: request.user_agent,
        referer: request.referer,
        message: error.message,
        timestamp: Time.now
      )
      LogStruct.error(security_log)

      # Report to error reporting service and/or re-raise
      context = extract_request_context(env, request)
      LogStruct.handle_exception(error, source: Source::Security, context: context)

      # If handle_exception raised an exception then Rails will deal with it (e.g. config.exceptions_app)
      # If we are only logging or reporting these security errors, then return a default response
      [FORBIDDEN_STATUS, CSRF_HEADERS.dup, [CSRF_HTML]]
    else
      # Extract request context for error reporting
      context = extract_request_context(env, request)

      # Create and log a structured exception with request context
      exception_log = Log.from_exception(Source::Rails, error, context)
      LogStruct.error(exception_log)

      # Re-raise any standard errors to let Rails or error reporter handle it.
      # Rails will also log the request details separately
      raise error
    end
  end
end