spamtrap 0.3.5 → 0.4.2

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.
data/lib/spamtrap.rb CHANGED
@@ -1,9 +1,18 @@
1
1
  require 'openssl'
2
+ require 'ipaddr'
2
3
 
3
4
  module Spamtrap
4
5
  class << self
5
6
  attr_accessor :on_trap
6
- attr_writer :nonce, :nonce_timeout, :mutate
7
+ attr_writer :stable_ids
8
+
9
+ # Whether mutated fields keep an id/for derived from the real field name (e.g.
10
+ # #message_name) instead of the opaque encrypted one. Default true.
11
+ def stable_ids
12
+ @stable_ids.nil? ? true : @stable_ids
13
+ end
14
+
15
+ attr_writer :nonce, :nonce_timeout, :mutate, :mutation_timeout, :nonce_skew, :allowed_params
7
16
 
8
17
  def nonce
9
18
  @nonce || false
@@ -13,12 +22,168 @@ module Spamtrap
13
22
  @nonce_timeout || 1800
14
23
  end
15
24
 
25
+ # false, true (strict), :strict, or :lenient (remap only; never traps a plaintext key).
16
26
  def mutate
17
27
  @mutate || false
18
28
  end
29
+
30
+ # Extra top-level param names a strict action accepts unencrypted (e.g. app-added form fields).
31
+ def allowed_params
32
+ Array(@allowed_params).map(&:to_s)
33
+ end
34
+
35
+ def mutation_timeout
36
+ @mutation_timeout || nonce_timeout
37
+ end
38
+
39
+ # How far a token's timestamp may sit in the future (clock skew), before it's rejected.
40
+ def nonce_skew
41
+ @nonce_skew || 60
42
+ end
43
+
44
+ attr_writer :nonce_store
45
+
46
+ # Where nonce: :single_use records seen nonce ids; defaults to the app's cache.
47
+ def nonce_store
48
+ @nonce_store || Rails.cache
49
+ end
50
+
51
+ attr_writer :trap_response
52
+
53
+ # How a trapped request responds: :head, :no_content, :unprocessable, :redirect_back,
54
+ # a callable, or a Hash keyed by trap reason. See Spamtrap::Controller#spamtrap_render_trap.
55
+ def trap_response
56
+ @trap_response || :head
57
+ end
58
+
59
+ attr_writer :enabled
60
+
61
+ # Global kill switch; false skips every spamtrap check (honeypot, nonce, mutation).
62
+ def enabled
63
+ @enabled.nil? ? true : @enabled
64
+ end
65
+
66
+ attr_writer :nonce_bind_ip
67
+
68
+ # true binds the nonce to the full client IP (default), :prefix to its /24 or /48
69
+ # network (tolerates mobile handoff/CGNAT), false does not bind it to the IP at all.
70
+ def nonce_bind_ip
71
+ @nonce_bind_ip.nil? ? true : @nonce_bind_ip
72
+ end
73
+
74
+ attr_writer :min_fill_time
75
+
76
+ # Minimum seconds between render and submission a human needs; false or 0 disables. Default 1.
77
+ def min_fill_time
78
+ @min_fill_time.nil? ? 1 : @min_fill_time
79
+ end
80
+
81
+ attr_writer :secret_key_base
82
+
83
+ # The current HKDF source secret; defaults to the app's own so no setup is needed to opt in.
84
+ def secret_key_base
85
+ @secret_key_base || Rails.application.secret_key_base
86
+ end
87
+
88
+ attr_writer :honeypot_styles, :js_proof, :suspicious_if
89
+
90
+ # Decoys f.spamtrap renders: any of :textarea, :text, :checkbox. Default is the textarea only.
91
+ def honeypot_styles
92
+ Array(@honeypot_styles.nil? ? :textarea : @honeypot_styles).map(&:to_sym)
93
+ end
94
+
95
+ # Require a hidden field that only an executing script fills in; excludes no-JS users.
96
+ def js_proof
97
+ @js_proof || false
98
+ end
99
+
100
+ # App-defined heuristic run after every other check; truthy result traps as :content.
101
+ def suspicious_if
102
+ @suspicious_if
103
+ end
104
+
105
+ attr_writer :previous_secret_key_base
106
+
107
+ # Set during a rotation window so tokens minted under the old secret still verify/decrypt.
108
+ def previous_secret_key_base
109
+ @previous_secret_key_base
110
+ end
111
+
112
+ attr_writer :token_context
113
+
114
+ # Callable taking the request and returning a String mixed into every token, so a token
115
+ # minted on one hostname doesn't verify on another. Off (nil) by default.
116
+ def token_context
117
+ @token_context
118
+ end
119
+ end
120
+
121
+ # nil when token_context is unset, so the token format is unchanged for apps that don't opt in.
122
+ def self.token_context_for(request)
123
+ token_context && token_context.call(request).to_s
124
+ end
125
+
126
+ # Normalises ip per mode: true keeps it as-is, :prefix masks to its /24 (IPv4) or /48 (IPv6)
127
+ # network (IPv4-mapped addresses unmapped first so they mask as IPv4), false discards it.
128
+ def self.normalize_ip(ip, mode = nonce_bind_ip)
129
+ case mode
130
+ when false
131
+ ''
132
+ when :prefix
133
+ begin
134
+ addr = IPAddr.new(ip.to_s)
135
+ addr = addr.native if addr.ipv4_mapped?
136
+ bits = addr.ipv4? ? 24 : 48
137
+ "#{addr.mask(bits)}/#{bits}"
138
+ rescue IPAddr::InvalidAddressError
139
+ ip.to_s
140
+ end
141
+ else
142
+ ip.to_s
143
+ end
144
+ end
145
+
146
+ # Parameter names for each honeypot style, derived from the declared name so the view and
147
+ # controller agree without extra configuration. With mutation on they are encrypted like any field.
148
+ def self.honeypot_fields(name)
149
+ { textarea: name.to_s, text: "#{name}_input", checkbox: "#{name}_check" }
150
+ end
151
+
152
+ # Key for Rack::Attack / Rails' rate_limit, scoped by the normalized client IP.
153
+ def self.throttle_key(request)
154
+ "spamtrap:#{normalize_ip(request.remote_ip)}"
155
+ end
156
+
157
+ # Wires the gem into Action Controller and Action View. The Railtie calls the two halves
158
+ # from on_load hooks; outside a Rails app call install! after those frameworks are loaded.
159
+ def self.install!
160
+ install_controller!
161
+ install_form_builder!
162
+ end
163
+
164
+ def self.install_controller!
165
+ ActionController::Base.include Spamtrap::Controller unless ActionController::Base < Spamtrap::Controller
166
+ # const_defined?(false) avoids autoloading action_controller/api.rb for apps that never use it.
167
+ return unless ActionController.const_defined?(:API, false)
168
+
169
+ ActionController::API.include Spamtrap::Controller unless ActionController::API < Spamtrap::Controller
170
+ end
171
+
172
+ def self.install_form_builder!
173
+ builder = ActionView::Helpers::FormBuilder
174
+ builder.prepend Spamtrap::FormBuilderMutation unless builder < Spamtrap::FormBuilderMutation
175
+ builder.include Spamtrap::FormBuilderHelper unless builder < Spamtrap::FormBuilderHelper
176
+ end
177
+
178
+ # True once action_view/helpers has loaded, checked without triggering the autoload
179
+ # (defined?(ActionView::Helpers::FormBuilder) would load it).
180
+ def self.form_builder_loaded?
181
+ defined?(ActionView) && ActionView.autoload?(:Helpers).nil? &&
182
+ ActionView.const_defined?(:Helpers, false) && ActionView::Helpers.const_defined?(:FormBuilder, false)
19
183
  end
20
184
 
21
185
  require 'spamtrap/crypto'
22
186
  require 'spamtrap/controller'
23
187
  require 'spamtrap/helper'
188
+ require 'spamtrap/railtie' if defined?(Rails::Railtie)
24
189
  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.3.5
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cedric Howe
@@ -10,19 +10,107 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rails
13
+ name: actionpack
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.0'
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '7.0'
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: actionview
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.2'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9'
52
+ - !ruby/object:Gem::Dependency
53
+ name: railties
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '7.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '7.2'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '9'
72
+ - !ruby/object:Gem::Dependency
73
+ name: rake
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ - !ruby/object:Gem::Dependency
87
+ name: minitest
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: activemodel
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
26
114
  description: Create bogus form fields (honeypots) that will be filled-in by spambots.
27
115
  When submitted, the form data will be discarded while still returning a 200 response.
28
116
  email: cedric@freezerbox.com
@@ -30,15 +118,16 @@ executables: []
30
118
  extensions: []
31
119
  extra_rdoc_files: []
32
120
  files:
121
+ - lib/generators/spamtrap/install/install_generator.rb
122
+ - lib/generators/spamtrap/install/templates/spamtrap.rb
33
123
  - lib/spamtrap.rb
34
124
  - lib/spamtrap/controller.rb
35
125
  - lib/spamtrap/crypto.rb
36
126
  - lib/spamtrap/helper.rb
127
+ - lib/spamtrap/railtie.rb
128
+ - lib/spamtrap/test_helper.rb
37
129
  - lib/spamtrap/version.rb
38
- - test/controller_test.rb
39
- - test/form_builder_mutation_test.rb
40
- - test/test_helper.rb
41
- homepage: http://github.com/cedric/spamtrap/
130
+ homepage: https://github.com/cedric/spamtrap/
42
131
  licenses:
43
132
  - MIT
44
133
  metadata: {}
@@ -49,17 +138,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
138
  requirements:
50
139
  - - ">="
51
140
  - !ruby/object:Gem::Version
52
- version: 3.0.0
141
+ version: 3.1.0
53
142
  required_rubygems_version: !ruby/object:Gem::Requirement
54
143
  requirements:
55
144
  - - ">="
56
145
  - !ruby/object:Gem::Version
57
146
  version: 1.3.6
58
147
  requirements: []
59
- rubygems_version: 4.0.6
148
+ rubygems_version: 4.0.20
60
149
  specification_version: 4
61
150
  summary: Simple spamtrap for spambots.
62
- test_files:
63
- - test/controller_test.rb
64
- - test/form_builder_mutation_test.rb
65
- - test/test_helper.rb
151
+ test_files: []