altcha 0.1.0 → 0.2.1
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/.gitignore +2 -0
- data/Gemfile.lock +1 -30
- data/README.md +5 -5
- data/lib/altcha/version.rb +1 -1
- data/lib/altcha.rb +107 -29
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26bd90d5f8f25ec3b565a8686fe424421326163149ce34e2fe94d9b335815394
|
4
|
+
data.tar.gz: d8a48c54a34721b77847b765c428b1068c1c6f611c4a133c5da6e77074c6e6c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 646c34c483cfbb68003e149b0ccf297d0b57072f6a1df6df3fbae555fd714a1a37dd3c8333e5dec61c024ef92655ba1983333b79984372775559291c2b635db0
|
7
|
+
data.tar.gz: 8670ab596f21009624bc1808fc7f952de9431df58a2752b5c2170d34fb8970d953550e35566bdfba7d383a27be09b17fbdecd6b4946becf7fd1b2eac49be98bf
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,25 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
altcha (0.1
|
4
|
+
altcha (0.2.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
ast (2.4.2)
|
10
9
|
diff-lcs (1.5.1)
|
11
|
-
json (2.7.2)
|
12
|
-
language_server-protocol (3.17.0.3)
|
13
|
-
parallel (1.25.1)
|
14
|
-
parser (3.3.4.0)
|
15
|
-
ast (~> 2.4.1)
|
16
|
-
racc
|
17
|
-
racc (1.8.1)
|
18
|
-
rainbow (3.1.1)
|
19
10
|
rake (10.5.0)
|
20
|
-
regexp_parser (2.9.2)
|
21
|
-
rexml (3.3.4)
|
22
|
-
strscan
|
23
11
|
rspec (3.13.0)
|
24
12
|
rspec-core (~> 3.13.0)
|
25
13
|
rspec-expectations (~> 3.13.0)
|
@@ -33,22 +21,6 @@ GEM
|
|
33
21
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
22
|
rspec-support (~> 3.13.0)
|
35
23
|
rspec-support (3.13.1)
|
36
|
-
rubocop (1.65.1)
|
37
|
-
json (~> 2.3)
|
38
|
-
language_server-protocol (>= 3.17.0)
|
39
|
-
parallel (~> 1.10)
|
40
|
-
parser (>= 3.3.0.2)
|
41
|
-
rainbow (>= 2.2.2, < 4.0)
|
42
|
-
regexp_parser (>= 2.4, < 3.0)
|
43
|
-
rexml (>= 3.2.5, < 4.0)
|
44
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
45
|
-
ruby-progressbar (~> 1.7)
|
46
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
47
|
-
rubocop-ast (1.31.3)
|
48
|
-
parser (>= 3.3.1.0)
|
49
|
-
ruby-progressbar (1.13.0)
|
50
|
-
strscan (3.1.0)
|
51
|
-
unicode-display_width (2.5.0)
|
52
24
|
|
53
25
|
PLATFORMS
|
54
26
|
arm64-darwin-23
|
@@ -59,7 +31,6 @@ DEPENDENCIES
|
|
59
31
|
bundler (~> 2.5)
|
60
32
|
rake (~> 10.0)
|
61
33
|
rspec (~> 3.0)
|
62
|
-
rubocop (~> 1.65)
|
63
34
|
|
64
35
|
BUNDLED WITH
|
65
36
|
2.5.11
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ This library is compatible with:
|
|
17
17
|
To install the ALTCHA Ruby Library, add it to your Gemfile:
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
gem 'altcha'
|
20
|
+
gem 'altcha'
|
21
21
|
```
|
22
22
|
|
23
23
|
Then run:
|
@@ -42,10 +42,10 @@ require 'altcha'
|
|
42
42
|
hmac_key = 'secret hmac key'
|
43
43
|
|
44
44
|
# Create a new challenge
|
45
|
-
options = Altcha::ChallengeOptions.new
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
options = Altcha::ChallengeOptions.new(
|
46
|
+
hmac_key: hmac_key,
|
47
|
+
max_number: 100000 # the maximum random number
|
48
|
+
)
|
49
49
|
|
50
50
|
challenge = Altcha.create_challenge(options)
|
51
51
|
|
data/lib/altcha/version.rb
CHANGED
data/lib/altcha.rb
CHANGED
@@ -23,12 +23,31 @@ module Altcha
|
|
23
23
|
# Class representing options for generating a challenge.
|
24
24
|
class ChallengeOptions
|
25
25
|
attr_accessor :algorithm, :max_number, :salt_length, :hmac_key, :salt, :number, :expires, :params
|
26
|
+
|
27
|
+
def initialize(algorithm: nil, max_number: nil, salt_length: nil, hmac_key:, salt: nil, number: nil, expires: nil, params: nil)
|
28
|
+
@algorithm = algorithm
|
29
|
+
@max_number = max_number
|
30
|
+
@salt_length = salt_length
|
31
|
+
@hmac_key = hmac_key
|
32
|
+
@salt = salt
|
33
|
+
@number = number
|
34
|
+
@expires = expires
|
35
|
+
@params = params
|
36
|
+
end
|
26
37
|
end
|
27
38
|
|
28
39
|
# Class representing a challenge with its attributes.
|
29
40
|
class Challenge
|
30
41
|
attr_accessor :algorithm, :challenge, :maxnumber, :salt, :signature
|
31
42
|
|
43
|
+
def initialize(algorithm:, challenge:, maxnumber: nil, salt:, signature:)
|
44
|
+
@algorithm = algorithm
|
45
|
+
@challenge = challenge
|
46
|
+
@maxnumber = maxnumber
|
47
|
+
@salt = salt
|
48
|
+
@signature = signature
|
49
|
+
end
|
50
|
+
|
32
51
|
# Converts the Challenge object to a JSON string.
|
33
52
|
# @param options [Hash] options to customize JSON encoding.
|
34
53
|
# @return [String] JSON representation of the Challenge object.
|
@@ -41,20 +60,20 @@ module Altcha
|
|
41
60
|
signature: @signature
|
42
61
|
}.to_json(options)
|
43
62
|
end
|
44
|
-
|
45
|
-
# Creates a Challenge object from a JSON string.
|
46
|
-
# @param string [String] JSON string to parse.
|
47
|
-
# @return [Challenge] Parsed Challenge object.
|
48
|
-
def from_json(string)
|
49
|
-
data = JSON.parse(string)
|
50
|
-
new data['algorithm'], data['challenge'], data['maxnumber'], data['salt'], data['signature']
|
51
|
-
end
|
52
63
|
end
|
53
64
|
|
54
65
|
# Class representing the payload of a challenge.
|
55
66
|
class Payload
|
56
67
|
attr_accessor :algorithm, :challenge, :number, :salt, :signature
|
57
68
|
|
69
|
+
def initialize(algorithm:, challenge:, number:, salt:, signature:)
|
70
|
+
@algorithm = algorithm
|
71
|
+
@challenge = challenge
|
72
|
+
@number = number
|
73
|
+
@salt = salt
|
74
|
+
@signature = signature
|
75
|
+
end
|
76
|
+
|
58
77
|
# Converts the Payload object to a JSON string.
|
59
78
|
# @param options [Hash] options to customize JSON encoding.
|
60
79
|
# @return [String] JSON representation of the Payload object.
|
@@ -71,9 +90,15 @@ module Altcha
|
|
71
90
|
# Creates a Payload object from a JSON string.
|
72
91
|
# @param string [String] JSON string to parse.
|
73
92
|
# @return [Payload] Parsed Payload object.
|
74
|
-
def from_json(string)
|
93
|
+
def self.from_json(string)
|
75
94
|
data = JSON.parse(string)
|
76
|
-
new
|
95
|
+
new(
|
96
|
+
algorithm: data['algorithm'],
|
97
|
+
challenge: data['challenge'],
|
98
|
+
number: data['number'],
|
99
|
+
salt: data['salt'],
|
100
|
+
signature: data['signature']
|
101
|
+
)
|
77
102
|
end
|
78
103
|
end
|
79
104
|
|
@@ -81,6 +106,13 @@ module Altcha
|
|
81
106
|
class ServerSignaturePayload
|
82
107
|
attr_accessor :algorithm, :verification_data, :signature, :verified
|
83
108
|
|
109
|
+
def initialize(algorithm:, verification_data:, signature:, verified:)
|
110
|
+
@algorithm = algorithm
|
111
|
+
@verification_data = verification_data
|
112
|
+
@signature = signature
|
113
|
+
@verified = verified
|
114
|
+
end
|
115
|
+
|
84
116
|
# Converts the ServerSignaturePayload object to a JSON string.
|
85
117
|
# @param options [Hash] options to customize JSON encoding.
|
86
118
|
# @return [String] JSON representation of the ServerSignaturePayload object.
|
@@ -96,9 +128,14 @@ module Altcha
|
|
96
128
|
# Creates a ServerSignaturePayload object from a JSON string.
|
97
129
|
# @param string [String] JSON string to parse.
|
98
130
|
# @return [ServerSignaturePayload] Parsed ServerSignaturePayload object.
|
99
|
-
def from_json(string)
|
131
|
+
def self.from_json(string)
|
100
132
|
data = JSON.parse(string)
|
101
|
-
new
|
133
|
+
new(
|
134
|
+
algorithm: data['algorithm'],
|
135
|
+
verification_data: data['verificationData'],
|
136
|
+
signature: data['signature'],
|
137
|
+
verified: data['verified']
|
138
|
+
)
|
102
139
|
end
|
103
140
|
end
|
104
141
|
|
@@ -106,6 +143,26 @@ module Altcha
|
|
106
143
|
class ServerSignatureVerificationData
|
107
144
|
attr_accessor :classification, :country, :detected_language, :email, :expire, :fields, :fields_hash,
|
108
145
|
:ip_address, :reasons, :score, :time, :verified
|
146
|
+
|
147
|
+
# Converts the ServerSignatureVerificationData object to a JSON string.
|
148
|
+
# @param options [Hash] options to customize JSON encoding.
|
149
|
+
# @return [String] JSON representation of the ServerSignatureVerificationData object.
|
150
|
+
def to_json(options = {})
|
151
|
+
{
|
152
|
+
classification: @classification,
|
153
|
+
country: @country,
|
154
|
+
detectedLanguage: @detected_language,
|
155
|
+
email: @email,
|
156
|
+
expire: @expire,
|
157
|
+
fields: @fields,
|
158
|
+
fieldsHash: @fields_hash,
|
159
|
+
ipAddress: @ip_address,
|
160
|
+
reasons: @reasons,
|
161
|
+
score: @score,
|
162
|
+
time: @time,
|
163
|
+
verified: @verified
|
164
|
+
}.to_json(options)
|
165
|
+
end
|
109
166
|
end
|
110
167
|
|
111
168
|
# Class representing the solution to a challenge.
|
@@ -204,13 +261,13 @@ module Altcha
|
|
204
261
|
challenge = hash_hex(algorithm, challenge_str)
|
205
262
|
signature = hmac_hex(algorithm, challenge, options.hmac_key)
|
206
263
|
|
207
|
-
Challenge.new
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
264
|
+
Challenge.new(
|
265
|
+
algorithm: algorithm,
|
266
|
+
challenge: challenge,
|
267
|
+
maxnumber: max_number,
|
268
|
+
salt: salt,
|
269
|
+
signature: signature,
|
270
|
+
)
|
214
271
|
end
|
215
272
|
|
216
273
|
# Verifies the solution provided by the client.
|
@@ -224,7 +281,17 @@ module Altcha
|
|
224
281
|
# Decode and parse base64 JSON string if it's a String
|
225
282
|
if payload.is_a?(String)
|
226
283
|
decoded_payload = Base64.decode64(payload)
|
227
|
-
payload =
|
284
|
+
payload = Payload.from_json(decoded_payload)
|
285
|
+
|
286
|
+
# Convert payload from hash to Payload if it's a plain object
|
287
|
+
elsif payload.is_a?(Hash)
|
288
|
+
payload = Payload.new(
|
289
|
+
algorithm: payload[:algorithm],
|
290
|
+
challenge: payload[:challenge],
|
291
|
+
number: payload[:number],
|
292
|
+
salt: payload[:salt],
|
293
|
+
signature: payload[:signature]
|
294
|
+
)
|
228
295
|
end
|
229
296
|
|
230
297
|
# Ensure payload is an instance of Payload
|
@@ -243,12 +310,12 @@ module Altcha
|
|
243
310
|
end
|
244
311
|
|
245
312
|
# Convert payload to ChallengeOptions
|
246
|
-
challenge_options = ChallengeOptions.new
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
313
|
+
challenge_options = ChallengeOptions.new(
|
314
|
+
algorithm: payload.algorithm,
|
315
|
+
hmac_key: hmac_key,
|
316
|
+
number: payload.number,
|
317
|
+
salt: payload.salt
|
318
|
+
)
|
252
319
|
|
253
320
|
# Create expected challenge and compare with the provided payload
|
254
321
|
expected_challenge = create_challenge(challenge_options)
|
@@ -272,7 +339,7 @@ module Altcha
|
|
272
339
|
# @param algorithm [String] The hashing algorithm to use.
|
273
340
|
# @return [Boolean] True if the fields hash matches, false otherwise.
|
274
341
|
def self.verify_fields_hash(form_data, fields, fields_hash, algorithm)
|
275
|
-
lines = fields.map { |field| form_data[field].
|
342
|
+
lines = fields.map { |field| form_data[field].to_s }
|
276
343
|
joined_data = lines.join("\n")
|
277
344
|
computed_hash = hash_hex(algorithm, joined_data)
|
278
345
|
computed_hash == fields_hash
|
@@ -286,7 +353,16 @@ module Altcha
|
|
286
353
|
# Decode and parse base64 JSON string if it's a String
|
287
354
|
if payload.is_a?(String)
|
288
355
|
decoded_payload = Base64.decode64(payload)
|
289
|
-
payload =
|
356
|
+
payload = ServerSignaturePayload.from_json(decoded_payload)
|
357
|
+
|
358
|
+
# Convert payload from hash to ServerSignaturePayload if it's a plain object
|
359
|
+
elsif payload.is_a?(Hash)
|
360
|
+
payload = ServerSignaturePayload.new(
|
361
|
+
algorithm: payload[:algorithm],
|
362
|
+
verification_data: payload[:verification_data],
|
363
|
+
signature: payload[:signature],
|
364
|
+
verified: payload[:verified]
|
365
|
+
)
|
290
366
|
end
|
291
367
|
|
292
368
|
# Ensure payload is an instance of ServerSignaturePayload
|
@@ -300,7 +376,7 @@ module Altcha
|
|
300
376
|
|
301
377
|
hash_data = hash(payload.algorithm, payload.verification_data)
|
302
378
|
expected_signature = hmac_hex(payload.algorithm, hash_data, hmac_key)
|
303
|
-
|
379
|
+
|
304
380
|
params = URI.decode_www_form(payload.verification_data).to_h
|
305
381
|
verification_data = ServerSignatureVerificationData.new.tap do |v|
|
306
382
|
v.classification = params['classification'] || nil
|
@@ -309,6 +385,8 @@ module Altcha
|
|
309
385
|
v.email = params['email'] || nil
|
310
386
|
v.expire = params['expire'] ? params['expire'].to_i : nil
|
311
387
|
v.fields = params['fields'] ? params['fields'].split(',') : nil
|
388
|
+
v.fields_hash = params['fieldsHash'] || nil
|
389
|
+
v.ip_address = params['ipAddress'] || nil
|
312
390
|
v.reasons = params['reasons'] ? params['reasons'].split(',') : nil
|
313
391
|
v.score = params['score'] ? params['score'].to_f : nil
|
314
392
|
v.time = params['time'] ? params['time'].to_i : nil
|