Web UI 垃圾邮件防护和 CAPTCHA 支持
为 GitLab 应用程序的新 UI 区域添加垃圾邮件防护和 CAPTCHA 支持的方法,取决于现有代码的实现方式。
支持的请求提交场景
支持三种不同的场景。两种用于 JavaScript XHR/Fetch 请求(配合 Apollo 或 Axios),一种仅用于标准 HTML 表单请求:
- 基于 JavaScript 的提交(可能通过 Vue)
- 使用 Apollo(通过 Fetch/XHR 请求的 GraphQL API)
- 使用 Axios(通过 Fetch/XHR 请求的 REST API)
- 标准 HTML 表单提交(HTML 请求)
部分实现取决于您需要支持哪种场景。
针对 JavaScript XHR/Fetch 请求的特定实现任务
两种方法都完全支持:
- 使用 GraphQL API 的 Apollo
- 使用 GraphQL API 的 Axios
前端和后端之间关于垃圾邮件和 CAPTCHA 的数据通信,无需在模型中添加额外字段。相反,通信通过以下方式处理:
- 通过请求中的自定义头值
- 通过响应中的顶层 JSON 字段
垃圾邮件和 CAPTCHA 相关的逻辑也被清晰地抽象为可重用的模块和辅助方法,可以包装现有逻辑,仅在检测到潜在垃圾邮件或需要显示 CAPTCHA 时修改现有流程。这种方法允许以最小的现有逻辑更改,将垃圾邮件和 CAPTCHA 支持添加到应用程序的新区域。在前端的情况下,可能需要 零 更改!
在前端,这通过使用 Apollo 的 ApolloLink 和 Axios 的拦截器进行抽象和透明处理。CAPTCHA 显示由标准的 GitLab UI / Pajamas 模态组件处理。您可以在 app/assets/javascripts/captcha 下找到所有相关的前端代码。
但是,尽管请求拦截和模态的实际处理是透明的,无需对表单或页面的相关 JavaScript 或 Vue 组件进行强制性更改,但可能需要更改请求或错误处理。之所以需要更改,是因为现有行为可能无法正常工作:例如,失败的或取消的 CAPTCHA 显示可能会中断标准请求流程或 UI 更新。对所有场景进行仔细的探索性测试,以发现任何潜在问题,这一点很重要。
此序列图说明了前端 JavaScript XHR/Fetch 请求的标准 CAPTCHA 流程:
sequenceDiagram
participant U as User
participant V as Vue/JS Application
participant A as ApolloLink or Axios Interceptor
participant G as GitLab API
U->>V: Save model
V->>A: Request
A->>G: Request
G--xA: Response with error and spam/CAPTCHA related fields
A->>U: CAPTCHA presented in modal
U->>A: CAPTCHA solved to obtain valid CAPTCHA response
A->>G: Request with valid CAPTCHA response and SpamLog ID in headers
G-->>A: Response with success
A-->>V: Response with success
后端也通过 mixin 模块和辅助方法进行了清晰的抽象。相关后端控制器操作(通常只是 create/update)需要三个主要更改:
- 将
perform_spam_check: true传递给更新服务类构造函数。 在创建服务中,它默认设置为true。 - 如果垃圾邮件检查表明对模型的更改可能是垃圾邮件,则:
- 向模型添加错误。
- 将模型上的
needs_recaptcha属性设置为 true。
- 将现有的控制器操作返回值(渲染或重定向)包装在传递给
#with_captcha_check_json_format辅助方法的块中,该方法透明地处理:- 检查 CAPTCHA 是否已启用,如果是,则继续下一步。
- 检查模型是否包含错误,并且
needs_recaptcha标志为 true。- 如果是:将适当的垃圾邮件或 CAPTCHA 字段添加到 JSON 响应中,并返回
409 - ConflictHTTP 状态码。 - 如果否(如果 CAPTCHA 被禁用或未检测到垃圾邮件):运行块中传递的标准请求返回逻辑。
- 如果是:将适当的垃圾邮件或 CAPTCHA 字段添加到 JSON 响应中,并返回
得益于这些抽象,实现起来比解释它更简单。您不必担心隐藏的细节!
进行这些更改:
为控制器操作添加支持
如果功能的前端直接提交到控制器操作,而不仅仅使用 GraphQL API,那么您必须为相应的控制器添加支持。
操作方法可能直接在控制器类中,或者可能被抽象为包含在控制器类中的模块。我们的示例使用模块。直接修改控制器时的唯一区别:
不需要 extend ActiveSupport::Concern。
module WidgetsActions
# 注意:这个 `extend` 可能已经存在,但它必须移动到所有 `include` 语句之前发生。
# 否则,可能会出现令人困惑的错误,即无法找到包含模块中的方法。
extend ActiveSupport::Concern
include SpammableActions::CaptchaCheck::JsonFormatActionsSupport
def create
widget = ::Widgets::CreateService.new(
project: project,
current_user: current_user,
params: params
).execute
respond_to do |format|
format.json do
with_captcha_check_json_format do
# 操作现有的 `render json: ...`(或包装方法)和相关逻辑。可能
# 包括模型有效或无效时的不同渲染情况。所有这些都包装在
# `with_captcha_check_json_format` 块中。例如:
if widget.valid?
render json: serializer.represent(widget)
else
render json: { errors: widget.errors.full_messages }, status: :unprocessable_entity
end
end
end
end
end
end针对 HTML 表单请求的特定实现任务
应用程序的某些区域尚未转换为使用通过 JavaScript 客户端的 GraphQL API,而是依赖通过 HTML MIME 类型请求的标准 Rails HAML 表单提交。在这些区域,操作返回预渲染的 HTML(HAML)页面作为响应体。不幸的是,在这种情况下
不可能使用 上述任何基于 JavaScript 的前端支持。相反,我们必须使用替代方法,通过 HAML 模板处理 CAPTCHA 表单的渲染。
一切仍然被清晰地抽象,后端控制器的实现与基于 JavaScript/JSON 的方法几乎相同。在模块名称和辅助方法中将单词 JSON 替换为 HTML(使用适当的大小写)。
操作方法可能直接在控制器中,或者可能在模块中。在此示例中,它们直接在控制器中,并且我们还执行 update 方法而不是 create:
class WidgetsController < ApplicationController
include SpammableActions::CaptchaCheck::HtmlFormatActionsSupport
def update
# 现有逻辑以查找 `widget` 模型实例...
::Widgets::UpdateService.new(
project: project,
current_user: current_user,
params: params,
perform_spam_check: true
).execute(widget)
respond_to do |format|
format.html do
if widget.valid?
# 注意:`spammable_path` 是 `SpammableActions::AkismetMarkAsSpamAction`
# 模块所必需的,并且应该已经根据
# 上述说明在此控制器上实现。这里重用它以避免重复路由助手调用。
redirect_to spammable_path
else
# 如果我们到达这里,模型实例上有错误 - 来自失败的垃圾邮件检查
# 和/或模型上的其他验证错误。无论哪种方式,我们都会重新渲染表单,
# 并且如果需要 CAPTCHA 渲染,它将由
# `with_captcha_check_html_format` 自动处理
with_captcha_check_html_format { render :edit }
end
end
end
end
end