First commit!
This commit is contained in:
75
app/controllers/api/events_controller.rb
Normal file
75
app/controllers/api/events_controller.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::EventsController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
# POST /api/:project_id/events
|
||||
def create
|
||||
project = authenticate_project!
|
||||
return head :not_found unless project
|
||||
|
||||
# Parse the incoming WAF event data
|
||||
event_data = parse_event_data(request)
|
||||
|
||||
# Create event asynchronously
|
||||
ProcessWafEventJob.perform_later(
|
||||
project_id: project.id,
|
||||
event_data: event_data,
|
||||
headers: extract_serializable_headers(request)
|
||||
)
|
||||
|
||||
# Always return 200 OK to avoid agent retries
|
||||
head :ok
|
||||
rescue DsnAuthenticationService::AuthenticationError => e
|
||||
Rails.logger.warn "DSN authentication failed: #{e.message}"
|
||||
head :unauthorized
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "Invalid JSON in event data: #{e.message}"
|
||||
head :bad_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_project!
|
||||
DsnAuthenticationService.authenticate(request, params[:project_id])
|
||||
end
|
||||
|
||||
def parse_event_data(request)
|
||||
# Handle different content types
|
||||
content_type = request.content_type || "application/json"
|
||||
|
||||
case content_type
|
||||
when /application\/json/
|
||||
JSON.parse(request.body.read)
|
||||
when /application\/x-www-form-urlencoded/
|
||||
# Convert form data to JSON-like hash
|
||||
request.request_parameters
|
||||
else
|
||||
# Try to parse as JSON anyway
|
||||
JSON.parse(request.body.read)
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to parse event data: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
request.body.rewind if request.body.respond_to?(:rewind)
|
||||
end
|
||||
|
||||
def extract_serializable_headers(request)
|
||||
# Only extract the headers we need for analytics, avoiding IO objects
|
||||
important_headers = %w[
|
||||
User-Agent Content-Type Content-Length Accept
|
||||
X-Forwarded-For X-Real-IP X-Forwarded-Proto
|
||||
Authorization X-Baffle-Auth X-Sentry-Auth
|
||||
Referer Accept-Language Accept-Encoding
|
||||
]
|
||||
|
||||
headers = {}
|
||||
important_headers.each do |header|
|
||||
value = request.headers[header]
|
||||
headers[header] = value if value.present?
|
||||
end
|
||||
|
||||
headers
|
||||
end
|
||||
end
|
||||
9
app/controllers/application_controller.rb
Normal file
9
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
||||
allow_browser versions: :modern
|
||||
|
||||
# Changes to the importmap will invalidate the etag for HTML responses
|
||||
stale_when_importmap_changes
|
||||
|
||||
include Pagy::Backend
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
33
app/controllers/events_controller.rb
Normal file
33
app/controllers/events_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class EventsController < ApplicationController
|
||||
before_action :set_project
|
||||
|
||||
def index
|
||||
@events = @project.events.order(timestamp: :desc)
|
||||
Rails.logger.debug "Found project? #{@project.name} / #{@project.events.count} / #{@events.count}"
|
||||
Rails.logger.debug "Action: #{params[:waf_action]}"
|
||||
# Apply filters
|
||||
@events = @events.by_ip(params[:ip]) if params[:ip].present?
|
||||
@events = @events.by_waf_action(params[:waf_action]) if params[:waf_action].present?
|
||||
@events = @events.where(country_code: params[:country]) if params[:country].present?
|
||||
|
||||
Rails.logger.debug "after filter #{@project.name} / #{@project.events.count} / #{@events.count}"
|
||||
# Debug info
|
||||
Rails.logger.debug "Events count before pagination: #{@events.count}"
|
||||
Rails.logger.debug "Project: #{@project&.name} (ID: #{@project&.id})"
|
||||
|
||||
# Paginate
|
||||
@pagy, @events = pagy(@events, items: 50)
|
||||
|
||||
Rails.logger.debug "Events count after pagination: #{@events.count}"
|
||||
Rails.logger.debug "Pagy info: #{@pagy.count} total, #{@pagy.pages} pages"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_project
|
||||
@project = Project.find(params[:project_id]) || Project.find_by(slug: params[:project_id])
|
||||
redirect_to projects_path, alert: "Project not found" unless @project
|
||||
end
|
||||
end
|
||||
99
app/controllers/projects_controller.rb
Normal file
99
app/controllers/projects_controller.rb
Normal file
@@ -0,0 +1,99 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProjectsController < ApplicationController
|
||||
before_action :set_project, only: [:show, :edit, :update, :events, :analytics]
|
||||
|
||||
def index
|
||||
@projects = Project.order(created_at: :desc)
|
||||
end
|
||||
|
||||
def show
|
||||
@recent_events = @project.recent_events(limit: 10)
|
||||
@event_count = @project.event_count(24.hours.ago)
|
||||
@blocked_count = @project.blocked_count(24.hours.ago)
|
||||
@waf_status = @project.waf_status
|
||||
end
|
||||
|
||||
def new
|
||||
@project = Project.new
|
||||
end
|
||||
|
||||
def create
|
||||
@project = Project.new(project_params)
|
||||
|
||||
if @project.save
|
||||
redirect_to @project, notice: "Project was successfully created. Use this DSN for your baffle-agent: #{@project.dsn}"
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @project.update(project_params)
|
||||
redirect_to @project, notice: "Project was successfully updated."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def events
|
||||
@events = @project.events.recent.includes(:project)
|
||||
|
||||
# Apply filters
|
||||
@events = @events.by_ip(params[:ip]) if params[:ip].present?
|
||||
@events = @events.by_action(params[:action]) if params[:action].present?
|
||||
@events = @events.where(country_code: params[:country]) if params[:country].present?
|
||||
|
||||
# Debug info
|
||||
Rails.logger.debug "Events count before pagination: #{@events.count}"
|
||||
Rails.logger.debug "Project: #{@project&.name} (ID: #{@project&.id})"
|
||||
|
||||
# Paginate
|
||||
@pagy, @events = pagy(@events, items: 50)
|
||||
|
||||
Rails.logger.debug "Events count after pagination: #{@events.count}"
|
||||
Rails.logger.debug "Pagy info: #{@pagy.count} total, #{@pagy.pages} pages"
|
||||
end
|
||||
|
||||
def analytics
|
||||
@time_range = params[:time_range]&.to_i || 24 # hours
|
||||
|
||||
# Basic analytics
|
||||
@total_events = @project.event_count(@time_range.hours.ago)
|
||||
@blocked_events = @project.blocked_count(@time_range.hours.ago)
|
||||
@allowed_events = @project.allowed_count(@time_range.hours.ago)
|
||||
|
||||
# Top blocked IPs
|
||||
@top_blocked_ips = @project.top_blocked_ips(limit: 10, time_range: @time_range.hours.ago)
|
||||
|
||||
# Country distribution
|
||||
@country_stats = @project.events
|
||||
.where(timestamp: @time_range.hours.ago..Time.current)
|
||||
.where.not(country_code: nil)
|
||||
.group(:country_code)
|
||||
.select('country_code, COUNT(*) as count')
|
||||
.order('count DESC')
|
||||
.limit(10)
|
||||
|
||||
# Action distribution
|
||||
@action_stats = @project.events
|
||||
.where(timestamp: @time_range.hours.ago..Time.current)
|
||||
.group(:action)
|
||||
.select('action, COUNT(*) as count')
|
||||
.order('count DESC')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_project
|
||||
@project = Project.find_by(slug: params[:id]) || Project.find_by(id: params[:id])
|
||||
redirect_to projects_path, alert: "Project not found" unless @project
|
||||
end
|
||||
|
||||
def project_params
|
||||
params.require(:project).permit(:name, :enabled, settings: {})
|
||||
end
|
||||
end
|
||||
53
app/controllers/rule_sets_controller.rb
Normal file
53
app/controllers/rule_sets_controller.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RuleSetsController < ApplicationController
|
||||
before_action :set_rule_set, only: [:show, :edit, :update, :push_to_agents]
|
||||
|
||||
def index
|
||||
@rule_sets = RuleSet.includes(:rules).by_priority
|
||||
end
|
||||
|
||||
def show
|
||||
@rules = @rule_set.rules.includes(:rule_set).by_priority
|
||||
end
|
||||
|
||||
def new
|
||||
@rule_set = RuleSet.new
|
||||
end
|
||||
|
||||
def create
|
||||
@rule_set = RuleSet.new(rule_set_params)
|
||||
|
||||
if @rule_set.save
|
||||
redirect_to @rule_set, notice: "Rule set was successfully created."
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @rule_set.update(rule_set_params)
|
||||
redirect_to @rule_set, notice: "Rule set was successfully updated."
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def push_to_agents
|
||||
@rule_set.push_to_agents!
|
||||
redirect_to @rule_set, notice: "Rule set pushed to agents successfully."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_rule_set
|
||||
@rule_set = RuleSet.find_by(slug: params[:id]) || RuleSet.find(params[:id])
|
||||
end
|
||||
|
||||
def rule_set_params
|
||||
params.require(:rule_set).permit(:name, :description, :enabled, :priority)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user