0
votes

I am using devise. Before I was able to login without email confirmation. Then I had to setup email confirmation for my application. So I added confirmable on user.rb and made changes on devise user migration. Also, I made changes on devise.rb file. Also I did setup for mailer as smtp on config/environment/development.rb file.

I think setup is correct for mailer but the problem is that: When user signs up as new user, mail with confirmation token will goes to that user. But even without clicking that mail link User is able to login. What is the use of setup for mailer if user can login without confirmation token? How to prevent user from logging if they don't verify mail confirmation token?
I have tired making customized confirmable controller but result is same.

application_controller.rb

# frozen_string_literal: true

# Router entry point
require 'json_web_token'
class ApplicationController < ActionController::Base
    before_action :configure_permitted_parameters, if: :devise_controller?
    before_action :make_action_mailer_use_request_host_and_protocol
    # before_action :authenticate_user!, :set_mailer_host
    protect_from_forgery with: :exception

    respond_to :html, :json

    def index
        render template: 'application'
    end

    def not_found
        render json: { error: 'not_found' }
    end

    def authorize_request
        header = request.headers['Authorization']
        header = header.split(' ').last if header
        begin
        @decoded = JsonWebToken.decode(header)
        @current_user = User.find(@decoded[:user_id])
        rescue ActiveRecord::RecordNotFound => e
        render json: { errors: e.message }, status: :unauthorized
        rescue JWT::DecodeError => e
        render json: { errors: e.message }, status: :unauthorized
        end
    end

    protected

    def current_user
        @current_user ||= User.find_by(id: session[:user_id])
    end

    def signed_in?
        !!current_user
    end
    helper_method :current_user, :signed_in?

    def current_user=(user)
        session[:user_id] = user&.id
        @current_user = user
    end

    def configure_permitted_parameters
        update_attrs = [:password, :password_confirmation, :current_password]
        devise_parameter_sanitizer.permit :account_update, keys: update_attrs
        devise_parameter_sanitizer.permit(:login, keys: [ :email, :password ])
    end

    private

    def make_action_mailer_use_request_host_and_protocol
        ActionMailer::Base.default_url_options[:protocol] = request.protocol
        ActionMailer::Base.default_url_options[:host] = request.host_with_port
    end 
end

authorization_controller.rb

module Api
    module V1
        class AuthenticationController < ApplicationController
            skip_before_action :verify_authenticity_token
            before_action :authorize_request, except: :login

            # POST /auth/login
            def login
                @user = User.find_by_email(params[:email])
                if @user&.valid_password?(params[:password])
                token = JsonWebToken.encode(user_id: @user.id)
                time = Time.now + 24.hours.to_i
                render json: { token: token, exp: time.strftime("%m-%d-%Y %H:%M"),
                                username: @user.username, user_id: @user.id }, status: :ok
                else
                render json: { error: 'unauthorized' }, status: :unauthorized
                end
            end

            private

            def login_params
                params.permit(:email, :password)
            end
        end
    end
end

users_controller.rb

module Api
    module V1
        class UsersController < ApplicationController
            skip_before_action :verify_authenticity_token

            before_action :authorize_request, except: :create

            # GET /users
            def index
                @users = User.all
                render json: @users, status: :ok
            end

            def create
                # render plain: params.inspect
                @user = User.new(user_params)
                # render plain: user_params.insp
                if @user.save
                    render json: @user, status: :created
                else
                    render json: { errors: @user.errors.full_messages },
                        status: :unprocessable_entity
                end
            end

            def update
                user = User.find(params[:id])
                if user.update(user_params)
                render json: user, status: :created
                else
                render json: { errors: user.errors.full_messages },
                        status: :unprocessable_entity
                end
            end

            def show
                user = User.find(params[:id])
                if !user.nil?
                    render json: user, status: :ok
                else
                    render json: {errors: user.errors.full_messages}, status: :unprocessable_entity
                end
            end

            def destroy
                user = User.find(params[:id])
                if user.destroy
                    render json: {success: "deleted successfully"}, status: :ok
                else
                    render json: {errors: user.errors.full_messages}, status: :not_acceptable
                end
            end

            private

            def find_user
                @user = User.find_by_username!(params[:_username])
                rescue ActiveRecord::RecordNotFound
                render json: { errors: 'User not found' }, status: :not_found
            end

            def user_params
                params.permit(
                    :first_name, :last_name, :username, :email, :password, :password_confirmation
                )
            end
        end
    end
end

registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController
    skip_before_action :require_no_authentication
    def update_resource(resource, params)
        if resource.encrypted_password.blank?
            resource.email = params[:email] if params[:email]
            if !params[:password].blank? && params[:password] == params[:password_confirmation]
                resource.password = params[:password]
                resource.save
            end
            if resource.valid?
            resource.update_without_password(params)
            end
        else
            resource.update_with_password(params)
        end
    end
end

user.rb

class User < ApplicationRecord
    # has_secure_password
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable and :omniauthable
    devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :trackable, :validatable,
        :confirmable, :omniauthable, password_length: 8..36
    has_many :identities
    has_one :testimonials
    has_many :questions
    has_many :answers


    def facebook
        identities.where(provider: 'facebook').first
    end

    def facebook_client
        @facebook_client ||= Facebook.client(access_token: facebook.accesstoken)
    end

    def twitter
        identities.where(provider: 'twitter').first
    end

    def twitter_client
        @twitter_client ||= Twitter.client(access_token: twitter.accesstoken)
    end

    def google_oauth2
        identities.where(provider: 'google_oauth2').first
    end

    def google_oauth2_client
        unless @google_oauth2_client
        @google_oauth2_client = Google::APIClient.new(application_name: '',
                                                        application_version: '')
        @google_oauth2_client.authorization.update_token!(access_token: google_oauth2.accesstoken,
                                                            refresh_token: google_oauth2.refreshtoken)
        end
        @google_oauth2_client
    end

    #validation for users
    validates :username, presence: true, uniqueness: {case_sensitive: false}

    # validates_format_of :username, with: /^[a-zA-Z0-9_\.]*$/, :multiline => true
    validates :email, presence: true, uniqueness: {case_senstive: false}
    PASSWORD_FORMAT = /\A
        (?=.{8,})          # Must contain 8 or more characters
        (?=.*\d)           # Must contain a digit
        (?=.*[a-z])        # Must contain a lower case character
        (?=.*[A-Z])        # Must contain an upper case character
        (?=.*[[:^alnum:]]) # Must contain a symbol
        /x

    validates :password,
        presence: true,
        # length: { in: Devise.password_length },
        format: { with: PASSWORD_FORMAT, message: 'must contain 8 Characters with at least One Lowercase, One Uppercase, One Number and One Special Character' },
        confirmation: true,
        on: :create

        validates :password_confirmation,
        presence: true

    validates :password,
        # allow_nil: true,
        # length: { in: Devise.password_length },
        format: { with: PASSWORD_FORMAT, message: 'must contain 8 Characters with at least one Uppercase, One Number and One Special Character' },
        confirmation: true,
        on: :update

end
2
Wild guess but is there a boolean (to say a user is activated) which is defaulting to true?Kris
Hello, Kris does front end matters??susie
I'm talking about a database migration which has default: true for the attribute which marks a user as confirmed.Kris

2 Answers

1
votes

Take a look at your devise initializer, you need to set allow_unconfirmed_access_for to 0 (actually, it should be zero by default). According to devise documentation:

#   * +allow_unconfirmed_access_for+: the time you want to allow the user to access their account
#     before confirming it. After this period, the user access is denied. You can
#     use this to let your user access some features of your application without
#     confirming the account, but blocking it after a certain period (ie 7 days).
#     By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.

Devise is using this method to verify unconfirmed access.

Could you also post the results of this methods:

  • User.find(id_of_user_that_was_just_created).confirmation_required?
  • User.find(id_of_user_that_was_just_created).confirmed?
  • User.find(id_of_user_that_was_just_created).confirmation_period_valid?
0
votes

There was not any mistake with logic on above code. I had mistake on views which routes generates link.

THE CORRECT url is:

<p>
   <%= link_to 'Confirm my account', confirmation_url(@resource,
   confirmation_token: @token) %>
</p>