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) ⇒ void

Parameters:

  • app (T.untyped)


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) ⇒ T.untyped

Parameters:

  • env (T.untyped)

Returns:

  • (T.untyped)


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
118
119
120
121
122
123
124
125
# File 'lib/log_struct/integrations/rack_error_handler/middleware.rb', line 55

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

  # Try to process the request
  begin
    @app.call(env)
  rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError => ip_spoof_error
    # Create a security log for IP spoofing
    security_log = Log::Security.new(
      event: Event::IPSpoof,
      message: ip_spoof_error.message,
      # Can't call .remote_ip on the request because that's what raises the error.
      # Have to pass the client_ip and x_forwarded_for headers.
      client_ip: env["HTTP_CLIENT_IP"],
      x_forwarded_for: env["HTTP_X_FORWARDED_FOR"],
      path: env["PATH_INFO"],
      http_method: env["REQUEST_METHOD"],
      user_agent: env["HTTP_USER_AGENT"],
      referer: env["HTTP_REFERER"],
      request_id: env["action_dispatch.request_id"]
    )

    # Log the structured data
    ::Rails.logger.warn(security_log)

    # Report the error
    context = extract_request_context(env)
    LogStruct.handle_exception(ip_spoof_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, IP_SPOOF_HEADERS, [IP_SPOOF_HTML]]
  rescue ::ActionController::InvalidAuthenticityToken => invalid_auth_token_error
    # Create a security log for CSRF error
    request = ::ActionDispatch::Request.new(env)
    security_log = Log::Security.new(
      event: Event::CSRFViolation,
      message: invalid_auth_token_error.message,
      path: request.path,
      http_method: request.method,
      source_ip: request.remote_ip,
      user_agent: request.user_agent,
      referer: request.referer,
      request_id: request.request_id
    )
    LogStruct.error(security_log)

    # Report to error reporting service and/or re-raise
    context = extract_request_context(env)
    LogStruct.handle_exception(invalid_auth_token_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, [CSRF_HTML]]
  rescue => error
    # Extract request context for error reporting
    context = extract_request_context(env)

    # Create and log a structured exception with request context
    exception_log = Log::Error.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