trackguard 0.29.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/controllers/concerns/trackguard/page_tracker.rb +1 -0
- data/app/helpers/trackguard/application_helper.rb +2 -0
- data/app/jobs/trackguard/detect_suspicious_visitors_job.rb +14 -1
- data/config/routes.rb +2 -0
- data/lib/trackguard/adapters/base.rb +11 -1
- data/lib/trackguard/adapters/hub.rb +30 -3
- data/lib/trackguard/adapters/local.rb +3 -3
- data/lib/trackguard/engine.rb +1 -0
- data/lib/trackguard/rack_attack.rb +7 -11
- data/lib/trackguard/version.rb +1 -1
- data/lib/trackguard.rb +96 -6
- metadata +1 -1
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
|
|
@@ -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
|
data/config/routes.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
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?"
|
|
@@ -10,6 +12,7 @@ module Trackguard
|
|
|
10
12
|
|
|
11
13
|
def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, tracking_layer:,
|
|
12
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)
|
|
@@ -22,7 +25,10 @@ module Trackguard
|
|
|
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
|
|
@@ -31,6 +37,10 @@ module Trackguard
|
|
|
31
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,14 +57,14 @@ module Trackguard
|
|
|
30
57
|
rules.fetch(:flagged_ips, []).include?(ip)
|
|
31
58
|
end
|
|
32
59
|
|
|
33
|
-
|
|
60
|
+
protected
|
|
61
|
+
|
|
62
|
+
def perform_track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
34
63
|
Trackguard::Hub::SubmitBlockedRequestJob.perform_later(
|
|
35
64
|
ip: ip, user_agent: user_agent, path: path, http_method: http_method, block_reason: block_reason
|
|
36
65
|
)
|
|
37
66
|
end
|
|
38
67
|
|
|
39
|
-
protected
|
|
40
|
-
|
|
41
68
|
def perform_track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:,
|
|
42
69
|
tracking_layer:, http_method:, prefetch: false)
|
|
43
70
|
return if prefetch
|
|
@@ -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,8 +31,6 @@ 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
35
|
tracking_layer:, http_method:, prefetch: false)
|
|
36
36
|
TrackPageViewJob.perform_later(
|
data/lib/trackguard/engine.rb
CHANGED
|
@@ -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
|
@@ -9,10 +9,84 @@ require "trackguard/adapters/local"
|
|
|
9
9
|
require "trackguard/adapters/hub"
|
|
10
10
|
|
|
11
11
|
module Trackguard
|
|
12
|
+
class ConfigurationError < StandardError; end
|
|
13
|
+
|
|
12
14
|
class << self
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
16
90
|
|
|
17
91
|
def authenticate_admin_with
|
|
18
92
|
@authenticate_admin_with ||= proc {}
|
|
@@ -42,20 +116,36 @@ module Trackguard
|
|
|
42
116
|
@throttle_period ||= 60
|
|
43
117
|
end
|
|
44
118
|
|
|
119
|
+
def hub_url
|
|
120
|
+
@hub_url ||= "https://app.trackguard.dev"
|
|
121
|
+
end
|
|
122
|
+
|
|
45
123
|
def hub_secret_key = @hub_secret_key.to_s
|
|
46
124
|
def hub_api_key = @hub_api_key.to_s
|
|
47
|
-
def hub_rules_ttl
|
|
125
|
+
def hub_rules_ttl = @hub_rules_ttl ||= 5.minutes
|
|
48
126
|
|
|
49
127
|
def adapter
|
|
50
128
|
@adapter ||= Trackguard::Adapters::Local.new
|
|
51
129
|
end
|
|
52
130
|
|
|
53
|
-
def
|
|
54
|
-
@
|
|
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?
|
|
55
137
|
end
|
|
56
138
|
|
|
57
139
|
private
|
|
58
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
|
+
|
|
59
149
|
def resolve_adapter(value)
|
|
60
150
|
case value
|
|
61
151
|
when Symbol then Trackguard::Adapters.const_get(value.to_s.camelize).new
|