Class: Shugoi::Pow
- Inherits:
-
Object
- Object
- Shugoi::Pow
- Defined in:
- lib/shugoi/pow.rb
Overview
Proof-of-work anti-curl + cookies HMAC — parité avec core.ts (module Node) :
salt = HMAC(secret, ts + ':' + nonce) (nonce 64 bits ALEATOIRE par challenge)
proof = "ts:nonce:solution" où SHA256(salt:solution) a >= POW_DIFFICULTY bits à zéro.
__sg_ok : ts:ipBucket:uaFp:HMAC(secret, "sg_ok:ts:ipBucket:uaFp") — 30 jours,
lié au bucket IP + empreinte UA (non rejouable depuis une autre IP),
saute le pre-flight PoW.
__sg_authorized: ts:HMAC(secret, "sg_authorized:ts") — 120 s, protège les assets /assets/*
Constant Summary collapse
- POW_OK_TTL_MS =
30 * 24 * 3600 * 1000
- AUTHORIZED_TTL_MS =
120_000
Instance Method Summary collapse
-
#challenge ⇒ Object
Génère le challenge à injecter (window.__sg_pow).
-
#initialize(signing_secret, difficulty = 14, ttl_ms = 60_000) ⇒ Pow
constructor
A new instance of Pow.
- #sg_authorized_cookie ⇒ Object
- #sg_authorized_valid?(cookie_val) ⇒ Boolean
-
#sg_authorized_value ⇒ Object
Cookie __sg_authorized posé par handleRender après un render réussi (grant valide).
-
#sg_ok_cookie(proof, ip = "", ua = "") ⇒ String?
String Set-Cookie pour __sg_ok (posé par le middleware après une preuve valide).
- #sg_ok_valid?(cookie_val, ip = "", ua = "") ⇒ Boolean
-
#sg_ok_value(ip = "", ua = "") ⇒ Object
Valeur du cookie __sg_ok (HMAC serveur, 30 j) — lié au bucket IP + empreinte UA.
-
#valid?(proof) ⇒ Boolean
Vérifie un proof "ts:nonce:solution" (fenêtre @ttl_ms, comptage de bits CORRIGÉ).
Constructor Details
#initialize(signing_secret, difficulty = 14, ttl_ms = 60_000) ⇒ Pow
Returns a new instance of Pow.
17 18 19 20 21 |
# File 'lib/shugoi/pow.rb', line 17 def initialize(signing_secret, difficulty = 14, ttl_ms = 60_000) @secret = signing_secret.to_s @difficulty = difficulty @ttl_ms = ttl_ms end |
Instance Method Details
#challenge ⇒ Object
Génère le challenge à injecter (window.__sg_pow).
24 25 26 27 |
# File 'lib/shugoi/pow.rb', line 24 def challenge ts = Utils.now_sec { ts: ts, nonce: nonce, salt: salt(ts, nonce), difficulty: @difficulty } end |
#sg_authorized_cookie ⇒ Object
94 95 96 97 |
# File 'lib/shugoi/pow.rb', line 94 def secure = production? ? "; Secure" : "" "__sg_authorized=#{}; Path=/; HttpOnly; SameSite=Strict; Max-Age=120#{secure}" end |
#sg_authorized_valid?(cookie_val) ⇒ Boolean
81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/shugoi/pow.rb', line 81 def () return false if @secret.empty? || .to_s.empty? ts_str, sig = .to_s.split(":", 2) return false if ts_str.nil? || sig.nil? ts = ts_str.to_i return false if ts.zero? return false if Utils.now_ms - ts * 1000 > AUTHORIZED_TTL_MS return false if ts * 1000 > Utils.now_ms + 60_000 Utils.secure_equals(sig, Utils.hmac_hex(@secret, "sg_authorized:#{ts_str}")) end |
#sg_authorized_value ⇒ Object
Cookie __sg_authorized posé par handleRender après un render réussi (grant valide).
76 77 78 79 |
# File 'lib/shugoi/pow.rb', line 76 def ts = Utils.now_sec "#{ts}:#{Utils.hmac_hex(@secret, "sg_authorized:#{ts}")}" end |
#sg_ok_cookie(proof, ip = "", ua = "") ⇒ String?
String Set-Cookie pour __sg_ok (posé par le middleware après une preuve valide).
69 70 71 72 73 |
# File 'lib/shugoi/pow.rb', line 69 def (proof, ip = "", ua = "") return nil unless valid?(proof) secure = production? ? "; Secure" : "" "__sg_ok=#{sg_ok_value(ip, ua)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=#{POW_OK_TTL_MS / 1000}#{secure}" end |
#sg_ok_valid?(cookie_val, ip = "", ua = "") ⇒ Boolean
52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/shugoi/pow.rb', line 52 def sg_ok_valid?(, ip = "", ua = "") return false if @secret.empty? || .to_s.empty? ts_str, bucket, fp, sig = .to_s.split(":", 4) return false if ts_str.nil? || bucket.nil? || fp.nil? || sig.nil? ts = ts_str.to_i return false if ts.zero? return false if Utils.now_ms - ts * 1000 > POW_OK_TTL_MS return false if ts * 1000 > Utils.now_ms + 60_000 # Lier au bucket IP + UA courants : un cookie d'une autre IP/UA → invalide. return false unless bucket == ip_bucket(ip) && fp == ua_fp(ua) Utils.secure_equals(sig, Utils.hmac_hex(@secret, "sg_ok:#{ts_str}:#{bucket}:#{fp}")) end |
#sg_ok_value(ip = "", ua = "") ⇒ Object
Valeur du cookie __sg_ok (HMAC serveur, 30 j) — lié au bucket IP + empreinte UA.
45 46 47 48 49 50 |
# File 'lib/shugoi/pow.rb', line 45 def sg_ok_value(ip = "", ua = "") ts = Utils.now_sec bucket = ip_bucket(ip) fp = ua_fp(ua) "#{ts}:#{bucket}:#{fp}:#{Utils.hmac_hex(@secret, "sg_ok:#{ts}:#{bucket}:#{fp}")}" end |
#valid?(proof) ⇒ Boolean
Vérifie un proof "ts:nonce:solution" (fenêtre @ttl_ms, comptage de bits CORRIGÉ).
30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/shugoi/pow.rb', line 30 def valid?(proof) return false if proof.to_s.empty? || @secret.empty? ts_str, nonce, solution = proof.to_s.split(":", 3) return false if ts_str.nil? || nonce.nil? || solution.nil? return false unless nonce.match?(/\A[0-9a-f]{16}\z/) ts = ts_str.to_i return false if ts.zero? return false if (Utils.now_ms - ts * 1000).abs > @ttl_ms digest = Utils.sha256_hex("#{salt(ts_str, nonce)}:#{solution}") Utils.leading_zero_bits(digest) >= @difficulty end |