Skip to content

Commit 6af91b6

Browse files
committed
Streamline email confirmation for existing users
By default, Devise's `:confirmable` module generates a `confirmation_token` and sends confirmation instructions when a new user is created. This commit enhances that behaviour to streamline the email confirmation process for **existing** users. A new rake task (`lib/tasks/email_confirmation.rake#clear_all`) resets the following confirmation-related fields—`confirmed_at`, `confirmation_token`, and `confirmation_sent_at`—to `nil` for all non-superusers. After this reset, these users will be unable to sign in until they confirm their email. To avoid requiring manual re-sending of confirmation instructions, we introduce a new check: `User#confirmed_or_has_confirmation_token?`, which returns `false` if a user is unconfirmed *and* has no outstanding confirmation_token. In the `SessionsController#create` method, if a signing-in user fails the `confirmed_or_has_confirmation_token?` check, we invoke `handle_missing_confirmation_instructions(user)`. This generates a new confirmation_token and sends email instructions. On subsequent sign-in attempts, the check will return `true`, preventing redundant emails. This approach ensures that email confirmations are triggered automatically and only once per affected user, minimising friction while preserving security.
1 parent 2f2bdc6 commit 6af91b6

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

app/controllers/sessions_controller.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ def create
1313
existing_user = User.find_by(email: params[:user][:email])
1414
unless existing_user.nil?
1515

16+
unless existing_user.confirmed_or_has_confirmation_token?
17+
handle_missing_confirmation_instructions(existing_user)
18+
return
19+
end
20+
1621
# Until ORCID login is supported
1722
unless session['devise.shibboleth_data'].nil?
1823
args = {
@@ -45,3 +50,13 @@ def destroy
4550
set_locale
4651
end
4752
end
53+
54+
private
55+
56+
def handle_missing_confirmation_instructions(user)
57+
# Generate a confirmation_token and email confirmation instructions to the user
58+
user.send_confirmation_instructions
59+
# Notify the user they are unconfirmed but confirmation instructions have been sent
60+
flash[:notice] = I18n.t('devise.registrations.signed_up_but_unconfirmed')
61+
redirect_to root_path
62+
end

app/models/user.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ def deliver_invitation(options = {})
382382
)
383383
end
384384

385+
def confirmed_or_has_confirmation_token?
386+
confirmed? || confirmation_token.present?
387+
end
388+
385389
# Case insensitive search over User model
386390
#
387391
# field - The name of the field being queried

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)