From 29aa3de3ccac3e2e08d6453fb7f70f82734a08e5 Mon Sep 17 00:00:00 2001 From: Michal Barla Date: Sat, 27 Apr 2024 13:00:00 +0200 Subject: [PATCH] Add Altcha captcha to notifications submissions --- Gemfile | 2 + Gemfile.lock | 2 + app/controllers/altcha_controller.rb | 5 ++ .../notification_subscriptions_controller.rb | 24 +++++---- app/models/altcha_solution.rb | 28 ++++++++++ app/views/layouts/application.html.erb | 1 + .../notification_subscriptions/_form.html.erb | 2 + .../notification_subscriptions/failure.js.erb | 1 + config/initializers/altcha.rb | 8 +++ config/routes.rb | 1 + .../20240427082705_create_altcha_solutions.rb | 15 ++++++ db/structure.sql | 53 ++++++++++++++++++- 12 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 app/controllers/altcha_controller.rb create mode 100644 app/models/altcha_solution.rb create mode 100644 app/views/notification_subscriptions/failure.js.erb create mode 100644 config/initializers/altcha.rb create mode 100644 db/migrate/20240427082705_create_altcha_solutions.rb diff --git a/Gemfile b/Gemfile index 661419ff..10d143b9 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,8 @@ gem 'omniauth' gem 'omniauth-google-oauth2' gem 'omniauth-rails_csrf_protection' +gem 'altcha-rails' + gem 'pry-rails' gem 'aws-sdk-rails' diff --git a/Gemfile.lock b/Gemfile.lock index fad1fa79..6716c984 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -62,6 +62,7 @@ GEM zeitwerk (~> 2.3) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) + altcha-rails (0.0.5) aws-eventstream (1.2.0) aws-partitions (1.734.0) aws-record (2.10.1) @@ -460,6 +461,7 @@ PLATFORMS ruby DEPENDENCIES + altcha-rails aws-sdk-cloudwatch aws-sdk-rails aws-sdk-s3 diff --git a/app/controllers/altcha_controller.rb b/app/controllers/altcha_controller.rb new file mode 100644 index 00000000..cd31d3a2 --- /dev/null +++ b/app/controllers/altcha_controller.rb @@ -0,0 +1,5 @@ +class AltchaController < ApplicationController + def new + render json: Altcha::Challenge.create.to_json + end +end diff --git a/app/controllers/notification_subscriptions_controller.rb b/app/controllers/notification_subscriptions_controller.rb index 42da9231..d008bf72 100644 --- a/app/controllers/notification_subscriptions_controller.rb +++ b/app/controllers/notification_subscriptions_controller.rb @@ -7,17 +7,23 @@ def index def create @group = NotificationSubscriptionGroup.new(notification_group_params.except(:more)) - @group.user = current_user - @group.journey = Journey.find(params[:notification_subscription_group][:journey_id]) if params[:notification_subscription_group][:journey_id].present? - - respond_to do |format| - if @group.save - format.html { redirect_to root_path } - format.js - else - format.js { render :new } + if AltchaSolution.verify_and_save(params.permit(:altcha)[:altcha]) + @group.user = current_user + @group.journey = Journey.find(params[:notification_subscription_group][:journey_id]) if params[:notification_subscription_group][:journey_id].present? + respond_to do |format| + if @group.save + format.html { redirect_to root_path } + format.js + else + format.js { render :new } + end + end + else + respond_to do |format| + format.js { render :failure } end end + end def confirm diff --git a/app/models/altcha_solution.rb b/app/models/altcha_solution.rb new file mode 100644 index 00000000..5f6143c1 --- /dev/null +++ b/app/models/altcha_solution.rb @@ -0,0 +1,28 @@ +class AltchaSolution < ApplicationRecord + validates :algorithm, :challenge, :salt, :signature, :number, presence: true + attr_accessor :took + + def self.verify_and_save(base64encoded) + p = JSON.parse(Base64.decode64(base64encoded)) rescue nil + return false if p.nil? + + submission = Altcha::Submission.new(p) + return false unless submission.valid? + + solution = self.new(p) + + begin + return solution.save + rescue ActiveRecord::RecordNotUnique + # Replay attack + return false + end + end + + def self.cleanup + # Replay attacks are protected by the time stamp in the salt of the challenge for + # the duration configured in the timeout. All solutions in the database that older + # can be deleted. + AltchaSolution.where('created_at < ?', Time.now - Altcha.timeout).delete_all + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 703f7e93..c44cc2bb 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -21,6 +21,7 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + diff --git a/app/views/notification_subscriptions/_form.html.erb b/app/views/notification_subscriptions/_form.html.erb index 76ca9e44..59dcd322 100644 --- a/app/views/notification_subscriptions/_form.html.erb +++ b/app/views/notification_subscriptions/_form.html.erb @@ -43,6 +43,8 @@ <% if !form.journey.nil? and form.journey.blank? %> <%= hidden_field_tag 'notification_subscription_group[journey_id]', form.journey.id %> <% end %> + +
<%= submit_tag 'Chcem dostávať tieto notifikácie', class: 'govuk-button' %> diff --git a/app/views/notification_subscriptions/failure.js.erb b/app/views/notification_subscriptions/failure.js.erb new file mode 100644 index 00000000..738b4525 --- /dev/null +++ b/app/views/notification_subscriptions/failure.js.erb @@ -0,0 +1 @@ +$('.altcha-error').html('Nie sme si isti, či nie ste robot... zaškrtli ste, že nie ste?'); diff --git a/config/initializers/altcha.rb b/config/initializers/altcha.rb new file mode 100644 index 00000000..3a0cad1f --- /dev/null +++ b/config/initializers/altcha.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +Altcha.setup do |config| + config.algorithm = 'SHA-256' + config.num_range = (50_000..300_000) + config.timeout = 5.minutes + config.hmac_key = 'dfa06d467a84fea13941f1c52c38c6458a67617a' +end diff --git a/config/routes.rb b/config/routes.rb index c4c46b57..9721f055 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ Rails.application.routes.draw do + get '/altcha', to: 'altcha#new' get :health, to: 'health#index' get 'robots.:format', to: 'robots#index' diff --git a/db/migrate/20240427082705_create_altcha_solutions.rb b/db/migrate/20240427082705_create_altcha_solutions.rb new file mode 100644 index 00000000..5ddc7726 --- /dev/null +++ b/db/migrate/20240427082705_create_altcha_solutions.rb @@ -0,0 +1,15 @@ +class CreateAltchaSolutions < ActiveRecord::Migration[6.1] + def change + create_table(:altcha_solutions) do |t| + t.string :algorithm + t.string :challenge + t.string :salt + t.string :signature + t.integer :number + + t.timestamps + end + + add_index :altcha_solutions, [ :algorithm, :challenge, :salt, :signature, :number ], unique: true, name: 'index_altcha_solutions' + end +end diff --git a/db/structure.sql b/db/structure.sql index 22e99246..073141f5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -467,6 +467,41 @@ CREATE SEQUENCE public.active_storage_variant_records_id_seq ALTER SEQUENCE public.active_storage_variant_records_id_seq OWNED BY public.active_storage_variant_records.id; +-- +-- Name: altcha_solutions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.altcha_solutions ( + id bigint NOT NULL, + algorithm character varying, + challenge character varying, + salt character varying, + signature character varying, + number integer, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: altcha_solutions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.altcha_solutions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: altcha_solutions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.altcha_solutions_id_seq OWNED BY public.altcha_solutions.id; + + -- -- Name: apps; Type: TABLE; Schema: public; Owner: - -- @@ -1686,6 +1721,14 @@ ALTER TABLE ONLY public.active_storage_variant_records ADD CONSTRAINT active_storage_variant_records_pkey PRIMARY KEY (id); +-- +-- Name: altcha_solutions altcha_solutions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.altcha_solutions + ADD CONSTRAINT altcha_solutions_pkey PRIMARY KEY (id); + + -- -- Name: apps apps_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1946,6 +1989,13 @@ CREATE UNIQUE INDEX index_active_storage_blobs_on_key ON public.active_storage_b CREATE UNIQUE INDEX index_active_storage_variant_records_uniqueness ON public.active_storage_variant_records USING btree (blob_id, variation_digest); +-- +-- Name: index_altcha_solutions; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_altcha_solutions ON public.altcha_solutions USING btree (algorithm, challenge, salt, signature, number); + + -- -- Name: index_categories_categorizations; Type: INDEX; Schema: public; Owner: - -- @@ -2486,6 +2536,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20221022143119'), ('20230325092744'), ('20230325095737'), -('20230325151049'); +('20230325151049'), +('20240427082705');