Skip to content

Commit 6299194

Browse files
committed
Protect entries creation API endpoint with basic auth password.
1 parent e5e640a commit 6299194

File tree

7 files changed

+180
-2
lines changed

7 files changed

+180
-2
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@
2323
/public/packs-test
2424
/node_modules
2525
/spec/examples.txt
26+
27+
/config/credentials/development.key
28+
29+
/config/credentials/test.key
30+
31+
/config/credentials/production.key

app/controllers/pitboss_entries_controller.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
class PitbossEntriesController < ApplicationController
22
skip_before_action :verify_authenticity_token
3+
before_action :authenticate_with_password, only: [:create]
34

45
def index
56
@pitboss_entries = PitbossEntry.order("id ASC").paginate(page: params[:page], per_page: 30)
@@ -12,6 +13,16 @@ def create
1213

1314
private
1415

16+
def authenticate_with_password
17+
expected_password = Rails.application.credentials.pitboss_entries_password
18+
19+
return head :unauthorized unless expected_password
20+
21+
authenticate_or_request_with_http_basic do |username, password|
22+
password == expected_password
23+
end
24+
end
25+
1526
def pitboss_entry_params
1627
params.require(:pitboss_entry).permit(:game_name, :entry_type, :value, :timestamp)
1728
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
w5bT8eQYGizNXDKFUrYlXbvZIDbddrbJiN4qarVgHDgsq0EuWb2aJPTKnZ5IpyT0KsDuQOkezZucHs2pIvL05PkXpPq++s/R9Liq7mk4F67+bpC8gs7tDnFQKMH+K4d1iD3j3ujCaB2KfH6LgFDDD/Ra--u9XjoFdxLJzb9NB3--74vhM3pmCx6gqfgMXV4LUQ==
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GOM2YhEQ5YUWCKZr9YAOssSye2uYmXEKz2BmPxj2TinRE9yo+N1NNWhMsiACyWjXmLeOtmSOEKGBos1GhjGBTg4UPuTN8P8mfGqagA8fiFV39kEc+aSmbO6QpBk8HYY0C/rP8bLZ5m19ULGy8GfAlHPLLhlppHEQ6bMaWjtQ1qoGzU975n3aiCKPK62PlUPHQTMHUhpn9TKSTjl2mgO3sTQCDSuCQ8bQRpPBI1Sb0TwVvsPSMpIUMWPU7VyH1DpGK2Yp8MdKluevTwesNCJi+8AtOkkpJgTyUgIt--zN3Bu6CmDiXo7sQy--UFC0/xxK2zC3ppMIB00xVw==

config/credentials/test.yml.enc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BvuC1bI4KFd3/SJGYOGD8JVIDqJOjidd0f/gUtabTB/ph/s7L5QgqfocT1mhreIUzng1GPeIl4cwKUKIM1+C7cu4AEyXPLKi1FpG4CRqKrSBmRSrvy38KwiTLmPTxB1EbJYuDTTq4Npzvkc=--6BYbTULSIbs+nYkl--GBBeNObL80Kq5eHldhyJ+Q==

logs_parser/lib/logs_parser.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ class HttpAdapter
7373
NetworkError = Class.new(StandardError)
7474
ServerError = Class.new(StandardError)
7575

76-
def initialize(host:)
76+
def initialize(host:, password: nil)
7777
@http = Net::HTTP.new(host)
78+
@password = password
7879
end
7980

8081
def send_data(payload)
@@ -87,6 +88,11 @@ def send_data(payload)
8788
"pitboss_entry[timestamp]" => payload.timestamp
8889
}
8990
)
91+
92+
if @password
93+
request.basic_auth("", @password)
94+
end
95+
9096
response = http.request(request)
9197
raise ServerError, response.code unless response.code_type == Net::HTTPNoContent
9298
return response
@@ -104,6 +110,6 @@ def send_data(payload)
104110

105111
private
106112

107-
attr_reader :http
113+
attr_reader :http, :password
108114
end
109115
end

script/pbs5.rb

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
require "net/http"
2+
require_relative "../logs_parser/lib/logs_parser"
3+
4+
class LogFileMonitor
5+
def initialize(log_file_path, initial_position = 0)
6+
@log_file_path = log_file_path
7+
@file_position = initial_position
8+
@last_size = 0
9+
@last_mtime = Time.at(0)
10+
@file_handle = nil
11+
end
12+
13+
def file_changed?
14+
return false unless File.exist?(@log_file_path)
15+
16+
stat = File.stat(@log_file_path)
17+
size_changed = stat.size != @last_size
18+
mtime_changed = stat.mtime != @last_mtime
19+
20+
@last_size = stat.size
21+
@last_mtime = stat.mtime
22+
23+
size_changed || mtime_changed
24+
end
25+
26+
def file_rotated?
27+
return false unless File.exist?(@log_file_path)
28+
29+
stat = File.stat(@log_file_path)
30+
stat.size < @file_position
31+
end
32+
33+
def read_new_lines
34+
return [] unless File.exist?(@log_file_path)
35+
36+
if file_rotated?
37+
puts "Log file rotated, starting from beginning"
38+
@file_position = 0
39+
close_file
40+
end
41+
42+
open_file
43+
@file_handle.seek(@file_position)
44+
45+
new_lines = []
46+
while line = @file_handle.gets
47+
new_lines << line.chomp
48+
end
49+
50+
@file_position = @file_handle.tell
51+
new_lines
52+
end
53+
54+
def close
55+
close_file
56+
end
57+
58+
private
59+
60+
def open_file
61+
unless @file_handle && !@file_handle.closed?
62+
@file_handle = File.open(@log_file_path, "r")
63+
end
64+
end
65+
66+
def close_file
67+
if @file_handle && !@file_handle.closed?
68+
@file_handle.close
69+
@file_handle = nil
70+
end
71+
end
72+
end
73+
74+
def main
75+
if ARGV.length < 3
76+
puts "Usage: ruby pbs5.rb <game_name> <players_count> <pitboss_entries_password> [initial_position] [host]"
77+
puts "Example: ruby pbs5.rb my_game 4 secret_password 0 fierce-reaches-40697.herokuapp.com"
78+
exit 1
79+
end
80+
81+
game_name = ARGV[0]
82+
players_count = ARGV[1].to_i
83+
password = ARGV[2]
84+
initial_position = ARGV[3].to_i || 0
85+
host = ARGV[4] || "fierce-reaches-40697.herokuapp.com"
86+
log_file_path = "net_message_debug.log"
87+
88+
unless game_name && players_count > 0 && password
89+
puts "Error: game_name, players_count, and password are required"
90+
exit 1
91+
end
92+
93+
parser = LogsParser::Service.new(game_name, players_count)
94+
http_adapter = LogsParser::HttpAdapter.new(host: host, password: password)
95+
monitor = LogFileMonitor.new(log_file_path, initial_position)
96+
97+
iterations_counter = 0
98+
processed_lines = 0
99+
100+
puts "Starting log monitor for #{game_name} (#{players_count} players)"
101+
puts "Initial position: #{initial_position}"
102+
puts "Host: #{host}"
103+
puts "Authentication: enabled"
104+
105+
begin
106+
loop do
107+
iterations_counter += 1
108+
109+
if monitor.file_changed?
110+
puts "Iteration #{iterations_counter} - File changed, processing new lines..."
111+
112+
new_lines = monitor.read_new_lines
113+
114+
if new_lines.empty?
115+
puts "No new lines to process"
116+
else
117+
puts "Processing #{new_lines.count} new lines..."
118+
119+
new_lines.each do |line|
120+
begin
121+
if result = parser.call(line)
122+
response = http_adapter.send_data(result)
123+
puts "Sent: #{result.entry_type} - #{response.code}"
124+
processed_lines += 1
125+
end
126+
rescue LogsParser::HttpAdapter::NetworkError, LogsParser::HttpAdapter::ServerError => e
127+
puts "Error sending data: #{e.class} - #{e.message}"
128+
puts "Will retry on next iteration"
129+
end
130+
end
131+
end
132+
133+
puts "Current position: #{monitor.instance_variable_get(:@file_position)}"
134+
puts "Total processed lines: #{processed_lines}"
135+
else
136+
puts "Iteration #{iterations_counter} - No changes detected"
137+
end
138+
139+
sleep 5
140+
end
141+
rescue Interrupt
142+
puts "\nShutting down gracefully..."
143+
ensure
144+
monitor.close
145+
puts "Final position: #{monitor.instance_variable_get(:@file_position)}"
146+
puts "Total processed lines: #{processed_lines}"
147+
end
148+
end
149+
150+
if __FILE__ == $0
151+
main
152+
end

0 commit comments

Comments
 (0)