shugoi 0.4.6 → 0.4.7

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.
@@ -0,0 +1,82 @@
1
+ module Shugoi
2
+ class ShieldPage
3
+ Input = Struct.new(:title, :message, :badge, :host, :remaining_seconds, :locale, keyword_init: true)
4
+
5
+ def self.render(**attributes)
6
+ new(Input.new(**attributes)).render
7
+ end
8
+
9
+ def initialize(input)
10
+ @input = input
11
+ @messages = Locales.messages(input.locale)
12
+ end
13
+
14
+ def render
15
+ title = escaped(@input.title, :blocked_title)
16
+ badge = escaped(@input.badge, :blocked_badge)
17
+ host = Utils.escape_html((@input.host.to_s.empty? ? "shugoi.com" : @input.host).slice(0, 120))
18
+ description = Utils.escape_html(description_text)
19
+ language = @input.locale == "fr" ? "fr" : "en"
20
+ <<~HTML
21
+ <!DOCTYPE html>
22
+ <html lang="#{language}">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width,initial-scale=1">
26
+ <style>#{styles}</style>
27
+ </head>
28
+ <body>
29
+ <div id="c">
30
+ <img src="https://shugoi.com/favicon.png" alt class="l">
31
+ <img src="https://shugoi.com/brand.png" alt class="b">
32
+ <div class="bdg">#{badge}</div>
33
+ <h2>#{title}</h2>
34
+ <p class="desc">#{description}</p>
35
+ <p class="ft">#{host} · Shugoi</p>
36
+ </div>
37
+ </body>
38
+ </html>
39
+ HTML
40
+ end
41
+
42
+ private
43
+
44
+ def escaped(value, fallback)
45
+ Utils.escape_html(value.to_s.empty? ? @messages[fallback] : value)
46
+ end
47
+
48
+ def description_text
49
+ return @input.message.to_s unless @input.remaining_seconds.positive?
50
+
51
+ prefix = @input.message.to_s.sub(/Il reste \d+ seconde?s?.*$/, "").sub(/Retry in \d+s?.*$/, "").strip
52
+ "#{prefix} #{@messages[:retry_in_seconds].call(@input.remaining_seconds)}"
53
+ end
54
+
55
+ def countdown_script
56
+ return "" unless @input.remaining_seconds.positive?
57
+
58
+ <<~JS.delete("\n").strip
59
+ <script>
60
+ var s=#{@input.remaining_seconds};
61
+ var i=setInterval(function(){
62
+ s--;
63
+ var e=document.getElementById("cd");
64
+ if(e){
65
+ if(s<=0){e.innerHTML="0s";clearInterval(i);setTimeout(function(){location.reload()},500)}
66
+ else{e.innerHTML=s+"s"}
67
+ }
68
+ },1000)
69
+ </script>
70
+ JS
71
+ end
72
+
73
+ def styles
74
+ <<~CSS.delete("\n").strip
75
+ @font-face{font-family:'Alex Brush';src:url(https://shugoi.com/alex-brush.woff2?v=2) format('woff2');font-display:swap}
76
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
77
+ html,body{height:100%;background:#fcf9f5}
78
+ body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;padding:1.2rem}
79
+ CSS
80
+ end
81
+ end
82
+ end
@@ -1,8 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Shugoi
4
- # Génère le skeleton HTML (bootcode unicode) injecté dans la page.
5
- # Parité avec generateSkeleton (render.ts) — mêmes fragments JS, même encodage unicode.
6
2
  class SkeletonGenerator
7
3
  def initialize(config, guard_cache, config_cache, token_signer)
8
4
  @config = config
@@ -11,7 +7,6 @@ module Shugoi
11
7
  @token_signer = token_signer
12
8
  end
13
9
 
14
- # @return [String] HTML du skeleton (<script>…eval([...])…</script>)
15
10
  def generate(site_key, token, base_url, render_url = "./__shugoi/render", locale = nil)
16
11
  locale ||= Locales.resolve_locale(@config.locale, nil)
17
12
  @guard_cache.ensure_ready(site_key, @config.signing_secret)
@@ -24,10 +19,11 @@ module Shugoi
24
19
  fragments << "window.__sg_siteKey=#{json(site_key)}"
25
20
  fragments << "window.__sg_baseUrl=#{json(base_url)}"
26
21
  fragments << "window.__sg_config=#{json(flags)}"
27
- # Mode debug (audit #8) : piloté UNIQUEMENT par le serveur. En production ce flag
28
- # est toujours false → le guard n'active jamais ses traces via ?sg_probe_debug=1.
29
22
  fragments << "window.__sg_diagEnabled=#{production? ? 'false' : 'true'}"
30
- fragments << "try{if((location.search||'').indexOf('sg_proof=')>=0){var _qs=location.search.replace(/[?&]sg_proof=[^&]*/,'');var _cu=location.pathname+(_qs?_qs:'')+location.hash;history.replaceState(null,'',_cu)}}catch(e){}"
23
+ fragments << "try{if((location.search||'').indexOf('sg_proof=')>=0){" \
24
+ "var _qs=location.search.replace(/[?&]sg_proof=[^&]*/," \
25
+ "'');var _cu=location.pathname+(_qs?_qs:'')+location.hash;" \
26
+ "history.replaceState(null,'',_cu)}}catch(e){}"
31
27
 
32
28
  pow = Pow.new(@config.signing_secret, @config.pow_difficulty, @config.pow_ttl_ms).challenge
33
29
  fragments << "window.__sg_pow=#{json(pow)}"
@@ -37,30 +33,21 @@ module Shugoi
37
33
  fragments << "window.__sg_clockts=#{now_ms}"
38
34
  fragments << "window.__sg_disableRestrictedAccess=true" unless @config.restricted_access
39
35
  fragments << "try{#{detect}}catch(e){window.__sg_blocked=true}" if detect
40
-
41
- # __sg_showBlock (page de blocage néobrutaliste) — messages localisés interpolés.
42
36
  fragments << show_block_fragment(msgs)
43
37
 
44
- fragments << "var t=\"#{token}\""
45
- fragments << "window.__sg_token=\"#{token}\""
46
- fragments << "var k=\"#{site_key}\""
47
- fragments << "var b=\"#{base_url}\""
48
- fragments << "var r=\"#{render_url}\""
49
-
50
- # rd(p,n) : remplacement du document par le contenu rendu.
38
+ fragments << "var t=#{json(token)}"
39
+ fragments << "window.__sg_token=#{json(token)}"
40
+ fragments << "var k=#{json(site_key)}"
41
+ fragments << "var b=#{json(base_url)}"
42
+ fragments << "var r=#{json(render_url)}"
51
43
  fragments << rd_fragment(msgs)
52
44
 
53
45
  fragments << "_gw(function(){rd(r+\"?token=\"+t,0);setTimeout(_sgCl,1500)})"
54
46
  fragments << cleanup_fragment
55
47
 
56
48
  combined = fragments.join(";")
57
- # Échappe `</script>` et `</style>` AVANT l'encodage unicode : sans ça, le HTML parser
58
- # du navigateur coupe le <script> dès qu'il rencontre `</script>` dans le bootcode.
59
49
  combined = combined.gsub(%r{</(script|style)}i, "<\\/$1")
60
- enc = Utils.unicode_encode(combined)
61
- decoded_call = "[...'#{enc}'].map(x=>String.fromCodePoint(x.codePointAt(0)-917504)).join('')"
62
- # <meta charset=UTF-8> : garantit que le navigateur lit le bootcode unicode en UTF-8.
63
- "<meta charset=\"UTF-8\"><script>eval(#{decoded_call})</script>"
50
+ "<meta charset=\"UTF-8\"><script>#{combined}</script>"
64
51
  end
65
52
 
66
53
  private
@@ -73,7 +60,6 @@ module Shugoi
73
60
  ENV["NODE_ENV"] == "production" || ENV["RACK_ENV"] == "production" || ENV["RAILS_ENV"] == "production"
74
61
  end
75
62
 
76
- # jsStr : chaîne JSON sans les guillemets externes + échappe `<` (parité render.ts).
77
63
  def js_str(s)
78
64
  JSON.generate(s.to_s).slice(1..-2).gsub("<", "\\x3c")
79
65
  end
@@ -81,23 +67,32 @@ module Shugoi
81
67
  def show_block_fragment(msgs)
82
68
  fb_badge = js_str(msgs[:blocked_badge])
83
69
  fb_title = js_str(msgs[:blocked_title])
84
- css = "html,body{height:100%;background:#fcf9f5}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;padding:1.2rem}#c{max-width:460px;width:100%;background:#fff;border:4px solid #000;border-radius:28px 6px 32px 10px;box-shadow:12px 12px 0 #000;padding:3rem 2.4rem 2.8rem;text-align:center}#c .bdg{display:inline-block;border:2px solid #000;border-radius:10px 2px 14px 4px;padding:.3rem .9rem;font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#E87090;margin-bottom:1.4rem}#c h2{font-family:'Alex Brush',Georgia,'Times New Roman',serif;font-size:2.2rem;color:#E87090;font-weight:400;margin:0 auto .6rem}#c p.desc{font-size:.9rem;color:#555;line-height:1.8;max-width:380px;margin:0 auto}#c p.ft{font-size:.55rem;color:#E87090;margin-top:1.8rem}"
85
- "window.__sg_showBlock=function(msg,title,badge){var h=\"<head><meta charset=UTF-8><meta name=viewport content=width=device-width,initial-scale=1><style>@font-face{font-family:\\x27Alex Brush\\x27;src:url(https://shugoi.com/alex-brush.woff2?v=2) format(\\x27woff2\\x27);font-display:swap}*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}#{css}</style></head><body><div id=c><img src=https://shugoi.com/favicon-block.png class=l><img src=https://shugoi.com/brand-block.png class=b><div class=bdg>\"+(badge||\"#{fb_badge}\")+\"</div><h2>\"+(title||\"#{fb_title}\")+\"</h2><p class=desc>\"+(msg||\"\")+\"</p><p class=ft>\"+location.hostname+\" \\u00b7 Shugoi</p></div></body>\";document.documentElement.innerHTML=h}"
70
+ css = <<~CSS.delete("\\n").strip
71
+ html,body{height:100%;background:#fcf9f5}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;padding:1.2rem}#c{max-width:460px;width:100%;background:#fff;border:4px solid #000;border-radius:28px 6px 32px 10px;box-shadow:12px 12px 0 #000;padding:3rem 2.4rem 2.8rem;text-align:center}
72
+ CSS
73
+ <<~JS.delete("\\n").strip
74
+ window.__sg_showBlock=function(msg,title,badge){var h="<head><meta charset=UTF-8><meta name=viewport content=width=device-width,initial-scale=1><style>@font-face{font-family:\\x27Alex Brush\\x27;src:url(https://shugoi.com/alex-brush.woff2?v=2) format(\\x27woff2\\x27);font-display:swap}*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}#{css}</style></head>
75
+ <body><div id=c><img src=https://shugoi.com/favicon-block.png class=l><img src=https://shugoi.com/brand-block.png class=b><div class=bdg>"+(badge||"#{fb_badge}")+"</div><h2>"+(title||"#{fb_title}")+"</h2><p class=desc>"+(msg||"")+"</p><p class=ft>"+location.hostname+" \\u00b7 Shugoi</p></div></body>";document.documentElement.innerHTML=h}
76
+ JS
86
77
  end
87
78
 
88
79
  def rd_fragment(msgs)
89
80
  devtools = js_str(msgs[:devtools_body])
90
81
  tamper_title = js_str(msgs[:tamper_title])
91
- "var _gw=function(cb){if(window.__sg_guardsReady||window.__sg_blocked)cb();else setTimeout(function(){_gw(cb)},100)};" \
82
+ "var _gw=function(cb){if(window.__sg_guardsReady||window.__sg_blocked)cb();" \
83
+ "else setTimeout(function(){_gw(cb)},100)};" \
92
84
  "function rd(p,n){if(window.__sg_blocked)return;if(!document.body)return setTimeout(function(){rd(p,n)},50);" \
93
- "if(n>6){if((window.__sg_config||{}).enableContentReplacementCheck===true)window.__sg_showBlock&&window.__sg_showBlock(\"#{devtools}\",\"#{tamper_title}\");return}" \
85
+ "if(n>6){if((window.__sg_config||{}).enableContentReplacementCheck===true)" \
86
+ "window.__sg_showBlock&&window.__sg_showBlock(\"#{devtools}\",\"#{tamper_title}\");return}" \
94
87
  "var _g=(window.__sg_grant||\"\");if(_g){p=p+(\"&grant=\"+encodeURIComponent(_g))}" \
95
88
  "var _m=(window.__sg_detectMid||window.__sg_mid||\"\");if(_m){p=p+(\"&mid=\"+encodeURIComponent(_m))}" \
96
89
  "fetch(p).then(function(x){return x.json()}).then(function(d){if(window.__sg_blocked)return;" \
97
- "if(!document.body)return setTimeout(function(){rd(p,n+1)},50);" \
90
+ "if(!document.body)return setTimeout(function(){rd(p,n+1)}," \
91
+ "50);" \
98
92
  "if(d.html){document.open(\"text/html\");document.write(d.html);document.close();window.scrollTo(0,0)}" \
99
93
  "if(d.blocked){window.__sg_showBlock&&window.__sg_showBlock(d.message,d.title)}" \
100
- "if(d.error){if((window.__sg_config||{}).enableContentReplacementCheck===true)window.__sg_showBlock&&window.__sg_showBlock(\"#{devtools}\",\"#{tamper_title}\")}" \
94
+ "if(d.error){if((window.__sg_config||{}).enableContentReplacementCheck===true)" \
95
+ "window.__sg_showBlock&&window.__sg_showBlock(\"#{devtools}\",\"#{tamper_title}\")}" \
101
96
  "else if(!d.html&&!d.blocked){setTimeout(function(){rd(p,n+1)},300)}})" \
102
97
  ".catch(function(){setTimeout(function(){rd(p,n+1)},300)})}"
103
98
  end
@@ -1,19 +1,11 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Shugoi
4
- # Signature des tokens render + vérification des render-grants.
5
- # Parité exacte avec le module Node :
6
- # token : siteKey:timestamp:nonce:sig (sig = HMAC(secret, "siteKey:ts:nonce"))
7
- # grant : base36(ts):HMAC(secret, "render-grant:siteKey:mid:token:ip:ts")
8
2
  class TokenSigner
9
- # Parité module Node (render.ts) : TTL du render-grant réduit à 60 s (audit 2026-08-03).
10
3
  GRANT_TTL_MS = 60_000
11
4
 
12
5
  def initialize(secret)
13
6
  @secret = secret.to_s
14
7
  end
15
8
 
16
- # @return [String] token signé, ou "" si pas de secret
17
9
  def sign(site_key, timestamp = Utils.now_ms)
18
10
  return "" if @secret.empty?
19
11
  nonce = SecureRandom.hex(8)
@@ -22,12 +14,6 @@ module Shugoi
22
14
  "#{payload}:#{sig}"
23
15
  end
24
16
 
25
- # Vérifie un render-grant émis par le wlc serveur.
26
- # @param mid [String] machineId (64 hex)
27
- # @param grant [String] grant brut "base36ts:sig"
28
- # @param token [String] token render lié
29
- # @param ip [String] IP du client
30
- # @param expected_site_key [String] siteKey attendu (celui du middleware)
31
17
  def verify_render_grant(mid, grant, token, ip, expected_site_key)
32
18
  return true if @secret.empty? # fail-safe : pas de secret → pas de vérification
33
19
  return false if grant.to_s.empty? || mid.to_s.empty?
@@ -46,7 +32,6 @@ module Shugoi
46
32
  Utils.secure_equals(sig, expected)
47
33
  end
48
34
 
49
- # Vérifie un token render complet (siteKey:ts:nonce:sig).
50
35
  def verify_token(token)
51
36
  parts = token.to_s.split(":")
52
37
  return false unless parts.length == 4 && parts[3].length == 64
data/lib/shugoi/utils.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require "openssl"
4
2
  require "securerandom"
5
3
  require "json"
@@ -8,7 +6,6 @@ module Shugoi
8
6
  module Utils
9
7
  module_function
10
8
 
11
- # Comparaison constant-time de deux hex strings.
12
9
  def secure_equals(a, b)
13
10
  return false unless a.is_a?(String) && b.is_a?(String)
14
11
  return false unless a.bytesize == b.bytesize
@@ -39,11 +36,6 @@ module Shugoi
39
36
  str.to_s.to_i(36)
40
37
  end
41
38
 
42
- # Nombre de bits à zéro en tête du digest hex (parité avec la fonction JS `bits`
43
- # du module Node). Audit 2026-08-03 : comptage CORRIGÉ — l'ancienne version
44
- # (nib.to_s(2).match(/^0*/)[0].length) sous-comptait les zéros internes du premier
45
- # nibble non-nul (`3` → '11' → 0 au lieu de 2). Le comptage ci-dessous est exact et
46
- # DOIT rester synchrone avec core.rb (isPowValid), le challenge JS et le guard.
47
39
  def leading_zero_bits(hex_digest)
48
40
  leading = 0
49
41
  hex_digest.each_char do |c|
@@ -66,27 +58,10 @@ module Shugoi
66
58
  Time.now.to_i
67
59
  end
68
60
 
69
- # Encodage unicode (parité Node). Le module Node encode avec `charCodeAt(i)` qui donne
70
- # le CODE UNIT UTF-16 (0-65535) — pour les caractères non-BMP (émojis…), charCodeAt
71
- # produit DEUX unités (surrogate pair). Ruby `each_codepoint` donnerait le code point
72
- # complet (> 65535) → désynchronisation avec le décodage navigateur `codePointAt(0)`.
73
- # On encode donc par code unit UTF-16, exactement comme JS charCodeAt.
74
- # Le guard-detect (raw=1) peut être en ASCII-8BIT (bytes obfusqués > 0x7F) : on force
75
- # une conversion UTF-8 avec remplacement, comme le fetch().text() du module Node.
76
- def unicode_encode(code)
77
- str = code.dup.force_encoding(Encoding::UTF_8)
78
- str = str.encode(Encoding::UTF_16LE, invalid: :replace, undef: :replace, replace: "\uFFFD")
79
- str.bytes.each_slice(2).map do |lo, hi|
80
- unit = lo | (hi << 8)
81
- (917504 + unit).chr(Encoding::UTF_8)
82
- end.join
83
- end
84
-
85
61
  def escape_json_string(s)
86
62
  JSON.generate(s.to_s)[1..-2]
87
63
  end
88
64
 
89
- # Échappement HTML (parité escapeHtml de core.ts).
90
65
  def escape_html(s)
91
66
  s.to_s
92
67
  .gsub("&", "&amp;")
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Shugoi
4
- VERSION = "0.4.6"
2
+ VERSION = "0.4.7".freeze
5
3
  end
data/lib/shugoi.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative "shugoi/version"
4
2
  require_relative "shugoi/errors"
5
3
  require_relative "shugoi/constants"
@@ -13,6 +11,17 @@ require_relative "shugoi/bot_verifier"
13
11
  require_relative "shugoi/api_client"
14
12
  require_relative "shugoi/guard_cache"
15
13
  require_relative "shugoi/config_cache"
14
+ require_relative "shugoi/rate_limit_formatter"
15
+ require_relative "shugoi/challenge_path"
16
+ require_relative "shugoi/challenge_limiter"
17
+ require_relative "shugoi/shield_page"
18
+ require_relative "shugoi/key_validator"
19
+ require_relative "shugoi/challenge_script"
20
+ require_relative "shugoi/bot_policy"
21
+ require_relative "shugoi/challenge_url"
22
+ require_relative "shugoi/render_endpoint"
23
+ require_relative "shugoi/guard_injector"
24
+ require_relative "shugoi/response_processor"
16
25
  require_relative "shugoi/html_store"
17
26
  require_relative "shugoi/notice"
18
27
  require_relative "shugoi/skeleton_generator"
@@ -20,7 +29,7 @@ require_relative "shugoi/render_handler"
20
29
  require_relative "shugoi/core"
21
30
  require_relative "shugoi/rack/middleware"
22
31
  require_relative "shugoi/rails/configuration"
23
- require_relative "shugoi/rails/railtie" if defined?(::Rails::Railtie)
32
+ require_relative "shugoi/rails/railtie" if defined?(Rails::Railtie)
24
33
 
25
34
  module Shugoi
26
35
  def self.new(app, options = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shugoi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugoi
@@ -10,59 +10,83 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rack
13
+ name: activesupport
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '2.2'
18
+ version: '6.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '2.2'
28
+ version: '6.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
26
32
  - !ruby/object:Gem::Dependency
27
- name: activesupport
33
+ name: concurrent-ruby
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
36
  - - ">="
31
37
  - !ruby/object:Gem::Version
32
- version: '6.0'
38
+ version: '1.1'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '2'
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
46
  - - ">="
38
47
  - !ruby/object:Gem::Version
39
- version: '6.0'
48
+ version: '1.1'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '2'
40
52
  - !ruby/object:Gem::Dependency
41
- name: concurrent-ruby
53
+ name: rack
42
54
  requirement: !ruby/object:Gem::Requirement
43
55
  requirements:
44
56
  - - ">="
45
57
  - !ruby/object:Gem::Version
46
- version: '1.1'
58
+ version: '2.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '4'
47
62
  type: :runtime
48
63
  prerelease: false
49
64
  version_requirements: !ruby/object:Gem::Requirement
50
65
  requirements:
51
66
  - - ">="
52
67
  - !ruby/object:Gem::Version
53
- version: '1.1'
68
+ version: '2.2'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '4'
54
72
  description: Rack middleware + Rails integration for Shugoi. Blocks bots, scrapers
55
- and headless clients with a proof-of-work challenge, machine fingerprinting and
56
- split-render, mirroring the Node.js module.
73
+ and headless clients with a proof-of-work challenge, environment signals and split-render,
74
+ mirroring the Node.js module.
57
75
  executables: []
58
76
  extensions: []
59
77
  extra_rdoc_files: []
60
78
  files:
79
+ - CHANGELOG.md
61
80
  - LICENSE
62
81
  - README.md
63
82
  - lib/shugoi.rb
64
83
  - lib/shugoi/api_client.rb
84
+ - lib/shugoi/bot_policy.rb
65
85
  - lib/shugoi/bot_verifier.rb
86
+ - lib/shugoi/challenge_limiter.rb
87
+ - lib/shugoi/challenge_path.rb
88
+ - lib/shugoi/challenge_script.rb
89
+ - lib/shugoi/challenge_url.rb
66
90
  - lib/shugoi/config.rb
67
91
  - lib/shugoi/config_cache.rb
68
92
  - lib/shugoi/constants.rb
@@ -70,14 +94,20 @@ files:
70
94
  - lib/shugoi/csp.rb
71
95
  - lib/shugoi/errors.rb
72
96
  - lib/shugoi/guard_cache.rb
97
+ - lib/shugoi/guard_injector.rb
73
98
  - lib/shugoi/html_store.rb
99
+ - lib/shugoi/key_validator.rb
74
100
  - lib/shugoi/locales.rb
75
101
  - lib/shugoi/notice.rb
76
102
  - lib/shugoi/pow.rb
77
103
  - lib/shugoi/rack/middleware.rb
78
104
  - lib/shugoi/rails/configuration.rb
79
105
  - lib/shugoi/rails/railtie.rb
106
+ - lib/shugoi/rate_limit_formatter.rb
107
+ - lib/shugoi/render_endpoint.rb
80
108
  - lib/shugoi/render_handler.rb
109
+ - lib/shugoi/response_processor.rb
110
+ - lib/shugoi/shield_page.rb
81
111
  - lib/shugoi/skeleton_generator.rb
82
112
  - lib/shugoi/token_signer.rb
83
113
  - lib/shugoi/utils.rb
@@ -87,7 +117,8 @@ licenses:
87
117
  - MIT
88
118
  metadata:
89
119
  homepage_uri: https://shugoi.com
90
- source_code_uri: https://github.com/RoxasYTB/shugoi-ruby
120
+ source_code_uri: https://github.com/ShugoiTeam/shugoi-ruby
121
+ rubygems_mfa_required: 'true'
91
122
  rdoc_options: []
92
123
  require_paths:
93
124
  - lib