is_it_spam_rails 2.0.0
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 +7 -0
- data/CHANGELOG.md +53 -0
- data/CLAUDE.md +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +253 -0
- data/Rakefile +8 -0
- data/lib/generators/is_it_spam_rails/install_generator.rb +47 -0
- data/lib/generators/is_it_spam_rails/templates/initializer.rb +76 -0
- data/lib/is_it_spam_rails/client.rb +274 -0
- data/lib/is_it_spam_rails/configuration.rb +76 -0
- data/lib/is_it_spam_rails/controller_extension.rb +126 -0
- data/lib/is_it_spam_rails/railtie.rb +25 -0
- data/lib/is_it_spam_rails/version.rb +5 -0
- data/lib/is_it_spam_rails.rb +99 -0
- data/lib/tasks/is_it_spam_rails.rake +91 -0
- data/sig/is_it_spam_rails.rbs +4 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e52a3a701411ca162d602b08ef9672ef5e4fadcb8852021af50777a555a76b82
|
|
4
|
+
data.tar.gz: 90f33118154bf77776ba35778e662512ae3c6ca8569a38c7bc1d5c20f36591ca
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3c6060a1e7d1dff377d55e699d71cc662dc447713e914b6da8e5cfe403ac4c2788f0438e8ae49641b6a487ae77049dc5320de4677eae0e22451958b6dec72978
|
|
7
|
+
data.tar.gz: 0f57ee01f7cff1f90c590c728a1de6faa44c83126b637f144f905bb19166dfe2bdcbcb1f76781673ce21c80ee25efcb687173cb944d0f376cf9471181c0b3ccc
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [2.0.0] - TBD
|
|
4
|
+
|
|
5
|
+
### ⚠️ Breaking Changes
|
|
6
|
+
- **IP tracking is now ON by default** - The gem automatically captures and sends end user IP addresses (`request.remote_ip`) to the API
|
|
7
|
+
- End user IPs are tracked for spam detection and blocking across all apps
|
|
8
|
+
- To disable: set `config.track_end_user_ip = false` in your initializer
|
|
9
|
+
- Review your privacy policy before upgrading - IP addresses are personal data under GDPR
|
|
10
|
+
- Existing apps upgrading from 0.1.x will start tracking IPs immediately upon deployment
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **End user IP tracking** - Capture the actual IP address of the person filling out your form
|
|
14
|
+
- New configuration option: `track_end_user_ip` (default: true)
|
|
15
|
+
- Sends `end_user_ip` parameter to API for IP-based spam blocking
|
|
16
|
+
- Enables blocking malicious users across your entire hosting system
|
|
17
|
+
- IP tracking can be disabled per-app via configuration
|
|
18
|
+
- Backward compatible with is-it-spam.com API (falls back to server IP if not provided)
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `Client#check_spam` now accepts optional `end_user_ip:` parameter
|
|
22
|
+
- `IsItSpamRails.check_spam` module method now accepts optional `end_user_ip:` parameter
|
|
23
|
+
- Controller extension automatically captures `request.remote_ip` when tracking is enabled
|
|
24
|
+
|
|
25
|
+
### Privacy & GDPR
|
|
26
|
+
- End user IP addresses are now sent to is-it-spam.com by default
|
|
27
|
+
- IPs are stored in spam check logs for spam detection and analytics
|
|
28
|
+
- Legal basis: Legitimate interest (spam prevention)
|
|
29
|
+
- Users can opt-out with `config.track_end_user_ip = false`
|
|
30
|
+
- App owners should update privacy policies to disclose IP tracking
|
|
31
|
+
|
|
32
|
+
## [0.1.0] - 2025-06-14
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Rails integration gem for is-it-spam.com anti-spam service
|
|
36
|
+
- `is_it_spam` controller method with before_action hooks
|
|
37
|
+
- Manual spam handling mode (returns @spam_check_result for custom handling)
|
|
38
|
+
- Automatic spam handling mode with `on_spam` configuration for redirects
|
|
39
|
+
- Rails configuration through credentials and environment variables
|
|
40
|
+
- HTTP client with comprehensive error handling and rate limiting
|
|
41
|
+
- Rails Railtie for automatic controller extension inclusion
|
|
42
|
+
- Rails generator for easy setup
|
|
43
|
+
- Comprehensive test suite with Rails integration testing
|
|
44
|
+
- Support for nested parameter extraction (contact, commission, inquiry forms)
|
|
45
|
+
- Configurable API timeouts and base URL
|
|
46
|
+
- Rails logger integration for error reporting
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
- **Controller Integration**: Simple `is_it_spam only: [:create], on_spam: { redirect_to: root_path, notice: 'message' }` API
|
|
50
|
+
- **Flexible Configuration**: Rails credentials, environment variables, or manual configuration
|
|
51
|
+
- **Error Handling**: Graceful degradation with proper logging on API failures
|
|
52
|
+
- **Rails Native**: Uses ActiveSupport::Concern and Rails conventions
|
|
53
|
+
- **Production Ready**: Comprehensive error handling and logging
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a Ruby gem that provides Rails integration for the is-it-spam.com anti-spam service. The gem adds `check_spam` before_action hooks to Rails controllers for automatic spam detection.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
The gem follows a modular architecture:
|
|
12
|
+
|
|
13
|
+
- **Core API Client** (`lib/is_it_spam_rails/client.rb`) - HTTP client for is-it-spam.com API
|
|
14
|
+
- **Configuration** (`lib/is_it_spam_rails/configuration.rb`) - Handles API credentials and settings
|
|
15
|
+
- **Rails Integration** (`lib/is_it_spam_rails/railtie.rb`) - Integrates with Rails through Railtie
|
|
16
|
+
- **Controller Extension** (`lib/is_it_spam_rails/controller_extension.rb`) - Adds `check_spam` method to controllers
|
|
17
|
+
- **Spam Checker** (`lib/is_it_spam_rails/spam_checker.rb`) - Core spam checking logic
|
|
18
|
+
- **Rails Generator** (`lib/generators/is_it_spam_rails/install_generator.rb`) - Creates initializer file
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
### Testing
|
|
23
|
+
```bash
|
|
24
|
+
rake test # Run all tests
|
|
25
|
+
rake test test/test_client.rb # Run specific test file
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Development
|
|
29
|
+
```bash
|
|
30
|
+
bin/setup # Install dependencies
|
|
31
|
+
bin/console # Start interactive console
|
|
32
|
+
bundle install # Install gem dependencies
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Gem Tasks
|
|
36
|
+
```bash
|
|
37
|
+
rake build # Build the gem
|
|
38
|
+
rake install # Install locally
|
|
39
|
+
rake release # Release to RubyGems
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Rails Integration Testing
|
|
43
|
+
```bash
|
|
44
|
+
bin/rails is_it_spam:test_connection # Test API connection
|
|
45
|
+
bin/rails is_it_spam:test_spam_check # Test spam detection
|
|
46
|
+
bin/rails is_it_spam:config # Show configuration
|
|
47
|
+
bin/rails is_it_spam:install # Install initializer
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Development Notes
|
|
51
|
+
|
|
52
|
+
- Uses Minitest for testing with WebMock for HTTP stubbing
|
|
53
|
+
- All methods should include YARD documentation
|
|
54
|
+
- Classes should have description comments
|
|
55
|
+
- Environment variables: `IS_IT_SPAM_API_KEY`, `IS_IT_SPAM_API_SECRET`, `IS_IT_SPAM_BASE_URL`
|
|
56
|
+
- Rails credentials path: `is_it_spam_rails.api_key`, `is_it_spam_rails.api_secret`
|
|
57
|
+
- Gem supports Rails 6.0+ and Ruby 3.1+
|
|
58
|
+
- Uses HTTParty for HTTP requests
|
|
59
|
+
- Configuration auto-loads from Rails credentials or environment variables
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Benjamin Deutscher
|
|
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 above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# IsItSpamRails
|
|
2
|
+
|
|
3
|
+
Rails integration gem for [is-it-spam.com](https://is-it-spam.com) anti-spam service. Provides easy-to-use before_action hooks for automatic spam detection in your Rails controllers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'is_it_spam_rails'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Run the installer to create the initializer:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ bin/rails is_it_spam:install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Configure your API credentials in `config/initializers/is_it_spam_rails.rb`:
|
|
28
|
+
|
|
29
|
+
### Option 1: Rails Credentials (Recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ rails credentials:edit
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Add your credentials:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
is_it_spam_rails:
|
|
39
|
+
api_key: your_api_key_here
|
|
40
|
+
api_secret: your_api_secret_here
|
|
41
|
+
base_url: https://is-it-spam.com # optional
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then in your initializer:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
IsItSpamRails.configure do |config|
|
|
48
|
+
config.api_key = Rails.application.credentials.is_it_spam_rails[:api_key]
|
|
49
|
+
config.api_secret = Rails.application.credentials.is_it_spam_rails[:api_secret]
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Option 2: Environment Variables
|
|
54
|
+
|
|
55
|
+
Set environment variables:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export IS_IT_SPAM_API_KEY=your_api_key_here
|
|
59
|
+
export IS_IT_SPAM_API_SECRET=your_api_secret_here
|
|
60
|
+
export IS_IT_SPAM_BASE_URL=https://is-it-spam.com # optional
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The gem will automatically use these if no explicit configuration is provided.
|
|
64
|
+
|
|
65
|
+
### End User IP Tracking
|
|
66
|
+
|
|
67
|
+
By default, the gem tracks the IP address of the person filling out your form (`request.remote_ip`) and sends it to the is-it-spam.com API for IP-based spam blocking.
|
|
68
|
+
|
|
69
|
+
**Privacy Considerations:**
|
|
70
|
+
- End user IPs are stored in spam check logs for analytics and spam detection
|
|
71
|
+
- IPs can be blocked at system, user, or app level
|
|
72
|
+
- Consider your privacy policy and GDPR requirements when deploying this feature
|
|
73
|
+
|
|
74
|
+
**Configuration:**
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
IsItSpamRails.configure do |config|
|
|
78
|
+
config.api_key = "your_api_key"
|
|
79
|
+
config.api_secret = "your_api_secret"
|
|
80
|
+
config.track_end_user_ip = true # Default: true
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**To disable IP tracking:**
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
IsItSpamRails.configure do |config|
|
|
88
|
+
config.api_key = "your_api_key"
|
|
89
|
+
config.api_secret = "your_api_secret"
|
|
90
|
+
config.track_end_user_ip = false # Disable end user IP tracking
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When disabled, the API will still receive the server's IP address (for rate limiting purposes), but individual form submitters won't be tracked.
|
|
95
|
+
|
|
96
|
+
**Breaking Change:** Starting with version 2.0.0, IP tracking is **ON by default**. If you upgrade from an earlier version, end user IPs will automatically be tracked unless you explicitly disable it. Review your privacy policy before upgrading.
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
### Basic Usage
|
|
101
|
+
|
|
102
|
+
Add spam checking to your controllers with the `is_it_spam` method. By default, the gem sets `@spam_check_result` and lets you handle spam detection manually:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
class ContactController < ApplicationController
|
|
106
|
+
is_it_spam only: [:create]
|
|
107
|
+
|
|
108
|
+
def create
|
|
109
|
+
# Check if spam was detected
|
|
110
|
+
if @spam_check_result&.spam?
|
|
111
|
+
# Handle spam manually
|
|
112
|
+
flash[:alert] = "Your submission appears to be spam"
|
|
113
|
+
redirect_to root_path
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Process legitimate submissions
|
|
118
|
+
confidence = @spam_check_result&.confidence_score
|
|
119
|
+
Rails.logger.info "Legitimate submission with #{(confidence * 100).round(1)}% confidence"
|
|
120
|
+
# ...
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Automatic Spam Handling
|
|
126
|
+
|
|
127
|
+
For automatic spam handling, use the `on_spam` configuration to redirect spam submissions:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
class ContactController < ApplicationController
|
|
131
|
+
is_it_spam only: [:create], on_spam: {
|
|
132
|
+
redirect_to: root_path,
|
|
133
|
+
notice: 'Thank you for your message'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def create
|
|
137
|
+
# This action only executes for legitimate (non-spam) submissions
|
|
138
|
+
# @spam_check_result is available and will never be spam
|
|
139
|
+
|
|
140
|
+
# Process the legitimate submission
|
|
141
|
+
# ...
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Configuration Options
|
|
147
|
+
|
|
148
|
+
The `on_spam` hash accepts the following options:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class ContactController < ApplicationController
|
|
152
|
+
is_it_spam only: [:create], on_spam: {
|
|
153
|
+
redirect_to: root_path, # Path to redirect on spam detection
|
|
154
|
+
notice: 'Thank you for contacting us' # Flash notice message
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
You can also use `alert` instead of `notice`:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class ContactController < ApplicationController
|
|
163
|
+
is_it_spam only: [:create], on_spam: {
|
|
164
|
+
redirect_to: root_path,
|
|
165
|
+
alert: 'There was an issue with your submission'
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Dynamic Redirect Paths
|
|
171
|
+
|
|
172
|
+
Use route helpers or callable objects for dynamic paths:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
class ContactController < ApplicationController
|
|
176
|
+
is_it_spam only: [:create], on_spam: {
|
|
177
|
+
redirect_to: Rails.application.routes.url_helpers.root_path,
|
|
178
|
+
notice: I18n.t('contact.success')
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Parameter Detection
|
|
184
|
+
|
|
185
|
+
The gem automatically detects form parameters from common nested keys:
|
|
186
|
+
- `:commission`, `:contact`, `:inquiry`, `:message`, `:form`
|
|
187
|
+
- Maps `name`, `email`, and `message` fields
|
|
188
|
+
- Supports `first_name`/`last_name` combination for name field
|
|
189
|
+
|
|
190
|
+
## Testing and Debugging
|
|
191
|
+
|
|
192
|
+
The gem includes rake tasks for testing your configuration:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Test API connection
|
|
196
|
+
$ bin/rails is_it_spam:test_connection
|
|
197
|
+
|
|
198
|
+
# Test spam detection with sample data
|
|
199
|
+
$ bin/rails is_it_spam:test_spam_check
|
|
200
|
+
|
|
201
|
+
# Show current configuration
|
|
202
|
+
$ bin/rails is_it_spam:config
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## API
|
|
206
|
+
|
|
207
|
+
### Direct API Usage
|
|
208
|
+
|
|
209
|
+
You can also use the API directly without the before_action:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
result = IsItSpamRails.check_spam(
|
|
213
|
+
name: "John Doe",
|
|
214
|
+
email: "john@example.com",
|
|
215
|
+
message: "Your message here",
|
|
216
|
+
custom_fields: { company: "Acme Corp" }
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if result.spam?
|
|
220
|
+
puts "Spam detected: #{result.spam_reasons.join(', ')}"
|
|
221
|
+
puts "Confidence: #{(result.confidence_score * 100).round(1)}%"
|
|
222
|
+
else
|
|
223
|
+
puts "Message appears legitimate"
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Health Check
|
|
228
|
+
|
|
229
|
+
Check if the API service is available:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
if IsItSpamRails.health_check
|
|
233
|
+
puts "API is healthy"
|
|
234
|
+
else
|
|
235
|
+
puts "API is not responding"
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Error Handling
|
|
240
|
+
|
|
241
|
+
The gem is designed to fail gracefully. If the API is unavailable, rate limited, or returns errors, your application will continue to function normally. Errors are logged but don't block legitimate users.
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
|
246
|
+
|
|
247
|
+
## Contributing
|
|
248
|
+
|
|
249
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/its-benjamin-deutscher/is-it-spam.com.
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module IsItSpamRails
|
|
6
|
+
module Generators
|
|
7
|
+
# Rails generator for installing IsItSpamRails
|
|
8
|
+
#
|
|
9
|
+
# Creates an initializer file with configuration template
|
|
10
|
+
class InstallGenerator < Rails::Generators::Base
|
|
11
|
+
desc "Install IsItSpamRails by creating an initializer"
|
|
12
|
+
|
|
13
|
+
# Define source location for templates
|
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
|
15
|
+
|
|
16
|
+
# Create the initializer file
|
|
17
|
+
#
|
|
18
|
+
# @return [void]
|
|
19
|
+
def create_initializer
|
|
20
|
+
template "initializer.rb", "config/initializers/is_it_spam_rails.rb"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Display installation instructions
|
|
24
|
+
#
|
|
25
|
+
# @return [void]
|
|
26
|
+
def show_instructions
|
|
27
|
+
say ""
|
|
28
|
+
say "IsItSpamRails has been installed!", :green
|
|
29
|
+
say ""
|
|
30
|
+
say "Next steps:"
|
|
31
|
+
say "1. Configure your API credentials in config/initializers/is_it_spam_rails.rb"
|
|
32
|
+
say "2. Add your credentials to Rails credentials or environment variables"
|
|
33
|
+
say "3. Use is_it_spam in your controllers as a before_action"
|
|
34
|
+
say ""
|
|
35
|
+
say "Example usage in a controller:"
|
|
36
|
+
say " class ContactController < ApplicationController"
|
|
37
|
+
say " is_it_spam only: [:create], on_spam: {"
|
|
38
|
+
say " redirect_to: root_path,"
|
|
39
|
+
say " notice: 'Thank you for your message'"
|
|
40
|
+
say " }"
|
|
41
|
+
say " end"
|
|
42
|
+
say ""
|
|
43
|
+
say "For more information, visit: https://is-it-spam.com/docs"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Configuration for IsItSpamRails gem
|
|
4
|
+
#
|
|
5
|
+
# This initializer configures the connection to the is-it-spam.com API
|
|
6
|
+
# for automated spam detection in your Rails application.
|
|
7
|
+
#
|
|
8
|
+
# For more information, visit: https://is-it-spam.com/docs
|
|
9
|
+
|
|
10
|
+
IsItSpamRails.configure do |config|
|
|
11
|
+
# Your API credentials from is-it-spam.com
|
|
12
|
+
# You can get these from your dashboard at https://is-it-spam.com/dashboard
|
|
13
|
+
|
|
14
|
+
# Option 1: Use Rails credentials (recommended)
|
|
15
|
+
# Run: rails credentials:edit
|
|
16
|
+
# Add:
|
|
17
|
+
# is_it_spam_rails:
|
|
18
|
+
# api_key: your_api_key_here
|
|
19
|
+
# api_secret: your_api_secret_here
|
|
20
|
+
# base_url: https://is-it-spam.com # optional, defaults to production
|
|
21
|
+
#
|
|
22
|
+
# Then uncomment these lines:
|
|
23
|
+
# config.api_key = Rails.application.credentials.is_it_spam_rails[:api_key]
|
|
24
|
+
# config.api_secret = Rails.application.credentials.is_it_spam_rails[:api_secret]
|
|
25
|
+
# config.base_url = Rails.application.credentials.is_it_spam_rails[:base_url] # optional
|
|
26
|
+
|
|
27
|
+
# Option 2: Use environment variables
|
|
28
|
+
# Set these in your environment:
|
|
29
|
+
# IS_IT_SPAM_API_KEY=your_api_key_here
|
|
30
|
+
# IS_IT_SPAM_API_SECRET=your_api_secret_here
|
|
31
|
+
# IS_IT_SPAM_BASE_URL=https://is-it-spam.com # optional
|
|
32
|
+
#
|
|
33
|
+
# Then uncomment these lines:
|
|
34
|
+
# config.api_key = ENV["IS_IT_SPAM_API_KEY"]
|
|
35
|
+
# config.api_secret = ENV["IS_IT_SPAM_API_SECRET"]
|
|
36
|
+
# config.base_url = ENV["IS_IT_SPAM_BASE_URL"] # optional
|
|
37
|
+
|
|
38
|
+
# Option 3: Direct configuration (not recommended for production)
|
|
39
|
+
# config.api_key = "your_api_key_here"
|
|
40
|
+
# config.api_secret = "your_api_secret_here"
|
|
41
|
+
|
|
42
|
+
# Optional configuration
|
|
43
|
+
# config.base_url = "https://is-it-spam.com" # API base URL
|
|
44
|
+
# config.timeout = 30 # Request timeout in seconds
|
|
45
|
+
# config.logger = Rails.logger # Logger for debugging
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Usage in controllers:
|
|
49
|
+
#
|
|
50
|
+
# class ContactController < ApplicationController
|
|
51
|
+
# # Basic usage - detects spam and redirects with notice
|
|
52
|
+
# check_spam only: :create
|
|
53
|
+
#
|
|
54
|
+
# # Custom configuration
|
|
55
|
+
# check_spam only: :create,
|
|
56
|
+
# redirect_path: root_path,
|
|
57
|
+
# notice: "Thank you for your message"
|
|
58
|
+
#
|
|
59
|
+
# # With custom parameter mapping
|
|
60
|
+
# check_spam only: :create,
|
|
61
|
+
# param_key: :contact_form, # if your params are nested under contact_form
|
|
62
|
+
# custom_fields: { # map additional fields
|
|
63
|
+
# company: :company_name,
|
|
64
|
+
# phone: :phone_number
|
|
65
|
+
# }
|
|
66
|
+
#
|
|
67
|
+
# def create
|
|
68
|
+
# # Your normal controller logic
|
|
69
|
+
# # Spam checking happens automatically before this action
|
|
70
|
+
#
|
|
71
|
+
# # You can access the spam check result if needed:
|
|
72
|
+
# # @spam_check_result.spam?
|
|
73
|
+
# # @spam_check_result.confidence_score
|
|
74
|
+
# # @spam_check_result.spam_reasons
|
|
75
|
+
# end
|
|
76
|
+
# end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httparty"
|
|
4
|
+
|
|
5
|
+
module IsItSpamRails
|
|
6
|
+
# Main client class for interacting with the Is It Spam API
|
|
7
|
+
#
|
|
8
|
+
# Provides methods for checking spam and communicating with the API
|
|
9
|
+
class Client
|
|
10
|
+
include HTTParty
|
|
11
|
+
|
|
12
|
+
# API version
|
|
13
|
+
API_VERSION = "v1"
|
|
14
|
+
# Default timeout for requests
|
|
15
|
+
DEFAULT_TIMEOUT = 30
|
|
16
|
+
|
|
17
|
+
# Initialize client with credentials
|
|
18
|
+
#
|
|
19
|
+
# @param api_key [String] Your API key from Is It Spam
|
|
20
|
+
# @param api_secret [String] Your API secret from Is It Spam
|
|
21
|
+
# @param base_url [String] Base URL for the API (default: https://is-it-spam.com)
|
|
22
|
+
# @param timeout [Integer] Request timeout in seconds (default: 30)
|
|
23
|
+
# @raise [ConfigurationError] When credentials are missing
|
|
24
|
+
def initialize(api_key:, api_secret:, base_url: "https://is-it-spam.com", timeout: DEFAULT_TIMEOUT)
|
|
25
|
+
@api_key = api_key
|
|
26
|
+
@api_secret = api_secret
|
|
27
|
+
@base_url = base_url.chomp("/")
|
|
28
|
+
@timeout = timeout
|
|
29
|
+
|
|
30
|
+
validate_credentials!
|
|
31
|
+
|
|
32
|
+
self.class.base_uri @base_url
|
|
33
|
+
self.class.default_timeout @timeout
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if the provided content is spam
|
|
37
|
+
#
|
|
38
|
+
# @param name [String] Name from the contact form
|
|
39
|
+
# @param email [String] Email address from the contact form
|
|
40
|
+
# @param message [String] Message content from the contact form
|
|
41
|
+
# @param custom_fields [Hash] Additional custom fields to check (optional)
|
|
42
|
+
# @param end_user_ip [String, nil] IP address of the end user filling the form (optional)
|
|
43
|
+
# @return [SpamCheckResult] The result of the spam check
|
|
44
|
+
# @raise [ValidationError] When required parameters are missing or invalid
|
|
45
|
+
# @raise [ApiError] When the API returns an error
|
|
46
|
+
# @raise [RateLimitError] When rate limits are exceeded
|
|
47
|
+
def check_spam(name:, email:, message:, custom_fields: {}, end_user_ip: nil)
|
|
48
|
+
validate_required_params(name: name, email: email, message: message)
|
|
49
|
+
|
|
50
|
+
payload = {
|
|
51
|
+
spam_check: {
|
|
52
|
+
name: name,
|
|
53
|
+
email: email,
|
|
54
|
+
message: message,
|
|
55
|
+
additional_fields: custom_fields
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Only include end_user_ip if tracking is enabled and IP is provided
|
|
60
|
+
if IsItSpamRails.configuration.track_end_user_ip && end_user_ip.present?
|
|
61
|
+
payload[:spam_check][:end_user_ip] = end_user_ip
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
response = make_request(:post, "/api/#{API_VERSION}/spam_checks", body: payload.to_json)
|
|
65
|
+
SpamCheckResult.new(response.parsed_response)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check the health of the API service
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean] true if the service is healthy
|
|
71
|
+
# @raise [ApiError] When the API is unavailable
|
|
72
|
+
def health_check
|
|
73
|
+
make_request(:get, "/up")
|
|
74
|
+
true
|
|
75
|
+
rescue ApiError => e
|
|
76
|
+
raise e unless e.status_code == 503
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Validate that API credentials are present
|
|
83
|
+
#
|
|
84
|
+
# @raise [ConfigurationError] When credentials are missing
|
|
85
|
+
def validate_credentials!
|
|
86
|
+
raise ConfigurationError, "API key is required" if @api_key.nil? || @api_key.empty?
|
|
87
|
+
raise ConfigurationError, "API secret is required" if @api_secret.nil? || @api_secret.empty?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Validate required parameters for spam checking
|
|
91
|
+
#
|
|
92
|
+
# @param name [String] Name parameter
|
|
93
|
+
# @param email [String] Email parameter
|
|
94
|
+
# @param message [String] Message parameter
|
|
95
|
+
# @raise [ValidationError] When parameters are invalid
|
|
96
|
+
def validate_required_params(name:, email:, message:)
|
|
97
|
+
errors = {}
|
|
98
|
+
|
|
99
|
+
errors[:name] = ["can't be blank"] if name.nil? || name.empty?
|
|
100
|
+
errors[:email] = ["can't be blank"] if email.nil? || email.empty?
|
|
101
|
+
errors[:message] = ["can't be blank"] if message.nil? || message.empty?
|
|
102
|
+
|
|
103
|
+
# Basic email format validation
|
|
104
|
+
if email && !email.match?(/\A[^@\s]+@[^@\s]+\z/)
|
|
105
|
+
errors[:email] = (errors[:email] || []) << "is not a valid email address"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
unless errors.empty?
|
|
109
|
+
raise ValidationError.new("Validation failed", errors: errors)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Make an HTTP request to the API
|
|
114
|
+
#
|
|
115
|
+
# @param method [Symbol] HTTP method (:get, :post, etc.)
|
|
116
|
+
# @param path [String] API endpoint path
|
|
117
|
+
# @param options [Hash] Additional request options
|
|
118
|
+
# @return [HTTParty::Response] HTTP response object
|
|
119
|
+
# @raise [ApiError, RateLimitError, ValidationError] Based on response
|
|
120
|
+
def make_request(method, path, **options)
|
|
121
|
+
request_options = {
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type" => "application/json",
|
|
124
|
+
"X-API-Key" => @api_key,
|
|
125
|
+
"X-API-Secret" => @api_secret,
|
|
126
|
+
"User-Agent" => "IsItSpam Rails Gem #{IsItSpamRails::VERSION}"
|
|
127
|
+
}
|
|
128
|
+
}.merge(options)
|
|
129
|
+
|
|
130
|
+
response = self.class.send(method, path, request_options)
|
|
131
|
+
handle_response(response)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Handle API response and raise appropriate errors
|
|
135
|
+
#
|
|
136
|
+
# @param response [HTTParty::Response] HTTP response object
|
|
137
|
+
# @return [HTTParty::Response] Response for successful requests
|
|
138
|
+
# @raise [ApiError, RateLimitError, ValidationError] Based on status code
|
|
139
|
+
def handle_response(response)
|
|
140
|
+
case response.code
|
|
141
|
+
when 200..299
|
|
142
|
+
response
|
|
143
|
+
when 400
|
|
144
|
+
handle_error_response(response, ApiError)
|
|
145
|
+
when 401
|
|
146
|
+
handle_error_response(response, ApiError)
|
|
147
|
+
when 404
|
|
148
|
+
raise ApiError.new("Endpoint not found", status_code: 404, response_body: response.body)
|
|
149
|
+
when 422
|
|
150
|
+
handle_validation_error(response)
|
|
151
|
+
when 429
|
|
152
|
+
handle_error_response(response, RateLimitError)
|
|
153
|
+
when 500..599
|
|
154
|
+
handle_error_response(response, ApiError)
|
|
155
|
+
else
|
|
156
|
+
raise ApiError.new("Unexpected response code: #{response.code}",
|
|
157
|
+
status_code: response.code,
|
|
158
|
+
response_body: response.body)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Handle error responses with JSON body
|
|
163
|
+
#
|
|
164
|
+
# @param response [HTTParty::Response] HTTP response object
|
|
165
|
+
# @param error_class [Class] Error class to raise
|
|
166
|
+
# @raise [ApiError, RateLimitError] Specified error class
|
|
167
|
+
def handle_error_response(response, error_class)
|
|
168
|
+
begin
|
|
169
|
+
error_data = response.parsed_response
|
|
170
|
+
message = error_data["error"] || "API request failed"
|
|
171
|
+
rescue
|
|
172
|
+
message = "API request failed"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
raise error_class.new(message, status_code: response.code, response_body: response.body)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Handle validation error responses
|
|
179
|
+
#
|
|
180
|
+
# @param response [HTTParty::Response] HTTP response object
|
|
181
|
+
# @raise [ValidationError] With field-specific errors
|
|
182
|
+
def handle_validation_error(response)
|
|
183
|
+
begin
|
|
184
|
+
error_data = response.parsed_response
|
|
185
|
+
message = error_data["error"] || "Validation failed"
|
|
186
|
+
errors = error_data["errors"] || {}
|
|
187
|
+
rescue
|
|
188
|
+
message = "Validation failed"
|
|
189
|
+
errors = {}
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
raise ValidationError.new(message, errors: errors, status_code: 422, response_body: response.body)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Represents the result of a spam check operation
|
|
197
|
+
#
|
|
198
|
+
# Provides convenient methods for accessing spam detection results
|
|
199
|
+
class SpamCheckResult
|
|
200
|
+
# @return [Boolean] Whether content was identified as spam
|
|
201
|
+
attr_reader :spam
|
|
202
|
+
# @return [Float] Confidence score between 0.0 and 1.0
|
|
203
|
+
attr_reader :confidence
|
|
204
|
+
# @return [Array<String>] Reasons why content was flagged as spam
|
|
205
|
+
attr_reader :reasons
|
|
206
|
+
|
|
207
|
+
# Initialize spam check result
|
|
208
|
+
#
|
|
209
|
+
# @param data [Hash] Response data from the API
|
|
210
|
+
def initialize(data)
|
|
211
|
+
@spam = data["spam"]
|
|
212
|
+
@confidence = data["confidence"].to_f
|
|
213
|
+
@reasons = (data["reasons"] || []).dup.freeze
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Check if content was identified as spam
|
|
217
|
+
#
|
|
218
|
+
# @return [Boolean] true if content was identified as spam
|
|
219
|
+
def spam?
|
|
220
|
+
@spam
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Check if content appears legitimate
|
|
224
|
+
#
|
|
225
|
+
# @return [Boolean] true if content appears legitimate
|
|
226
|
+
def legitimate?
|
|
227
|
+
!spam?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Get confidence score
|
|
231
|
+
#
|
|
232
|
+
# @return [Float] Confidence score between 0.0 and 1.0
|
|
233
|
+
def confidence_score
|
|
234
|
+
@confidence
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Get reasons for spam detection
|
|
238
|
+
#
|
|
239
|
+
# @return [Array<String>] Reasons why content was flagged as spam
|
|
240
|
+
def spam_reasons
|
|
241
|
+
@reasons
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Get human-readable summary of the result
|
|
245
|
+
#
|
|
246
|
+
# @return [String] Human-readable summary of the result
|
|
247
|
+
def summary
|
|
248
|
+
if spam?
|
|
249
|
+
"Spam detected (#{(@confidence * 100).round(1)}% confidence): #{@reasons.join(', ')}"
|
|
250
|
+
else
|
|
251
|
+
"Content appears legitimate (#{(@confidence * 100).round(1)}% confidence)"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Convert result to hash
|
|
256
|
+
#
|
|
257
|
+
# @return [Hash] Hash representation of the result
|
|
258
|
+
def to_h
|
|
259
|
+
{
|
|
260
|
+
spam: @spam,
|
|
261
|
+
confidence: @confidence,
|
|
262
|
+
reasons: @reasons
|
|
263
|
+
}
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Convert result to JSON
|
|
267
|
+
#
|
|
268
|
+
# @param args [Array] Arguments passed to to_json
|
|
269
|
+
# @return [String] JSON representation of the result
|
|
270
|
+
def to_json(*args)
|
|
271
|
+
to_h.to_json(*args)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IsItSpamRails
|
|
4
|
+
# Configuration class for IsItSpamRails gem
|
|
5
|
+
#
|
|
6
|
+
# Handles global configuration settings for the Rails integration
|
|
7
|
+
class Configuration
|
|
8
|
+
# @return [String, nil] API key for authentication
|
|
9
|
+
attr_accessor :api_key
|
|
10
|
+
# @return [String, nil] API secret for authentication
|
|
11
|
+
attr_accessor :api_secret
|
|
12
|
+
# @return [String] Base URL for the API
|
|
13
|
+
attr_accessor :base_url
|
|
14
|
+
# @return [Integer] Request timeout in seconds
|
|
15
|
+
attr_accessor :timeout
|
|
16
|
+
# @return [Logger, nil] Logger instance for debugging
|
|
17
|
+
attr_accessor :logger
|
|
18
|
+
# @return [Boolean] Whether to track end user IP addresses (default: true)
|
|
19
|
+
attr_accessor :track_end_user_ip
|
|
20
|
+
|
|
21
|
+
# Initialize configuration with default values
|
|
22
|
+
def initialize
|
|
23
|
+
@base_url = "https://is-it-spam.com"
|
|
24
|
+
@timeout = 30
|
|
25
|
+
@logger = rails_logger
|
|
26
|
+
@track_end_user_ip = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get configured client instance
|
|
30
|
+
#
|
|
31
|
+
# @return [Client] The configured client
|
|
32
|
+
# @raise [ConfigurationError] When required configuration is missing
|
|
33
|
+
def client
|
|
34
|
+
@client ||= Client.new(
|
|
35
|
+
api_key: api_key,
|
|
36
|
+
api_secret: api_secret,
|
|
37
|
+
base_url: base_url,
|
|
38
|
+
timeout: timeout
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Reset the client (useful for testing or credential changes)
|
|
43
|
+
def reset_client!
|
|
44
|
+
@client = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check if configuration is valid
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean] true if configuration has required values
|
|
50
|
+
def valid?
|
|
51
|
+
!api_key.nil? && !api_key.empty? &&
|
|
52
|
+
!api_secret.nil? && !api_secret.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Validate configuration and raise error if invalid
|
|
56
|
+
#
|
|
57
|
+
# @raise [ConfigurationError] When configuration is invalid
|
|
58
|
+
def validate!
|
|
59
|
+
raise ConfigurationError, "API key is required" if api_key.nil? || api_key.empty?
|
|
60
|
+
raise ConfigurationError, "API secret is required" if api_secret.nil? || api_secret.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Get Rails logger if available, nil otherwise
|
|
66
|
+
#
|
|
67
|
+
# @return [Logger, nil] Rails logger or nil
|
|
68
|
+
def rails_logger
|
|
69
|
+
return nil unless defined?(Rails)
|
|
70
|
+
return nil unless Rails.respond_to?(:logger)
|
|
71
|
+
Rails.logger
|
|
72
|
+
rescue
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IsItSpamRails
|
|
4
|
+
# Controller extension providing spam checking functionality
|
|
5
|
+
#
|
|
6
|
+
# Adds the `is_it_spam` class method to Rails controllers for automatic spam detection
|
|
7
|
+
module ControllerExtension
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
# Add spam checking as a before_action
|
|
12
|
+
#
|
|
13
|
+
# @param options [Hash] Configuration options
|
|
14
|
+
# @option options [Hash] :on_spam Options for handling spam detection
|
|
15
|
+
# @option options [String, Proc] :on_spam[:redirect_to] Path to redirect to when spam is detected
|
|
16
|
+
# @option options [String] :on_spam[:notice] Flash notice message to display
|
|
17
|
+
# @option options [String] :on_spam[:alert] Flash alert message to display
|
|
18
|
+
# @option options [Symbol, String] :form_param_name Name of the nested parameter containing form data
|
|
19
|
+
def is_it_spam(options = {})
|
|
20
|
+
on_spam_options = options.delete(:on_spam) || {}
|
|
21
|
+
form_param_name = options.delete(:form_param_name)
|
|
22
|
+
|
|
23
|
+
before_action(options) do
|
|
24
|
+
check_for_spam(on_spam_options, form_param_name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Check for spam and handle accordingly
|
|
32
|
+
#
|
|
33
|
+
# @param on_spam_options [Hash] Options for spam handling
|
|
34
|
+
# @param form_param_name [Symbol, String, nil] Name of the nested parameter containing form data
|
|
35
|
+
# @return [void]
|
|
36
|
+
def check_for_spam(on_spam_options = {}, form_param_name = nil)
|
|
37
|
+
# Extract form parameters - try custom parameter name first if provided
|
|
38
|
+
form_params = extract_form_params(form_param_name)
|
|
39
|
+
|
|
40
|
+
# Skip if essential parameters are blank
|
|
41
|
+
return unless form_params[:name].present? && form_params[:email].present? && form_params[:message].present?
|
|
42
|
+
|
|
43
|
+
# Capture end user IP if tracking is enabled and request is available
|
|
44
|
+
end_user_ip = if IsItSpamRails.configuration.track_end_user_ip && respond_to?(:request) && request.respond_to?(:remote_ip)
|
|
45
|
+
request.remote_ip
|
|
46
|
+
else
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
@spam_check_result = IsItSpamRails.check_spam(
|
|
52
|
+
name: form_params[:name],
|
|
53
|
+
email: form_params[:email],
|
|
54
|
+
message: form_params[:message],
|
|
55
|
+
custom_fields: {},
|
|
56
|
+
end_user_ip: end_user_ip
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if @spam_check_result&.spam? && on_spam_options.any?
|
|
60
|
+
handle_spam_detection(on_spam_options)
|
|
61
|
+
end
|
|
62
|
+
rescue IsItSpamRails::ValidationError => e
|
|
63
|
+
Rails.logger&.warn("Spam check validation failed: #{e.message}")
|
|
64
|
+
rescue IsItSpamRails::RateLimitError => e
|
|
65
|
+
Rails.logger&.warn("Spam check rate limit exceeded: #{e.message}")
|
|
66
|
+
rescue IsItSpamRails::ApiError => e
|
|
67
|
+
Rails.logger&.error("Spam check API error: #{e.message}")
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
Rails.logger&.error("Spam check unexpected error: #{e.message}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Extract form parameters from nested params
|
|
74
|
+
#
|
|
75
|
+
# @param form_param_name [Symbol, String, nil] Name of the nested parameter containing form data
|
|
76
|
+
# @return [Hash] Extracted form parameters
|
|
77
|
+
def extract_form_params(form_param_name = nil)
|
|
78
|
+
# First try custom form parameter name if provided
|
|
79
|
+
if form_param_name && params[form_param_name.to_sym].is_a?(ActionController::Parameters)
|
|
80
|
+
nested_params = params[form_param_name.to_sym]
|
|
81
|
+
return {
|
|
82
|
+
name: nested_params[:name] || nested_params[:first_name] || "#{nested_params[:first_name]} #{nested_params[:last_name]}".strip,
|
|
83
|
+
email: nested_params[:email],
|
|
84
|
+
message: nested_params[:message] || nested_params[:body] || nested_params[:content]
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Try common form parameter keys for backward compatibility
|
|
89
|
+
common_keys = [:commission, :contact, :inquiry, :message, :form]
|
|
90
|
+
|
|
91
|
+
# Try nested parameters
|
|
92
|
+
common_keys.each do |key|
|
|
93
|
+
if params[key].is_a?(ActionController::Parameters)
|
|
94
|
+
nested_params = params[key]
|
|
95
|
+
return {
|
|
96
|
+
name: nested_params[:name] || nested_params[:first_name] || "#{nested_params[:first_name]} #{nested_params[:last_name]}".strip,
|
|
97
|
+
email: nested_params[:email],
|
|
98
|
+
message: nested_params[:message] || nested_params[:body] || nested_params[:content]
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Fallback to direct parameter access
|
|
104
|
+
{
|
|
105
|
+
name: params[:name] || params[:first_name] || "#{params[:first_name]} #{params[:last_name]}".strip,
|
|
106
|
+
email: params[:email],
|
|
107
|
+
message: params[:message] || params[:body] || params[:content]
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Handle spam detection by redirecting with flash message
|
|
112
|
+
#
|
|
113
|
+
# @param options [Hash] Spam handling options
|
|
114
|
+
# @return [void]
|
|
115
|
+
def handle_spam_detection(options = {})
|
|
116
|
+
redirect_path = options[:redirect_to] || root_path
|
|
117
|
+
redirect_path = redirect_path.call if redirect_path.respond_to?(:call)
|
|
118
|
+
|
|
119
|
+
flash_options = {}
|
|
120
|
+
flash_options[:notice] = options[:notice] if options[:notice]
|
|
121
|
+
flash_options[:alert] = options[:alert] if options[:alert]
|
|
122
|
+
|
|
123
|
+
redirect_to redirect_path, flash_options
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IsItSpamRails
|
|
4
|
+
# Rails integration through Railtie
|
|
5
|
+
#
|
|
6
|
+
# Automatically configures the gem when Rails loads and adds controller helpers
|
|
7
|
+
class Railtie < Rails::Railtie
|
|
8
|
+
# Add spam checking functionality to ActionController::Base
|
|
9
|
+
initializer "is_it_spam_rails.add_controller_helpers" do
|
|
10
|
+
ActiveSupport.on_load(:action_controller) do
|
|
11
|
+
include IsItSpamRails::ControllerExtension
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add rake tasks
|
|
16
|
+
rake_tasks do
|
|
17
|
+
load File.expand_path("../tasks/is_it_spam_rails.rake", __dir__)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add generators
|
|
21
|
+
generators do
|
|
22
|
+
require_relative "../generators/is_it_spam_rails/install_generator"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "is_it_spam_rails/version"
|
|
4
|
+
require_relative "is_it_spam_rails/client"
|
|
5
|
+
require_relative "is_it_spam_rails/configuration"
|
|
6
|
+
require_relative "is_it_spam_rails/controller_extension"
|
|
7
|
+
require_relative "is_it_spam_rails/railtie" if defined?(Rails::Railtie)
|
|
8
|
+
|
|
9
|
+
# Rails integration gem for is-it-spam.com anti-spam service
|
|
10
|
+
#
|
|
11
|
+
# Provides Rails-specific functionality including:
|
|
12
|
+
# - Configuration through Rails initializers
|
|
13
|
+
# - Before action hooks for controllers
|
|
14
|
+
# - Rails generator for setup
|
|
15
|
+
module IsItSpamRails
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
# Configuration error raised when credentials are missing or invalid
|
|
19
|
+
class ConfigurationError < Error; end
|
|
20
|
+
|
|
21
|
+
# API error raised when the API returns an error response
|
|
22
|
+
class ApiError < Error
|
|
23
|
+
# @return [Integer, nil] HTTP status code
|
|
24
|
+
attr_reader :status_code
|
|
25
|
+
# @return [String, nil] Raw response body
|
|
26
|
+
attr_reader :response_body
|
|
27
|
+
|
|
28
|
+
# Initialize API error
|
|
29
|
+
#
|
|
30
|
+
# @param message [String] Error message
|
|
31
|
+
# @param status_code [Integer, nil] HTTP status code
|
|
32
|
+
# @param response_body [String, nil] Raw response body
|
|
33
|
+
def initialize(message, status_code: nil, response_body: nil)
|
|
34
|
+
super(message)
|
|
35
|
+
@status_code = status_code
|
|
36
|
+
@response_body = response_body
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Rate limit error raised when API rate limits are exceeded
|
|
41
|
+
class RateLimitError < ApiError; end
|
|
42
|
+
|
|
43
|
+
# Validation error raised when request parameters are invalid
|
|
44
|
+
class ValidationError < ApiError
|
|
45
|
+
# @return [Hash] Field-specific validation errors
|
|
46
|
+
attr_reader :errors
|
|
47
|
+
|
|
48
|
+
# Initialize validation error
|
|
49
|
+
#
|
|
50
|
+
# @param message [String] Error message
|
|
51
|
+
# @param errors [Hash] Field-specific validation errors
|
|
52
|
+
# @param options [Hash] Additional options
|
|
53
|
+
def initialize(message, errors: {}, **options)
|
|
54
|
+
super(message, **options)
|
|
55
|
+
@errors = errors
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Global configuration for the gem
|
|
60
|
+
#
|
|
61
|
+
# @return [Configuration] The global configuration instance
|
|
62
|
+
def self.configuration
|
|
63
|
+
@configuration ||= Configuration.new
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Configure the gem with a block
|
|
67
|
+
#
|
|
68
|
+
# @yield [configuration] Configuration block
|
|
69
|
+
# @yieldparam configuration [Configuration] The configuration instance
|
|
70
|
+
def self.configure
|
|
71
|
+
yield(configuration)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get the configured client instance
|
|
75
|
+
#
|
|
76
|
+
# @return [Client] The configured client
|
|
77
|
+
def self.client
|
|
78
|
+
configuration.client
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Shortcut method for checking spam
|
|
82
|
+
#
|
|
83
|
+
# @param name [String] Name from the contact form
|
|
84
|
+
# @param email [String] Email address from the contact form
|
|
85
|
+
# @param message [String] Message content from the contact form
|
|
86
|
+
# @param custom_fields [Hash] Additional custom fields to check
|
|
87
|
+
# @param end_user_ip [String, nil] IP address of the end user filling the form (optional)
|
|
88
|
+
# @return [SpamCheckResult] The result of the spam check
|
|
89
|
+
def self.check_spam(name:, email:, message:, custom_fields: {}, end_user_ip: nil)
|
|
90
|
+
client.check_spam(name: name, email: email, message: message, custom_fields: custom_fields, end_user_ip: end_user_ip)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check API health
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean] true if the service is healthy
|
|
96
|
+
def self.health_check
|
|
97
|
+
client.health_check
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :is_it_spam do
|
|
4
|
+
desc "Install IsItSpamRails by creating an initializer"
|
|
5
|
+
task :install => :environment do
|
|
6
|
+
Rails::Generators.invoke("is_it_spam_rails:install")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "Test connection to Is It Spam API"
|
|
10
|
+
task :test_connection => :environment do
|
|
11
|
+
begin
|
|
12
|
+
puts "Testing connection to Is It Spam API..."
|
|
13
|
+
|
|
14
|
+
if IsItSpamRails.configuration.valid?
|
|
15
|
+
result = IsItSpamRails.health_check
|
|
16
|
+
if result
|
|
17
|
+
puts "✅ Connection successful!"
|
|
18
|
+
puts "API Key: #{IsItSpamRails.configuration.api_key[0..7]}..."
|
|
19
|
+
puts "Base URL: #{IsItSpamRails.configuration.base_url}"
|
|
20
|
+
else
|
|
21
|
+
puts "❌ API is not healthy (returned false)"
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
puts "❌ Configuration is invalid. Please check your API credentials."
|
|
25
|
+
puts "Current configuration:"
|
|
26
|
+
puts " API Key: #{IsItSpamRails.configuration.api_key.present? ? '[SET]' : '[NOT SET]'}"
|
|
27
|
+
puts " API Secret: #{IsItSpamRails.configuration.api_secret.present? ? '[SET]' : '[NOT SET]'}"
|
|
28
|
+
puts " Base URL: #{IsItSpamRails.configuration.base_url}"
|
|
29
|
+
end
|
|
30
|
+
rescue IsItSpamRails::ConfigurationError => e
|
|
31
|
+
puts "❌ Configuration error: #{e.message}"
|
|
32
|
+
rescue IsItSpamRails::ApiError => e
|
|
33
|
+
puts "❌ API error: #{e.message}"
|
|
34
|
+
puts "Status code: #{e.status_code}" if e.status_code
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
puts "❌ Unexpected error: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "Test spam detection with sample data"
|
|
41
|
+
task :test_spam_check => :environment do
|
|
42
|
+
begin
|
|
43
|
+
puts "Testing spam detection with sample data..."
|
|
44
|
+
|
|
45
|
+
# Test with legitimate content
|
|
46
|
+
puts "\n--- Testing legitimate content ---"
|
|
47
|
+
legitimate_result = IsItSpamRails.check_spam(
|
|
48
|
+
name: "John Doe",
|
|
49
|
+
email: "john@example.com",
|
|
50
|
+
message: "I'm interested in your services. Could you please provide more information?"
|
|
51
|
+
)
|
|
52
|
+
puts "Result: #{legitimate_result.spam? ? 'SPAM' : 'LEGITIMATE'}"
|
|
53
|
+
puts "Confidence: #{(legitimate_result.confidence_score * 100).round(1)}%"
|
|
54
|
+
puts "Reasons: #{legitimate_result.spam_reasons.join(', ')}" if legitimate_result.spam_reasons.any?
|
|
55
|
+
|
|
56
|
+
# Test with spam content
|
|
57
|
+
puts "\n--- Testing spam content ---"
|
|
58
|
+
spam_result = IsItSpamRails.check_spam(
|
|
59
|
+
name: "Spammer",
|
|
60
|
+
email: "spam@suspicious.com",
|
|
61
|
+
message: "URGENT!!! FREE MONEY!!! Click here now to get rich quick! Act fast!"
|
|
62
|
+
)
|
|
63
|
+
puts "Result: #{spam_result.spam? ? 'SPAM' : 'LEGITIMATE'}"
|
|
64
|
+
puts "Confidence: #{(spam_result.confidence_score * 100).round(1)}%"
|
|
65
|
+
puts "Reasons: #{spam_result.spam_reasons.join(', ')}" if spam_result.spam_reasons.any?
|
|
66
|
+
|
|
67
|
+
puts "\n✅ Spam detection test completed!"
|
|
68
|
+
|
|
69
|
+
rescue IsItSpamRails::ConfigurationError => e
|
|
70
|
+
puts "❌ Configuration error: #{e.message}"
|
|
71
|
+
rescue IsItSpamRails::ValidationError => e
|
|
72
|
+
puts "❌ Validation error: #{e.message}"
|
|
73
|
+
puts "Errors: #{e.errors}" if e.errors.any?
|
|
74
|
+
rescue IsItSpamRails::ApiError => e
|
|
75
|
+
puts "❌ API error: #{e.message}"
|
|
76
|
+
puts "Status code: #{e.status_code}" if e.status_code
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
puts "❌ Unexpected error: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
desc "Show current configuration"
|
|
83
|
+
task :config => :environment do
|
|
84
|
+
puts "IsItSpamRails Configuration:"
|
|
85
|
+
puts " API Key: #{IsItSpamRails.configuration.api_key.present? ? IsItSpamRails.configuration.api_key[0..7] + '...' : '[NOT SET]'}"
|
|
86
|
+
puts " API Secret: #{IsItSpamRails.configuration.api_secret.present? ? '[SET]' : '[NOT SET]'}"
|
|
87
|
+
puts " Base URL: #{IsItSpamRails.configuration.base_url}"
|
|
88
|
+
puts " Timeout: #{IsItSpamRails.configuration.timeout} seconds"
|
|
89
|
+
puts " Valid: #{IsItSpamRails.configuration.valid? ? '✅' : '❌'}"
|
|
90
|
+
end
|
|
91
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: is_it_spam_rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 2.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Benjamin Deutscher
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: httparty
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.21'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.21'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webmock
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.18'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.18'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sqlite3
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.4'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.4'
|
|
69
|
+
description: Provides Rails integration for the is-it-spam.com API with before_action
|
|
70
|
+
hooks for automatic spam detection in controllers.
|
|
71
|
+
email:
|
|
72
|
+
- ben@bdeutscher.org
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- CHANGELOG.md
|
|
78
|
+
- CLAUDE.md
|
|
79
|
+
- LICENSE.txt
|
|
80
|
+
- README.md
|
|
81
|
+
- Rakefile
|
|
82
|
+
- lib/generators/is_it_spam_rails/install_generator.rb
|
|
83
|
+
- lib/generators/is_it_spam_rails/templates/initializer.rb
|
|
84
|
+
- lib/is_it_spam_rails.rb
|
|
85
|
+
- lib/is_it_spam_rails/client.rb
|
|
86
|
+
- lib/is_it_spam_rails/configuration.rb
|
|
87
|
+
- lib/is_it_spam_rails/controller_extension.rb
|
|
88
|
+
- lib/is_it_spam_rails/railtie.rb
|
|
89
|
+
- lib/is_it_spam_rails/version.rb
|
|
90
|
+
- lib/tasks/is_it_spam_rails.rake
|
|
91
|
+
- sig/is_it_spam_rails.rbs
|
|
92
|
+
homepage: https://is-it-spam.com
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata:
|
|
96
|
+
allowed_push_host: https://rubygems.org
|
|
97
|
+
homepage_uri: https://is-it-spam.com
|
|
98
|
+
source_code_uri: https://github.com/its-benjamin-deutscher/is-it-spam.com
|
|
99
|
+
changelog_uri: https://github.com/its-benjamin-deutscher/is-it-spam.com/blob/main/lib/is_it_spam_rails/CHANGELOG.md
|
|
100
|
+
post_install_message:
|
|
101
|
+
rdoc_options: []
|
|
102
|
+
require_paths:
|
|
103
|
+
- lib
|
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - ">="
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: 3.1.0
|
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - ">="
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '0'
|
|
114
|
+
requirements: []
|
|
115
|
+
rubygems_version: 3.5.23
|
|
116
|
+
signing_key:
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: Rails integration for is-it-spam.com anti-spam service
|
|
119
|
+
test_files: []
|