altcha 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|