Merge pull request 'Require a LibreCaptcha challenge for the signup action' (#157) from libre-captcha into main
Some checks failed
Check usage of free licenses / check-licenses (push) Successful in 48s
Run unit tests / unit_tests (push) Successful in 1m42s
Build Nginx-based docker image / build-static-assets (push) Failing after 17m20s

Reviewed-on: #157
This commit is contained in:
bustikiller 2024-12-01 19:03:56 +00:00
commit 822b2b0fad
12 changed files with 106 additions and 1 deletions

View File

@ -21,6 +21,7 @@ gem 'rack-cors'
gem 'react-rails'
gem 'rubytree'
gem 'acts_as_tenant'
gem 'httparty'
group :development, :test do
gem 'annotaterb'

View File

@ -126,6 +126,10 @@ GEM
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
importmap-rails (2.0.3)
@ -188,6 +192,8 @@ GEM
money (6.19.0)
i18n (>= 0.6.4, <= 2)
msgpack (1.7.2)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
net-imap (0.5.1)
date
net-protocol
@ -398,6 +404,7 @@ DEPENDENCIES
devise (~> 4.9)
factory_bot_rails
faker
httparty
importmap-rails
jbuilder
jsonapi-rails

View File

@ -30,6 +30,18 @@ class ApplicationController < ActionController::Base
private
def validate_captcha!
Rails.logger.info("Captcha params: #{captcha_params}")
return if LibreCaptcha.new.valid?(id: captcha_params[:id], answer: captcha_params[:answer])
render json: { error: 'Incorrect CAPTCHA solution' }, status: :unprocessable_entity
end
def captcha_params
params.expect(captcha: [:id, :answer])
end
def default_url_options(options = {})
options.merge(path_params: { slug: ActsAsTenant.current_tenant&.slug })
end

View File

@ -0,0 +1,13 @@
# Copyright (C) 2024 Manuel Bustillo
class CaptchaController < ApplicationController
skip_before_action :authenticate_user!
def create
id = LibreCaptcha.new.get_id
render json: {
id:,
media_url: media_captcha_index_url(id:)
}, status: :created
end
end

View File

@ -4,6 +4,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
clear_respond_to
respond_to :json
before_action :validate_captcha!, only: :create
def create
wedding = Wedding.create(wedding_params)
unless wedding.persisted?

View File

@ -0,0 +1,20 @@
# Copyright (C) 2024 Manuel Bustillo
class LibreCaptcha
def get_id
HTTParty.post("http://libre-captcha:8888/v2/captcha",
body: {
input_type: "text",
level: :hard,
media: 'image/png',
size: '350x100'
}.to_json
).then { |raw| JSON.parse(raw)['id'] }
end
def valid?(id:, answer:)
HTTParty.post("http://libre-captcha:8888/v2/answer",
body: { id:, answer: }.to_json
).then { |raw| JSON.parse(raw)['result'] == 'True' }
end
end

View File

@ -23,6 +23,9 @@ Rails.application.routes.draw do
resources :tables_arrangements, only: %i[index show]
end
resources :captcha, only: :create do
get 'v2/media', to: 'captcha#media', on: :collection, as: :media
end
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'

View File

@ -36,6 +36,12 @@ services:
- backend
volumes:
- ../wedding-planner-frontend/:/app
libre-captcha:
image: librecaptcha/lc-core:latest
volumes:
- "./tmp/libre-captcha-data:/lc-core/data"
ports:
- 8888
nginx:
image: nginx:latest
ports:

View File

@ -12,6 +12,11 @@ server {
proxy_set_header Host $http_host;
}
location /captcha/v2/media/ {
proxy_pass http://libre-captcha:8888/v2/media/;
proxy_set_header Host $http_host;
}
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $http_host;

View File

@ -0,0 +1,24 @@
# Copyright (C) 2024 Manuel Bustillo
require 'swagger_helper'
RSpec.describe 'captcha', type: :request do
path '/captcha' do
post('create a CAPTCHA challenge') do
tags 'CAPTCHA'
consumes 'application/json'
produces 'application/json'
response(201, 'created') do
schema type: :object,
required: %i[id],
properties: {
id: { type: :string, format: :uuid },
media_url: { type: :string, format: :uri },
}
xit
end
end
end
end

View File

@ -18,5 +18,16 @@ module Swagger
example: :default,
description: 'Wedding slug'
}
CAPTCHA = {
captcha: {
type: :object,
required: %i[id answer],
properties: {
id: { type: :string, format: :uuid },
answer: { type: :string }
}
}
}
end
end

View File

@ -30,7 +30,8 @@ RSpec.describe 'users/registrations', type: :request do
properties: {
date: { type: :string, format: :date},
}
}
},
**Swagger::Schema::CAPTCHA
}
}