forked from External/greenlight
GRN2-xx: Restructured email verification and password reset (#1444)
* Restructured email verification and password reset * Fixed issue with password reset Co-authored-by: Jesus Federico <jesus@123it.ca>
This commit is contained in:
parent
8f3ba8a038
commit
28302107bd
|
@ -29,7 +29,7 @@ class AccountActivationsController < ApplicationController
|
||||||
# GET /account_activations/edit
|
# GET /account_activations/edit
|
||||||
def edit
|
def edit
|
||||||
# If the user exists and is not verified and provided the correct token
|
# If the user exists and is not verified and provided the correct token
|
||||||
if @user && !@user.activated? && @user.authenticated?(:activation, params[:token])
|
if @user && !@user.activated?
|
||||||
# Verify user
|
# Verify user
|
||||||
@user.activate
|
@user.activate
|
||||||
|
|
||||||
|
@ -51,8 +51,7 @@ class AccountActivationsController < ApplicationController
|
||||||
flash[:alert] = I18n.t("verify.already_verified")
|
flash[:alert] = I18n.t("verify.already_verified")
|
||||||
else
|
else
|
||||||
# Resend
|
# Resend
|
||||||
@user.create_activation_token
|
send_activation_email(@user, @user.create_activation_token)
|
||||||
send_activation_email(@user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
|
@ -61,7 +60,7 @@ class AccountActivationsController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_user
|
def find_user
|
||||||
@user = User.find_by!(activation_digest: User.digest(params[:token]), provider: @user_domain)
|
@user = User.find_by!(activation_digest: User.hash_token(params[:token]), provider: @user_domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_unauthenticated
|
def ensure_unauthenticated
|
||||||
|
|
|
@ -133,9 +133,7 @@ class AdminsController < ApplicationController
|
||||||
|
|
||||||
# GET /admins/reset
|
# GET /admins/reset
|
||||||
def reset
|
def reset
|
||||||
@user.create_reset_digest
|
send_password_reset_email(@user, @user.create_reset_digest)
|
||||||
|
|
||||||
send_password_reset_email(@user)
|
|
||||||
|
|
||||||
if session[:prev_url].present?
|
if session[:prev_url].present?
|
||||||
redirect_path = session[:prev_url]
|
redirect_path = session[:prev_url]
|
||||||
|
|
|
@ -20,11 +20,11 @@ module Emailer
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
# Sends account activation email.
|
# Sends account activation email.
|
||||||
def send_activation_email(user)
|
def send_activation_email(user, token)
|
||||||
begin
|
begin
|
||||||
return unless Rails.configuration.enable_email_verification
|
return unless Rails.configuration.enable_email_verification
|
||||||
|
|
||||||
UserMailer.verify_email(user, user_verification_link(user), @settings).deliver
|
UserMailer.verify_email(user, user_verification_link(token), @settings).deliver
|
||||||
rescue => e
|
rescue => e
|
||||||
logger.error "Support: Error in email delivery: #{e}"
|
logger.error "Support: Error in email delivery: #{e}"
|
||||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||||
|
@ -34,11 +34,11 @@ module Emailer
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sends password reset email.
|
# Sends password reset email.
|
||||||
def send_password_reset_email(user)
|
def send_password_reset_email(user, token)
|
||||||
begin
|
begin
|
||||||
return unless Rails.configuration.enable_email_verification
|
return unless Rails.configuration.enable_email_verification
|
||||||
|
|
||||||
UserMailer.password_reset(user, reset_link(user), @settings).deliver_now
|
UserMailer.password_reset(user, reset_link(token), @settings).deliver_now
|
||||||
rescue => e
|
rescue => e
|
||||||
logger.error "Support: Error in email delivery: #{e}"
|
logger.error "Support: Error in email delivery: #{e}"
|
||||||
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
flash[:alert] = I18n.t(params[:message], default: I18n.t("delivery_error"))
|
||||||
|
@ -124,8 +124,8 @@ module Emailer
|
||||||
private
|
private
|
||||||
|
|
||||||
# Returns the link the user needs to click to verify their account
|
# Returns the link the user needs to click to verify their account
|
||||||
def user_verification_link(user)
|
def user_verification_link(token)
|
||||||
edit_account_activation_url(token: user.activation_token)
|
edit_account_activation_url(token: token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_emails
|
def admin_emails
|
||||||
|
@ -139,8 +139,8 @@ module Emailer
|
||||||
admins.collect(&:email).join(",")
|
admins.collect(&:email).join(",")
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_link(user)
|
def reset_link(token)
|
||||||
edit_password_reset_url(user.reset_token)
|
edit_password_reset_url(token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invitation_link(token)
|
def invitation_link(token)
|
||||||
|
|
|
@ -21,7 +21,6 @@ class PasswordResetsController < ApplicationController
|
||||||
|
|
||||||
before_action :disable_password_reset, unless: -> { Rails.configuration.enable_email_verification }
|
before_action :disable_password_reset, unless: -> { Rails.configuration.enable_email_verification }
|
||||||
before_action :find_user, only: [:edit, :update]
|
before_action :find_user, only: [:edit, :update]
|
||||||
before_action :valid_user, only: [:edit, :update]
|
|
||||||
before_action :check_expiration, only: [:edit, :update]
|
before_action :check_expiration, only: [:edit, :update]
|
||||||
|
|
||||||
# POST /password_resets/new
|
# POST /password_resets/new
|
||||||
|
@ -34,8 +33,7 @@ class PasswordResetsController < ApplicationController
|
||||||
# Check if user exists and throw an error if he doesn't
|
# Check if user exists and throw an error if he doesn't
|
||||||
@user = User.find_by!(email: params[:password_reset][:email].downcase, provider: @user_domain)
|
@user = User.find_by!(email: params[:password_reset][:email].downcase, provider: @user_domain)
|
||||||
|
|
||||||
@user.create_reset_digest
|
send_password_reset_email(@user, @user.create_reset_digest)
|
||||||
send_password_reset_email(@user)
|
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
rescue
|
rescue
|
||||||
# User doesn't exist
|
# User doesn't exist
|
||||||
|
@ -68,7 +66,7 @@ class PasswordResetsController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_user
|
def find_user
|
||||||
@user = User.find_by(reset_digest: User.digest(params[:id]), provider: @user_domain)
|
@user = User.find_by(reset_digest: User.hash_token(params[:id]), provider: @user_domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
|
@ -80,14 +78,6 @@ class PasswordResetsController < ApplicationController
|
||||||
redirect_to new_password_reset_url, alert: I18n.t("expired_reset_token") if @user.password_reset_expired?
|
redirect_to new_password_reset_url, alert: I18n.t("expired_reset_token") if @user.password_reset_expired?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Confirms a valid user.
|
|
||||||
def valid_user
|
|
||||||
unless @user.authenticated?(:reset, params[:id])
|
|
||||||
@user&.activate unless @user&.activated?
|
|
||||||
redirect_to root_url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Redirects to 404 if emails are not enabled
|
# Redirects to 404 if emails are not enabled
|
||||||
def disable_password_reset
|
def disable_password_reset
|
||||||
redirect_to '/404'
|
redirect_to '/404'
|
||||||
|
|
|
@ -88,10 +88,7 @@ class SessionsController < ApplicationController
|
||||||
# Check that the user is a Greenlight account
|
# Check that the user is a Greenlight account
|
||||||
return redirect_to(root_path, alert: I18n.t("invalid_login_method")) unless user.greenlight_account?
|
return redirect_to(root_path, alert: I18n.t("invalid_login_method")) unless user.greenlight_account?
|
||||||
# Check that the user has verified their account
|
# Check that the user has verified their account
|
||||||
unless user.activated?
|
return redirect_to(account_activation_path(token: user.create_activation_token)) unless user.activated?
|
||||||
user.create_activation_token
|
|
||||||
return redirect_to(account_activation_path(token: user.activation_token))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
login(user)
|
login(user)
|
||||||
|
@ -247,8 +244,7 @@ class SessionsController < ApplicationController
|
||||||
logger.info "Switching social account to local account for #{user.uid}"
|
logger.info "Switching social account to local account for #{user.uid}"
|
||||||
|
|
||||||
# Send the user a reset password email
|
# Send the user a reset password email
|
||||||
user.create_reset_digest
|
send_password_reset_email(user, user.create_reset_digest)
|
||||||
send_password_reset_email(user)
|
|
||||||
|
|
||||||
# Overwrite the flash with a more descriptive message if successful
|
# Overwrite the flash with a more descriptive message if successful
|
||||||
flash[:success] = I18n.t("reset_password.auth_change") if flash[:success].present?
|
flash[:success] = I18n.t("reset_password.auth_change") if flash[:success].present?
|
||||||
|
|
|
@ -58,9 +58,7 @@ class UsersController < ApplicationController
|
||||||
# Sign in automatically if email verification is disabled or if user is already verified.
|
# Sign in automatically if email verification is disabled or if user is already verified.
|
||||||
login(@user) && return if !Rails.configuration.enable_email_verification || @user.email_verified
|
login(@user) && return if !Rails.configuration.enable_email_verification || @user.email_verified
|
||||||
|
|
||||||
@user.create_activation_token
|
send_activation_email(@user, @user.create_activation_token)
|
||||||
|
|
||||||
send_activation_email(@user)
|
|
||||||
|
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,6 @@ require 'bbb_api'
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
include Deleteable
|
include Deleteable
|
||||||
|
|
||||||
attr_accessor :reset_token, :activation_token
|
|
||||||
after_create :setup_user
|
after_create :setup_user
|
||||||
|
|
||||||
before_save { email.try(:downcase!) }
|
before_save { email.try(:downcase!) }
|
||||||
|
@ -110,24 +109,28 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
# Activates an account and initialize a users main room
|
# Activates an account and initialize a users main room
|
||||||
def activate
|
def activate
|
||||||
update_attributes(email_verified: true, activated_at: Time.zone.now)
|
update_attributes(email_verified: true, activated_at: Time.zone.now, activation_digest: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def activated?
|
def activated?
|
||||||
Rails.configuration.enable_email_verification ? email_verified : true
|
Rails.configuration.enable_email_verification ? email_verified : true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the password reset attributes.
|
def self.hash_token(token)
|
||||||
def create_reset_digest
|
Digest::SHA2.hexdigest(token)
|
||||||
self.reset_token = User.new_token
|
|
||||||
update_attributes(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the given token matches the digest.
|
# Sets the password reset attributes.
|
||||||
def authenticated?(attribute, token)
|
def create_reset_digest
|
||||||
digest = send("#{attribute}_digest")
|
new_token = SecureRandom.urlsafe_base64
|
||||||
return false if digest.nil?
|
update_attributes(reset_digest: User.hash_token(new_token), reset_sent_at: Time.zone.now)
|
||||||
digest == Digest::SHA256.base64digest(token)
|
new_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_activation_token
|
||||||
|
new_token = SecureRandom.urlsafe_base64
|
||||||
|
update_attributes(activation_digest: User.hash_token(new_token))
|
||||||
|
new_token
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return true if password reset link expires
|
# Return true if password reset link expires
|
||||||
|
@ -158,11 +161,6 @@ class User < ApplicationRecord
|
||||||
social_uid.nil?
|
social_uid.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_activation_token
|
|
||||||
self.activation_token = User.new_token
|
|
||||||
update_attributes(activation_digest: User.digest(activation_token))
|
|
||||||
end
|
|
||||||
|
|
||||||
def admin_of?(user, permission)
|
def admin_of?(user, permission)
|
||||||
has_correct_permission = highest_priority_role.get_permission(permission) && id != user.id
|
has_correct_permission = highest_priority_role.get_permission(permission) && id != user.id
|
||||||
|
|
||||||
|
@ -171,15 +169,6 @@ class User < ApplicationRecord
|
||||||
has_correct_permission && provider == user.provider && !user.has_role?(:super_admin)
|
has_correct_permission && provider == user.provider && !user.has_role?(:super_admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.digest(string)
|
|
||||||
Digest::SHA256.base64digest(string)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a random token.
|
|
||||||
def self.new_token
|
|
||||||
SecureRandom.urlsafe_base64
|
|
||||||
end
|
|
||||||
|
|
||||||
# role functions
|
# role functions
|
||||||
def highest_priority_role
|
def highest_priority_role
|
||||||
roles.min_by(&:priority)
|
roles.min_by(&:priority)
|
||||||
|
|
|
@ -35,8 +35,7 @@ describe AccountActivationsController, type: :controller do
|
||||||
it "renders the verify view if the user is not signed in and is not verified" do
|
it "renders the verify view if the user is not signed in and is not verified" do
|
||||||
user = create(:user, email_verified: false, provider: "greenlight")
|
user = create(:user, email_verified: false, provider: "greenlight")
|
||||||
|
|
||||||
user.create_activation_token
|
get :show, params: { token: user.create_activation_token }
|
||||||
get :show, params: { token: user.activation_token }
|
|
||||||
|
|
||||||
expect(response).to render_template(:show)
|
expect(response).to render_template(:show)
|
||||||
end
|
end
|
||||||
|
@ -46,8 +45,7 @@ describe AccountActivationsController, type: :controller do
|
||||||
it "activates a user if they have the correct activation token" do
|
it "activates a user if they have the correct activation token" do
|
||||||
@user = create(:user, email_verified: false, provider: "greenlight")
|
@user = create(:user, email_verified: false, provider: "greenlight")
|
||||||
|
|
||||||
@user.create_activation_token
|
get :edit, params: { token: @user.create_activation_token }
|
||||||
get :edit, params: { token: @user.activation_token }
|
|
||||||
@user.reload
|
@user.reload
|
||||||
|
|
||||||
expect(@user.email_verified).to eq(true)
|
expect(@user.email_verified).to eq(true)
|
||||||
|
@ -64,8 +62,7 @@ describe AccountActivationsController, type: :controller do
|
||||||
it "does not allow the user to click the verify link again" do
|
it "does not allow the user to click the verify link again" do
|
||||||
@user = create(:user, provider: "greenlight")
|
@user = create(:user, provider: "greenlight")
|
||||||
|
|
||||||
@user.create_activation_token
|
get :edit, params: { token: @user.create_activation_token }
|
||||||
get :edit, params: { token: @user.activation_token }
|
|
||||||
expect(flash[:alert]).to be_present
|
expect(flash[:alert]).to be_present
|
||||||
expect(response).to redirect_to(root_path)
|
expect(response).to redirect_to(root_path)
|
||||||
end
|
end
|
||||||
|
@ -75,8 +72,7 @@ describe AccountActivationsController, type: :controller do
|
||||||
|
|
||||||
@user.add_role :pending
|
@user.add_role :pending
|
||||||
|
|
||||||
@user.create_activation_token
|
get :edit, params: { token: @user.create_activation_token }
|
||||||
get :edit, params: { token: @user.activation_token }
|
|
||||||
|
|
||||||
expect(flash[:success]).to be_present
|
expect(flash[:success]).to be_present
|
||||||
expect(response).to redirect_to(root_path)
|
expect(response).to redirect_to(root_path)
|
||||||
|
@ -87,8 +83,8 @@ describe AccountActivationsController, type: :controller do
|
||||||
it "resends the email to the current user if the resend button is clicked" do
|
it "resends the email to the current user if the resend button is clicked" do
|
||||||
user = create(:user, email_verified: false, provider: "greenlight")
|
user = create(:user, email_verified: false, provider: "greenlight")
|
||||||
|
|
||||||
user.create_activation_token
|
expect { get :resend, params: { token: user.create_activation_token } }
|
||||||
expect { get :resend, params: { token: user.activation_token } }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
.to change { ActionMailer::Base.deliveries.count }.by(1)
|
||||||
expect(flash[:success]).to be_present
|
expect(flash[:success]).to be_present
|
||||||
expect(response).to redirect_to(root_path)
|
expect(response).to redirect_to(root_path)
|
||||||
end
|
end
|
||||||
|
@ -96,8 +92,7 @@ describe AccountActivationsController, type: :controller do
|
||||||
it "redirects a verified user to the root path" do
|
it "redirects a verified user to the root path" do
|
||||||
user = create(:user, provider: "greenlight")
|
user = create(:user, provider: "greenlight")
|
||||||
|
|
||||||
user.create_activation_token
|
get :resend, params: { token: user.create_activation_token }
|
||||||
get :resend, params: { token: user.activation_token }
|
|
||||||
|
|
||||||
expect(flash[:alert]).to be_present
|
expect(flash[:alert]).to be_present
|
||||||
expect(response).to redirect_to(root_path)
|
expect(response).to redirect_to(root_path)
|
||||||
|
|
|
@ -81,8 +81,6 @@ describe PasswordResetsController, type: :controller do
|
||||||
context "valid user" do
|
context "valid user" do
|
||||||
it "reloads page with notice if password is empty" do
|
it "reloads page with notice if password is empty" do
|
||||||
token = "reset_token"
|
token = "reset_token"
|
||||||
|
|
||||||
allow(controller).to receive(:valid_user).and_return(nil)
|
|
||||||
allow(controller).to receive(:check_expiration).and_return(nil)
|
allow(controller).to receive(:check_expiration).and_return(nil)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
@ -99,7 +97,6 @@ describe PasswordResetsController, type: :controller do
|
||||||
it "reloads page with notice if password is confirmation doesn't match" do
|
it "reloads page with notice if password is confirmation doesn't match" do
|
||||||
token = "reset_token"
|
token = "reset_token"
|
||||||
|
|
||||||
allow(controller).to receive(:valid_user).and_return(nil)
|
|
||||||
allow(controller).to receive(:check_expiration).and_return(nil)
|
allow(controller).to receive(:check_expiration).and_return(nil)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
@ -116,14 +113,12 @@ describe PasswordResetsController, type: :controller do
|
||||||
|
|
||||||
it "updates attributes if the password update is a success" do
|
it "updates attributes if the password update is a success" do
|
||||||
user = create(:user, provider: "greenlight")
|
user = create(:user, provider: "greenlight")
|
||||||
user.create_reset_digest
|
|
||||||
old_digest = user.password_digest
|
old_digest = user.password_digest
|
||||||
|
|
||||||
allow(controller).to receive(:valid_user).and_return(nil)
|
|
||||||
allow(controller).to receive(:check_expiration).and_return(nil)
|
allow(controller).to receive(:check_expiration).and_return(nil)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
id: user.reset_token,
|
id: user.create_reset_digest,
|
||||||
user: {
|
user: {
|
||||||
password: :password,
|
password: :password,
|
||||||
password_confirmation: :password,
|
password_confirmation: :password,
|
||||||
|
|
|
@ -138,8 +138,13 @@ describe User, type: :model do
|
||||||
it 'creates token and respective reset digest' do
|
it 'creates token and respective reset digest' do
|
||||||
user = create(:user)
|
user = create(:user)
|
||||||
|
|
||||||
reset_digest_success = user.create_reset_digest
|
expect(user.create_reset_digest).to be_truthy
|
||||||
expect(reset_digest_success).to eq(true)
|
end
|
||||||
|
|
||||||
|
it 'correctly verifies the token' do
|
||||||
|
user = create(:user)
|
||||||
|
token = user.create_reset_digest
|
||||||
|
expect(User.exists?(reset_digest: User.hash_token(token))).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'verifies if password reset link expired' do
|
it 'verifies if password reset link expired' do
|
||||||
|
|
Loading…
Reference in New Issue