Class: Shugoi::Pow

Inherits:
Object
  • Object
show all
Defined in:
lib/shugoi/pow.rb

Constant Summary collapse

POW_OK_TTL_MS =
30 * 24 * 3600 * 1000
AUTHORIZED_TTL_MS =
120_000

Instance Method Summary collapse

Constructor Details

#initialize(signing_secret, difficulty = 14, ttl_ms = 60_000) ⇒ Pow

Returns a new instance of Pow.



8
9
10
11
12
# File 'lib/shugoi/pow.rb', line 8

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

#challengeObject



14
15
16
17
# File 'lib/shugoi/pow.rb', line 14

def challenge
  ts = Utils.now_sec
  { ts: ts, nonce: nonce, salt: salt(ts, nonce), difficulty: @difficulty }
end


78
79
80
81
# File 'lib/shugoi/pow.rb', line 78

def sg_authorized_cookie
  secure = production? ? "; Secure" : ""
  "__sg_authorized=#{sg_authorized_value}; Path=/; HttpOnly; SameSite=Strict; Max-Age=120#{secure}"
end

#sg_authorized_valid?(cookie_val) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/shugoi/pow.rb', line 65

def sg_authorized_valid?(cookie_val)
  return false if @secret.empty? || cookie_val.to_s.empty?
  ts_str, sig = cookie_val.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_valueObject



60
61
62
63
# File 'lib/shugoi/pow.rb', line 60

def sg_authorized_value
  ts = Utils.now_sec
  "#{ts}:#{Utils.hmac_hex(@secret, "sg_authorized:#{ts}")}"
end


54
55
56
57
58
# File 'lib/shugoi/pow.rb', line 54

def sg_ok_cookie(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

Returns:

  • (Boolean)


40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/shugoi/pow.rb', line 40

def sg_ok_valid?(cookie_val, ip = "", ua = "")
  return false if @secret.empty? || cookie_val.to_s.empty?
  ts_str, bucket, fp, sig = cookie_val.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
  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



33
34
35
36
37
38
# File 'lib/shugoi/pow.rb', line 33

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

Returns:

  • (Boolean)


19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/shugoi/pow.rb', line 19

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