trackguard 0.17.0 → 0.19.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/images/trackguard/logo.png +0 -0
- data/app/assets/stylesheets/trackguard/admin.css +29 -29
- data/app/services/trackguard/page_view_recorder.rb +3 -11
- data/app/views/layouts/trackguard/admin.html.erb +1 -21
- data/lib/trackguard/adapters/base.rb +19 -0
- data/lib/trackguard/adapters/local.rb +43 -0
- data/lib/trackguard/rack_attack.rb +8 -6
- data/lib/trackguard/version.rb +1 -1
- data/lib/trackguard.rb +20 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90c710c8d2149047c75e5b99151b708e9741e29385d17de7c4f101ad9db697cf
|
|
4
|
+
data.tar.gz: 49aaf485a932c241d9c18986120f639305485f4c316653923e9ad9a018566e01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1a17b99009baaacee17c4473bf33b811870df411541cee1c055807f2b8981b4ab09ba0c28eeee15adc0a8ab87081f6b463b494435618cafafb2aea424b95226
|
|
7
|
+
data.tar.gz: 8da17d592514268ced0ab53cb0c2331671eea10702deaac46264f90874f2af33e60f952a1c89b7c1f920b6ad35ad93297bfe6ab786c796816c3704d35a17424d
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
|
|
8
8
|
font-size: 16px;
|
|
9
9
|
line-height: 1.5;
|
|
10
|
-
background-color: #
|
|
10
|
+
background-color: #0f0909;
|
|
11
11
|
color: #e0e0e0;
|
|
12
12
|
-webkit-font-smoothing: antialiased;
|
|
13
13
|
min-height: 100vh;
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
/* ── Header ────────────────────────────────────────────────────────── */
|
|
24
24
|
.tg-header {
|
|
25
|
-
background: #
|
|
26
|
-
border-bottom: 1px solid #
|
|
25
|
+
background: #190e0e;
|
|
26
|
+
border-bottom: 1px solid #2e1515;
|
|
27
27
|
padding: 0.875rem 0;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
font-size: 0.9375rem;
|
|
41
41
|
font-weight: 600;
|
|
42
42
|
letter-spacing: 0.03em;
|
|
43
|
-
color: #
|
|
43
|
+
color: #f87171;
|
|
44
44
|
text-decoration: none;
|
|
45
45
|
flex: 1;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.tg-brand__logo {
|
|
49
49
|
width: 20px;
|
|
50
|
-
height:
|
|
50
|
+
height: auto;
|
|
51
51
|
flex-shrink: 0;
|
|
52
|
-
filter: drop-shadow(0 0 4px rgba(
|
|
52
|
+
filter: drop-shadow(0 0 4px rgba(239, 68, 68, 0.4));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/* ── Back link ─────────────────────────────────────────────────────── */
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
|
|
77
77
|
/* ── Nav ───────────────────────────────────────────────────────────── */
|
|
78
78
|
.tg-nav {
|
|
79
|
-
background: #
|
|
80
|
-
border-bottom: 1px solid #
|
|
79
|
+
background: #190e0e;
|
|
80
|
+
border-bottom: 1px solid #2e1515;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
.tg-nav > .tg-container {
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
.tg-nav__link:hover { color: #e0e0e0; }
|
|
98
98
|
|
|
99
99
|
.tg-nav__link--active {
|
|
100
|
-
color: #
|
|
101
|
-
border-bottom-color: #
|
|
100
|
+
color: #f87171;
|
|
101
|
+
border-bottom-color: #e53e3e;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/* ── Main ──────────────────────────────────────────────────────────── */
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
.tg-stat {
|
|
131
|
-
background: #
|
|
132
|
-
border: 1px solid #
|
|
131
|
+
background: #190e0e;
|
|
132
|
+
border: 1px solid #2e1515;
|
|
133
133
|
border-radius: 8px;
|
|
134
134
|
padding: 1.25rem;
|
|
135
135
|
}
|
|
@@ -167,8 +167,8 @@
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
.tg-panel {
|
|
170
|
-
background: #
|
|
171
|
-
border: 1px solid #
|
|
170
|
+
background: #190e0e;
|
|
171
|
+
border: 1px solid #2e1515;
|
|
172
172
|
border-radius: 8px;
|
|
173
173
|
padding: 1rem;
|
|
174
174
|
margin-bottom: 1rem;
|
|
@@ -211,14 +211,14 @@
|
|
|
211
211
|
font-size: 0.8125rem;
|
|
212
212
|
color: #e0e0e0;
|
|
213
213
|
text-decoration: none;
|
|
214
|
-
background: #
|
|
214
|
+
background: #2e1515;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
.tg-pagination__link:hover { background: #
|
|
217
|
+
.tg-pagination__link:hover { background: #3d1c1c; }
|
|
218
218
|
|
|
219
219
|
.tg-pagination__link--active {
|
|
220
|
-
background: #
|
|
221
|
-
color: #
|
|
220
|
+
background: #e53e3e;
|
|
221
|
+
color: #ffffff;
|
|
222
222
|
font-weight: 600;
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -249,14 +249,14 @@
|
|
|
249
249
|
text-transform: uppercase;
|
|
250
250
|
letter-spacing: 0.08em;
|
|
251
251
|
color: #666666;
|
|
252
|
-
border-bottom: 1px solid #
|
|
252
|
+
border-bottom: 1px solid #2e1515;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
.tg-th--right { text-align: right; }
|
|
256
256
|
|
|
257
257
|
.tg-td {
|
|
258
258
|
padding: 0.75rem 0.875rem;
|
|
259
|
-
border-bottom: 1px solid #
|
|
259
|
+
border-bottom: 1px solid #2e1515;
|
|
260
260
|
color: #f0f0f0;
|
|
261
261
|
font-weight: 500;
|
|
262
262
|
}
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
|
|
272
272
|
.tg-td--bare {
|
|
273
273
|
padding: 0;
|
|
274
|
-
border-bottom: 1px solid #
|
|
274
|
+
border-bottom: 1px solid #2e1515;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/* ── Empty state ───────────────────────────────────────────────────── */
|
|
@@ -361,7 +361,7 @@
|
|
|
361
361
|
/* ── Detail panel ──────────────────────────────────────────────────── */
|
|
362
362
|
.tg-detail {
|
|
363
363
|
padding: 0 1rem 1rem 1rem;
|
|
364
|
-
background: #
|
|
364
|
+
background: #190e0e;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
.tg-detail__grid {
|
|
@@ -423,7 +423,7 @@
|
|
|
423
423
|
align-items: center;
|
|
424
424
|
gap: 0.5rem;
|
|
425
425
|
padding: 0.75rem 0 0;
|
|
426
|
-
border-top: 1px solid #
|
|
426
|
+
border-top: 1px solid #2e1515;
|
|
427
427
|
margin-top: 0.75rem;
|
|
428
428
|
}
|
|
429
429
|
|
|
@@ -436,8 +436,8 @@
|
|
|
436
436
|
|
|
437
437
|
.tg-input {
|
|
438
438
|
flex: 1;
|
|
439
|
-
background: #
|
|
440
|
-
border: 1px solid #
|
|
439
|
+
background: #0f0909;
|
|
440
|
+
border: 1px solid #2e1515;
|
|
441
441
|
border-radius: 4px;
|
|
442
442
|
color: #e0e0e0;
|
|
443
443
|
font-size: 0.8125rem;
|
|
@@ -445,7 +445,7 @@
|
|
|
445
445
|
outline: none;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
.tg-input:focus { border-color: #
|
|
448
|
+
.tg-input:focus { border-color: #e53e3e; }
|
|
449
449
|
.tg-input::placeholder { color: #444; }
|
|
450
450
|
|
|
451
451
|
.tg-btn {
|
|
@@ -461,8 +461,8 @@
|
|
|
461
461
|
.tg-btn--danger { background: #7f1d1d; color: #fca5a5; }
|
|
462
462
|
.tg-btn--danger:hover { background: #991b1b; }
|
|
463
463
|
|
|
464
|
-
.tg-btn--ghost { background: #
|
|
465
|
-
.tg-btn--ghost:hover { background: #
|
|
464
|
+
.tg-btn--ghost { background: #2e1515; color: #e0e0e0; }
|
|
465
|
+
.tg-btn--ghost:hover { background: #3d1c1c; }
|
|
466
466
|
|
|
467
467
|
.tg-btn--whitelist { background: #064e3b; color: #6ee7b7; }
|
|
468
468
|
.tg-btn--whitelist:hover { background: #065f46; }
|
|
@@ -476,4 +476,4 @@
|
|
|
476
476
|
.tg-stats { grid-template-columns: 1fr; }
|
|
477
477
|
.tg-detail__grid { grid-template-columns: 1fr; }
|
|
478
478
|
.tg-summary__ip { display: none; }
|
|
479
|
-
}
|
|
479
|
+
}
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
module Trackguard
|
|
2
2
|
class PageViewRecorder < ApplicationService
|
|
3
|
-
BOT_REGEX = /
|
|
4
|
-
Googlebot|Bingbot|Slurp|DuckDuckBot|Baidu|YandexBot|
|
|
5
|
-
facebookexternalhit|Twitterbot|LinkedInBot|
|
|
6
|
-
curl|wget|python-requests|python-urllib|
|
|
7
|
-
Go-http-client|libwww|Java|Ruby|
|
|
8
|
-
bot|crawl|spider
|
|
9
|
-
/ix
|
|
10
|
-
|
|
11
3
|
def initialize(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source: nil, initial: false,
|
|
12
4
|
http_method: nil)
|
|
13
5
|
@path = path.to_s
|
|
@@ -22,11 +14,11 @@ module Trackguard
|
|
|
22
14
|
end
|
|
23
15
|
|
|
24
16
|
def call
|
|
25
|
-
|
|
26
|
-
return if
|
|
17
|
+
adapter = Trackguard.adapter
|
|
18
|
+
return if adapter.blocked_user_agent?(@user_agent)
|
|
27
19
|
return if @path.blank? || @path.start_with?("/admin")
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
adapter.track_page_view(
|
|
30
22
|
path: @path,
|
|
31
23
|
ip: @ip,
|
|
32
24
|
user_agent: @user_agent,
|
|
@@ -11,27 +11,7 @@
|
|
|
11
11
|
<div class="tg-container">
|
|
12
12
|
<div class="tg-header__inner">
|
|
13
13
|
<span class="tg-brand">
|
|
14
|
-
|
|
15
|
-
<defs>
|
|
16
|
-
<linearGradient id="tg-shield-grad" x1="0" y1="0" x2="0" y2="1">
|
|
17
|
-
<stop offset="0%" stop-color="#1e3a5f"/>
|
|
18
|
-
<stop offset="100%" stop-color="#0d1f3c"/>
|
|
19
|
-
</linearGradient>
|
|
20
|
-
</defs>
|
|
21
|
-
<!-- Shield -->
|
|
22
|
-
<path d="M10 1L19 4.5V11.5C19 16.4 15 20.3 10 21.5C5 20.3 1 16.4 1 11.5V4.5Z"
|
|
23
|
-
fill="url(#tg-shield-grad)" stroke="#3b82f6" stroke-width="1.25" stroke-linejoin="round"/>
|
|
24
|
-
<!-- Left rail -->
|
|
25
|
-
<line x1="7.5" y1="6" x2="7.5" y2="17" stroke="#60a5fa" stroke-width="1.6" stroke-linecap="round"/>
|
|
26
|
-
<!-- Right rail -->
|
|
27
|
-
<line x1="12.5" y1="6" x2="12.5" y2="17" stroke="#60a5fa" stroke-width="1.6" stroke-linecap="round"/>
|
|
28
|
-
<!-- Cross-tie 1 -->
|
|
29
|
-
<line x1="6.25" y1="8" x2="13.75" y2="8" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
|
|
30
|
-
<!-- Cross-tie 2 -->
|
|
31
|
-
<line x1="6.25" y1="11.5" x2="13.75" y2="11.5" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
|
|
32
|
-
<!-- Cross-tie 3 -->
|
|
33
|
-
<line x1="6.25" y1="15" x2="13.75" y2="15" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
|
|
34
|
-
</svg>
|
|
14
|
+
<%= image_tag "trackguard/logo.png", class: "tg-brand__logo", alt: "Trackguard" %>
|
|
35
15
|
Trackguard
|
|
36
16
|
</span>
|
|
37
17
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trackguard
|
|
4
|
+
module Adapters
|
|
5
|
+
class Base
|
|
6
|
+
def blocked_user_agent?(user_agent) = raise NotImplementedError, "#{self.class}#blocked_user_agent?"
|
|
7
|
+
def whitelisted_ip?(ip) = raise NotImplementedError, "#{self.class}#whitelisted_ip?"
|
|
8
|
+
def flagged_visitor?(ip) = raise NotImplementedError, "#{self.class}#flagged_visitor?"
|
|
9
|
+
|
|
10
|
+
def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, initial:, http_method:)
|
|
11
|
+
raise NotImplementedError, "#{self.class}#track_page_view"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
15
|
+
raise NotImplementedError, "#{self.class}#track_blocked_request"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trackguard
|
|
4
|
+
module Adapters
|
|
5
|
+
class Local < Base
|
|
6
|
+
def blocked_user_agent?(user_agent)
|
|
7
|
+
BlockedUserAgent.blocked?(user_agent)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def whitelisted_ip?(ip)
|
|
11
|
+
WhitelistedIp.whitelisted?(ip)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def flagged_visitor?(ip)
|
|
15
|
+
Visitor.flagged?(ip)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, initial:, http_method:)
|
|
19
|
+
TrackPageViewJob.perform_later(
|
|
20
|
+
path: path,
|
|
21
|
+
ip: ip,
|
|
22
|
+
user_agent: user_agent,
|
|
23
|
+
referer: referer,
|
|
24
|
+
session_id: session_id,
|
|
25
|
+
trace_id: trace_id,
|
|
26
|
+
source: source,
|
|
27
|
+
initial: initial,
|
|
28
|
+
http_method: http_method
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
|
|
33
|
+
TrackBlockedRequestJob.perform_later(
|
|
34
|
+
ip: ip,
|
|
35
|
+
user_agent: user_agent,
|
|
36
|
+
path: path,
|
|
37
|
+
http_method: http_method,
|
|
38
|
+
block_reason: block_reason
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -5,20 +5,22 @@ require "rack/attack"
|
|
|
5
5
|
module Trackguard
|
|
6
6
|
module RackAttack
|
|
7
7
|
def self.configure
|
|
8
|
+
adapter = Trackguard.adapter
|
|
9
|
+
|
|
8
10
|
::Rack::Attack.safelist("trackguard/allow local") do |req|
|
|
9
11
|
[ "127.0.0.1", "::1" ].include?(req.ip)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
::Rack::Attack.safelist("trackguard/allow whitelisted ips") do |req|
|
|
13
|
-
|
|
15
|
+
adapter.whitelisted_ip?(req.ip)
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
::Rack::Attack.blocklist("trackguard/block known scanners") do |req|
|
|
17
|
-
|
|
19
|
+
adapter.blocked_user_agent?(req.user_agent)
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
::Rack::Attack.blocklist("trackguard/flagged visitors") do |req|
|
|
21
|
-
|
|
23
|
+
adapter.flagged_visitor?(req.ip)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
::Rack::Attack.throttle(
|
|
@@ -27,15 +29,15 @@ module Trackguard
|
|
|
27
29
|
period: Trackguard.throttle_period, &:ip
|
|
28
30
|
)
|
|
29
31
|
|
|
30
|
-
subscribe_to_blocked_requests
|
|
32
|
+
subscribe_to_blocked_requests(adapter)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
def self.subscribe_to_blocked_requests
|
|
35
|
+
def self.subscribe_to_blocked_requests(adapter)
|
|
34
36
|
@subscribe_to_blocked_requests ||= ActiveSupport::Notifications.subscribe("rack.attack") do |*, payload|
|
|
35
37
|
req = payload[:request]
|
|
36
38
|
next unless req.env["rack.attack.match_type"] == :blocklist
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
adapter.track_blocked_request(
|
|
39
41
|
ip: req.ip,
|
|
40
42
|
user_agent: req.user_agent.to_s,
|
|
41
43
|
path: req.path,
|
data/lib/trackguard/version.rb
CHANGED
data/lib/trackguard.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "trackguard/version"
|
|
4
4
|
require "trackguard/engine"
|
|
5
5
|
require "trackguard/rack_attack"
|
|
6
|
+
require "trackguard/adapters/base"
|
|
7
|
+
require "trackguard/adapters/local"
|
|
6
8
|
|
|
7
9
|
module Trackguard
|
|
8
10
|
class << self
|
|
@@ -36,5 +38,23 @@ module Trackguard
|
|
|
36
38
|
def throttle_period
|
|
37
39
|
@throttle_period ||= 60
|
|
38
40
|
end
|
|
41
|
+
|
|
42
|
+
def adapter
|
|
43
|
+
@adapter ||= Trackguard::Adapters::Local.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def adapter=(value)
|
|
47
|
+
@adapter = resolve_adapter(value)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def resolve_adapter(value)
|
|
53
|
+
case value
|
|
54
|
+
when Symbol then Trackguard::Adapters.const_get(value.to_s.camelize).new
|
|
55
|
+
when Class then value.new
|
|
56
|
+
else value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
39
59
|
end
|
|
40
60
|
end
|
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.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krzysztof Rygielski
|
|
@@ -43,6 +43,7 @@ executables: []
|
|
|
43
43
|
extensions: []
|
|
44
44
|
extra_rdoc_files: []
|
|
45
45
|
files:
|
|
46
|
+
- app/assets/images/trackguard/logo.png
|
|
46
47
|
- app/assets/javascripts/controllers/page_tracker_controller.js
|
|
47
48
|
- app/assets/stylesheets/trackguard/admin.css
|
|
48
49
|
- app/controllers/concerns/trackguard/page_tracker.rb
|
|
@@ -80,6 +81,8 @@ files:
|
|
|
80
81
|
- lib/generators/trackguard/upgrade_generator.rb
|
|
81
82
|
- lib/tasks/trackguard.rake
|
|
82
83
|
- lib/trackguard.rb
|
|
84
|
+
- lib/trackguard/adapters/base.rb
|
|
85
|
+
- lib/trackguard/adapters/local.rb
|
|
83
86
|
- lib/trackguard/engine.rb
|
|
84
87
|
- lib/trackguard/rack_attack.rb
|
|
85
88
|
- lib/trackguard/version.rb
|