Skip to content

Commit

Permalink
feat: reset password
Browse files Browse the repository at this point in the history
  • Loading branch information
amrhossamdev committed Sep 25, 2024
1 parent 1b350c3 commit 9525df3
Show file tree
Hide file tree
Showing 32 changed files with 288 additions and 90 deletions.
32 changes: 0 additions & 32 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

1 change: 0 additions & 1 deletion app/controllers/account_activations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class AccountActivationsController < ApplicationController

def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
Expand Down
1 change: 0 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
include SessionsHelper

end
64 changes: 64 additions & 0 deletions app/controllers/password_resets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class PasswordResetsController < ApplicationController
before_action :get_user, only: [ :edit, :update ]
before_action :valid_user, only: [ :edit, :update ]
before_action :check_expiration, only: [ :edit, :update ]

def new
end

def update
if params[:user][:password].empty?
@user.errors.add(:password, "can't be empty")
render "edit", status: :unprocessable_entity
elsif @user.update(user_params)
reset_session
log_in @user
@user.update_attribute(:reset_digest, nil)
flash[:success] = "Password has been reset."
redirect_to @user
else
render "edit", status: :unprocessable_entity
end
end

def create
@user = User.find_by(email: params[:password_reset][:email].downcase)
if @user
@user.create_reset_digest
@user.send_password_reset_email
flash[:info] = "Email sent with password reset instructions"
redirect_to root_url
else
flash.now[:danger] = "Email address not found"
render "new", status: :unprocessable_entity
end
end

def edit
end

private

def get_user
@user = User.find_by(email: params[:email])
end

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

# Confirms a valid user.
def valid_user
unless @user && @user.activated? &&
@user.authenticated?(:reset, params[:id])
redirect_to root_url
end
end

def check_expiration
if @user.password_reset_expired?
flash[:danger] = "Password reset has expired."
redirect_to new_password_reset_path
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :logged_in_user, only: [ :index, :edit, :update, :destroy ]
before_action :correct_user, only: [ :edit, :update ]
before_action :admin_user, only: :destroy

def show
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/password_reset_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module PasswordResetHelper
end
2 changes: 0 additions & 2 deletions app/helpers/sessions_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module SessionsHelper

def log_in(user)
session[:user_id] = user.id
end
Expand Down Expand Up @@ -51,4 +50,3 @@ def store_location
session[:forwarding_url] = request.url if request.get?
end
end

2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
default from: Rails.env.test? ? "noreply@example.com" : ENV["FROM_MAIL_ADDRESS"]
default from: (Rails.env.test? || Rails.env.development?) ? "noreply@example.com" : ENV["FROM_MAIL_ADDRESS"]
layout "mailer"
end
9 changes: 4 additions & 5 deletions app/mailers/user_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ def account_activation(user)
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.user_mailer.password_reset.subject
# en.user_mailer.password_resets.subject
#
def password_reset
@greeting = "Hi"

mail to: "to@example.org"
def password_reset(user)
@user = user
mail to: user.email, subject: "Password reset"
end
end
19 changes: 18 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
Expand All @@ -19,6 +19,18 @@ def User.new_token
SecureRandom.urlsafe_base64
end

# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end

# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end

# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
Expand Down Expand Up @@ -48,6 +60,11 @@ def authenticated?(attribute, token)
BCrypt::Password.new(digest).is_password?(token)
end

# Return true if the password reset has expired
def password_reset_expired?
reset_sent_at < 2.hours.ago
end

private

# Converts email to all lower-case.
Expand Down
20 changes: 20 additions & 0 deletions app/views/password_resets/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>

<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, url: password_reset_path(params[:id])) do |f| %>
<%= render 'shared/error_messages' %>
<%= hidden_field_tag :email, @user.email %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Update password", class: "btn btn-primary" %>
<% end %>
</div>
</div>
13 changes: 13 additions & 0 deletions app/views/password_resets/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>

<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(url: password_resets_path, scope: :password_reset) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
1 change: 1 addition & 0 deletions app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= link_to "(forgot password)",new_password_reset_path %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :remember_me, class: "checkbox inline" do %>
Expand Down
14 changes: 11 additions & 3 deletions app/views/user_mailer/password_reset.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<h1>User#password_reset</h1>
<h1>Password reset</h1>

<p>To reset your password click the link below:</p>

<%= link_to "Reset password", edit_password_reset_url(@user.reset_token,
email: @user.email) %>

<p>This link will expire in two hours.</p>

<p>
<%= @greeting %>, find me in app/views/user_mailer/password_reset.html.erb
</p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
7 changes: 5 additions & 2 deletions app/views/user_mailer/password_reset.text.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
User#password_reset
To reset your password click on the link below:

<%= @greeting %>, find me in app/views/user_mailer/password_reset.text.erb
<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
3 changes: 1 addition & 2 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
enable_starttls_auto: true
}

#Rails.application.routes.default_url_options = { host: 'simpleapp-production-5d8a.up.railway.app/', protocol: 'https' }
# Rails.application.routes.default_url_options = { host: 'simpleapp-production-5d8a.up.railway.app/', protocol: 'https' }

# Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
# key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
Expand Down Expand Up @@ -116,5 +116,4 @@
# Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
# config/application.rb

end
20 changes: 12 additions & 8 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
Rails.application.routes.draw do
get "password_resets/new"
get "password_resets/edit"
get "account_activations/edit"
get "sessions/new"
root "static_pages#home"
# Pages
get "help", to: "static_pages#help"
get "about", to: "static_pages#about"
get "contact", to: "static_pages#contact"
get "help", to: "static_pages#help"
get "about", to: "static_pages#about"
get "contact", to: "static_pages#contact"

# Signup
get "signup", to: "users#new"
get "signup", to: "users#new"

# Sessions
get "/login", to: "sessions#new"
post "/login", to: "sessions#create"
delete "/logout", to: "sessions#destroy"
get "/login", to: "sessions#new"
post "/login", to: "sessions#create"
delete "/logout", to: "sessions#destroy"
resources :users

# Account Activations
resources :account_activations, only: [:edit]
resources :account_activations, only: [ :edit ]
# Password Reset
resources :password_resets, only: [ :new, :create, :edit, :update ]
end
6 changes: 6 additions & 0 deletions db/migrate/20240923021729_add_reset_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddResetToUsers < ActiveRecord::Migration[7.2]
def change
add_column :users, :reset_digest, :string
add_column :users, :reset_sent_at, :datetime
end
end
4 changes: 3 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions test/controllers/password_resets_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
test "should get new" do
get password_resets_new_url
assert_response :success
end

# test "should get edit" do
# get password_resets_edit_url
# assert_response :success
# end
end
1 change: 0 additions & 1 deletion test/controllers/static_pages_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

test "should get home" do
get root_path
assert_response :success
Expand Down
2 changes: 0 additions & 2 deletions test/controllers/users_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest

def setup
@user = users(:amr)
@other_user = users(:amr2)
Expand Down Expand Up @@ -67,5 +66,4 @@ def setup
end
assert_redirected_to root_url
end

end
5 changes: 2 additions & 3 deletions test/helpers/sessions_helper_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require 'test_helper'
require "test_helper"
class SessionsHelperTest < ActionView::TestCase

def setup
@user = users(:amr)
remember(@user)
Expand All @@ -13,4 +12,4 @@ def setup
@user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
end
Loading

0 comments on commit 9525df3

Please sign in to comment.