Skip to content

Commit

Permalink
Merge pull request #2 from KanzaTahreem/feature/authentication
Browse files Browse the repository at this point in the history
User Authentication
  • Loading branch information
KanzaTahreem committed Apr 14, 2023
2 parents 42eebe0 + 154ee4c commit 097abab
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ gem 'rubocop', '>= 1.0', '< 2.0'
# Use .env file to store environment variables
gem 'dotenv-rails'

# Use Json Web Token (JWT) for token based authentication
gem 'jwt'

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem 'rails', '~> 7.0.4', '>= 7.0.4.3'

Expand All @@ -28,7 +31,7 @@ gem 'puma', '~> 5.0'
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
gem 'bcrypt', '~> 3.1.7'

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ GEM
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
bcrypt (3.1.18)
bootsnap (1.16.0)
msgpack (~> 1.2)
builder (3.2.4)
Expand All @@ -89,6 +90,7 @@ GEM
irb (1.6.4)
reline (>= 0.3.0)
json (2.6.3)
jwt (2.7.0)
loofah (2.20.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand Down Expand Up @@ -183,9 +185,11 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
bcrypt (~> 3.1.7)
bootsnap
debug
dotenv-rails
jwt
pg (~> 1.1)
puma (~> 5.0)
rails (~> 7.0.4, >= 7.0.4.3)
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
class ApplicationController < ActionController::API
before_action :authorize_request, except: %i[login signup]

def not_found
render json: { message: 'Unable to access token', error: 'Token not found' }, status: :unauthorized
end

# rubocop:disable Lint/UselessAssignment

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
not_found
end
end

# rubocop:enable Lint/UselessAssignment
end
66 changes: 66 additions & 0 deletions app/controllers/authentication_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class AuthenticationController < ApplicationController
# rubocop:disable Metrics/MethodLength

def signup
@user = User.find_by_email(signup_params[:email])
if @user.present?
render json: { message: 'Failed to create a user', error: 'User already exists' }, status: :conflict
else
@user = User.new(signup_params)
if @user.save
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'),
user: {
id: @user.id,
name: @user.name,
email: @user.email
}
}, status: :ok
elsif @user
render json: { message: 'Failed to create an account', error: 'Password cannot be less than 6 letters' },
status: :unprocessable_entity
else
render json: { message: 'Failed to create an account', error: 'Validation failed' },
status: :unprocessable_entity
end
end
end

# rubocop:enable Metrics/MethodLength

def login
@user = User.find_by_email(login_params[:email])
if @user&.authenticate(login_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'),
user: {
id: @user.id,
name: @user.name,
email: @user.email
}
}, status: :ok
elsif @user
render json: { message: 'You are not authorize to access this account', error: 'Incorrect password' },
status: :unauthorized
else
render json: { message: 'You are not authorize to access this account', error: 'Incorrect email' },
status: :unauthorized
end
end

private

def signup_params
params.require(:user).permit(:name, :email, :password)
end

def login_params
params.require(:user).permit(:email, :password)
end
end
40 changes: 40 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class UsersController < ApplicationController
before_action :authorize_request
before_action :find_user

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

def show
render json: @user, status: :ok
end

def update
return if @user.update(user_params)

render json: { errors: @user.errors.full_messages },
status: :unprocessable_entity
end

def destroy
if @user.destroy
render json: { success: 'User destroyed successfully' }, status: :ok
else
render :json, { error: 'Unable to destroy a user' }, status: :unprocessable_entity
end
end

private

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

def user_params
params.permit(:name, :email, :password)
end
end
13 changes: 13 additions & 0 deletions app/lib/json_web_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s

def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end

def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
end
end
8 changes: 8 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password,
length: { minimum: 6 },
if: -> { new_record? || !password.nil? }
end
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

resources :users
post '/auth/login', to: 'authentication#login'
post '/auth/signup', to: 'authentication#signup'

# Defines the root path route ("/")
# root "articles#index"
end
11 changes: 11 additions & 0 deletions db/migrate/20230413205555_create_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_digest

t.timestamps
end
end
end
25 changes: 25 additions & 0 deletions db/schema.rb

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

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

class AuthenticationControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
7 changes: 7 additions & 0 deletions test/controllers/users_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
11 changes: 11 additions & 0 deletions test/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
name: MyString
email: MyString
password_digest: MyString

two:
name: MyString
email: MyString
password_digest: MyString
7 changes: 7 additions & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'test_helper'

class UserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

0 comments on commit 097abab

Please sign in to comment.