otori 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +23 -4
- data/lib/otori/hanami.rb +3 -2
- data/lib/otori/rails.rb +3 -7
- data/lib/otori/version.rb +1 -1
- data/lib/otori.rb +16 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9139cc5240c7900afc1038f7634429d8cac065d9e22c3645e4891dd9f1fd9103
|
|
4
|
+
data.tar.gz: d0adef9a1384d5123b7e304879fb9691ebc35e77e238b39a6348419ac8bde074
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e03586c88e10498d34371303c0d87b09cb8bf643d99e8e30f5b0b19a6ab2851476f6290c547b74d6cfd7c819b281269ccf76d706bc5e42dd03131fa49d4965cf
|
|
7
|
+
data.tar.gz: 44b8afe56864c015efc19ee2a00ede9167f53bfe471ec7e74ef5ec5d05414522c2835c173e3c5005248447b2241d15c8b1c20e38ed5d4981d986e6d8456ab823
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-10-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Otori.reason` returns the specific catch reason as a symbol
|
|
15
|
+
(`:filled` when the honeypot field was filled, `:too_fast` when the
|
|
16
|
+
form was submitted before the configured delay elapsed, `nil` when the
|
|
17
|
+
submission looks clean). This lets callers distinguish genuinely
|
|
18
|
+
suspicious submissions from users who merely autofilled and submitted
|
|
19
|
+
faster than the delay allows.
|
|
20
|
+
- The Rails adapter's `on_caught` block now receives the reason symbol
|
|
21
|
+
as its sole argument, so applications can log, flag, or branch on it
|
|
22
|
+
(`honeypot(:website) { |reason| ... }`). Zero-arg blocks continue to
|
|
23
|
+
work unchanged.
|
|
24
|
+
- The Hanami adapter's `on_caught` block now receives the reason symbol
|
|
25
|
+
as a third argument after `request` and `response`. Two-arg blocks
|
|
26
|
+
continue to work unchanged.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `Otori.caught?` is now a thin predicate over `Otori.reason`. Its
|
|
31
|
+
return value and session side effects are unchanged.
|
|
32
|
+
|
|
10
33
|
## [0.3.0] - 2026-10-12
|
|
11
34
|
|
|
12
35
|
### Changed
|
data/README.md
CHANGED
|
@@ -78,6 +78,20 @@ session. `signals_field` renders a hidden input plus the JavaScript that
|
|
|
78
78
|
tracks human input. `caught?` checks the submitted form and returns `true`
|
|
79
79
|
when the request looks like a bot.
|
|
80
80
|
|
|
81
|
+
If you want to know _why_ a submission was caught (for logging, flagging,
|
|
82
|
+
or scoring), use `Otori.reason`:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
Otori.reason("user[website]", params: request.params, session: request.session)
|
|
86
|
+
# => :filled when the honeypot field was filled
|
|
87
|
+
# => :too_fast when the form was submitted before the delay elapsed
|
|
88
|
+
# => nil when the submission looks clean
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`caught?` is implemented as `!reason(...).nil?`. Both methods perform the
|
|
92
|
+
same session timestamp bookkeeping, so pick one and stick with it per
|
|
93
|
+
request.
|
|
94
|
+
|
|
81
95
|
Field names use standard HTML bracket notation, so `"user[website]"` lives
|
|
82
96
|
under `params[:user][:website]` once submitted. Flat names like `"note"` work
|
|
83
97
|
too. Pick whichever fits the surrounding form, the more believable the
|
|
@@ -138,10 +152,13 @@ end
|
|
|
138
152
|
```
|
|
139
153
|
|
|
140
154
|
When the honeypot is tripped, the action halts with `204 No Content` by
|
|
141
|
-
default. To customize the response, pass a block
|
|
155
|
+
default. To customize the response, pass a block. The block optionally
|
|
156
|
+
receives the catch reason (`:filled` or `:too_fast`) as a third argument,
|
|
157
|
+
handy for logging or branching:
|
|
142
158
|
|
|
143
159
|
```ruby
|
|
144
|
-
honeypot "user[website]" do |_request, response|
|
|
160
|
+
honeypot "user[website]" do |_request, response, reason|
|
|
161
|
+
SecurityLog.record!(reason: reason)
|
|
145
162
|
response.flash[:info] = "Moving on..."
|
|
146
163
|
response.redirect_to "/", status: 303
|
|
147
164
|
end
|
|
@@ -227,10 +244,12 @@ end
|
|
|
227
244
|
```
|
|
228
245
|
|
|
229
246
|
When the honeypot is tripped, the request halts with `:no_content` by default.
|
|
230
|
-
To customize the response, pass a block
|
|
247
|
+
To customize the response, pass a block. The block optionally receives the
|
|
248
|
+
catch reason (`:filled` or `:too_fast`), handy for logging or branching:
|
|
231
249
|
|
|
232
250
|
```ruby
|
|
233
|
-
honeypot :website, only: :create do
|
|
251
|
+
honeypot :website, only: :create do |reason|
|
|
252
|
+
SecurityLog.record!(reason: reason)
|
|
234
253
|
redirect_to root_path, notice: "Moving on..."
|
|
235
254
|
end
|
|
236
255
|
```
|
data/lib/otori/hanami.rb
CHANGED
|
@@ -16,15 +16,16 @@ module Otori
|
|
|
16
16
|
caught_block = on_caught
|
|
17
17
|
|
|
18
18
|
before do |request, response|
|
|
19
|
-
|
|
19
|
+
reason = Otori.reason(
|
|
20
20
|
field_name,
|
|
21
21
|
params: request.params.to_h,
|
|
22
22
|
session: request.session,
|
|
23
23
|
wait: field_wait
|
|
24
24
|
)
|
|
25
|
+
next unless reason
|
|
25
26
|
|
|
26
27
|
if caught_block
|
|
27
|
-
instance_exec(request, response, &caught_block)
|
|
28
|
+
instance_exec(request, response, reason, &caught_block)
|
|
28
29
|
else
|
|
29
30
|
halt 204
|
|
30
31
|
end
|
data/lib/otori/rails.rb
CHANGED
|
@@ -11,15 +11,11 @@ module Otori
|
|
|
11
11
|
filter_options[:except] = except if except
|
|
12
12
|
|
|
13
13
|
before_action(**filter_options) do
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
params:,
|
|
17
|
-
session:,
|
|
18
|
-
wait:
|
|
19
|
-
)
|
|
14
|
+
reason = Otori.reason(name, params:, session:, wait:)
|
|
15
|
+
next unless reason
|
|
20
16
|
|
|
21
17
|
if on_caught
|
|
22
|
-
instance_exec(&on_caught)
|
|
18
|
+
instance_exec(reason, &on_caught)
|
|
23
19
|
else
|
|
24
20
|
head :no_content
|
|
25
21
|
end
|
data/lib/otori/version.rb
CHANGED
data/lib/otori.rb
CHANGED
|
@@ -31,19 +31,23 @@ module Otori
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def caught?(name, params:, session:, wait: nil)
|
|
34
|
+
!reason(name, params:, session:, wait:).nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reason(name, params:, session:, wait: nil)
|
|
34
38
|
wait ||= config.default_delay
|
|
35
39
|
session_key = config.session_key(name)
|
|
36
40
|
stored = session_get(session, session_key)
|
|
37
41
|
filled = Validator.filled?(param_value(params, name))
|
|
38
42
|
elapsed = Validator.elapsed?(stored&.to_i, wait)
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
false
|
|
43
|
-
else
|
|
44
|
+
caught = catch_reason(filled:, elapsed:)
|
|
45
|
+
if caught
|
|
44
46
|
session[session_key] = Validator.monotonic_ms.to_s
|
|
45
|
-
|
|
47
|
+
else
|
|
48
|
+
session_delete(session, session_key)
|
|
46
49
|
end
|
|
50
|
+
caught
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def signals_rating(params)
|
|
@@ -55,6 +59,13 @@ module Otori
|
|
|
55
59
|
|
|
56
60
|
private
|
|
57
61
|
|
|
62
|
+
def catch_reason(filled:, elapsed:)
|
|
63
|
+
return :filled if filled
|
|
64
|
+
return :too_fast unless elapsed
|
|
65
|
+
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
58
69
|
def param_value(params, name)
|
|
59
70
|
param_keys(name).reduce(params) do |scope, key|
|
|
60
71
|
break nil unless scope.respond_to?(:[])
|