Skip to content

Commit 2535269

Browse files
authored
Merge pull request #3507 from DMPRoadmap/upstream/aaron/add-email-confirmation
Re-Implement Email Confirmation
2 parents cbb6dad + ab61ae0 commit 2535269

File tree

17 files changed

+339
-68
lines changed

17 files changed

+339
-68
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Fix failing eslint workflow / upgrade `actions/checkout` & `actions/setup-node` to v3 [#3503](https://github.com/DMPRoadmap/roadmap/pull/3503)
66
- Fix rendering of `confirm_merge` partial [#3515](https://github.com/DMPRoadmap/roadmap/pull/3515)
77
- Remove Auto-Generated TinyMCE Skins and Add `public/tinymce/skins/` to `.gitignore` [#3466](https://github.com/DMPRoadmap/roadmap/pull/3466)
8+
- Re-implement email confirmation [#3507](https://github.com/DMPRoadmap/roadmap/pull/3507)
9+
- Update `spec/support/faker.rb` to use `I18n.default_locale` [#3507](https://github.com/DMPRoadmap/roadmap/pull/3507)
810

911
## v5.0.0
1012

app/controllers/sessions_controller.rb

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# Controller that handles user login and logout
44
class SessionsController < Devise::SessionsController
5+
include EmailConfirmationHandler
6+
57
def new
68
redirect_to(root_path)
79
end
@@ -11,28 +13,22 @@ def new
1113
# rubocop:disable Metrics/AbcSize
1214
def create
1315
existing_user = User.find_by(email: params[:user][:email])
14-
unless existing_user.nil?
16+
if existing_user.present?
17+
18+
# (see app/models/concerns/email_confirmation_handler.rb)
19+
return if confirmation_instructions_missing_and_handled?(existing_user)
1520

1621
# Until ORCID login is supported
17-
unless session['devise.shibboleth_data'].nil?
18-
args = {
19-
identifier_scheme: IdentifierScheme.find_by(name: 'shibboleth'),
20-
value: session['devise.shibboleth_data']['uid'],
21-
identifiable: existing_user,
22-
attrs: session['devise.shibboleth_data']
23-
}
24-
@ui = Identifier.new(args)
25-
end
22+
@ui = create_shibboleth_identifier(existing_user) unless session['devise.shibboleth_data'].nil?
2623
session[:locale] = existing_user.locale unless existing_user.locale.nil?
2724
# Method defined at controllers/application_controller.rb
2825
set_locale
2926
end
3027

3128
super do
3229
if !@ui.nil? && @ui.save
33-
# rubocop:disable Layout/LineLength
34-
flash[:notice] = _('Your account has been successfully linked to your institutional credentials. You will now be able to sign in with them.')
35-
# rubocop:enable Layout/LineLength
30+
flash[:notice] = _('Your account has been successfully linked to your institutional credentials. ' \
31+
'You will now be able to sign in with them.')
3632
end
3733
end
3834
end
@@ -45,3 +41,15 @@ def destroy
4541
set_locale
4642
end
4743
end
44+
45+
private
46+
47+
def create_shibboleth_identifier(user)
48+
args = {
49+
identifier_scheme: IdentifierScheme.find_by(name: 'shibboleth'),
50+
value: session['devise.shibboleth_data']['uid'],
51+
identifiable: user,
52+
attrs: session['devise.shibboleth_data']
53+
}
54+
Identifier.new(args)
55+
end

app/controllers/users/omniauth_callbacks_controller.rb

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module Users
44
# Controller that handles callbacks from OmniAuth integrations (e.g. Shibboleth and ORCID)
55
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
6+
include EmailConfirmationHandler
67
##
78
# Dynamically build a handler for each omniauth provider
89
# -------------------------------------------------------------
@@ -21,8 +22,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
2122
#
2223
# scheme - The IdentifierScheme for the provider
2324
#
24-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
2625
def handle_omniauth(scheme)
2726
user = if request.env['omniauth.auth'].nil?
2827
User.from_omniauth(request.env)
@@ -32,55 +31,68 @@ def handle_omniauth(scheme)
3231

3332
# If the user isn't logged in
3433
if current_user.nil?
35-
# If the uid didn't have a match in the system send them to register
36-
if user.nil?
37-
session["devise.#{scheme.name.downcase}_data"] = request.env['omniauth.auth']
38-
redirect_to new_user_registration_url
39-
40-
# Otherwise sign them in
41-
elsif scheme.name == 'shibboleth'
42-
# Until ORCID becomes supported as a login method
43-
set_flash_message(:notice, :success, kind: scheme.description) if is_navigational_format?
44-
sign_in_and_redirect user, event: :authentication
45-
else
46-
flash[:notice] = _('Successfully signed in')
47-
redirect_to new_user_registration_url
48-
end
49-
34+
handle_omniauth_for_signed_out_user(user, scheme)
5035
# The user is already logged in and just registering the uid with us
5136
else
52-
# If the user could not be found by that uid then attach it to their record
53-
if user.nil?
54-
if Identifier.create(identifier_scheme: scheme,
55-
value: request.env['omniauth.auth'].uid,
56-
attrs: request.env['omniauth.auth'],
57-
identifiable: current_user)
58-
flash[:notice] =
59-
format(_('Your account has been successfully linked to %{scheme}.'),
60-
scheme: scheme.description)
37+
handle_omniauth_for_signed_in_user(user, scheme)
38+
end
39+
end
40+
41+
def failure
42+
redirect_to root_path
43+
end
6144

62-
else
63-
flash[:alert] = format(_('Unable to link your account to %{scheme}.'),
64-
scheme: scheme.description)
65-
end
45+
private
6646

67-
elsif user.id != current_user.id
68-
# If a user was found but does NOT match the current user then the identifier has
69-
# already been attached to another account (likely the user has 2 accounts)
70-
# rubocop:disable Layout/LineLength
71-
flash[:alert] = _("The current #{scheme.description} iD has been already linked to a user with email #{identifier.user.email}")
72-
# rubocop:enable Layout/LineLength
47+
# rubocop:disable Metrics/AbcSize
48+
def handle_omniauth_for_signed_in_user(user, scheme)
49+
# If the user could not be found by that uid then attach it to their record
50+
if user.nil?
51+
if Identifier.create(identifier_scheme: scheme,
52+
value: request.env['omniauth.auth'].uid,
53+
attrs: request.env['omniauth.auth'],
54+
identifiable: current_user)
55+
flash[:notice] = format(_('Your account has been successfully linked to %{scheme}.'),
56+
scheme: scheme.description)
57+
58+
else
59+
flash[:alert] = format(_('Unable to link your account to %{scheme}.'),
60+
scheme: scheme.description)
7361
end
7462

75-
# Redirect to the User Profile page
76-
redirect_to edit_user_registration_path
63+
elsif user.id != current_user.id
64+
# If a user was found but does NOT match the current user then the identifier has
65+
# already been attached to another account (likely the user has 2 accounts)
66+
flash[:alert] = _("The current #{scheme.description} iD has been already linked " \
67+
"to a user with email #{identifier.user.email}")
7768
end
69+
70+
# Redirect to the User Profile page
71+
redirect_to edit_user_registration_path
7872
end
79-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
80-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
73+
# rubocop:enable Metrics/AbcSize
8174

82-
def failure
83-
redirect_to root_path
75+
# rubocop:disable Metrics/AbcSize
76+
def handle_omniauth_for_signed_out_user(user, scheme)
77+
# If the uid didn't have a match in the system send them to register
78+
if user.nil?
79+
session["devise.#{scheme.name.downcase}_data"] = request.env['omniauth.auth']
80+
redirect_to new_user_registration_url
81+
82+
# Otherwise sign them in
83+
elsif scheme.name == 'shibboleth'
84+
# Until ORCID becomes supported as a login method
85+
86+
# (see app/models/concerns/email_confirmation_handler.rb)
87+
return if confirmation_instructions_missing_and_handled?(user)
88+
89+
set_flash_message(:notice, :success, kind: scheme.description) if is_navigational_format?
90+
sign_in_and_redirect user, event: :authentication
91+
else
92+
flash[:notice] = _('Successfully signed in')
93+
redirect_to new_user_registration_url
94+
end
8495
end
96+
# rubocop:enable Metrics/AbcSize
8597
end
8698
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
# Some users in our db are both unconfirmed AND have no outstanding confirmation_token
4+
# This is true for those users due to the following:
5+
# - We haven't always used Devise's :confirmable module (it generates a confirmation_token when a user is created)
6+
# - We have set `confirmed_at` and `confirmation_token` to nil via Rake tasks (lib/tasks/email_confirmation.rake)
7+
# This concern is meant to streamline the confirmation process for those users
8+
module EmailConfirmationHandler
9+
extend ActiveSupport::Concern
10+
11+
def confirmation_instructions_missing_and_handled?(user)
12+
# A user's "confirmation instructions are missing" if they're both unconfirmed and have no confirmation_token
13+
return false if user_confirmed_or_has_confirmation_token?(user)
14+
15+
handle_missing_confirmation_instructions(user)
16+
true
17+
end
18+
19+
private
20+
21+
def user_confirmed_or_has_confirmation_token?(user)
22+
user.confirmed? || user.confirmation_token.present?
23+
end
24+
25+
def handle_missing_confirmation_instructions(user)
26+
user.send_confirmation_instructions
27+
redirect_to root_path, notice: I18n.t('devise.registrations.signed_up_but_unconfirmed')
28+
end
29+
end

app/models/user.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class User < ApplicationRecord
6565
# :token_authenticatable, :confirmable,
6666
# :lockable, :timeoutable and :omniauthable
6767
devise :invitable, :database_authenticatable, :registerable, :recoverable,
68-
:rememberable, :trackable, :validatable, :omniauthable,
68+
:rememberable, :trackable, :validatable, :omniauthable, :confirmable,
6969
omniauth_providers: %i[shibboleth orcid]
7070

7171
# default user language to the default language

config/environments/test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
# The :test delivery method accumulates sent emails in the
4646
# ActionMailer::Base.deliveries array.
4747
config.action_mailer.delivery_method = :test
48+
config.action_mailer.default_options = { from: '[email protected]' }
4849

4950
# Print deprecation notices to the stderr.
5051
config.active_support.deprecation = :stderr

config/locales/translation.en-CA.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ en-CA:
230230
not_found_in_database: Invalid %{authentication_keys} or password.
231231
timeout: Your session expired. Please sign in again to continue.
232232
unauthenticated: You need to sign in or sign up before continuing.
233-
unconfirmed: You have to confirm your email address before continuing.
233+
unconfirmed: You need to confirm your account before continuing. <a href="/users/confirmation/new"
234+
class="a-orange">(Click to request a new confirmation email)</a>
234235
invited:
235236
mailer:
236237
confirmation_instructions:

config/locales/translation.en-GB.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ en-GB:
227227
not_found_in_database: Invalid email or password.
228228
timeout: Your session expired, please sign in again to continue.
229229
unauthenticated: You need to sign in or sign up before continuing.
230-
unconfirmed: You have to confirm your account before continuing.
230+
unconfirmed: You have to confirm your account before continuing. <a href="/users/confirmation/new"
231+
class="a-orange">(Click to request a new confirmation email)</a>
231232
invited: You have a pending invitation, accept it to finish creating your account.
232233
mailer:
233234
confirmation_instructions:

config/locales/translation.fr-CA.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ fr-CA:
242242
not_found_in_database: "%{authentication_keys} ou mot de passe incorrect."
243243
timeout: Votre session est expirée. Veuillez vous reconnecter pour continuer.
244244
unauthenticated: Vous devez vous connecter ou vous inscrire pour continuer.
245-
unconfirmed: Vous devez confirmer votre adresse courriel pour continuer.
245+
unconfirmed: Vous devez confirmer votre compte avant de continuer. <a href="/users/confirmation/new"
246+
class="a-orange">(cliquez pour demander un nouveau courriel de confirmation)</a>
246247
invited: Vous avez une invitation en attente. Acceptez-la pour terminer la création
247248
de votre compte.
248249
mailer:

lib/tasks/email_confirmation.rake

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
namespace :email_confirmation do
4+
desc 'Reset confirmation status for all users, excluding superusers'
5+
task clear_all: :environment do
6+
p '------------------------------------------------------------------------'
7+
p 'Beginning task: Unconfirming all users except superusers'
8+
p '------------------------------------------------------------------------'
9+
unconfirm_all_users_except_superusers
10+
p 'Task completed: Unconfirmed all users except superusers'
11+
end
12+
13+
private
14+
15+
def unconfirm_all_users_except_superusers
16+
p 'Updating :confirmable columns to nil for all users'
17+
p '(i.e. Setting confirmed_at, confirmation_token, and confirmation_sent_at to nil for all users)'
18+
p '------------------------------------------------------------------------'
19+
set_confirmable_cols_to_nil_for_all_users
20+
p '------------------------------------------------------------------------'
21+
p 'Updating superusers so that they are not required to confirm their email addresses'
22+
p '(i.e. Setting `confirmed_at = Time.current` for superusers)'
23+
p '------------------------------------------------------------------------'
24+
confirm_superusers
25+
end
26+
27+
def set_confirmable_cols_to_nil_for_all_users
28+
count = User.update_all(confirmed_at: nil, confirmation_token: nil, confirmation_sent_at: nil)
29+
p ":confirmable columns updated to nil for #{count} users"
30+
end
31+
32+
# Sets `confirmed_at` to `Time.current` for all superusers
33+
def confirm_superusers
34+
confirmed_at = Time.current
35+
count = User.joins(:perms).where(perms: { id: super_admin_perm_ids })
36+
.distinct
37+
.update_all(confirmed_at: confirmed_at)
38+
p "Updated confirmed_at = #{confirmed_at} for #{count} superuser(s)"
39+
end
40+
41+
# Returns an array of all perm ids that are considered super admin perms
42+
# (Based off of `def can_super_admin?` in `app/models/user.rb`
43+
# i.e. `can_add_orgs? || can_grant_api_to_orgs? || can_change_org?` )
44+
def super_admin_perm_ids
45+
[Perm.add_orgs.id, Perm.grant_api.id, Perm.change_affiliation.id]
46+
end
47+
end

0 commit comments

Comments
 (0)