Reset Password with Rails

Asked

Viewed 177 times

0

I am working on a project and I need to do a password reset system. I’m not using g and I don’t want to use.

I found that the can_change filter runs and I am redirected to the user page, which makes no sense to me, since the can_change filter is in the Userscontroller, and not in the Passwordresetscontroller

The email is sent without errors. I click on the link and go to /password_resets/edit?token=fewgfeggrf, but when I click edit password, it doesn’t work. How to resolve?

mailers/password_reset.Rb

class PasswordReset < ActionMailer::Base

default from: '[email protected]'

def send_password_reset(user)
    @user = user

    @reset_link = edit_password_resets_url({
        token: @user.password_reset_token
    })

    mail({
        :to => user.email, 
        :bcc => ['reset password <[email protected]'],
        :subject => I18n.t('password_reset.send_password_reset.subject')
    }) 
end

end

views/password_reset/send_password_reset.html.erb

<h2><%= t '.greetings', full_name: @user.full_name %></h2>
 <p><%= t '.body_html', link: link_to(t('.click_here'), @reset_link) %></p>

controllers/password_resets_controller.Rb

class PasswordResetsController < ApplicationController
before_action :require_no_authentication, only: [:new, :create, :edit, :update]

def new

end

def create
    user = User.find_by(email: params[:email])

    if user.present?

        user.generate_password_reset

        PasswordReset.send_password_reset(user).deliver

        redirect_to root_url, notice: t('flash.notice.check_email_reset')
    else 
        flash[:alert] = t('flash.alert.cannot_find_email_reset')
        render :new
    end
end

def edit
    @user = User.find_by(password_reset_token: params[:token])
end

def update
    @user = User.find_by!(password_reset_token: params[:token])

    if @user.password_reset_sent_at < 2.hours.ago
        redirect_to new_password_reset_path, alert: t('flash.alert.time_expired')
    end

    if @user.update(password_reset_user_params)
        @user.password_reseted!
        redirect_to new_user_sessions_path, notice: t('flash.notice.password_reseted_complete')

    else
        render :edit
    end
end

private

def password_reset_user_params
    params.require(:user).permit(:password, :password_confirmation)
end

end

models/user.Rb

class User < ActiveRecord::Base

VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
VALID_BIRTHDAY_REGEX = /[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{4}/

validates_presence_of :full_name, :email, :birthday, :about

validates_length_of :about, minimum: 10, maximum: 100

validates_format_of :email, with: VALID_EMAIL_REGEX
validates_uniqueness_of :email
validates_format_of :birthday, with: VALID_BIRTHDAY_REGEX 

has_secure_password

scope :confirmed, -> { where.not(created_at: nil) }

before_create do |user|
    user.confirmation_token = SecureRandom.urlsafe_base64
end

def confirm!
    return if confirmed?

    self.confirmed_at = Time.current
    self.confirmation_token = ''

    save!
end 

def confirmed?
    confirmed_at.present?
end

def self.authenticate(email, password)
    user = confirmed.find_by(email: email)

    if user.present?
        user.authenticate(password)
    end
end

def generate_password_reset
    self.password_reset_token = SecureRandom.urlsafe_base64
    self.password_reset_sent_at = Time.zone.now
    save!
end

def password_reseted?
    password_reset_token.present?
end

def password_reseted!
    return if password_reseted?

    self.password_reset_token = ''
    self.password_reseted_at = Time.current

    save!
end


def password_reseted_expired?
    password_reset_sent_at < 1.hours.ago
end

end

views/password_resets/new.html.erb

<%= form_tag password_resets_path, :method => :post do %>
<div>
    <%= label_tag :email %>
    <%= text_field_tag :email, params[:email] %>
</div>
<div><%= submit_tag %></div>

views/password_resets/Edit.html.erb

<%= form_for @user do |f| %>
<p>
    <%= f.label :password %><br>
    <%= f.password_field :password %>
    <%= error_field(@user, :password) %>
</p>

<p>
    <%= f.label :password_confirmation %><br>
    <%= f.password_field :password_confirmation %>
    <%= error_field(@user, :password_confirmation) %>
</p>

<p>
    <%= f.submit %>
</p>

controllers/users_controller.Rb

class UsersController < ApplicationController

before_action :can_change, only: [:edit, :update]
before_action :require_no_authentication, only: [:new, :create]

def show
    @user = User.find(params[:id])
end

def new
    @user = User.new
end

def create
    @user = User.new(user_params)

    if @user.save
        Signup.confirm_email(@user).deliver

        redirect_to new_user_sessions_path, notice: t('flash.notice.user_created') 
    else
        render action: :new
    end
end

def edit
    @user = User.find(params[:id])
end

def update
    @user = User.find(params[:id])

    if @user.update(user_params)
        flash[:notice] = t('flash.notice.user_updated')
        redirect_to @user
    else
        render action: :edit
    end
end

private

def user_params
    params.require(:user).permit(:full_name, :email, :birthday, :password, :password_confirmation, :about)
end

def can_change
    unless user_signed_in? && current_user == user
        redirect_to user_path(params[:id])
    end
end

def user
    @user ||= User.find(params[:id])
end

end

config/Routes

resource :password_resets

1 answer

1

I don’t know why you don’t want to use a Gem for this. We have the gift that implements this functionality and is very well tested. If you really don’t want to implement it yourself, I suggest you study the Devise code to see how it was implemented.

As for your problem, I believe it is as follows: when you create a route with resources, you are setting the 7 default routes as shown in Rails Guide table.

Spin rake routes to see your routes. You will probably see:

GET /password_resets/:id/edit password_resets#edit

Then your link /password_resets/edit?token=fewgfeggrf will not map to edit route as the id. You could use the token itself as id, creating a link like /password_resets/fewgfeggrf/edit and use params[:id] in the controller to grab the token or create a custom route.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.