flucksite/otori

GitHub: flucksite/otori

Otori 是一个为任意 Rack based Ruby 应用提供隐形蜜罐、时间检测和 JS 行为信号三层反垃圾表单防护的轻量库。

Stars: 1 | Forks: 0

# Otori [![CI](https://codeberg.org/fluck/otori/actions/workflows/ci.yml/badge.svg)](https://codeberg.org/fluck/otori/actions?workflow=ci.yml) [![版本](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcodeberg.org%2Fapi%2Fv1%2Frepos%2Ffluck%2Fotori%2Ftags&query=%24%5B0%5D.name&label=version)](https://codeberg.org/fluck/otori/tags) 为 Hanami、Sinatra、Roda、Rails 以及任何其他基于 Rack 的 Ruby 应用提供隐形蜜罐垃圾防护。你可以将其视为一个与框架无关的 [invisible_captcha](https://github.com/markets/invisible_captcha),它拥有 一流的 Hanami 适配器,并额外增加了一层基于 JavaScript 的输入检测。 _Otori_ (囮) 在日语中意为“诱饵”,历史上指狩猎时用来吸引其他鸟类的鸟。 这是 [Crystal lucky_honeypot shard](https://codeberg.org/fluck/lucky_honeypot) 的 Ruby 配套版本。它将三种经典技术结合在一个 gem 中: 1. **隐形字段**。机器人会填写所有字段,包括那些用 CSS 隐藏的字段。 2. **时间检查**。机器人会瞬间提交表单,而人类需要更多时间。 3. **输入信号**。机器人通常不会触发鼠标、触摸、滚动、键盘或聚焦事件。 当前两项检查中有任何一项失败时,提交将被静默拒绝。 机器人会以为提交成功并继续下一步。第三项检查可用于在选定的_人类评分_阈值下拒绝或标记提交。 ## 对比 ### otori - 框架:Hanami、Sinatra、Roda、Rails,任何 Rack 应用 - 隐藏字段 + 时间检查 + JS 输入信号 - 活跃维护(2026 年新推出) ### [invisible_captcha](https://github.com/markets/invisible_captcha) - 框架:仅限 Rails - 隐藏字段 + 时间检查 - 积极维护中 ### [rack-honeypot](https://github.com/sunlightlabs/rack-honeypot) - 框架:任何 Rack 应用 - 仅限隐藏字段 - 自 2016 年起停止维护 如果你使用的是 Rails 且不需要 JS 信号层,`invisible_captcha` 是经过更多实战考验的选择。Otori 则为其他所有情况而生。 ## 安装说明 将此内容添加到你的 Gemfile 中: ``` gem "otori" ``` 然后运行 `bundle install`。需要 Ruby 3.2 或更高版本。 ## 快速入门 该 gem 提供了一个与框架无关的核心,以及一个可选的 Hanami 适配器。核心 API 包含三个调用: ``` Otori.field("user[website]", session: request.session) Otori.signals_field Otori.caught?("user[website]", params: request.params, session: request.session) ``` `field` 会渲染隐形输入框,并在 session 中存储加载时间戳。 `signals_field` 会渲染一个隐藏输入框以及用于追踪人类输入的 JavaScript。 `caught?` 会检查提交的表单,并在请求看起来像机器人时返回 `true`。 字段名称使用标准的 HTML 方括号表示法,因此 `"user[website]"` 在提交后会位于 `params[:user][:website]` 下。 像 `"note"` 这样的扁平名称也同样适用。选择最适合周围表单的名称即可,蜜罐在真实字段旁边看起来越可信,效果就越好。 ## 框架集成 ### Hanami Hanami 适配器需要显式加载,以保持基础 gem 无额外依赖: ``` require "otori/hanami" ``` 将这些辅助方法混入你的视图中: ``` # app/views/helpers.rb module MyApp module Views module Helpers include Otori::Hanami::Helpers end end end ``` 在表单模板中: ``` <%= honeypot_field("user[website]") %> <%= honeypot_signals %> ``` 这些辅助方法通过 `String#html_safe` 将其输出标记为安全,Hanami View 开箱即用地提供了此功能,因此 HTML 在流经 ERB 时不会被转义。 使用 `honeypot` DSL 方法保护接收操作: ``` # app/actions/sign_ups/create.rb module MyApp module Actions module SignUps class Create < MyApp::Action include Otori::Hanami::Action honeypot "user[website]" def handle(request, response) # ... end end end end end ``` 当蜜罐被触发时,默认情况下该操作将以 `204 No Content` 终止。要自定义响应,请传递一个 block: ``` honeypot "user[website]" do |_request, response| response.flash[:info] = "Moving on..." response.redirect_to "/", status: 303 end ``` 支持多个蜜罐,每个蜜罐都有自己的时间和处理程序: ``` honeypot "user[website]", wait: 5 honeypot "note" do |_req, _res| halt 422 end ``` 要根据输入信号评分采取行动,请在 `handle` 中对其进行评估: ``` def handle(request, response) rating = Otori.signals_rating(request.params.to_h) halt 204 if rating < 0.4 # ... end ``` ### Rack (Sinatra、Roda、纯 Rack) 没有需要额外引入的适配器,核心 API 就足够了。在 Sinatra 应用中: ``` require "otori" enable :sessions get "/sign_up" do erb :sign_up end post "/sign_up" do halt 204 if Otori.caught?( "user[website]", params: params, session: session ) # ... end ``` 在视图中: ``` <%= Otori.field("user[website]", session: session) %> <%= Otori.signals_field %> ``` ### Rails 当你只需要隐藏字段和时间检查时,Rails 已经有了很好的 [invisible_captcha](https://github.com/markets/invisible_captcha) 解决方案。如果你想在 Rails 中使用输入信号评分,请使用此 gem。 ``` # app/helpers/application_helper.rb module ApplicationHelper def honeypot_field(name, **attrs) Otori.field(name, session: session, **attrs).html_safe end def honeypot_signals(**attrs) Otori.signals_field(**attrs).html_safe end end ``` ``` # app/controllers/sign_ups_controller.rb class SignUpsController < ApplicationController before_action :check_honeypot, only: :create def create # ... end private def check_honeypot return unless Otori.caught?( "user[website]", params: params.to_unsafe_h, session: session ) head :no_content end end ``` ## 配置 ``` Otori.configure do |c| # Required delay (in seconds) between page load and form submission. c.default_delay = 2.0 # Disables the submission delay entirely. Useful in tests. c.disable_delay = false # Name of the hidden input that carries the signals payload. c.signals_input_name = "honeypot_signals" end ``` ## 隐形字段 默认情况下,该字段会使用内联 `style` 属性进行渲染,该属性会将其移出视觉流,且不会破坏辅助工具的可用性。传递你自己的 `class`(或 `style`)以取消默认样式,转而使用你的 CSS: ``` Otori.field("user[website]", session: session, class: "visually-hidden") ``` 带有下划线的属性键会转换为连字符,因此 `data_foo: "bar"` 会渲染为 `data-foo="bar"`。任何其他属性对都将原样传递。 ## 检测输入信号 `signals_field` 会渲染一个隐藏输入框以及一个小的内联 `