trackguard 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/trackguard/admin.css +9 -0
- data/app/controllers/concerns/trackguard/page_tracker.rb +10 -2
- data/app/helpers/trackguard/application_helper.rb +15 -5
- data/app/jobs/trackguard/detect_suspicious_visitors_job.rb +14 -1
- data/app/jobs/trackguard/hub/submit_blocked_request_job.rb +39 -0
- data/app/jobs/trackguard/hub/submit_page_view_job.rb +39 -0
- data/app/jobs/trackguard/track_blocked_request_job.rb +1 -14
- data/app/services/trackguard/track_blocked_request.rb +32 -0
- data/app/views/trackguard/admin/_stats_panel.html.erb +1 -1
- data/config/importmap.rb +3 -1
- data/config/routes.rb +2 -0
- data/lib/trackguard/adapters/base.rb +14 -4
- data/lib/trackguard/adapters/hub.rb +58 -22
- data/lib/trackguard/adapters/local.rb +4 -4
- data/lib/trackguard/engine.rb +5 -1
- data/lib/trackguard/rack_attack.rb +7 -11
- data/lib/trackguard/trace_id_middleware.rb +14 -0
- data/lib/trackguard/version.rb +1 -1
- data/lib/trackguard.rb +97 -6
- metadata +5 -2
- data/app/helpers/trackguard/hub_helper.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49c01824d558ae2b9416ed2451b7ecc9fd864a93979afb91ea1bdce297c198b2
|
|
4
|
+
data.tar.gz: 429ff2b09df7c9d9571ac7a863e3200a80b6ee4a50028284af0132d18b4b2b79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7da72f474a80e8b89b5549de51a3ebefa6cba99da4b9b1045b7d860b2628f10ac9950efcd7acdec94d438e1f72544e7c5189335285025c5c872a64b6e8d616ea
|
|
7
|
+
data.tar.gz: 7ce7519b8998ce10beab775c922e7c965cb017bfa4852a843173cbc586e0c8c3af95cf6df397a12b270869b6de04411c81c958fa53966f8185b75d47d5821a89
|
|
@@ -240,6 +240,7 @@
|
|
|
240
240
|
/* ── Table ─────────────────────────────────────────────────────────── */
|
|
241
241
|
.tg-table {
|
|
242
242
|
width: 100%;
|
|
243
|
+
table-layout: fixed;
|
|
243
244
|
border-collapse: collapse;
|
|
244
245
|
font-size: 0.875rem;
|
|
245
246
|
margin-bottom: 2rem;
|
|
@@ -269,6 +270,14 @@
|
|
|
269
270
|
text-align: right;
|
|
270
271
|
color: #9ca3af;
|
|
271
272
|
font-weight: 400;
|
|
273
|
+
white-space: nowrap;
|
|
274
|
+
width: 3.5rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.tg-td--label {
|
|
278
|
+
overflow: hidden;
|
|
279
|
+
text-overflow: ellipsis;
|
|
280
|
+
white-space: nowrap;
|
|
272
281
|
}
|
|
273
282
|
|
|
274
283
|
.tg-td--break { word-break: break-all; }
|
|
@@ -15,10 +15,11 @@ module Trackguard
|
|
|
15
15
|
private
|
|
16
16
|
|
|
17
17
|
def set_trace_id
|
|
18
|
-
@trace_id =
|
|
18
|
+
@trace_id = request.env["trackguard.trace_id"]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def track_page_view
|
|
22
|
+
return unless Trackguard.tracking_enabled?
|
|
22
23
|
return unless request.get? || request.head?
|
|
23
24
|
return unless request.format.html?
|
|
24
25
|
|
|
@@ -31,10 +32,17 @@ module Trackguard
|
|
|
31
32
|
trace_id: @trace_id,
|
|
32
33
|
source: extract_source,
|
|
33
34
|
tracking_layer: "backend",
|
|
34
|
-
http_method: request.request_method
|
|
35
|
+
http_method: request.request_method,
|
|
36
|
+
prefetch: turbo_prefetch?
|
|
35
37
|
)
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
def turbo_prefetch?
|
|
41
|
+
request.headers["Purpose"] == "prefetch" ||
|
|
42
|
+
request.headers["Sec-Purpose"]&.start_with?("prefetch") ||
|
|
43
|
+
request.headers["X-Sec-Purpose"]&.start_with?("prefetch")
|
|
44
|
+
end
|
|
45
|
+
|
|
38
46
|
def extract_source
|
|
39
47
|
raw = params[:ref].presence || params[:utm_source].presence
|
|
40
48
|
raw && raw.strip.downcase.first(64)
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
module Trackguard
|
|
2
2
|
module ApplicationHelper
|
|
3
|
-
def
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
def trackguard_header_tags
|
|
4
|
+
return "".html_safe unless Trackguard.tracking_enabled?
|
|
5
|
+
|
|
6
|
+
tags = [ tag.meta(name: "trace-id", content: @trace_id) ]
|
|
7
|
+
|
|
8
|
+
case Trackguard.adapter
|
|
9
|
+
when Trackguard::Adapters::Local
|
|
10
|
+
tags << tag.meta(name: "trackguard-url", content: trackguard.page_views_path)
|
|
11
|
+
when Trackguard::Adapters::Hub
|
|
12
|
+
if Rails.env.production?
|
|
13
|
+
tags << tag.script(src: "#{Trackguard.hub_url}/track.js", data: { api_key: Trackguard.hub_api_key })
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
safe_join(tags, "\n")
|
|
8
18
|
end
|
|
9
19
|
|
|
10
20
|
def trackguard_nav_links
|
|
@@ -40,7 +40,7 @@ module Trackguard
|
|
|
40
40
|
def analyze_visitor(visitor, views)
|
|
41
41
|
count = views.size
|
|
42
42
|
return if count.zero?
|
|
43
|
-
return if visitor
|
|
43
|
+
return if whitelisted?(visitor)
|
|
44
44
|
|
|
45
45
|
name = name_from_ua(visitor.user_agent)
|
|
46
46
|
|
|
@@ -183,6 +183,19 @@ module Trackguard
|
|
|
183
183
|
)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
+
protected
|
|
187
|
+
|
|
188
|
+
def whitelisted?(visitor)
|
|
189
|
+
wl = visitor.whitelisted_ip
|
|
190
|
+
unless wl
|
|
191
|
+
wl = WhitelistedIp.active.find_by(ip: visitor.ip)
|
|
192
|
+
wl&.update!(visitor: visitor)
|
|
193
|
+
end
|
|
194
|
+
wl&.active?
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
186
199
|
def name_from_ua(user_agent)
|
|
187
200
|
BlockedUserAgent.matching_pattern(user_agent)
|
|
188
201
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Trackguard
|
|
7
|
+
module Hub
|
|
8
|
+
class SubmitBlockedRequestJob < ApplicationJob
|
|
9
|
+
queue_as :default
|
|
10
|
+
|
|
11
|
+
def perform(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
12
|
+
uri = URI("#{Trackguard.hub_url}/api/backend/blocked_requests")
|
|
13
|
+
body = {
|
|
14
|
+
blocked_request: {
|
|
15
|
+
ip: ip, user_agent: user_agent, path: path,
|
|
16
|
+
http_method: http_method, block_reason: block_reason
|
|
17
|
+
}
|
|
18
|
+
}.to_json
|
|
19
|
+
|
|
20
|
+
req = Net::HTTP::Post.new(uri)
|
|
21
|
+
req["X-Api-Key"] = Trackguard.hub_api_key
|
|
22
|
+
req["Authorization"] = "Bearer #{Trackguard.hub_secret_key}"
|
|
23
|
+
req["Content-Type"] = "application/json"
|
|
24
|
+
req.body = body
|
|
25
|
+
|
|
26
|
+
opts = { use_ssl: uri.scheme == "https", open_timeout: 3, read_timeout: 5 }
|
|
27
|
+
response = Net::HTTP.start(uri.hostname, uri.port, **opts) { |http| http.request(req) }
|
|
28
|
+
|
|
29
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
30
|
+
Rails.logger.warn(
|
|
31
|
+
"[Trackguard::Hub::SubmitBlockedRequestJob] Unexpected response #{response.code} for path=#{path}"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Rails.logger.warn("[Trackguard::Hub::SubmitBlockedRequestJob] Failed to submit blocked request: #{e.message}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Trackguard
|
|
7
|
+
module Hub
|
|
8
|
+
class SubmitPageViewJob < ApplicationJob
|
|
9
|
+
queue_as :default
|
|
10
|
+
|
|
11
|
+
def perform(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, http_method:, **)
|
|
12
|
+
uri = URI("#{Trackguard.hub_url}/api/backend/page_views")
|
|
13
|
+
body = {
|
|
14
|
+
page_view: {
|
|
15
|
+
path: path, referer: referer, session_id: session_id, trace_id: trace_id,
|
|
16
|
+
source: source, http_method: http_method, ip: ip, user_agent: user_agent
|
|
17
|
+
}
|
|
18
|
+
}.to_json
|
|
19
|
+
|
|
20
|
+
req = Net::HTTP::Post.new(uri)
|
|
21
|
+
req["X-Api-Key"] = Trackguard.hub_api_key
|
|
22
|
+
req["Authorization"] = "Bearer #{Trackguard.hub_secret_key}"
|
|
23
|
+
req["Content-Type"] = "application/json"
|
|
24
|
+
req.body = body
|
|
25
|
+
|
|
26
|
+
opts = { use_ssl: uri.scheme == "https", open_timeout: 3, read_timeout: 5 }
|
|
27
|
+
response = Net::HTTP.start(uri.hostname, uri.port, **opts) { |http| http.request(req) }
|
|
28
|
+
|
|
29
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
30
|
+
Rails.logger.warn(
|
|
31
|
+
"[Trackguard::Hub::SubmitPageViewJob] Unexpected response #{response.code} for path=#{path}"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Rails.logger.warn("[Trackguard::Hub::SubmitPageViewJob] Failed to submit page view: #{e.message}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -5,20 +5,7 @@ module Trackguard
|
|
|
5
5
|
queue_as :default
|
|
6
6
|
|
|
7
7
|
def perform(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
8
|
-
|
|
9
|
-
v.user_agent = user_agent
|
|
10
|
-
v.first_seen_at = Time.current
|
|
11
|
-
v.last_seen_at = Time.current
|
|
12
|
-
end
|
|
13
|
-
visitor.update!(last_seen_at: Time.current, user_agent: user_agent)
|
|
14
|
-
|
|
15
|
-
BlockedRequest.create!(
|
|
16
|
-
path: path,
|
|
17
|
-
user_agent: user_agent,
|
|
18
|
-
http_method: http_method,
|
|
19
|
-
block_reason: block_reason,
|
|
20
|
-
visitor: visitor
|
|
21
|
-
)
|
|
8
|
+
TrackBlockedRequest.call(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
22
9
|
end
|
|
23
10
|
end
|
|
24
11
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trackguard
|
|
4
|
+
class TrackBlockedRequest < ApplicationService
|
|
5
|
+
def initialize(ip:, user_agent:, path:, http_method:, block_reason:, visitor_scope: {})
|
|
6
|
+
@ip = ip
|
|
7
|
+
@user_agent = user_agent
|
|
8
|
+
@path = path
|
|
9
|
+
@http_method = http_method
|
|
10
|
+
@block_reason = block_reason
|
|
11
|
+
@visitor_scope = visitor_scope
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
visitor = Visitor.find_or_create_by!(ip: @ip, **@visitor_scope) do |v|
|
|
16
|
+
v.user_agent = @user_agent
|
|
17
|
+
v.first_seen_at = Time.current
|
|
18
|
+
v.last_seen_at = Time.current
|
|
19
|
+
end
|
|
20
|
+
visitor.update!(last_seen_at: Time.current, user_agent: @user_agent)
|
|
21
|
+
|
|
22
|
+
BlockedRequest.create!(
|
|
23
|
+
path: @path,
|
|
24
|
+
user_agent: @user_agent,
|
|
25
|
+
http_method: @http_method,
|
|
26
|
+
block_reason: @block_reason,
|
|
27
|
+
visitor: visitor,
|
|
28
|
+
**@visitor_scope
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/config/importmap.rb
CHANGED
data/config/routes.rb
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
module Trackguard
|
|
4
4
|
module Adapters
|
|
5
5
|
class Base
|
|
6
|
+
def validate! = nil
|
|
7
|
+
|
|
6
8
|
def blocked_user_agent?(user_agent) = raise NotImplementedError, "#{self.class}#blocked_user_agent?"
|
|
7
9
|
def blocked_path?(path) = raise NotImplementedError, "#{self.class}#blocked_path?"
|
|
8
10
|
def whitelisted_ip?(ip) = raise NotImplementedError, "#{self.class}#whitelisted_ip?"
|
|
9
11
|
def flagged_visitor?(ip) = raise NotImplementedError, "#{self.class}#flagged_visitor?"
|
|
10
12
|
|
|
11
13
|
def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, tracking_layer:,
|
|
12
|
-
http_method:)
|
|
14
|
+
http_method:, prefetch: false)
|
|
15
|
+
return unless Trackguard.tracking_enabled?
|
|
13
16
|
return if blocked_user_agent?(user_agent)
|
|
14
17
|
return if blocked_path?(path)
|
|
15
18
|
return if path.blank? || path.start_with?(Trackguard.admin_path)
|
|
@@ -17,20 +20,27 @@ module Trackguard
|
|
|
17
20
|
perform_track_page_view(
|
|
18
21
|
path: path, ip: ip, user_agent: user_agent, referer: referer,
|
|
19
22
|
session_id: session_id, trace_id: trace_id, source: source,
|
|
20
|
-
tracking_layer: tracking_layer, http_method: http_method
|
|
23
|
+
tracking_layer: tracking_layer, http_method: http_method, prefetch: prefetch
|
|
21
24
|
)
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
25
|
-
|
|
28
|
+
return unless Trackguard.tracking_enabled?
|
|
29
|
+
|
|
30
|
+
perform_track_blocked_request(ip: ip, user_agent: user_agent, path: path,
|
|
31
|
+
http_method: http_method, block_reason: block_reason)
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
protected
|
|
29
35
|
|
|
30
36
|
def perform_track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:,
|
|
31
|
-
tracking_layer:, http_method:)
|
|
37
|
+
tracking_layer:, http_method:, prefetch: false)
|
|
32
38
|
raise NotImplementedError, "#{self.class}#perform_track_page_view"
|
|
33
39
|
end
|
|
40
|
+
|
|
41
|
+
def perform_track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
42
|
+
raise NotImplementedError, "#{self.class}#perform_track_blocked_request"
|
|
43
|
+
end
|
|
34
44
|
end
|
|
35
45
|
end
|
|
36
46
|
end
|
|
@@ -10,6 +10,33 @@ module Trackguard
|
|
|
10
10
|
STALE_KEY = "trackguard/hub_rules/stale"
|
|
11
11
|
ETAG_KEY = "trackguard/hub_rules/etag"
|
|
12
12
|
|
|
13
|
+
def validate!
|
|
14
|
+
return unless Trackguard.tracking_enabled?
|
|
15
|
+
|
|
16
|
+
missing = []
|
|
17
|
+
missing << "hub_api_key" if Trackguard.hub_api_key.blank?
|
|
18
|
+
missing << "hub_secret_key" if Trackguard.hub_secret_key.blank?
|
|
19
|
+
return if missing.empty?
|
|
20
|
+
|
|
21
|
+
raise Trackguard::ConfigurationError, <<~MSG.strip
|
|
22
|
+
Trackguard Hub adapter is missing required credentials: #{missing.join(', ')}.
|
|
23
|
+
|
|
24
|
+
Set them in your initializer:
|
|
25
|
+
|
|
26
|
+
Trackguard.configure do |c|
|
|
27
|
+
c.adapter = :hub
|
|
28
|
+
c.hub_api_key = ENV["TRACKGUARD_API_KEY"]
|
|
29
|
+
c.hub_secret_key = ENV["TRACKGUARD_SECRET_KEY"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Sign up at https://trackguard.dev to create a project and generate credentials.
|
|
33
|
+
|
|
34
|
+
To use local tracking instead (no external service needed):
|
|
35
|
+
|
|
36
|
+
Trackguard.configure { |c| c.adapter = :local }
|
|
37
|
+
MSG
|
|
38
|
+
end
|
|
39
|
+
|
|
13
40
|
def blocked_user_agent?(user_agent)
|
|
14
41
|
rules.fetch(:blocked_user_agents, []).any? do |p|
|
|
15
42
|
user_agent.to_s.downcase.include?(p.downcase)
|
|
@@ -30,15 +57,23 @@ module Trackguard
|
|
|
30
57
|
rules.fetch(:flagged_ips, []).include?(ip)
|
|
31
58
|
end
|
|
32
59
|
|
|
33
|
-
def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
34
|
-
# placeholder: hub adapter does not persist blocked requests locally
|
|
35
|
-
end
|
|
36
|
-
|
|
37
60
|
protected
|
|
38
61
|
|
|
62
|
+
def perform_track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
63
|
+
Trackguard::Hub::SubmitBlockedRequestJob.perform_later(
|
|
64
|
+
ip: ip, user_agent: user_agent, path: path, http_method: http_method, block_reason: block_reason
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
39
68
|
def perform_track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:,
|
|
40
|
-
tracking_layer:, http_method:)
|
|
41
|
-
|
|
69
|
+
tracking_layer:, http_method:, prefetch: false)
|
|
70
|
+
return if prefetch
|
|
71
|
+
|
|
72
|
+
Trackguard::Hub::SubmitPageViewJob.perform_later(
|
|
73
|
+
path: path, ip: ip, user_agent: user_agent, referer: referer,
|
|
74
|
+
session_id: session_id, trace_id: trace_id, source: source,
|
|
75
|
+
tracking_layer: tracking_layer, http_method: http_method
|
|
76
|
+
)
|
|
42
77
|
end
|
|
43
78
|
|
|
44
79
|
private
|
|
@@ -55,22 +90,8 @@ module Trackguard
|
|
|
55
90
|
end
|
|
56
91
|
|
|
57
92
|
def fetch_rules_from_hub
|
|
58
|
-
uri = URI("#{Trackguard.hub_url}/api/rules")
|
|
59
|
-
|
|
60
|
-
request["Authorization"] = "Bearer #{Trackguard.hub_secret_key}"
|
|
61
|
-
request["Accept"] = "application/json"
|
|
62
|
-
|
|
63
|
-
etag = Rails.cache.read(ETAG_KEY)
|
|
64
|
-
request["If-None-Match"] = etag if etag
|
|
65
|
-
|
|
66
|
-
response = Net::HTTP.start(
|
|
67
|
-
uri.hostname, uri.port,
|
|
68
|
-
use_ssl: uri.scheme == "https",
|
|
69
|
-
open_timeout: 3,
|
|
70
|
-
read_timeout: 5
|
|
71
|
-
) do |http|
|
|
72
|
-
http.request(request)
|
|
73
|
-
end
|
|
93
|
+
uri = URI("#{Trackguard.hub_url}/api/backend/rules")
|
|
94
|
+
response = execute_http(uri, build_request(uri))
|
|
74
95
|
|
|
75
96
|
return Rails.cache.read(STALE_KEY) || {} if response.is_a?(Net::HTTPNotModified)
|
|
76
97
|
|
|
@@ -80,6 +101,21 @@ module Trackguard
|
|
|
80
101
|
Rails.cache.write(ETAG_KEY, response["ETag"], expires_in: 24.hours) if response["ETag"]
|
|
81
102
|
fresh
|
|
82
103
|
end
|
|
104
|
+
|
|
105
|
+
def build_request(uri)
|
|
106
|
+
req = Net::HTTP::Get.new(uri)
|
|
107
|
+
req["Authorization"] = "Bearer #{Trackguard.hub_secret_key}"
|
|
108
|
+
req["X-Api-Key"] = Trackguard.hub_api_key
|
|
109
|
+
req["Accept"] = "application/json"
|
|
110
|
+
etag = Rails.cache.read(ETAG_KEY)
|
|
111
|
+
req["If-None-Match"] = etag if etag
|
|
112
|
+
req
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def execute_http(uri, request)
|
|
116
|
+
opts = { use_ssl: uri.scheme == "https", open_timeout: 3, read_timeout: 5 }
|
|
117
|
+
Net::HTTP.start(uri.hostname, uri.port, **opts) { |http| http.request(request) }
|
|
118
|
+
end
|
|
83
119
|
end
|
|
84
120
|
end
|
|
85
121
|
end
|
|
@@ -19,7 +19,9 @@ module Trackguard
|
|
|
19
19
|
Visitor.flagged?(ip)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def perform_track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
23
25
|
TrackBlockedRequestJob.perform_later(
|
|
24
26
|
ip: ip,
|
|
25
27
|
user_agent: user_agent,
|
|
@@ -29,10 +31,8 @@ module Trackguard
|
|
|
29
31
|
)
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
protected
|
|
33
|
-
|
|
34
34
|
def perform_track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:,
|
|
35
|
-
tracking_layer:, http_method:)
|
|
35
|
+
tracking_layer:, http_method:, prefetch: false)
|
|
36
36
|
TrackPageViewJob.perform_later(
|
|
37
37
|
path: path,
|
|
38
38
|
ip: ip,
|
data/lib/trackguard/engine.rb
CHANGED
|
@@ -6,7 +6,6 @@ module Trackguard
|
|
|
6
6
|
|
|
7
7
|
config.to_prepare do
|
|
8
8
|
ActionController::Base.helper Trackguard::ApplicationHelper
|
|
9
|
-
ActionController::Base.helper Trackguard::HubHelper
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
initializer "trackguard.assets" do |app|
|
|
@@ -17,7 +16,12 @@ module Trackguard
|
|
|
17
16
|
app.config.importmap.paths << root.join("config/importmap.rb") if app.config.respond_to?(:importmap)
|
|
18
17
|
end
|
|
19
18
|
|
|
19
|
+
initializer "trackguard.trace_id_middleware" do |app|
|
|
20
|
+
app.middleware.use Trackguard::TraceIdMiddleware
|
|
21
|
+
end
|
|
22
|
+
|
|
20
23
|
config.after_initialize do
|
|
24
|
+
Trackguard.adapter.validate!
|
|
21
25
|
Trackguard::RackAttack.configure
|
|
22
26
|
end
|
|
23
27
|
end
|
|
@@ -4,28 +4,25 @@ require "rack/attack"
|
|
|
4
4
|
|
|
5
5
|
module Trackguard
|
|
6
6
|
module RackAttack
|
|
7
|
-
# rubocop:disable Metrics/MethodLength
|
|
8
7
|
def self.configure
|
|
9
|
-
adapter = Trackguard.adapter
|
|
10
|
-
|
|
11
8
|
::Rack::Attack.safelist("trackguard/allow local") do |req|
|
|
12
9
|
[ "127.0.0.1", "::1" ].include?(req.ip)
|
|
13
10
|
end
|
|
14
11
|
|
|
15
12
|
::Rack::Attack.safelist("trackguard/allow whitelisted ips") do |req|
|
|
16
|
-
adapter.whitelisted_ip?(req.ip)
|
|
13
|
+
Trackguard.adapter.whitelisted_ip?(req.ip)
|
|
17
14
|
end
|
|
18
15
|
|
|
19
16
|
::Rack::Attack.blocklist("trackguard/block known scanners") do |req|
|
|
20
|
-
adapter.blocked_user_agent?(req.user_agent)
|
|
17
|
+
Trackguard.adapter.blocked_user_agent?(req.user_agent)
|
|
21
18
|
end
|
|
22
19
|
|
|
23
20
|
::Rack::Attack.blocklist("trackguard/block known paths") do |req|
|
|
24
|
-
adapter.blocked_path?(req.path)
|
|
21
|
+
Trackguard.adapter.blocked_path?(req.path)
|
|
25
22
|
end
|
|
26
23
|
|
|
27
24
|
::Rack::Attack.blocklist("trackguard/flagged visitors") do |req|
|
|
28
|
-
adapter.flagged_visitor?(req.ip)
|
|
25
|
+
Trackguard.adapter.flagged_visitor?(req.ip)
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
::Rack::Attack.throttle(
|
|
@@ -34,16 +31,15 @@ module Trackguard
|
|
|
34
31
|
period: Trackguard.throttle_period, &:ip
|
|
35
32
|
)
|
|
36
33
|
|
|
37
|
-
subscribe_to_blocked_requests
|
|
34
|
+
subscribe_to_blocked_requests
|
|
38
35
|
end
|
|
39
|
-
# rubocop:enable Metrics/MethodLength
|
|
40
36
|
|
|
41
|
-
def self.subscribe_to_blocked_requests
|
|
37
|
+
def self.subscribe_to_blocked_requests
|
|
42
38
|
@subscribe_to_blocked_requests ||= ActiveSupport::Notifications.subscribe("rack.attack") do |*, payload|
|
|
43
39
|
req = payload[:request]
|
|
44
40
|
next unless req.env["rack.attack.match_type"] == :blocklist
|
|
45
41
|
|
|
46
|
-
adapter.track_blocked_request(
|
|
42
|
+
Trackguard.adapter.track_blocked_request(
|
|
47
43
|
ip: req.ip,
|
|
48
44
|
user_agent: req.user_agent.to_s,
|
|
49
45
|
path: req.path,
|
data/lib/trackguard/version.rb
CHANGED
data/lib/trackguard.rb
CHANGED
|
@@ -3,15 +3,90 @@
|
|
|
3
3
|
require "trackguard/version"
|
|
4
4
|
require "trackguard/engine"
|
|
5
5
|
require "trackguard/rack_attack"
|
|
6
|
+
require "trackguard/trace_id_middleware"
|
|
6
7
|
require "trackguard/adapters/base"
|
|
7
8
|
require "trackguard/adapters/local"
|
|
8
9
|
require "trackguard/adapters/hub"
|
|
9
10
|
|
|
10
11
|
module Trackguard
|
|
12
|
+
class ConfigurationError < StandardError; end
|
|
13
|
+
|
|
11
14
|
class << self
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
def configure
|
|
16
|
+
@_configuring = true
|
|
17
|
+
yield self
|
|
18
|
+
ensure
|
|
19
|
+
@_configuring = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Setters — use configure block; direct assignment is deprecated.
|
|
23
|
+
|
|
24
|
+
def authenticate_admin_with=(value)
|
|
25
|
+
deprecated_direct_setter(:authenticate_admin_with)
|
|
26
|
+
@authenticate_admin_with = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def admin_layout=(value)
|
|
30
|
+
deprecated_direct_setter(:admin_layout)
|
|
31
|
+
@admin_layout = value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def admin_path=(value)
|
|
35
|
+
deprecated_direct_setter(:admin_path)
|
|
36
|
+
@admin_path = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def back_label=(value)
|
|
40
|
+
deprecated_direct_setter(:back_label)
|
|
41
|
+
@back_label = value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def local_api_token=(value)
|
|
45
|
+
deprecated_direct_setter(:local_api_token)
|
|
46
|
+
@local_api_token = value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def throttle_limit=(value)
|
|
50
|
+
deprecated_direct_setter(:throttle_limit)
|
|
51
|
+
@throttle_limit = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def throttle_period=(value)
|
|
55
|
+
deprecated_direct_setter(:throttle_period)
|
|
56
|
+
@throttle_period = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def hub_url=(value)
|
|
60
|
+
deprecated_direct_setter(:hub_url)
|
|
61
|
+
@hub_url = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def hub_secret_key=(value)
|
|
65
|
+
deprecated_direct_setter(:hub_secret_key)
|
|
66
|
+
@hub_secret_key = value
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def hub_api_key=(value)
|
|
70
|
+
deprecated_direct_setter(:hub_api_key)
|
|
71
|
+
@hub_api_key = value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def hub_rules_ttl=(value)
|
|
75
|
+
deprecated_direct_setter(:hub_rules_ttl)
|
|
76
|
+
@hub_rules_ttl = value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def adapter=(value)
|
|
80
|
+
deprecated_direct_setter(:adapter)
|
|
81
|
+
@adapter = resolve_adapter(value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def disable_on_development=(value)
|
|
85
|
+
deprecated_direct_setter(:disable_on_development)
|
|
86
|
+
@disable_on_development = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Readers
|
|
15
90
|
|
|
16
91
|
def authenticate_admin_with
|
|
17
92
|
@authenticate_admin_with ||= proc {}
|
|
@@ -41,20 +116,36 @@ module Trackguard
|
|
|
41
116
|
@throttle_period ||= 60
|
|
42
117
|
end
|
|
43
118
|
|
|
119
|
+
def hub_url
|
|
120
|
+
@hub_url ||= "https://app.trackguard.dev"
|
|
121
|
+
end
|
|
122
|
+
|
|
44
123
|
def hub_secret_key = @hub_secret_key.to_s
|
|
45
124
|
def hub_api_key = @hub_api_key.to_s
|
|
46
|
-
def hub_rules_ttl
|
|
125
|
+
def hub_rules_ttl = @hub_rules_ttl ||= 5.minutes
|
|
47
126
|
|
|
48
127
|
def adapter
|
|
49
128
|
@adapter ||= Trackguard::Adapters::Local.new
|
|
50
129
|
end
|
|
51
130
|
|
|
52
|
-
def
|
|
53
|
-
@
|
|
131
|
+
def disable_on_development
|
|
132
|
+
@disable_on_development.nil? || @disable_on_development
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def tracking_enabled?
|
|
136
|
+
!disable_on_development || !Rails.env.development?
|
|
54
137
|
end
|
|
55
138
|
|
|
56
139
|
private
|
|
57
140
|
|
|
141
|
+
def deprecated_direct_setter(attr)
|
|
142
|
+
return if @_configuring
|
|
143
|
+
|
|
144
|
+
warn "[DEPRECATED] Trackguard.#{attr}= called directly. " \
|
|
145
|
+
"Use `Trackguard.configure { |c| c.#{attr} = ... }` instead. " \
|
|
146
|
+
"Direct assignment will be removed in a future version."
|
|
147
|
+
end
|
|
148
|
+
|
|
58
149
|
def resolve_adapter(value)
|
|
59
150
|
case value
|
|
60
151
|
when Symbol then Trackguard::Adapters.const_get(value.to_s.camelize).new
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trackguard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.30.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krzysztof Rygielski
|
|
@@ -58,8 +58,9 @@ files:
|
|
|
58
58
|
- app/controllers/trackguard/admin/whitelisted_ips_controller.rb
|
|
59
59
|
- app/controllers/trackguard/page_views_controller.rb
|
|
60
60
|
- app/helpers/trackguard/application_helper.rb
|
|
61
|
-
- app/helpers/trackguard/hub_helper.rb
|
|
62
61
|
- app/jobs/trackguard/detect_suspicious_visitors_job.rb
|
|
62
|
+
- app/jobs/trackguard/hub/submit_blocked_request_job.rb
|
|
63
|
+
- app/jobs/trackguard/hub/submit_page_view_job.rb
|
|
63
64
|
- app/jobs/trackguard/track_blocked_request_job.rb
|
|
64
65
|
- app/jobs/trackguard/track_page_view_job.rb
|
|
65
66
|
- app/models/trackguard/blocked_path.rb
|
|
@@ -71,6 +72,7 @@ files:
|
|
|
71
72
|
- app/models/trackguard/whitelisted_ip.rb
|
|
72
73
|
- app/services/trackguard/analytics_query.rb
|
|
73
74
|
- app/services/trackguard/application_service.rb
|
|
75
|
+
- app/services/trackguard/track_blocked_request.rb
|
|
74
76
|
- app/services/trackguard/track_page_view.rb
|
|
75
77
|
- app/views/layouts/trackguard/admin.html.erb
|
|
76
78
|
- app/views/trackguard/admin/_nav.html.erb
|
|
@@ -98,6 +100,7 @@ files:
|
|
|
98
100
|
- lib/trackguard/adapters/local.rb
|
|
99
101
|
- lib/trackguard/engine.rb
|
|
100
102
|
- lib/trackguard/rack_attack.rb
|
|
103
|
+
- lib/trackguard/trace_id_middleware.rb
|
|
101
104
|
- lib/trackguard/version.rb
|
|
102
105
|
- trackguard.gemspec
|
|
103
106
|
homepage: https://github.com/riggy/trackguard
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Trackguard
|
|
4
|
-
module HubHelper
|
|
5
|
-
def trackguard_hub_js_tag
|
|
6
|
-
return unless Trackguard.adapter.is_a?(Trackguard::Adapters::Hub)
|
|
7
|
-
|
|
8
|
-
tag.script(src: "https://app.trackguard.dev/track.js", data: { api_key: Trackguard.hub_api_key })
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|