Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Altcha captcha to notifications submissions #661

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -460,6 +461,7 @@ PLATFORMS
ruby

DEPENDENCIES
altcha-rails
aws-sdk-cloudwatch
aws-sdk-rails
aws-sdk-s3
Expand Down
1 change: 0 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
//= require activestorage
//= require turbolinks
//= require cookieconsent.min
//= require newsletter_sign_up
//= require jscookie

$(document).on("turbolinks:load", function () {
Expand Down
42 changes: 0 additions & 42 deletions app/assets/javascripts/newsletter_sign_up.js

This file was deleted.

5 changes: 5 additions & 0 deletions app/controllers/altcha_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AltchaController < ApplicationController
def new
render json: Altcha::Challenge.create.to_json
end
end
22 changes: 22 additions & 0 deletions app/controllers/newsletter_subscriptions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class NewsletterSubscriptionsController < ApplicationController

def subscribe
email, altcha = subscription_params
if AltchaSolution.verify_and_save(altcha)
SubscribeToNewsletterJob.perform_later(email, 'NewsletterSubscription')
respond_to do |format|
format.js { render :success}
end
else
respond_to do |format|
format.js { render :altcha_failure, status: :unprocessable_entity}
end
end
end

private

def subscription_params
params.require([:email, :altcha])
end
end
23 changes: 14 additions & 9 deletions app/controllers/notification_subscriptions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ 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.js
else
format.js { render :new }
end
end
else
respond_to do |format|
format.js { render :failure, status: :unprocessable_entity}
end
end

end

def confirm
Expand Down
28 changes: 28 additions & 0 deletions app/models/altcha_solution.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 2 additions & 11 deletions app/views/components/_footer.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<span class="sdn-footer__newsletter-heading"><label for="email-input">Novinky emailom</label> <small class="sdn-footer__newsletter-heading-tooltip">(raz mesačne)</small></span>
<div id="newsletter-success" style="display: none">
<p style="color: white;">
<strong>Na Váš e-mail sme poslali potvrdzovaciu správu.</strong>
<strong>Na Váš e-mail sme poslali potvrdzovaciu správu. Ak nechodí, skúste to znova.</strong>
<br>
Ďakujeme za Váš záujem.
</p>
Expand All @@ -15,16 +15,7 @@
<strong></strong>
</p>
</div>
<form id="newsletter-form" role="form" action="https://my.sendinblue.com/users/subscribe/js_id/29wti/id/4" data-action="https://my.sendinblue.com/users/subscribeembed/js_id/29wti/id/4" method="post">
<div class="govuk-form-group sdn-footer__newsletter-form-group">
<input type="hidden" name="js_id" id="js_id" value="29wti">
<input type="hidden" name="listid" id="listid" value="45">
<input type="hidden" name="from_url" id="from_url" value="yes">
<input type="hidden" name="hdn_email_txt" id="hdn_email_txt" value="">
<input type="email" name="email" id="email-input" required class="govuk-input sdn-footer__newsletter-input" autocomplete="email" placeholder="Zadajte emailovú adresu"/>
<button type="submit" class="govuk-button sdn-footer__newsletter-button">Prihlásiť</button>
</div>
</form>
<%= render 'newsletter_subscriptions/form' %>
<span class="sdn-footer__newsletter-disclaimer">
Prihlásením dávam dobrovoľný súhlas OZ Slovensko.Digital na zasielanie bezplatného newslettera na mnou zadaný e-mail. Tento súhlas môžem kdykoľvek odvolať na <%= mail_to ENV['DEFAULT_EMAIL_FROM'], ENV['DEFAULT_EMAIL_FROM'], class: 'sdn-footer__link' %>. Oboznámil(a) som sa s informáciami v sekcii <%= link_to 'Ochrana osobných údajov', page_path('ochrana-osobnych-udajov'), class: 'sdn-footer__link' %>.</span>
</div>
Expand Down
1 change: 1 addition & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<script defer data-domain="navody.digital" src="https://plausible.io/js/plausible.js"></script>
<script async defer src="https://cdn.jsdelivr.net/gh/altcha-org/altcha@main/dist/altcha.min.js" type="module"></script>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you OK with including an externally hosted script here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls @jsuchal daj tu vedieť. Podľa mňa áno.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ide to do ameriky, cize nie.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To je iba CDN, potom to neposiela veci mimo. Ale teda volajú to self-hosted a GDPR-compliant, čiže asi vieme pridať priamo do projektu, ak nechceme ani CDN https://github.com/altcha-org/altcha

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, okay

</head>

<body class="govuk-template__body">
Expand Down
6 changes: 6 additions & 0 deletions app/views/newsletter_subscriptions/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= form_tag newsletter_subscribe_path, method: :post, remote: true, id: "newsletter-form" do |f| %>
<%= email_field_tag :email, '', class: 'govuk-input sdn-footer__newsletter-input', placeholder: "Zadajte emailovú adresu", autocomplete: :email %>
<%= submit_tag 'Prihlásiť', class: 'govuk-button sdn-footer__newsletter-button' %>
<altcha-widget hidelogo hidefooter strings="<%= JSON.dump({ 'label': 'Nie som robot', 'verifying': 'Overujem...', 'verified': 'Overené'}) %>" challengeurl="<%= altcha_url() %>"></altcha-widget>
<div class="newsletter-altcha-error"></div>
<% end %>
2 changes: 2 additions & 0 deletions app/views/newsletter_subscriptions/failure.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$('#newsletter-form').replaceWith('<%=j render partial: 'newsletter_subscriptions/form', object: @group %>');
$('.newsletter-altcha-error').html('Nie sme si isti, či nie ste robot... zaškrtli ste, že nie ste?');
4 changes: 4 additions & 0 deletions app/views/newsletter_subscriptions/success.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$('#newsletter-success').show();
if ($('#newsletter-warning').is(':visible')) {
$('#newsletter-warning').hide();
}
2 changes: 2 additions & 0 deletions app/views/notification_subscriptions/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
<% if !form.journey.nil? and form.journey.blank? %>
<%= hidden_field_tag 'notification_subscription_group[journey_id]', form.journey.id %>
<% end %>
<altcha-widget hidelogo hidefooter strings="<%= JSON.dump({ 'label': 'Nie som robot', 'verifying': 'Overujem...', 'verified': 'Overené'}) %>" challengeurl="<%= altcha_url() %>"></altcha-widget>
<div class="altcha-error"></div>
<%= submit_tag 'Chcem dostávať tieto notifikácie', class: 'govuk-button' %>
</div>

Expand Down
2 changes: 2 additions & 0 deletions app/views/notification_subscriptions/failure.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$('#new_subscription_notification_group').replaceWith('<%=j render partial: 'form', object: @group %>');
$('.altcha-error').html('Nie sme si isti, či nie ste robot... zaškrtli ste, že nie ste?');
8 changes: 8 additions & 0 deletions config/initializers/altcha.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

Altcha.setup do |config|
config.algorithm = 'SHA-256'
config.num_range = (50_000..51_000)
config.timeout = 5.minutes
config.hmac_key = 'dfa06d467a84fea13941f1c52c38c6458a67617a'
end
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -166,6 +167,8 @@
get :confirm, on: :member, path: 'potvrdit'
end

post 'newsletter/subscribe', to: 'newsletter_subscriptions#subscribe', as: :newsletter_subscribe

resource :session, only: [:new, :create, :destroy]
get '/auth/magiclink/info', to: 'sessions#magic_link_info'
get '/auth/failure', to: 'sessions#failure'
Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20240427082705_create_altcha_solutions.rb
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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: -
--
Expand Down Expand Up @@ -1647,6 +1682,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: -
--
Expand Down Expand Up @@ -1899,6 +1942,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: -
--
Expand Down Expand Up @@ -2440,6 +2490,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230325092744'),
('20230325095737'),
('20230325151049'),
('20240427082705'),
('20240427124856');


Loading