Send a confirmation email when the user adds a secondary email address. Utilizes the Devise confirmable capabilities. Issue #37385

This commit is contained in:
Brett Walker 2017-09-04 19:23:33 +02:00
parent b146415798
commit f9f4672275
19 changed files with 109 additions and 89 deletions

View file

@ -11,11 +11,12 @@ class ConfirmationsController < Devise::ConfirmationsController
end end
def after_confirmation_path_for(resource_name, resource) def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name) # incoming resource can either be a :user or an :email
if signed_in?(:user)
after_sign_in_path_for(resource) after_sign_in_path_for(resource)
else else
flash[:notice] += " Please sign in." flash[:notice] += " Please sign in."
new_session_path(resource_name) new_session_path(:user)
end end
end end
end end

View file

@ -6,10 +6,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
def create def create
@email = Emails::CreateService.new(current_user, email_params).execute @email = Emails::CreateService.new(current_user, email_params).execute
unless @email.errors.blank?
if @email.errors.blank?
NotificationService.new.new_email(@email)
else
flash[:alert] = @email.errors.full_messages.first flash[:alert] = @email.errors.full_messages.first
end end

View file

@ -7,12 +7,6 @@ module Emails
mail(to: @user.notification_email, subject: subject("Account was created for you")) mail(to: @user.notification_email, subject: subject("Account was created for you"))
end end
def new_email_email(email_id)
@email = Email.find(email_id)
@current_user = @user = @email.user
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
end
def new_ssh_key_email(key_id) def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id) @key = Key.find_by(id: key_id)

View file

@ -7,6 +7,9 @@ class Email < ActiveRecord::Base
validates :email, presence: true, uniqueness: true, email: true validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
devise :confirmable
self.reconfirmable = false # currently email can't be changed, no need to reconfirm
def email=(value) def email=(value)
write_attribute(:email, value.downcase.strip) write_attribute(:email, value.downcase.strip)
end end

View file

@ -31,13 +31,6 @@ class NotificationService
end end
end end
# Always notify user about email added to profile
def new_email(email)
if email.user&.can?(:receive_notifications)
mailer.new_email_email(email.id).deliver_later
end
end
# When create an issue we should send an email to: # When create an issue we should send an email to:
# #
# * issue assignee if their notification level is not Disabled # * issue assignee if their notification level is not Disabled

View file

@ -0,0 +1,8 @@
#content
= email_default_heading("#{@resource.user.name}, you've added a secondary email!")
%p Click the link below to confirm your email address (#{@resource.email})
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
%p
If this email was added in error, you can remove it here:
= link_to "Emails", profile_emails_url

View file

@ -0,0 +1,7 @@
<%= @resource.user.name %>, you've added a secondary email!
You can confirm your email (<%= @resource.email %>) through the link below:
<%= confirmation_url(@resource, confirmation_token: @token) %>
If this email was added in error, you can remove it here: <%= profile_emails_url %>

View file

@ -1,3 +1,6 @@
- if @resource.is_a?(Email)
= render partial: 'confirmation_instructions_secondary'
- else
- if @resource.unconfirmed_email.present? - if @resource.unconfirmed_email.present?
#content #content
= email_default_heading(@resource.unconfirmed_email) = email_default_heading(@resource.unconfirmed_email)

View file

@ -1,3 +1,6 @@
<% if @resource.is_a?(Email) %>
<%= render partial: 'confirmation_instructions_secondary' %>
<% else %>
Welcome, <%= @resource.name %>! Welcome, <%= @resource.name %>!
<% if @resource.unconfirmed_email.present? %> <% if @resource.unconfirmed_email.present? %>
@ -7,3 +10,4 @@ You can confirm your account through the link below:
<% end %> <% end %>
<%= confirmation_url(@resource, confirmation_token: @token) %> <%= confirmation_url(@resource, confirmation_token: @token) %>
<% end %>

View file

@ -1,10 +0,0 @@
%p
Hi #{@user.name}!
%p
A new email was added to your account:
%p
email:
%code= @email.email
%p
If this email was added in error, you can remove it here:
= link_to "Emails", profile_emails_url

View file

@ -1,7 +0,0 @@
Hi <%= @user.name %>!
A new email was added to your account:
email.................. <%= @email.email %>
If this email was added in error, you can remove it here: <%= profile_emails_url %>

View file

@ -47,4 +47,6 @@
%span.label.label-info Public email %span.label.label-info Public email
- if email.email === current_user.notification_email - if email.email === current_user.notification_email
%span.label.label-info Notification email %span.label.label-info Notification email
- if !email.confirmed?
%span.label.label-warning Unconfirmed
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10' = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'

View file

@ -0,0 +1,5 @@
---
title: A confirmation email is now sent when adding a secondary email address
merge_request:
author: digitalmoksha
type: added

View file

@ -11,6 +11,11 @@ devise_scope :user do
get '/users/almost_there' => 'confirmations#almost_there' get '/users/almost_there' => 'confirmations#almost_there'
end end
# for secondary email confirmations
devise_for :emails, controllers: { confirmations: :confirmations }
devise_scope :email do
end
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do
scope(path: 'users/:username', scope(path: 'users/:username',
as: :user, as: :user,

View file

@ -0,0 +1,34 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEmailConfirmation < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index", "remove_concurrent_index" or
# "add_column_with_default" you must disable the use of transactions
# as these methods can not run in an existing transaction.
# When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
# that either of them is the _only_ method called in the migration,
# any other changes should go in a separate migration.
# This ensures that upon failure _only_ the index creation or removing fails
# and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :emails, :confirmation_token, :string
add_column :emails, :confirmed_at, :datetime
add_column :emails, :confirmation_sent_at, :datetime
add_index :emails, :confirmation_token, unique: true
end
end

View file

@ -329,7 +329,6 @@ module API
email = Emails::CreateService.new(user, declared_params(include_missing: false)).execute email = Emails::CreateService.new(user, declared_params(include_missing: false)).execute
if email.errors.blank? if email.errors.blank?
NotificationService.new.new_email(email)
present email, with: Entities::Email present email, with: Entities::Email
else else
render_validation_error!(email) render_validation_error!(email)
@ -675,7 +674,6 @@ module API
email = Emails::CreateService.new(current_user, declared_params).execute email = Emails::CreateService.new(current_user, declared_params).execute
if email.errors.blank? if email.errors.blank?
NotificationService.new.new_email(email)
present email, with: Entities::Email present email, with: Entities::Email
else else
render_validation_error!(email) render_validation_error!(email)

View file

@ -0,0 +1,20 @@
require 'spec_helper'
describe Profiles::EmailsController do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#create' do
let(:email_params) { {email: "add_email@example.com" } }
it 'sends an email confirmation' do
expect {post(:create, { email: email_params })}.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
end
end
end

View file

@ -120,29 +120,4 @@ describe Emails::Profile do
it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error } it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error }
end end
end end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
end end

View file

@ -105,18 +105,6 @@ describe NotificationService, :mailer do
end end
end end
describe 'Email' do
describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
it 'sends email to email owner' do
expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
describe 'Notes' do describe 'Notes' do
context 'issue note' do context 'issue note' do
let(:project) { create(:project, :private) } let(:project) { create(:project, :private) }