spamtrap 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91ab3ea3a6d506bbc30ec82be0f584e51dbe9c51cc93b0677d9e97edc9d8ea47
4
- data.tar.gz: 6c3667c9e712016f6f668db20e4e7e175290e148f4d259032a6400b9bf75986c
3
+ metadata.gz: 619741f1ea048808a9235e1bc02a814c2d6606aed599e188c3ea99d464e2fc85
4
+ data.tar.gz: e309645da54a5ac5c898b83f8e90b5132361001724469ceabe18bf7f3195fbac
5
5
  SHA512:
6
- metadata.gz: 753c5c75e6a39d958b647bc45ee1e433f59f05c514a702e35e532f6a1dfc8abcb0dfeeecf5edbb3c0d489c488ec093fddb9c3cc342dab2f71027cd5a8f483ac6
7
- data.tar.gz: f5896fef5cad634cb91e2d46c52ab5b1fca74fbc76a9061fc05e209b793b0ddd8d066770ec02c33f74a45e565687ae63778140bfc5a8d485164809a0581b0a65
6
+ metadata.gz: 9e8328c22c89ee565ef34c2cff0e2ecad4aae2b9e71a9627bb6162e57e1013577fa53f65a56a217928e8d0503017b4c2508cec435ecf0c911eb257da53087f59
7
+ data.tar.gz: 49fa2e0f20b2489eb082c9bd4cd3fe1a09f4d4ddd03022676e12a91fc7779a0bab36e1a628205a0a1f29904da8292a984ef11a317bda486eed0c74f1a5d42909
@@ -1,4 +1,5 @@
1
1
  module Spamtrap::Controller
2
+ include Spamtrap::Crypto
2
3
 
3
4
  def self.included(base)
4
5
  base.extend ActsAsMethods
@@ -6,18 +7,70 @@ module Spamtrap::Controller
6
7
 
7
8
  module ActsAsMethods
8
9
  def spamtrap(honeypot = 'spamtrap', options = {}, &block)
10
+ # Capture explicit per-call values; use sentinel so globals are read
11
+ # at request time rather than at class definition time.
12
+ nonce_opt = options.key?(:nonce) ? options.delete(:nonce) : :global
13
+ timeout_opt = options.key?(:nonce_timeout) ? options.delete(:nonce_timeout) : :global
14
+ mutate_opt = options.key?(:mutate) ? options.delete(:mutate) : :global
15
+
9
16
  before_action(options) do |controller|
10
17
  controller.instance_eval(&block) if block_given?
11
18
  controller.instance_eval do
19
+ nonce_enabled = nonce_opt == :global ? Spamtrap.nonce : nonce_opt
20
+ nonce_timeout = timeout_opt == :global ? Spamtrap.nonce_timeout : timeout_opt
21
+ mutate_enabled = mutate_opt == :global ? Spamtrap.mutate : mutate_opt
22
+
23
+ spamtrap_remap_params if mutate_enabled
24
+
12
25
  if params[honeypot].present?
13
26
  Rails.logger.warn "Spamtrap triggered by #{request.remote_ip}."
14
27
  head 200
28
+ elsif nonce_enabled && !spamtrap_valid_nonce?(nonce_timeout)
29
+ Rails.logger.warn "Spamtrap nonce invalid from #{request.remote_ip}."
30
+ head 200
15
31
  end
16
32
  end
17
33
  end
18
34
  end
19
35
  end
20
36
 
37
+ def spamtrap_valid_nonce?(timeout)
38
+ timestamp = params[:spamtrap_timestamp].to_i
39
+ nonce = params[:spamtrap_nonce].to_s
40
+ return false if timestamp.zero? || nonce.empty?
41
+ return false if Time.now.to_i - timestamp > timeout.to_i
42
+
43
+ secret = Rails.application.secret_key_base
44
+ expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}:#{request.remote_ip}")
45
+ ActiveSupport::SecurityUtils.secure_compare(nonce, expected)
46
+ end
47
+
48
+ def spamtrap_remap_params
49
+ salt_hex = params[:spamtrap_mutation_salt].to_s
50
+ return if salt_hex.empty?
51
+
52
+ salt_bytes = [salt_hex].pack('H*')
53
+ return unless salt_bytes.bytesize == Spamtrap::Crypto::NONCE_LEN
54
+
55
+ spamtrap_remap_hash(params, salt_bytes)
56
+ rescue ArgumentError
57
+ nil
58
+ end
59
+
60
+ def spamtrap_remap_hash(hash, salt)
61
+ hash.each_key.to_a.each do |key|
62
+ real = spamtrap_decrypt_field(key.to_s, salt)
63
+ if real
64
+ hash[real] = hash.delete(key)
65
+ key = real
66
+ end
67
+ child = hash[key]
68
+ spamtrap_remap_hash(child, salt) if child.is_a?(ActionController::Parameters)
69
+ end
70
+ end
71
+
72
+ private :spamtrap_valid_nonce?, :spamtrap_remap_params, :spamtrap_remap_hash
73
+
21
74
  end
22
75
 
23
76
  ActionController::Base.send :include, Spamtrap::Controller
@@ -0,0 +1,46 @@
1
+ require 'base64'
2
+
3
+ module Spamtrap
4
+ module Crypto
5
+ CIPHER = 'aes-128-gcm'
6
+ KEY_LEN = 16
7
+ NONCE_LEN = 12
8
+ TAG_LEN = 16
9
+
10
+ private
11
+
12
+ def spamtrap_crypto_key
13
+ @spamtrap_crypto_key ||= OpenSSL::KDF.hkdf(
14
+ Rails.application.secret_key_base,
15
+ salt: 'spamtrap-mutation',
16
+ info: '',
17
+ length: KEY_LEN,
18
+ hash: 'SHA256'
19
+ )
20
+ end
21
+
22
+ def spamtrap_encrypt_field(field_name, salt_bytes)
23
+ cipher = OpenSSL::Cipher.new(CIPHER)
24
+ cipher.encrypt
25
+ cipher.key = spamtrap_crypto_key
26
+ cipher.iv = salt_bytes
27
+ ct = cipher.update(field_name.to_s) + cipher.final
28
+ Base64.urlsafe_encode64(ct + cipher.auth_tag(TAG_LEN), padding: false)
29
+ end
30
+
31
+ def spamtrap_decrypt_field(token, salt_bytes)
32
+ raw = Base64.urlsafe_decode64(token)
33
+ return nil if raw.bytesize <= TAG_LEN
34
+ ct = raw[0, raw.bytesize - TAG_LEN]
35
+ tag = raw[-TAG_LEN..]
36
+ cipher = OpenSSL::Cipher.new(CIPHER)
37
+ cipher.decrypt
38
+ cipher.key = spamtrap_crypto_key
39
+ cipher.iv = salt_bytes
40
+ cipher.auth_tag = tag
41
+ (cipher.update(ct) + cipher.final).to_sym
42
+ rescue OpenSSL::Cipher::CipherError, ArgumentError
43
+ nil
44
+ end
45
+ end
46
+ end
@@ -1,17 +1,68 @@
1
+ module Spamtrap
2
+ module FormBuilderMutation
3
+ include Spamtrap::Crypto
4
+
5
+ MUTABLE_FIELDS = %i[
6
+ text_field email_field password_field number_field url_field telephone_field
7
+ text_area check_box hidden_field file_field date_field time_field
8
+ datetime_local_field month_field week_field search_field color_field
9
+ range_field select collection_select grouped_collection_select label
10
+ ].freeze
11
+
12
+ MUTABLE_FIELDS.each do |m|
13
+ define_method(m) do |field, *args, &blk|
14
+ field = spamtrap_encrypt_field(field.to_s, @spamtrap_salt) if @spamtrap_salt
15
+ super(field, *args, &blk)
16
+ end
17
+ end
18
+
19
+ def initialize(object_name, object, template, options)
20
+ super
21
+ @spamtrap_salt = options[:spamtrap_salt] if options[:spamtrap_salt]
22
+ end
23
+
24
+ def fields_for(record_name, record_object = nil, fields_options = {}, &block)
25
+ if @spamtrap_salt
26
+ if record_object.is_a?(Hash) && record_object.extractable_options?
27
+ record_object = record_object.merge(spamtrap_salt: @spamtrap_salt)
28
+ else
29
+ fields_options = fields_options.merge(spamtrap_salt: @spamtrap_salt)
30
+ end
31
+ end
32
+ super(record_name, record_object, fields_options, &block)
33
+ end
34
+ end
35
+ end
36
+
1
37
  class ActionView::Helpers::FormBuilder
2
- def spamtrap(parameter='spamtrap', options={})
3
- options.reverse_merge!({:class => 'spamtrap'})
4
- @template.text_area_tag(parameter, nil, options)
38
+ prepend Spamtrap::FormBuilderMutation
39
+
40
+ def spamtrap(parameter = 'spamtrap', options = {})
41
+ mutate = options.key?(:mutate) ? options.delete(:mutate) : Spamtrap.mutate
42
+ nonce = options.key?(:nonce) ? options.delete(:nonce) : Spamtrap.nonce
43
+ options.reverse_merge!(class: 'spamtrap')
44
+
45
+ if mutate
46
+ @spamtrap_salt = SecureRandom.bytes(Spamtrap::Crypto::NONCE_LEN)
47
+ salt_field = @template.hidden_field_tag(
48
+ :spamtrap_mutation_salt, @spamtrap_salt.unpack1('H*')
49
+ )
50
+ end
51
+
52
+ @template.text_area_tag(parameter, nil, options) +
53
+ (nonce ? spamtrap_nonce_fields : ''.html_safe) +
54
+ (salt_field || ''.html_safe)
5
55
  end
6
56
 
7
- # # def nonce(method, tag_value, options = {})
8
- # def cryptographic_nonce(options = {})
9
- # # @template.hidden_field(@object_name, method, tag_value, objectify_options(options))
10
- # now, random = Time.now.to_i, SecureRandom.hex
11
- # # OpenSSL::HMAC.hexdigest()
12
- # cryptographic_nonce = Digest::MD5.hexdigest([timestamp, Rails.application.config.secret_key_base].join(':'))
13
- # @template.hidden_field(@object_name, :timestamp, now, objectify_options(options)) +
14
- # @template.hidden_field(@object_name, :random, random, objectify_options(options)) +
15
- # @template.hidden_field(@object_name, :spinner, cryptographic_nonce(now), objectify_options(options))
16
- # end
57
+ private
58
+
59
+ def spamtrap_nonce_fields
60
+ timestamp = Time.now.to_i
61
+ ip = @template.request.remote_ip
62
+ secret = Rails.application.secret_key_base
63
+ nonce = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}:#{ip}")
64
+
65
+ @template.hidden_field_tag(:spamtrap_timestamp, timestamp) +
66
+ @template.hidden_field_tag(:spamtrap_nonce, nonce)
67
+ end
17
68
  end
@@ -1,5 +1,5 @@
1
1
  module Spamtrap
2
2
 
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.1'
4
4
 
5
5
  end
data/lib/spamtrap.rb CHANGED
@@ -1,4 +1,23 @@
1
+ require 'openssl'
2
+
1
3
  module Spamtrap
4
+ class << self
5
+ attr_writer :nonce, :nonce_timeout, :mutate
6
+
7
+ def nonce
8
+ @nonce || false
9
+ end
10
+
11
+ def nonce_timeout
12
+ @nonce_timeout || 1800
13
+ end
14
+
15
+ def mutate
16
+ @mutate || false
17
+ end
18
+ end
19
+
20
+ require 'spamtrap/crypto'
2
21
  require 'spamtrap/controller'
3
22
  require 'spamtrap/helper'
4
23
  end
@@ -1,10 +1,347 @@
1
1
  require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
- class ControllerTest < Minitest::Test
3
+ # Test controllers — render a body so we can distinguish a real response
4
+ # from a spamtrap-blocked one (which uses head 200 and has an empty body).
4
5
 
5
- def test_foobar
6
- assert true
7
- assert_equal "hello", "hello"
6
+ class HoneypotController < ActionController::Base
7
+ spamtrap :trap_field, only: :create
8
+
9
+ def create
10
+ render plain: 'success'
11
+ end
12
+ end
13
+
14
+ class NonceController < ActionController::Base
15
+ spamtrap :trap_field, nonce: true, only: :create
16
+
17
+ def create
18
+ render plain: 'success'
19
+ end
20
+ end
21
+
22
+ class NonceTimeoutController < ActionController::Base
23
+ spamtrap :trap_field, nonce: true, nonce_timeout: 60, only: :create
24
+
25
+ def create
26
+ render plain: 'success'
27
+ end
28
+ end
29
+
30
+ class MutationController < ActionController::Base
31
+ spamtrap :trap_field, mutate: true, only: :create
32
+
33
+ def create
34
+ # Render the remapped comment param keys so tests can assert on them
35
+ render plain: params[:comment].to_unsafe_h.keys.sort.join(',')
36
+ end
37
+ end
38
+
39
+ class NestedMutationController < ActionController::Base
40
+ spamtrap :trap_field, mutate: true, only: :create
41
+
42
+ def create
43
+ # Render top-level and nested address keys to verify salt propagation
44
+ comment_keys = params[:comment].to_unsafe_h.except('address').keys.sort
45
+ address_keys = params.dig(:comment, :address).to_unsafe_h.keys.sort rescue []
46
+ render plain: "#{comment_keys.join(',')};#{address_keys.join(',')}"
47
+ end
48
+ end
49
+
50
+ module NonceTestHelper
51
+ REMOTE_IP = '0.0.0.0'
52
+
53
+ def generate_nonce(timestamp, ip = REMOTE_IP)
54
+ secret = Rails.application.secret_key_base
55
+ OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}:#{ip}")
56
+ end
57
+ end
58
+
59
+ module MutationTestHelper
60
+ include Spamtrap::Crypto
61
+
62
+ MUTATION_SALT = SecureRandom.bytes(Spamtrap::Crypto::NONCE_LEN)
63
+ MUTATION_SALT_HEX = MUTATION_SALT.unpack1('H*')
64
+ end
65
+
66
+ class HoneypotControllerTest < ActionController::TestCase
67
+ tests HoneypotController
68
+
69
+ def test_empty_honeypot_allows_request
70
+ post :create, params: { trap_field: '' }
71
+ assert_response :ok
72
+ assert_equal 'success', response.body
73
+ end
74
+
75
+ def test_filled_honeypot_silently_discards
76
+ post :create, params: { trap_field: 'buy cheap meds' }
77
+ assert_response :ok
78
+ assert_empty response.body
79
+ end
80
+ end
81
+
82
+ class NonceControllerTest < ActionController::TestCase
83
+ include NonceTestHelper
84
+ tests NonceController
85
+
86
+ def test_valid_nonce_allows_request
87
+ timestamp = Time.now.to_i
88
+ post :create, params: {
89
+ trap_field: '',
90
+ spamtrap_timestamp: timestamp,
91
+ spamtrap_nonce: generate_nonce(timestamp)
92
+ }
93
+ assert_response :ok
94
+ assert_equal 'success', response.body
95
+ end
96
+
97
+ def test_tampered_nonce_is_rejected
98
+ timestamp = Time.now.to_i
99
+ post :create, params: {
100
+ trap_field: '',
101
+ spamtrap_timestamp: timestamp,
102
+ spamtrap_nonce: 'deadbeef'
103
+ }
104
+ assert_response :ok
105
+ assert_empty response.body
106
+ end
107
+
108
+ def test_expired_nonce_is_rejected
109
+ timestamp = Time.now.to_i - 7200
110
+ post :create, params: {
111
+ trap_field: '',
112
+ spamtrap_timestamp: timestamp,
113
+ spamtrap_nonce: generate_nonce(timestamp)
114
+ }
115
+ assert_response :ok
116
+ assert_empty response.body
117
+ end
118
+
119
+ def test_missing_nonce_fields_are_rejected
120
+ post :create, params: { trap_field: '' }
121
+ assert_response :ok
122
+ assert_empty response.body
123
+ end
124
+
125
+ def test_filled_honeypot_takes_priority_over_valid_nonce
126
+ timestamp = Time.now.to_i
127
+ post :create, params: {
128
+ trap_field: 'spam',
129
+ spamtrap_timestamp: timestamp,
130
+ spamtrap_nonce: generate_nonce(timestamp)
131
+ }
132
+ assert_response :ok
133
+ assert_empty response.body
134
+ end
135
+ end
136
+
137
+ class NonceTimeoutControllerTest < ActionController::TestCase
138
+ include NonceTestHelper
139
+ tests NonceTimeoutController
140
+
141
+ def test_nonce_within_custom_timeout_passes
142
+ timestamp = Time.now.to_i - 30
143
+ post :create, params: {
144
+ trap_field: '',
145
+ spamtrap_timestamp: timestamp,
146
+ spamtrap_nonce: generate_nonce(timestamp)
147
+ }
148
+ assert_response :ok
149
+ assert_equal 'success', response.body
150
+ end
151
+
152
+ def test_nonce_beyond_custom_timeout_is_rejected
153
+ timestamp = Time.now.to_i - 120
154
+ post :create, params: {
155
+ trap_field: '',
156
+ spamtrap_timestamp: timestamp,
157
+ spamtrap_nonce: generate_nonce(timestamp)
158
+ }
159
+ assert_response :ok
160
+ assert_empty response.body
161
+ end
162
+ end
163
+
164
+ class MutationControllerTest < ActionController::TestCase
165
+ include MutationTestHelper
166
+ tests MutationController
167
+
168
+ def test_encrypted_field_names_are_remapped_to_real_names
169
+ body_token = spamtrap_encrypt_field('body', MUTATION_SALT)
170
+ email_token = spamtrap_encrypt_field('email', MUTATION_SALT)
171
+
172
+ post :create, params: {
173
+ trap_field: '',
174
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
175
+ comment: { body_token => 'Hello world', email_token => 'test@example.com' }
176
+ }
177
+
178
+ assert_response :ok
179
+ assert_equal 'body,email', response.body
180
+ end
181
+
182
+ def test_unencrypted_field_names_pass_through_unchanged
183
+ post :create, params: {
184
+ trap_field: '',
185
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
186
+ comment: { body: 'Hello', email: 'test@example.com' }
187
+ }
188
+
189
+ assert_response :ok
190
+ assert_equal 'body,email', response.body
191
+ end
192
+
193
+ def test_no_salt_leaves_encrypted_params_unmapped
194
+ body_token = spamtrap_encrypt_field('body', MUTATION_SALT)
195
+
196
+ post :create, params: {
197
+ trap_field: '',
198
+ comment: { body_token => 'Hello' }
199
+ }
200
+
201
+ assert_response :ok
202
+ refute_equal 'body', response.body
203
+ end
204
+
205
+ def test_wrong_salt_fails_to_decrypt
206
+ other_salt = SecureRandom.bytes(Spamtrap::Crypto::NONCE_LEN)
207
+ body_token = spamtrap_encrypt_field('body', other_salt)
208
+
209
+ post :create, params: {
210
+ trap_field: '',
211
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
212
+ comment: { body_token => 'Hello' }
213
+ }
214
+
215
+ assert_response :ok
216
+ refute_equal 'body', response.body
217
+ end
218
+ end
219
+
220
+ class NestedMutationControllerTest < ActionController::TestCase
221
+ include MutationTestHelper
222
+ tests NestedMutationController
223
+
224
+ def test_salt_propagates_to_fields_for_child_builder
225
+ body_token = spamtrap_encrypt_field('body', MUTATION_SALT)
226
+ street_token = spamtrap_encrypt_field('street', MUTATION_SALT)
227
+ city_token = spamtrap_encrypt_field('city', MUTATION_SALT)
228
+
229
+ post :create, params: {
230
+ trap_field: '',
231
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
232
+ comment: {
233
+ body_token => 'Hello',
234
+ address: {
235
+ street_token => '123 Main St',
236
+ city_token => 'Springfield'
237
+ }
238
+ }
239
+ }
240
+
241
+ assert_response :ok
242
+ assert_equal 'body;city,street', response.body
243
+ end
244
+ end
245
+
246
+ # Controller that relies entirely on global defaults (no per-call options).
247
+ class GlobalDefaultsController < ActionController::Base
248
+ spamtrap :trap_field, only: :create
249
+
250
+ def create
251
+ render plain: params[:comment].to_unsafe_h.keys.sort.join(',')
8
252
  end
253
+ end
254
+
255
+ # Controller that explicitly overrides global defaults with false.
256
+ class GlobalOverrideController < ActionController::Base
257
+ spamtrap :trap_field, nonce: false, mutate: false, only: :create
9
258
 
259
+ def create
260
+ render plain: params[:comment].to_unsafe_h.keys.sort.join(',')
261
+ end
262
+ end
263
+
264
+ class GlobalDefaultsControllerTest < ActionController::TestCase
265
+ include MutationTestHelper
266
+ include NonceTestHelper
267
+ tests GlobalDefaultsController
268
+
269
+ setup do
270
+ Spamtrap.nonce = true
271
+ Spamtrap.mutate = true
272
+ end
273
+
274
+ teardown do
275
+ Spamtrap.nonce = false
276
+ Spamtrap.mutate = false
277
+ end
278
+
279
+ def test_global_nonce_default_rejects_missing_nonce
280
+ post :create, params: { trap_field: '', comment: { body: 'Hello' } }
281
+ assert_response :ok
282
+ assert_empty response.body
283
+ end
284
+
285
+ def test_global_nonce_default_accepts_valid_nonce
286
+ timestamp = Time.now.to_i
287
+ post :create, params: {
288
+ trap_field: '',
289
+ spamtrap_timestamp: timestamp,
290
+ spamtrap_nonce: generate_nonce(timestamp),
291
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
292
+ comment: { spamtrap_encrypt_field('body', MUTATION_SALT) => 'Hello' }
293
+ }
294
+ assert_response :ok
295
+ assert_equal 'body', response.body
296
+ end
297
+
298
+ def test_global_mutate_default_remaps_encrypted_fields
299
+ body_token = spamtrap_encrypt_field('body', MUTATION_SALT)
300
+ timestamp = Time.now.to_i
301
+ post :create, params: {
302
+ trap_field: '',
303
+ spamtrap_timestamp: timestamp,
304
+ spamtrap_nonce: generate_nonce(timestamp),
305
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
306
+ comment: { body_token => 'Hello' }
307
+ }
308
+ assert_response :ok
309
+ assert_equal 'body', response.body
310
+ end
311
+ end
312
+
313
+ class GlobalOverrideControllerTest < ActionController::TestCase
314
+ include MutationTestHelper
315
+ tests GlobalOverrideController
316
+
317
+ setup do
318
+ Spamtrap.nonce = true
319
+ Spamtrap.mutate = true
320
+ end
321
+
322
+ teardown do
323
+ Spamtrap.nonce = false
324
+ Spamtrap.mutate = false
325
+ end
326
+
327
+ def test_per_call_false_overrides_global_nonce
328
+ # No nonce params — would fail if global nonce: true were in effect
329
+ post :create, params: {
330
+ trap_field: '',
331
+ comment: { body: 'Hello' }
332
+ }
333
+ assert_response :ok
334
+ assert_equal 'body', response.body
335
+ end
336
+
337
+ def test_per_call_false_overrides_global_mutate
338
+ # Plain field name — would be remapped if global mutate: true were in effect
339
+ post :create, params: {
340
+ trap_field: '',
341
+ spamtrap_mutation_salt: MUTATION_SALT_HEX,
342
+ comment: { body: 'Hello' }
343
+ }
344
+ assert_response :ok
345
+ assert_equal 'body', response.body
346
+ end
10
347
  end
data/test/test_helper.rb CHANGED
@@ -1,4 +1,30 @@
1
1
  require 'rubygems'
2
+ require 'rails'
2
3
  require 'action_controller'
4
+ require 'action_controller/test_case'
3
5
  require 'minitest/autorun'
4
6
  require 'spamtrap'
7
+
8
+ class SpamtrapTestApp < Rails::Application
9
+ config.secret_key_base = 'a' * 64
10
+ config.eager_load = false
11
+ config.logger = Logger.new(nil)
12
+ end
13
+
14
+ Rails.application.initialize!
15
+
16
+ Rails.application.routes.draw do
17
+ post 'honeypot/create', to: 'honeypot#create'
18
+ post 'nonce/create', to: 'nonce#create'
19
+ post 'nonce_timeout/create', to: 'nonce_timeout#create'
20
+ post 'mutation/create', to: 'mutation#create'
21
+ post 'nested_mutation/create', to: 'nested_mutation#create'
22
+ post 'global_defaults/create', to: 'global_defaults#create'
23
+ post 'global_override/create', to: 'global_override#create'
24
+ end
25
+
26
+ class ActionController::TestCase
27
+ setup do
28
+ @routes = Rails.application.routes
29
+ end
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spamtrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cedric Howe
@@ -32,6 +32,7 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - lib/spamtrap.rb
34
34
  - lib/spamtrap/controller.rb
35
+ - lib/spamtrap/crypto.rb
35
36
  - lib/spamtrap/helper.rb
36
37
  - lib/spamtrap/version.rb
37
38
  - test/controller_test.rb
@@ -54,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
55
  - !ruby/object:Gem::Version
55
56
  version: 1.3.6
56
57
  requirements: []
57
- rubygems_version: 4.0.3
58
+ rubygems_version: 4.0.6
58
59
  specification_version: 4
59
60
  summary: Simple spamtrap for spambots.
60
61
  test_files: