private_captcha 0.0.2 → 0.0.5
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/README.md +3 -2
- data/lib/private_captcha/client.rb +18 -10
- data/lib/private_captcha/errors.rb +19 -7
- data/lib/private_captcha/verify_output.rb +9 -5
- data/lib/private_captcha/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70c3d94ed5a3020b3f71df8c50558c2e1a09c5e245374844209d2734bc4d51a8
|
|
4
|
+
data.tar.gz: 6a3a267b62466abc243069c922af1440f77d6feaeac24c96238d1841aa9d4437
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88e3e2caef19070d9aba49ceaf62b9a9eb0fb0c80bf56e6c9c90a86d513137248c9a8435b5fb751604c1bd1814f5d771ba9bf021a3278aa6ac964f13e0021716
|
|
7
|
+
data.tar.gz: 7bb7b45b726e696b3ea57d38c2ad6fbb1a9a7e37ac2ece858f50e43741512c39469f68732894ac0696b70c98c977459f18ebecb2cba12c4e8f294b628d76f4de
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# private-captcha-ruby
|
|
2
2
|
|
|
3
|
-
](https://badge.fury.io/rb/private_captcha)
|
|
4
|
+

|
|
4
5
|
|
|
5
6
|
Ruby client for server-side verification of Private Captcha solutions.
|
|
6
7
|
|
|
@@ -37,7 +38,7 @@ end
|
|
|
37
38
|
# Verify a captcha solution
|
|
38
39
|
begin
|
|
39
40
|
result = client.verify('user-solution-from-frontend')
|
|
40
|
-
if result.
|
|
41
|
+
if result.ok?
|
|
41
42
|
puts 'Captcha verified successfully!'
|
|
42
43
|
else
|
|
43
44
|
puts "Verification failed: #{result.error_message}"
|
|
@@ -25,7 +25,7 @@ module PrivateCaptcha
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
28
|
-
def verify(solution, max_backoff_seconds: nil, attempts: nil)
|
|
28
|
+
def verify(solution, max_backoff_seconds: nil, attempts: nil, sitekey: nil)
|
|
29
29
|
raise EmptySolutionError if solution.nil? || solution.empty?
|
|
30
30
|
|
|
31
31
|
max_backoff = max_backoff_seconds || @config.max_backoff_seconds
|
|
@@ -38,6 +38,7 @@ module PrivateCaptcha
|
|
|
38
38
|
response = nil
|
|
39
39
|
error = nil
|
|
40
40
|
attempt = 0
|
|
41
|
+
trace_id = nil
|
|
41
42
|
|
|
42
43
|
max_attempts.times do |i|
|
|
43
44
|
attempt = i + 1
|
|
@@ -51,11 +52,12 @@ module PrivateCaptcha
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
begin
|
|
54
|
-
response = do_verify(solution)
|
|
55
|
+
response = do_verify(solution, sitekey: sitekey)
|
|
55
56
|
error = nil
|
|
56
57
|
break
|
|
57
58
|
rescue RetriableError => e
|
|
58
59
|
error = e.original_error
|
|
60
|
+
trace_id = e.trace_id if e.trace_id
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -65,7 +67,8 @@ module PrivateCaptcha
|
|
|
65
67
|
|
|
66
68
|
if error
|
|
67
69
|
@logger.error("Failed to verify solution after #{attempt} attempts")
|
|
68
|
-
raise VerificationFailedError.new("Failed to verify solution after #{attempt} attempts", attempt
|
|
70
|
+
raise VerificationFailedError.new("Failed to verify solution after #{attempt} attempts", attempt,
|
|
71
|
+
trace_id: trace_id)
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
response
|
|
@@ -78,7 +81,10 @@ module PrivateCaptcha
|
|
|
78
81
|
|
|
79
82
|
output = verify(solution)
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
unless output.ok?
|
|
85
|
+
raise Error.new("captcha verification failed: #{output.error_message}",
|
|
86
|
+
trace_id: output.trace_id)
|
|
87
|
+
end
|
|
82
88
|
|
|
83
89
|
output
|
|
84
90
|
end
|
|
@@ -94,20 +100,23 @@ module PrivateCaptcha
|
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
97
|
-
def do_verify(solution)
|
|
103
|
+
def do_verify(solution, sitekey: nil)
|
|
98
104
|
request = Net::HTTP::Post.new(@endpoint)
|
|
99
105
|
request['X-Api-Key'] = @config.api_key
|
|
100
106
|
request['User-Agent'] = "private-captcha-ruby/#{VERSION}"
|
|
101
107
|
request['Content-Type'] = 'text/plain'
|
|
108
|
+
request['X-PC-Sitekey'] = sitekey if sitekey
|
|
102
109
|
request.body = solution
|
|
103
110
|
|
|
104
111
|
@logger.debug('Sending HTTP request') { "path=#{@endpoint.path} method=POST" }
|
|
105
112
|
|
|
106
113
|
response = nil
|
|
114
|
+
trace_id = nil
|
|
107
115
|
begin
|
|
108
116
|
response = Net::HTTP.start(@endpoint.hostname, @endpoint.port, use_ssl: true) do |http|
|
|
109
117
|
http.request(request)
|
|
110
118
|
end
|
|
119
|
+
trace_id = response['X-Trace-ID']
|
|
111
120
|
rescue SocketError, IOError, Timeout::Error, SystemCallError => e
|
|
112
121
|
@logger.debug('Failed to send HTTP request') { "error=#{e.message}" }
|
|
113
122
|
raise RetriableError, e
|
|
@@ -118,7 +127,6 @@ module PrivateCaptcha
|
|
|
118
127
|
end
|
|
119
128
|
|
|
120
129
|
status_code = response.code.to_i
|
|
121
|
-
request_id = response['X-Trace-ID']
|
|
122
130
|
|
|
123
131
|
case status_code
|
|
124
132
|
when 429
|
|
@@ -126,16 +134,16 @@ module PrivateCaptcha
|
|
|
126
134
|
@logger.debug('Rate limited') do
|
|
127
135
|
"retryAfter=#{retry_after} rateLimit=#{response['X-RateLimit-Limit']}"
|
|
128
136
|
end
|
|
129
|
-
raise RetriableError, HTTPError.new(status_code, retry_after)
|
|
137
|
+
raise RetriableError, HTTPError.new(status_code, retry_after, trace_id: trace_id)
|
|
130
138
|
when 500, 502, 503, 504, 408, 425
|
|
131
|
-
raise RetriableError, HTTPError.new(status_code)
|
|
139
|
+
raise RetriableError, HTTPError.new(status_code, trace_id: trace_id)
|
|
132
140
|
when 300..599
|
|
133
|
-
raise HTTPError,
|
|
141
|
+
raise HTTPError.new(status_code, trace_id: trace_id)
|
|
134
142
|
end
|
|
135
143
|
|
|
136
144
|
begin
|
|
137
145
|
json_data = JSON.parse(response.body)
|
|
138
|
-
VerifyOutput.from_json(json_data,
|
|
146
|
+
VerifyOutput.from_json(json_data, trace_id: trace_id)
|
|
139
147
|
rescue JSON::ParserError => e
|
|
140
148
|
@logger.debug('Failed to parse response') { "error=#{e.message}" }
|
|
141
149
|
raise RetriableError, e
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PrivateCaptcha
|
|
4
|
-
|
|
4
|
+
# Error is the base class for Private Captcha errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :trace_id
|
|
7
|
+
|
|
8
|
+
def initialize(msg = nil, trace_id: nil)
|
|
9
|
+
@trace_id = trace_id
|
|
10
|
+
super(msg)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
5
13
|
|
|
6
14
|
# EmptyAPIKeyError is raised when the API key is not provided or is empty
|
|
7
15
|
class EmptyAPIKeyError < Error
|
|
8
|
-
def initialize(msg = 'API key is empty')
|
|
16
|
+
def initialize(msg = 'API key is empty', trace_id: nil)
|
|
9
17
|
super
|
|
10
18
|
end
|
|
11
19
|
end
|
|
12
20
|
|
|
13
21
|
# EmptySolutionError is raised when the solution is not provided or is empty
|
|
14
22
|
class EmptySolutionError < Error
|
|
15
|
-
def initialize(msg = 'solution is empty')
|
|
23
|
+
def initialize(msg = 'solution is empty', trace_id: nil)
|
|
16
24
|
super
|
|
17
25
|
end
|
|
18
26
|
end
|
|
@@ -21,10 +29,10 @@ module PrivateCaptcha
|
|
|
21
29
|
class HTTPError < Error
|
|
22
30
|
attr_reader :status_code, :seconds
|
|
23
31
|
|
|
24
|
-
def initialize(status_code, seconds = nil)
|
|
32
|
+
def initialize(status_code, seconds = nil, trace_id: nil)
|
|
25
33
|
@status_code = status_code
|
|
26
34
|
@seconds = seconds
|
|
27
|
-
super("HTTP error #{status_code}")
|
|
35
|
+
super("HTTP error #{status_code}", trace_id: trace_id)
|
|
28
36
|
end
|
|
29
37
|
end
|
|
30
38
|
|
|
@@ -36,15 +44,19 @@ module PrivateCaptcha
|
|
|
36
44
|
@original_error = error
|
|
37
45
|
super(error.message)
|
|
38
46
|
end
|
|
47
|
+
|
|
48
|
+
def trace_id
|
|
49
|
+
@original_error.respond_to?(:trace_id) ? @original_error.trace_id : nil
|
|
50
|
+
end
|
|
39
51
|
end
|
|
40
52
|
|
|
41
53
|
# VerificationFailedError is raised when verification fails after all retry attempts
|
|
42
54
|
class VerificationFailedError < Error
|
|
43
55
|
attr_reader :attempts
|
|
44
56
|
|
|
45
|
-
def initialize(message, attempts)
|
|
57
|
+
def initialize(message, attempts, trace_id: nil)
|
|
46
58
|
@attempts = attempts
|
|
47
|
-
super(message)
|
|
59
|
+
super(message, trace_id: trace_id)
|
|
48
60
|
end
|
|
49
61
|
end
|
|
50
62
|
end
|
|
@@ -33,28 +33,32 @@ module PrivateCaptcha
|
|
|
33
33
|
}.freeze
|
|
34
34
|
|
|
35
35
|
attr_accessor :success, :code, :origin, :timestamp
|
|
36
|
-
attr_reader :
|
|
36
|
+
attr_reader :trace_id, :attempt
|
|
37
37
|
|
|
38
|
-
def initialize(success: false, code: VERIFY_NO_ERROR, origin: nil, timestamp: nil,
|
|
38
|
+
def initialize(success: false, code: VERIFY_NO_ERROR, origin: nil, timestamp: nil, trace_id: nil, attempt: 0)
|
|
39
39
|
@success = success
|
|
40
40
|
@code = code
|
|
41
41
|
@origin = origin
|
|
42
42
|
@timestamp = timestamp
|
|
43
|
-
@
|
|
43
|
+
@trace_id = trace_id
|
|
44
44
|
@attempt = attempt
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
def ok?
|
|
48
|
+
@success == true && @code == VERIFY_NO_ERROR
|
|
49
|
+
end
|
|
50
|
+
|
|
47
51
|
def error_message
|
|
48
52
|
ERROR_MESSAGES.fetch(@code, 'error')
|
|
49
53
|
end
|
|
50
54
|
|
|
51
|
-
def self.from_json(json_data,
|
|
55
|
+
def self.from_json(json_data, trace_id: nil, attempt: 0)
|
|
52
56
|
new(
|
|
53
57
|
success: json_data['success'],
|
|
54
58
|
code: json_data['code'] || VERIFY_NO_ERROR,
|
|
55
59
|
origin: json_data['origin'],
|
|
56
60
|
timestamp: json_data['timestamp'],
|
|
57
|
-
|
|
61
|
+
trace_id: trace_id,
|
|
58
62
|
attempt: attempt
|
|
59
63
|
)
|
|
60
64
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: private_captcha
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Taras Kushnir
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -46,6 +46,7 @@ licenses:
|
|
|
46
46
|
metadata:
|
|
47
47
|
homepage_uri: https://privatecaptcha.com
|
|
48
48
|
source_code_uri: https://github.com/PrivateCaptcha/private-captcha-ruby
|
|
49
|
+
rubygems_mfa_required: 'true'
|
|
49
50
|
post_install_message:
|
|
50
51
|
rdoc_options: []
|
|
51
52
|
require_paths:
|
|
@@ -61,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
62
|
- !ruby/object:Gem::Version
|
|
62
63
|
version: '0'
|
|
63
64
|
requirements: []
|
|
64
|
-
rubygems_version: 3.4.
|
|
65
|
+
rubygems_version: 3.4.19
|
|
65
66
|
signing_key:
|
|
66
67
|
specification_version: 4
|
|
67
68
|
summary: Ruby client for server-side Private Captcha API
|