decidim-friendly_signup 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88bd76deb088270153f2b8d9dc3376d1f6333c0429e2433c2f896870027cbd30
4
- data.tar.gz: d2e2efec13ef916c6835e8f09b4d50e8e20ddef3c05a1f41bf24b51a1054c074
3
+ metadata.gz: df7a2d3137cd72541b5d7e63878f022eebb44e0b115044425e727c4f0fe68d59
4
+ data.tar.gz: dd63fddf8a154b335369dcf87cae2aaff1c9b28c9bd58ad1910a97088283b75f
5
5
  SHA512:
6
- metadata.gz: 4a23dbcb2623314b4fcfbe30b8025b97309049beb28a2ab66ab0bb070498624509805054e2fc476f04f078fd56ed3161f49c66accae5b5d06b9537547d1fcbdd
7
- data.tar.gz: f5312068c52ccb019f87e1af5beabc72e7f85396aee34ab4b1799001dcb905e17962c042186c55b749af4a4a69bdb19b7b915cfad4581e17bccb8e96529e31fe
6
+ metadata.gz: a03470e2cb20fec61fe7106cd59d96293d9c9e490cd132570aa514a841c43587cbcf675ba25bfd272c5dec497db6c3f6a1922c170effb21f1ddeadeec252f331
7
+ data.tar.gz: 36919447d408e1a91f22b8d650b12db69ac00c08282fd5b07862bc074a344e9d99075d53fe53436af12ba80dc209b25e9085d3ca2bc0a219a1f47061d91566be
data/README.md CHANGED
@@ -20,7 +20,7 @@ This module simply substitutes some pages to ease up the registration process in
20
20
 
21
21
  - [x] Remove the nickname field from the registration process and automatically create one on registering. ![Hide nickname](examples/nickname.png)
22
22
  - [x] Instant validate parameters when registering without having to send it for backend validation. ![Instant validation](examples/instant_validation.png)
23
- - [ ] Use checkout codes to validate the email instead of a link
23
+ - [x] Use numeric, confirmation codes to validate the email instead of a link. ![Confirmation codes](examples/confirmation_codes.png)
24
24
 
25
25
  ## Installation
26
26
 
@@ -42,6 +42,18 @@ And then execute:
42
42
  bundle
43
43
  ```
44
44
 
45
+ For security reasons, it is also recomended to set a expiration time on confirmation tokens, to do that, make sure your Devise initializer has the variable `confirm_within` to certain amount of time.
46
+
47
+ For instance, you can do that by creating an initializer such as:
48
+
49
+ ```ruby
50
+ # config/initializers/devise.rb
51
+
52
+ Devise.setup do |config|
53
+ config.confirm_within = 12.hours
54
+ end
55
+ ```
56
+
45
57
  **Note:**
46
58
 
47
59
  The correct version of FriendlySignup should resolved automatically by the Bundler.
@@ -51,6 +63,7 @@ Depending on your Decidim version, choose the corresponding FriendlySignup versi
51
63
 
52
64
  | FriendlySignup version | Compatible Decidim versions |
53
65
  |---|---|
66
+ | 0.4.x | 0.26.x |
54
67
  | 0.3.x | 0.26.x |
55
68
  | 0.2.x | 0.26.x |
56
69
  | 0.1.x | 0.26.x |
@@ -71,6 +84,9 @@ Decidim::FriendlySignup.configure do |config|
71
84
 
72
85
  # Hide nickname field and create one automatically from user's name or email (default is true)
73
86
  config.hide_nickname = false
87
+
88
+ # Send the users a 4-digit number that needs to be entered in a confirmation page instead of a confirmation link (default is true)
89
+ config.use_confirmation_codes = false
74
90
  end
75
91
  ```
76
92
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module FriendlySignup
7
+ module RegistrationsRedirect
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ def after_sign_up_path_for(user)
12
+ codes_confirmation_path(user) || super(user)
13
+ end
14
+
15
+ def after_inactive_sign_up_path_for(user)
16
+ codes_confirmation_path(user) || super(user)
17
+ end
18
+
19
+ def after_resending_confirmation_instructions_path_for(resource_name)
20
+ user = Decidim::User.find_by email: params.dig(:user, :email)
21
+ codes_confirmation_path(user) || super(resource_name)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def codes_confirmation_path(user)
28
+ return if Decidim::FriendlySignup.use_confirmation_codes.blank?
29
+ return if user.blank?
30
+ return unless user.inactive_message.to_s == "unconfirmed"
31
+
32
+ set_flash_message! :notice, :signed_up_but_code_required
33
+ decidim_friendly_signup.confirmation_codes_path(confirmation_token: user.confirmation_token)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module FriendlySignup
5
+ class ConfirmationCodesController < Decidim::Devise::ConfirmationsController
6
+ include Decidim::FormFactory
7
+ include NeedsHeaderSnippets
8
+
9
+ before_action :require_unconfirmed_user
10
+ helper_method :user, :confirmation_form
11
+
12
+ def index; end
13
+
14
+ def create
15
+ if confirmation_form.valid?
16
+ user.confirm
17
+ flash[:success] = I18n.t("confirmation_codes.create.user_confirmed", name: user.name, scope: "decidim.friendly_signup")
18
+ return sign_in_and_redirect user, event: :authentication
19
+ end
20
+
21
+ flash.now[:alert] = confirmation_form.errors.messages.values.flatten.join(" ")
22
+ render :index
23
+ end
24
+
25
+ private
26
+
27
+ def confirmation_form
28
+ @confirmation_form ||= form(ConfirmationCodeForm).from_params(params)
29
+ end
30
+
31
+ def user
32
+ @user ||= User.find_by(confirmation_token: params[:confirmation_token], organization: current_organization)
33
+ end
34
+
35
+ def require_unconfirmed_user
36
+ return redirect_to decidim.user_confirmation_path unless FriendlySignup.use_confirmation_codes
37
+
38
+ unless user.present? && !user.confirmed?
39
+ flash[:alert] = I18n.t("confirmation_code_form.invalid_token", scope: "decidim.friendly_signup")
40
+ return redirect_to decidim.new_user_session_path
41
+ end
42
+
43
+ if user.send(:confirmation_period_expired?)
44
+ flash[:alert] = I18n.t("confirmation_code_form.expired", scope: "decidim.friendly_signup")
45
+ redirect_to decidim.user_confirmation_path
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module FriendlySignup
5
+ class ConfirmationCodeForm < Decidim::Form
6
+ attribute :confirmation_token, String
7
+ attribute :code, Integer
8
+ attribute :confirmation_numbers, Array[Integer]
9
+
10
+ validates :confirmation_token, presence: true
11
+ validate :code_matches_confirmation_token
12
+ validate :user_exists
13
+
14
+ def user_code
15
+ code || confirmation_numbers.map(&:to_s).join("").to_i
16
+ end
17
+
18
+ private
19
+
20
+ def code_matches_confirmation_token
21
+ return if FriendlySignup.confirmation_code(confirmation_token) == user_code
22
+
23
+ errors.add(:code, I18n.t("confirmation_code_form.invalid", scope: "decidim.friendly_signup"))
24
+ end
25
+
26
+ def user_exists
27
+ return if User.exists?(confirmation_token: confirmation_token, organization: current_organization)
28
+
29
+ errors.add(:code, I18n.t("confirmation_code_form.invalid_token", scope: "decidim.friendly_signup"))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module FriendlySignup
5
+ class ConfirmationCodesMailer < ApplicationMailer
6
+ include Decidim::LocalisedMailer
7
+
8
+ def confirmation_instructions(user, opts)
9
+ @user = user
10
+ @email = opts[:to] || user.email
11
+ @token = opts[:token]
12
+ @organization = user.organization
13
+ @code = FriendlySignup.confirmation_code(@token)
14
+ @expires_at = @user.confirmation_sent_at + @user.class.confirm_within if @user.class.confirm_within
15
+
16
+ with_user(user) do
17
+ mail(to: "#{user.name} <#{@email}>", subject: I18n.t("decidim.friendly_signup.confirmation_codes.mailer.subject", organization: @organization.name))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module FriendlySignup
7
+ module NeedsRegistrationCodes
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ def send_confirmation_instructions
12
+ return super if Decidim::FriendlySignup.use_confirmation_codes.blank?
13
+ return super unless inactive_message.to_s == "unconfirmed"
14
+
15
+ generate_confirmation_token! unless @raw_confirmation_token
16
+
17
+ opts = pending_reconfirmation? ? { to: unconfirmed_email } : {}
18
+ opts[:token] = @raw_confirmation_token
19
+ ConfirmationCodesMailer.confirmation_instructions(self, opts).deliver_now
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,6 @@
1
1
  import "src/decidim/friendly_signup/setup_password"
2
2
  import "src/decidim/friendly_signup/setup_validations"
3
+ import "src/decidim/friendly_signup/setup_confirmations"
3
4
 
4
5
  // CSS
5
6
  import "entrypoints/decidim_friendly_signup.scss";
@@ -1,2 +1,3 @@
1
1
  /* css for decidim_friendly_signup */
2
2
  @import "stylesheets/decidim/friendly_signup/input-groups";
3
+ @import "stylesheets/decidim/friendly_signup/confirmation-codes";
@@ -15,6 +15,9 @@ export default class InstantValidator {
15
15
  }
16
16
 
17
17
  init() {
18
+ if (!this.url || !this.$form.length) {
19
+ return;
20
+ }
18
21
  this.$form.foundation("disableValidation");
19
22
  // this final validation prevents abide from resetting the field when user loses focus
20
23
  this.$inputs.on("blur", (evt) => {
@@ -0,0 +1,57 @@
1
+ $(() => {
2
+ const $inputs = $('.confirmation-code-inputs input[type="number"]');
3
+ const $form = $inputs.closest("form");
4
+ const intRegex = /^\d+$/;
5
+ let disableManual = false;
6
+
7
+ // Parses the individual digits into the individual boxes.
8
+ const pasteValues = (element, $first) => {
9
+ const values = element.split("");
10
+ let $inputBox = $first;
11
+
12
+ $(values).each((index) => {
13
+ $inputBox.val(values[index]);
14
+ $inputBox = $inputBox.next('input[type="number"]');
15
+ if ($inputBox.length === 0) {
16
+ $form.submit();
17
+ }
18
+ });
19
+ };
20
+
21
+ $inputs.on("focus", (e) => {
22
+ $(e.target).select();
23
+ });
24
+
25
+ // Prevents user from manually entering non-digits.
26
+ $inputs.on("input.fromManual", (e) => {
27
+ if (disableManual) {
28
+ return;
29
+ }
30
+ const $this = $(e.target);
31
+ if (intRegex.test($this.val())) {
32
+ pasteValues($this.val(), $this);
33
+ $this.next('input[type="number"]').focus();
34
+ } else {
35
+ $this.val("");
36
+ }
37
+ });
38
+
39
+ $inputs.on("paste", (evt) => {
40
+ const $this = $(evt.target);
41
+ const originalValue = $this.val();
42
+ $this.val("");
43
+ disableManual = true;
44
+ $this.one("input.fromPaste", (e) => {
45
+ const $currentInputBox = $(e.target);
46
+
47
+ const pastedValue = $currentInputBox.val();
48
+ if (intRegex.test(pastedValue)) {
49
+ pasteValues(pastedValue, $inputs.eq(0));
50
+ }
51
+ else {
52
+ $this.val(originalValue);
53
+ }
54
+ disableManual = false;
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,23 @@
1
+ .confirmation-code-inputs {
2
+ display: flex;
3
+ justify-content: center;
4
+ margin: 2rem;
5
+
6
+ input[type="number"] {
7
+ width: 15%;
8
+ font-size: 5em;
9
+ height: 1.2em;
10
+ line-height: 1;
11
+ padding: 0;
12
+ margin: 0 2%;
13
+ text-align: center;
14
+ box-shadow: 1px 3px 3px #bbb;
15
+ -moz-appearance: textfield;
16
+
17
+ &::-webkit-inner-spin-button,
18
+ &::-webkit-outer-spin-button {
19
+ -webkit-appearance: none;
20
+ margin: 0;
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,38 @@
1
+ <% add_decidim_page_title(t(".enter_code")) %>
2
+
3
+ <div class="wrapper">
4
+ <div class="row collapse">
5
+ <div class="row collapse">
6
+ <div class="columns large-8 large-centered text-center page-title">
7
+ <h1><%= t(".title") %></h1>
8
+ <h6><%= t(".subtitle") %></h6>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="row">
13
+ <div class="columns medium-10 large-8 medium-centered">
14
+ <div class="card">
15
+ <div class="card__content">
16
+ <p class="text-center"><%= t(".description", email: "<strong>#{user.email}</strong>").html_safe %></p>
17
+ <%= decidim_form_for(confirmation_form, url: decidim_friendly_signup.confirmation_codes_path(confirmation_token: confirmation_form.confirmation_token)) do |f| %>
18
+ <div class="field confirmation-code-inputs">
19
+ <%= number_field_tag "confirmation_numbers[0]", "", autofocus: true, autocomplete: :off %>
20
+ <%= number_field_tag "confirmation_numbers[1]", "", autocomplete: :off %>
21
+ <%= number_field_tag "confirmation_numbers[2]", "", autocomplete: :off %>
22
+ <%= number_field_tag "confirmation_numbers[3]", "", autocomplete: :off %>
23
+ </div>
24
+
25
+ <div class="actions text-center">
26
+ <%= f.submit t(".verify"), class: "button text-uppercase" %>
27
+ </div>
28
+ <% end %>
29
+
30
+ <p class="text-center">
31
+ <%= link_to t(".code_not_received"), decidim.user_confirmation_path(confirmation_token: confirmation_form.confirmation_token, "user[email]": user.email), method: :post, data: { confirm: t(".confirm_send_code", email: user.email) } %>
32
+ </p>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
@@ -0,0 +1,13 @@
1
+ <h1 class="text-center"><%= t(".title") %></h1>
2
+
3
+ <p class="email-instructions text-center"><%= t(".subtitle", organization: link_to(@organization.name, decidim_friendly_signup.confirmation_codes_path(confirmation_token: @token))).html_safe %></p>
4
+
5
+ <h3 class="email-instruction text-center" style="margin:1em 0 0.2em 0;"><%= t(".copy") %></h3>
6
+
7
+ <% if @expires_at %>
8
+ <p class="email-small text-center"><%= t(".expires_in", time: distance_of_time_in_words(Time.now, @expires_at, scope: "decidim.friendly_signup.datetime.distance_in_words")) %></p>
9
+ <% end %>
10
+
11
+ <p class="stat text-center" style="margin:1em 0;"><b style="font-size: 2em"><%= @code %></b></p>
12
+
13
+ <p class="email-small email-closing text-center"><%= t(".ignore").html_safe %></p>
@@ -1,10 +1,89 @@
1
1
  ---
2
2
  en:
3
+ activemodel:
4
+ attributes:
5
+ confirmation_code:
6
+ code: Confirmation code
3
7
  decidim:
4
8
  friendly_signup:
9
+ confirmation_code_form:
10
+ expired: Sorry, this code has expired, please generate a new one.
11
+ invalid: Code is invalid, please make sure to copy the code emailed to you.
12
+ invalid_token: Invalid token.
13
+ confirmation_codes:
14
+ create:
15
+ user_confirmed: Welcome %{name}! Your account has been succesfully confirmed!
16
+ index:
17
+ code_not_received: Didn't receive the code?
18
+ confirm_send_code: Do you want to resend the confirmation code to %{email}?<br>Be
19
+ sure to check the spam folder just in case!
20
+ description: You should receive a 4 digit code at %{email}.<br>If you can't
21
+ find it, please check your spam folder or wait up to 10 minutes.
22
+ enter_code: Enter code
23
+ subtitle: You need to confirm your email address to be able to submit proposals,
24
+ comment and vote.
25
+ title: One last step...
26
+ verify: Verify
27
+ mailer:
28
+ subject: Confirm your account at %{organization}
29
+ resend_code:
30
+ sent: The confirmation code has been sent to %{email}.
31
+ confirmation_codes_mailer:
32
+ confirmation_instructions:
33
+ copy: 'Copy this code:'
34
+ expires_in: It will expire in %{time}.
35
+ ignore: |-
36
+ If you didn't request this comunication, please ignore this email.<br />
37
+ Your account won't be active until your account is fully confirmed.
38
+ subtitle: To finalize the registration you just need to copy the 4 digit
39
+ code below, go back to the %{organization} signup page and paste it!
40
+ title: You're almost there!
41
+ datetime:
42
+ distance_in_words:
43
+ about_x_hours:
44
+ one: about 1 hour
45
+ other: about %{count} hours
46
+ about_x_months:
47
+ one: about 1 month
48
+ other: about %{count} months
49
+ about_x_years:
50
+ one: about 1 year
51
+ other: about %{count} years
52
+ almost_x_years:
53
+ one: almost 1 year
54
+ other: almost %{count} years
55
+ half_a_minute: half a minute
56
+ less_than_x_minutes:
57
+ one: less than a minute
58
+ other: less than %{count} minutes
59
+ less_than_x_seconds:
60
+ one: less than 1 second
61
+ other: less than %{count} seconds
62
+ over_x_years:
63
+ one: over 1 year
64
+ other: over %{count} years
65
+ x_days:
66
+ one: 1 day
67
+ other: "%{count} days"
68
+ x_minutes:
69
+ one: 1 minute
70
+ other: "%{count} minutes"
71
+ x_months:
72
+ one: 1 month
73
+ other: "%{count} months"
74
+ x_seconds:
75
+ one: 1 second
76
+ other: "%{count} seconds"
5
77
  shared:
6
78
  password_fields:
7
79
  hidden_password: Your password is hidden
8
80
  hide_password: Hide password
9
81
  show_password: Show password
10
82
  shown_password: Your password is shown
83
+ devise:
84
+ confirmations:
85
+ signed_up_but_code_required: A message with a code has been sent to your email
86
+ address. Please copy and paste the received code in this page.
87
+ registrations:
88
+ signed_up_but_code_required: A message with a code has been sent to your email
89
+ address. Please copy and paste the received code in this page.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rails"
4
4
  require "decidim/core"
5
+ require "rack/attack"
5
6
 
6
7
  module Decidim
7
8
  module FriendlySignup
@@ -10,6 +11,9 @@ module Decidim
10
11
  isolate_namespace Decidim::FriendlySignup
11
12
 
12
13
  routes do
14
+ devise_scope :user do
15
+ resources :confirmation_codes, only: [:index, :create]
16
+ end
13
17
  post :validate, to: "validator#validate"
14
18
  end
15
19
 
@@ -18,13 +22,24 @@ module Decidim
18
22
  # overrides
19
23
  config.after_initialize do
20
24
  Decidim::Devise::RegistrationsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
25
+ Decidim::Devise::RegistrationsController.include(Decidim::FriendlySignup::RegistrationsRedirect)
26
+ Decidim::Devise::ConfirmationsController.include(Decidim::FriendlySignup::RegistrationsRedirect)
21
27
  Decidim::Devise::InvitationsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
22
28
  Decidim::Devise::PasswordsController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
23
29
  Decidim::AccountController.include(Decidim::FriendlySignup::NeedsHeaderSnippets)
24
30
  Decidim::RegistrationForm.include(Decidim::FriendlySignup::AutoNickname)
31
+ Decidim::User.include(Decidim::FriendlySignup::NeedsRegistrationCodes)
25
32
  end
26
33
 
27
- initializer "FriendlySignup.webpacker.assets_path" do
34
+ initializer "friendly_signup.confirmation_throttling" do
35
+ # Throttle confirmation attempts for a given code parameter to 6 reqs/minute
36
+ # Return the confirmation_token as a discriminator on POST /users/sign_in requests
37
+ Rack::Attack.throttle("limit confirmations attempts per code", limit: 5, period: 60.seconds) do |request|
38
+ request.params["confirmation_token"] if request.path == "/friendly_signup/confirmation_codes" && request.post?
39
+ end
40
+ end
41
+
42
+ initializer "friendly_signup.webpacker.assets_path" do
28
43
  Decidim.register_assets_path File.expand_path("app/packs", root)
29
44
  end
30
45
  end
@@ -5,6 +5,6 @@ module Decidim
5
5
  module FriendlySignup
6
6
  DECIDIM_VERSION = "0.26.2"
7
7
  COMPAT_DECIDIM_VERSION = "~> 0.26.0"
8
- VERSION = "0.3"
8
+ VERSION = "0.4"
9
9
  end
10
10
  end
@@ -23,6 +23,18 @@ module Decidim
23
23
  config_accessor :hide_nickname do
24
24
  true
25
25
  end
26
+
27
+ # Use confirmation codes instead of confirmation links
28
+ config_accessor :use_confirmation_codes do
29
+ true
30
+ end
31
+
32
+ # Generates a secure code from a string
33
+ def self.confirmation_code(hash)
34
+ num = Decidim::Tokenizer.new(salt: Rails.application.secret_key_base).int_digest(hash).to_s[0..3]
35
+ num += "0" while num.size < 4
36
+ num.to_i
37
+ end
26
38
  end
27
39
  end
28
40
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decidim-friendly_signup
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Vergés
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-14 00:00:00.000000000 Z
11
+ date: 2022-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: decidim-core
@@ -49,21 +49,30 @@ files:
49
49
  - README.md
50
50
  - Rakefile
51
51
  - app/controllers/concerns/decidim/friendly_signup/needs_header_snippets.rb
52
+ - app/controllers/concerns/decidim/friendly_signup/registrations_redirect.rb
52
53
  - app/controllers/decidim/friendly_signup/application_controller.rb
54
+ - app/controllers/decidim/friendly_signup/confirmation_codes_controller.rb
53
55
  - app/controllers/decidim/friendly_signup/validator_controller.rb
54
56
  - app/forms/concerns/decidim/friendly_signup/auto_nickname.rb
57
+ - app/forms/decidim/friendly_signup/confirmation_code_form.rb
58
+ - app/mailers/decidim/friendly_signup/confirmation_codes_mailer.rb
59
+ - app/models/concerns/decidim/friendly_signup/needs_registration_codes.rb
55
60
  - app/packs/entrypoints/decidim_friendly_signup.js
56
61
  - app/packs/entrypoints/decidim_friendly_signup.scss
57
62
  - app/packs/images/decidim/friendly_signup/icon.svg
58
63
  - app/packs/src/decidim/friendly_signup/lib/instant_validator.js
59
64
  - app/packs/src/decidim/friendly_signup/lib/password_toggler.js
65
+ - app/packs/src/decidim/friendly_signup/setup_confirmations.js
60
66
  - app/packs/src/decidim/friendly_signup/setup_password.js
61
67
  - app/packs/src/decidim/friendly_signup/setup_validations.js
68
+ - app/packs/stylesheets/decidim/friendly_signup/_confirmation-codes.scss
62
69
  - app/packs/stylesheets/decidim/friendly_signup/_input-groups.scss
63
70
  - app/views/decidim/account/_password_fields.html.erb
64
71
  - app/views/decidim/devise/invitations/edit.html.erb
65
72
  - app/views/decidim/devise/passwords/edit.html.erb
66
73
  - app/views/decidim/devise/registrations/new.html.erb
74
+ - app/views/decidim/friendly_signup/confirmation_codes/index.html.erb
75
+ - app/views/decidim/friendly_signup/confirmation_codes_mailer/confirmation_instructions.html.erb
67
76
  - app/views/decidim/friendly_signup/shared/_password_fields.html.erb
68
77
  - config/assets.rb
69
78
  - config/i18n-tasks.yml