spam_protect 0.0.4

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 642e4962310843445840e8799d51e75a6f02ee4eaa16b3f3be8298446fb9ed28
4
+ data.tar.gz: e2b35c61e0a5e20c270a327b2eb63ec1cc0505604ff7422307e8e25cc943b187
5
+ SHA512:
6
+ metadata.gz: 40e5f2fc28e7761b82051430b5c66e09656ac93faf0fcb63925bcb43f26092226206d47e244606e091b3c453637b726bdebb0ca776fdf1c409e19fd43210b40a
7
+ data.tar.gz: 1f4feae42a54fd4e64dbe93e2320f4f6417172155beba6defc987493d34e7d414d1f6fb5b58153df9e47b03434bf5b6266af900d42dc2881af47bbfab04d64d7
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Full Fat Software Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # spam_protect
2
+
3
+ A lightweight Ruby gem to help reduce spam in Rails applications.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'spam_protect'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ bundle install
16
+
17
+ Then add the initializer to your Rails application:
18
+
19
+ bin/rails generate spam_protect:install
20
+
21
+ ## Usage
22
+
23
+ Use the form helper method `spam_protect_field` within your forms to include the necessary spam protection fields:
24
+
25
+ ```ruby
26
+ <%= form_for @comment do |f| >
27
+ <%= f.spam_protect_field %>
28
+ <% end %>
29
+ ```
30
+
31
+ This generates output similar to:
32
+
33
+ ```html
34
+ <input type="text" name="comment[hp_field]" class="sp_hp" autocomplete="off" tabindex="-1" />
35
+ <input type="hidden" name="comment[sp_timestamp]" value="encrypted_token_here" />
36
+ ```
37
+
38
+ Visually hide the honeypot field with CSS:
39
+
40
+ ```css
41
+ .sp_hp {
42
+ display: none !important;
43
+ }
44
+ ```
45
+
46
+ Include the JavaScript tag in any views where forms with spam protection are used:
47
+
48
+ ```ruby
49
+ <%= spam_protect_javascript_tag %>
50
+ ```
51
+ Check the results in your controller:
52
+
53
+ ```ruby
54
+ class CommentsController < ApplicationController
55
+ def create
56
+ if validate_spam_protect_params(params[:comment])
57
+ @comment = Comment.new(comment_params)
58
+ ...
59
+ else
60
+ # handle spam case
61
+ end
62
+ end
63
+ end
64
+ ```
65
+
66
+ ## Configuration
67
+
68
+ You can customize the behavior of the gem by modifying the configuration file located at `config/initializers/spam_protect.rb`. Here you can set options such as encryption keys, token expiration times, and honeypot field names.
69
+
70
+ ```ruby
71
+ # Example initializer for spam_protect
72
+ SpamProtect.configure do |config|
73
+ # Custom field names (symbols)
74
+ config.honeypot_field = :hp_phone
75
+ config.timestamp_field = :hp_ts
76
+
77
+ # CSS class applied to the honeypot field
78
+ config.honeypot_class = "sp_hp"
79
+ config.wrapper_class = "spam_protect"
80
+
81
+ # Require JavaScript checks
82
+ config.require_js = true # Default is true
83
+
84
+ # Minimum seconds required between form render and submission
85
+ config.min_seconds = 3
86
+
87
+ # Secret key for signing/encrypting timestamps
88
+ config.signature_secret = SecureRandom.hex(64) # Will default to Rails secret_key_base if nil
89
+
90
+ # Expiry duration for the signature
91
+ config.signature_expiry = 6.hours
92
+ end
93
+
94
+ ```
95
+
96
+ ## Development
97
+
98
+ Clone the repository and run:
99
+
100
+ bundle install
101
+ bundle exec rspec
102
+ bundle exec rubocop # for linting
103
+
104
+ ### Requirements
105
+
106
+ This gem requires Ruby 3.3 or newer. The repository `.ruby-version` is set to `3.4.1` but any Ruby >= 3.3 should work.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module SpamProtect
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Creates a SpamProtect initializer in config/initializers"
11
+
12
+ def copy_initializer
13
+ copy_file "spam_protect.rb", "config/initializers/spam_protect.rb"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example initializer for spam_protect
4
+ SpamProtect.configure do |config|
5
+ # Custom field names (symbols)
6
+ # config.honeypot_field = :hp_phone
7
+ # config.timestamp_field = :hp_ts
8
+
9
+ # CSS class applied to the honeypot field
10
+ # config.honeypot_class = "sp_hp"
11
+ # config.wrapper_class = "spam_protect"
12
+
13
+ # Require JavaScript checks
14
+ # config.require_js = true # Default is true
15
+
16
+ # Minimum seconds required between form render and submission
17
+ # config.min_seconds = 3
18
+
19
+ # Secret key for signing/encrypting timestamps
20
+ # config.signature_secret = SecureRandom.hex(64) # Will default to Rails secret_key_base if nil
21
+
22
+ # Expiry duration for the signature
23
+ # config.signature_expiry = 6.hours
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module ControllerHelpers
5
+ def validate_spam_protect_params(params, honeypot_key: nil, timestamp_key: nil, min_seconds: nil)
6
+ honeypot_key ||= SpamProtect.config.honeypot_field
7
+ timestamp_key ||= SpamProtect.config.timestamp_field
8
+ min_seconds ||= SpamProtect.config.min_seconds
9
+
10
+ unless params.is_a?(ActionController::Parameters)
11
+ raise ArgumentError, "params must be an instance of ActionController::Parameters"
12
+ end
13
+
14
+ unless params.key?(honeypot_key) && params.key?(timestamp_key)
15
+ raise ArgumentError, "params must include both #{honeypot_key} and #{timestamp_key} keys. Have you passed in params[:<model_name>]?"
16
+ end
17
+
18
+ honeypot_value = params[honeypot_key]
19
+ encrypted_timestamp = params[timestamp_key]
20
+
21
+ guardian = SpamProtect::Guardian.new(honeypot_value, encrypted_timestamp, cookies["spam_protect_token"], min_seconds)
22
+ guardian.valid?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module CurrentTime
5
+ module_function
6
+
7
+ def now
8
+ Time.respond_to?(:current) ? Time.current : Time.now
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Encryption
5
+ class Payload
6
+ VALID_METHODS = %w[timestamp expires_at].freeze
7
+
8
+ def self.generate
9
+ new({
10
+ timestamp: CurrentTime.now.to_i,
11
+ expires_at: CurrentTime.now.to_i + SpamProtect.config.signature_expiry.to_i
12
+ })
13
+ end
14
+
15
+ def initialize(hash)
16
+ @hash = hash
17
+ end
18
+
19
+ def [](key)
20
+ if VALID_METHODS.include?(key.to_s)
21
+ send(key)
22
+ end
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ "timestamp" => timestamp,
28
+ "expires_at" => expires_at
29
+ }
30
+ end
31
+
32
+ def expires_at
33
+ @hash[:expires_at] || @hash["expires_at"]
34
+ end
35
+
36
+ def timestamp
37
+ @hash[:timestamp] || @hash["timestamp"]
38
+ end
39
+
40
+ alias_method :expires_at?, :expires_at
41
+ alias_method :timestamp?, :timestamp
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Encryption
5
+ class SecretKey
6
+ class << self
7
+ def relevant_key!
8
+ result = from_configuration || from_rails
9
+
10
+ unless result.present?
11
+ raise Errors::NoSecretKey
12
+ end
13
+
14
+ unless defined?(ActiveSupport::KeyGenerator) && defined?(ActiveSupport::MessageEncryptor)
15
+ raise Errors::EncryptionUnavailable
16
+ end
17
+
18
+ key_len = ActiveSupport::MessageEncryptor.key_len
19
+
20
+ ActiveSupport::KeyGenerator.new(result).generate_key("spam_protect", key_len)
21
+ end
22
+
23
+ private
24
+
25
+ def from_configuration
26
+ SpamProtect.config.signature_secret
27
+ end
28
+
29
+ def from_rails
30
+ (defined?(Rails) && Rails.application.respond_to?(:secret_key_base)) ? Rails.application.secret_key_base : nil
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Encryption
5
+ module_function
6
+
7
+ # Encrypt and sign a payload (hash). Returns a token string or nil on failure.
8
+ def encrypt(payload)
9
+ encryptor = ActiveSupport::MessageEncryptor.new(secret_key)
10
+ encryptor.encrypt_and_sign(payload)
11
+ end
12
+
13
+ # Decrypt and verify a token. Returns the payload (usually a Hash) or nil.
14
+ def decrypt(token)
15
+ encryptor = ActiveSupport::MessageEncryptor.new(secret_key)
16
+ encryptor.decrypt_and_verify(token)
17
+ end
18
+
19
+ def secret_key
20
+ SecretKey.relevant_key!
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Errors
5
+ class EncryptionUnavailable < Error
6
+ def message
7
+ "ActiveSupport encryption helpers are not available. Please ensure the 'activesupport' gem is included in your project."
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Errors
5
+ class Error < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Errors
5
+ class NoSecretKey < Error
6
+ def message
7
+ "No secret key available for signing/encryption. Please set `SpamProtect.config.signature_secret` or ensure Rails is loaded with a valid `secret_key_base`."
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module FormBuilderMethods
5
+ def spam_protect_field(name: nil, timestamp_name: nil, wrapper: false)
6
+ name ||= SpamProtect.config.honeypot_field
7
+ timestamp_name ||= SpamProtect.config.timestamp_field
8
+ honeypot_class = SpamProtect.config.honeypot_class
9
+ wrapper_class = SpamProtect.config.wrapper_class
10
+
11
+ payload = Encryption::Payload.generate
12
+ token = Encryption.encrypt(payload.to_h)
13
+
14
+ honeypot = @template.text_field_tag("#{@object_name}[#{name}]", nil, class: honeypot_class, autocomplete: "off", tabindex: "-1")
15
+ signature_input = @template.hidden_field_tag("#{@object_name}[#{timestamp_name}]", token)
16
+
17
+ if wrapper
18
+ @template.content_tag(:div, honeypot + signature_input, class: wrapper_class)
19
+ else
20
+ (honeypot + signature_input).html_safe
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ class Guardian
5
+ attr_reader :errors
6
+
7
+ def initialize(honeypot_value, encrypted_timestamp, cookie, min_seconds)
8
+ @honeypot_value = honeypot_value
9
+ @encrypted_timestamp = encrypted_timestamp
10
+ @cookie = cookie
11
+ @min_seconds = min_seconds
12
+ @errors = []
13
+ end
14
+
15
+ def valid?
16
+ payload = Encryption::Payload.new(
17
+ Encryption.decrypt(@encrypted_timestamp)
18
+ )
19
+
20
+ cookie_payload = if SpamProtect.config.require_js
21
+ Encryption::Payload.new(
22
+ Encryption.decrypt(@cookie)
23
+ )
24
+ end
25
+
26
+ payloads = [
27
+ payload,
28
+ cookie_payload
29
+ ].compact
30
+
31
+ honeypot_policy = Policies::HoneypotPolicy.new(@honeypot_value)
32
+ if honeypot_policy.invalid?
33
+ @errors.append "Honeypot field is filled in"
34
+ return false
35
+ end
36
+
37
+ cookie_policy = Policies::CookiePolicy.new(@cookie)
38
+ if cookie_policy.invalid?
39
+ @errors.append "Cookie is invalid"
40
+ return false
41
+ end
42
+
43
+ payloads.each do |p|
44
+ encryption_policy = Policies::EncryptionPolicy.new(p)
45
+ if encryption_policy.invalid?
46
+ @errors.append "Payload encryption is invalid or expired"
47
+ return false
48
+ end
49
+
50
+ timestamp_policy = Policies::TimestampPolicy.new(p["timestamp"], @min_seconds)
51
+ if timestamp_policy.invalid?
52
+ @errors.append "Form submitted too quickly"
53
+ return false
54
+ end
55
+ end
56
+
57
+ true
58
+ rescue
59
+ @errors.append "Encryption failure"
60
+ false
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Policies
5
+ class BasePolicy
6
+ def valid?
7
+ raise NotImplementedError, "Subclasses must implement the valid? method"
8
+ end
9
+
10
+ def invalid?
11
+ !valid?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Policies
5
+ class CookiePolicy < BasePolicy
6
+ def initialize(cookie)
7
+ @cookie = cookie
8
+ end
9
+
10
+ def valid?
11
+ unless SpamProtect.config.require_js
12
+ return true
13
+ end
14
+
15
+ # Decryption/validity is checked elsewhere
16
+ @cookie.to_s.strip.present?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Policies
5
+ class EncryptionPolicy < BasePolicy
6
+ def initialize(payload)
7
+ @payload = payload
8
+ end
9
+
10
+ def valid?
11
+ correct_shape? && between_timestamps?
12
+ end
13
+
14
+ protected
15
+
16
+ def correct_shape?
17
+ @payload.timestamp? && @payload.expires_at?
18
+ end
19
+
20
+ def between_timestamps?
21
+ CurrentTime.now.between?(
22
+ Time.at(@payload["timestamp"].to_i),
23
+ Time.at(@payload["expires_at"].to_i)
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Policies
5
+ class HoneypotPolicy < BasePolicy
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def valid?
11
+ @value.nil? || @value.to_s.strip.empty?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module Policies
5
+ class TimestampPolicy < BasePolicy
6
+ def initialize(timestamp, min_seconds)
7
+ @timestamp = timestamp
8
+ @min_seconds = min_seconds
9
+ end
10
+
11
+ def valid?
12
+ return false if @timestamp.blank?
13
+
14
+ now = CurrentTime.now
15
+
16
+ submitted_at = Time.at(@timestamp.to_i)
17
+
18
+ (now - submitted_at) >= @min_seconds
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module SpamProtect
6
+ class Railtie < Rails::Railtie
7
+ initializer "spam_protect.action_view" do
8
+ ActiveSupport.on_load(:action_view) do
9
+ require_relative "form_builder"
10
+ require_relative "view_helpers"
11
+ ActionView::Helpers::FormBuilder.include(SpamProtect::FormBuilderMethods)
12
+ ActionView::Base.include(SpamProtect::ViewHelpers)
13
+ end
14
+ end
15
+
16
+ initializer "spam_protect.action_controller" do
17
+ ActiveSupport.on_load(:action_controller) do
18
+ require_relative "controller_helpers"
19
+ ActionController::Base.include(SpamProtect::ControllerHelpers) if defined?(ActionController::Base)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ VERSION = "0.0.4"
5
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpamProtect
4
+ module ViewHelpers
5
+ def spam_protect_javascript_tag
6
+ payload = Encryption::Payload.generate
7
+ token = Encryption.encrypt(payload.to_h)
8
+
9
+ js = <<~JS
10
+ (function(){
11
+ var token = #{token.to_json};
12
+ document.cookie = "spam_protect_token=" + encodeURIComponent(token) + "; path=/; SameSite=Lax; Secure";
13
+ })();
14
+ JS
15
+
16
+ %(<script>#{js}</script>).html_safe
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+ require "active_support"
5
+ require "active_support/core_ext/numeric/time"
6
+ require "active_support/message_encryptor"
7
+ require "active_support/key_generator"
8
+
9
+ require_relative "spam_protect/errors/error"
10
+ require_relative "spam_protect/errors/encryption_unavailable"
11
+ require_relative "spam_protect/errors/no_secret_key"
12
+ require_relative "spam_protect/encryption"
13
+ require_relative "spam_protect/encryption/payload"
14
+ require_relative "spam_protect/encryption/secret_key"
15
+ require_relative "spam_protect/policies/base_policy"
16
+ require_relative "spam_protect/policies/honeypot_policy"
17
+ require_relative "spam_protect/policies/timestamp_policy"
18
+ require_relative "spam_protect/policies/encryption_policy"
19
+ require_relative "spam_protect/policies/cookie_policy"
20
+ require_relative "spam_protect/guardian"
21
+ require_relative "spam_protect/current_time"
22
+ require_relative "spam_protect/form_builder"
23
+ require_relative "spam_protect/view_helpers"
24
+ require_relative "spam_protect/version"
25
+ require_relative "spam_protect/railtie"
26
+ require_relative "spam_protect/controller_helpers"
27
+
28
+ module SpamProtect
29
+ # Configuration object for the gem
30
+ class Config
31
+ attr_accessor :honeypot_field, :timestamp_field, :honeypot_class,
32
+ :wrapper_class, :require_js, :min_seconds, :signature_secret,
33
+ :signature_expiry
34
+
35
+ def initialize
36
+ @honeypot_field = :hp_phone
37
+ @timestamp_field = :hp_ts
38
+ @honeypot_class = "sp_hp"
39
+ @wrapper_class = "spam_protect"
40
+ @require_js = true
41
+ @min_seconds = 2
42
+ @signature_secret = nil
43
+ @signature_expiry = 6.hours
44
+ end
45
+ end
46
+
47
+ def self.config
48
+ @config ||= Config.new
49
+ end
50
+
51
+ def self.configure
52
+ yield config if block_given?
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spam_protect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Full Fat Software
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-11-14 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubocop
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: standard
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop-performance
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rails
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '7.2'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '7.2'
82
+ description: spam_protect stops contact message spam in rails applications by adding
83
+ honeypot fields and timestamp checks.
84
+ email:
85
+ - hey@fullfatsoftware.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE
91
+ - README.md
92
+ - lib/generators/spam_protect/install/install_generator.rb
93
+ - lib/generators/spam_protect/install/templates/spam_protect.rb
94
+ - lib/spam_protect.rb
95
+ - lib/spam_protect/controller_helpers.rb
96
+ - lib/spam_protect/current_time.rb
97
+ - lib/spam_protect/encryption.rb
98
+ - lib/spam_protect/encryption/payload.rb
99
+ - lib/spam_protect/encryption/secret_key.rb
100
+ - lib/spam_protect/errors/encryption_unavailable.rb
101
+ - lib/spam_protect/errors/error.rb
102
+ - lib/spam_protect/errors/no_secret_key.rb
103
+ - lib/spam_protect/form_builder.rb
104
+ - lib/spam_protect/guardian.rb
105
+ - lib/spam_protect/policies/base_policy.rb
106
+ - lib/spam_protect/policies/cookie_policy.rb
107
+ - lib/spam_protect/policies/encryption_policy.rb
108
+ - lib/spam_protect/policies/honeypot_policy.rb
109
+ - lib/spam_protect/policies/timestamp_policy.rb
110
+ - lib/spam_protect/railtie.rb
111
+ - lib/spam_protect/version.rb
112
+ - lib/spam_protect/view_helpers.rb
113
+ homepage: https://github.com/fullfatsoftware/spam_protect
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '3.3'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.6.2
132
+ specification_version: 4
133
+ summary: A lightweight Ruby gem to help reduce spam in rails applications
134
+ test_files: []