Skip to content

Commit

Permalink
Add base User authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
andresag4 committed Jul 10, 2024
1 parent f31cca5 commit fdcbff7
Show file tree
Hide file tree
Showing 33 changed files with 638 additions and 21 deletions.
8 changes: 6 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ gem "turbo-rails"
# Authorization
gem "action_policy", "~> 0.7.0"

# Authentication
gem "bcrypt", "~> 3.1.20"

# Other
gem "bootsnap", require: false
gem "puma", ">= 5.0"
Expand All @@ -41,7 +44,8 @@ group :development, :test do
gem "dotenv"
gem "erb_lint", require: false
gem "factory_bot_rails"
gem "letter_opener"
gem "faker"
gem "letter_opener", "~> 1.10"
gem "pry-byebug"
gem "rspec-rails"
gem "rubocop-capybara", require: false
Expand All @@ -55,7 +59,6 @@ end

group :development do
gem "annotate"
gem "faker"
gem "rack-mini-profiler"
gem "web-console"
end
Expand All @@ -64,6 +67,7 @@ group :test do
gem "capybara"
gem "cuprite"
gem "fuubar"
gem "rails-controller-testing"
gem "rspec-instafail", require: false
gem "rspec-retry"
end
10 changes: 9 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.2.0)
bcrypt (3.1.20)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
Expand Down Expand Up @@ -315,6 +316,10 @@ GEM
activesupport (= 7.1.3.4)
bundler (>= 1.15.0)
railties (= 7.1.3.4)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
Expand Down Expand Up @@ -485,13 +490,15 @@ GEM

PLATFORMS
arm64-darwin-21
arm64-darwin-22
arm64-darwin-23
x86_64-linux

DEPENDENCIES
action_policy (~> 0.7.0)
activerecord-enhancedsqlite3-adapter (~> 0.8.0)
annotate
bcrypt (~> 3.1.20)
better_errors
binding_of_caller
bootsnap
Expand All @@ -507,13 +514,14 @@ DEPENDENCIES
faker
fuubar
importmap-rails
letter_opener
letter_opener (~> 1.10)
mission_control-jobs
propshaft
pry-byebug
puma (>= 5.0)
rack-mini-profiler
rails (~> 7.1.3, >= 7.1.3.4)
rails-controller-testing
rspec-instafail
rspec-rails
rspec-retry
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
class ApplicationController < ActionController::Base
include Authentication

before_action :authenticate_user!
end
34 changes: 34 additions & 0 deletions app/controllers/concerns/authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Authentication
extend ActiveSupport::Concern

included do
helper_method :current_user, :user_signed_in?

def authenticate_user!
redirect_to root_path, alert: t("controllers.concerns.authentication.unauthorized") unless user_signed_in?
end

def current_user
Current.user ||= authenticate_user_from_session
end

def authenticate_user_from_session
User.find_by(id: session[:user_id])
end

def user_signed_in?
current_user.present?
end

def login(user)
Current.user = user
reset_session
session[:user_id] = user.id
end

def logout
Current.user = nil
reset_session
end
end
end
5 changes: 5 additions & 0 deletions app/controllers/main_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class MainController < ApplicationController
skip_before_action :authenticate_user!
def index
end
end
43 changes: 43 additions & 0 deletions app/controllers/password_resets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class PasswordResetsController < ApplicationController
skip_before_action :authenticate_user!, only: [:new, :create]
before_action :set_user_by_token, only: [:edit, :update]

def new
end

def edit
end

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

if @user.present?
PasswordMailer.with(
user: @user,
token: @user.generate_token_for(:password_reset)
).password_reset.deliver_now
end

redirect_to root_path, notice: t("controllers.password_resets.create.notice")
end

def update
if @user.update(password_params)
redirect_to new_session_path, notice: t("controllers.password_resets.update.notice")
else
render :edit, status: :unprocessable_entity
end
end

private

def set_user_by_token
@user = User.find_by_token_for(:password_reset, params[:token])

redirect_to new_password_reset_path alert: t("controllers.password_resets.errors.invalid_token") if @user.blank?
end

def password_params
params.require(:user).permit(:password, :password_confirmation)
end
end
22 changes: 22 additions & 0 deletions app/controllers/passwords_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class PasswordsController < ApplicationController
def edit
end

def update
if current_user.update(password_params)
redirect_to edit_password_path, notice: t("controllers.passwords.update.notice")
else
render :edit, status: :unprocessable_entity
end
end

private

def password_params
params.require(:user).permit(
:password,
:password_confirmation,
:password_challenge
).with_defaults(password_challenge: "")
end
end
24 changes: 24 additions & 0 deletions app/controllers/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class RegistrationsController < ApplicationController
skip_before_action :authenticate_user!

def new
@user = User.new
end

def create
@user = User.new(registration_params)

if @user.save
login @user
redirect_to root_path
else
render :new, status: :unprocessable_entity
end
end

private

def registration_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
29 changes: 29 additions & 0 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class SessionsController < ApplicationController
skip_before_action :authenticate_user!

def new
end

def create
@user = User.authenticate_by(email: session_params[:email], password: session_params[:password])

if @user
login @user
redirect_to root_path, notice: t("controllers.sessions.create.notice")
else
flash[:alert] = t("controllers.sessions.create.alert")
render :new, status: :unprocessable_entity
end
end

def destroy
logout
redirect_to root_path, notice: t("controllers.sessions.destroy.notice")
end

private

def session_params
params.require(:user).permit(:email, :password)
end
end
5 changes: 5 additions & 0 deletions app/mailers/password_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class PasswordMailer < ApplicationMailer
def password_reset
mail to: params[:user].email
end
end
3 changes: 3 additions & 0 deletions app/models/current.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
11 changes: 11 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@
# index_users_on_email (email) UNIQUE
#
class User < ApplicationRecord
PASSWORD_RESET_EXPIRATION = 15.minutes

normalizes :email, with: ->(email) { email.strip.downcase }

has_one :profile, as: :profileable, dependent: :destroy

has_and_belongs_to_many :events

validates :email, presence: true, uniqueness: true
validates :password_digest, presence: true

normalizes :email, with: ->(email) { email.strip.downcase }

has_secure_password

generates_token_for :password_reset, expires_in: PASSWORD_RESET_EXPIRATION do
password_salt&.last(10)
end
end
37 changes: 23 additions & 14 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>RailsWorld</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<head>
<title>RailsWorld</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

<body>
<main class="container mx-auto mt-28 px-5 flex">
<%= yield %>
</main>
</body>
<body>
<main class="container mx-auto mt-28 px-5 flex">
<div><%= notice %></div>
<div><%= alert %></div>
<% if user_signed_in? %>
<%= link_to "Edit Password", edit_password_path %>
<%= button_to "Log out", session_path, method: :delete %>
<% else %>
<%= link_to "Sign Up", new_registration_path %>
<%= link_to "Log in", new_session_path %>
<% end %>
<%= yield %>
</main>
</body>
</html>
1 change: 1 addition & 0 deletions app/views/main/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Homepage</h1>
1 change: 1 addition & 0 deletions app/views/password_mailer/password_reset.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= link_to "Reset your password", edit_password_reset_url(token: params[:token]) %>
23 changes: 23 additions & 0 deletions app/views/password_resets/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<h1>Reset Your Password</h1>

<%= form_with model: @user, url: password_reset_path(token: params[:token]) do |form| %>
<% if form.object.errors.any? %>
<% form.object.errors.full_messages.each do |message| %>
<div> <%= message %></div>
<% end %>
<% end %>

<div>
<%= form.label :password %>
<%= form.password_field :password %>
</div>

<div>
<%= form.label :password_confirmation %>
<%= form.password_field :password_confirmation %>
</div>

<div>
<%= form.submit "Reset Your Password" %>
</div>
<% end %>
12 changes: 12 additions & 0 deletions app/views/password_resets/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h1>Reset Your Password</h1>

<%= form_with model: @user, url: password_reset_path do |form| %>
<div>
<%= form.label :email %>
<%= form.email_field :email %>
</div>

<div>
<%= form.submit "Reset password" %>
</div>
<% end %>
Loading

0 comments on commit fdcbff7

Please sign in to comment.