Skip to content

Milestone 13 : JWT authorization #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
6 changes: 0 additions & 6 deletions .env

This file was deleted.

5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@
node_modules/

# Ignore blog.http, used for testing api endpoints with REST Client
blog.http
blog.http

# Ignore .env file for storing environment variables
.env
12 changes: 12 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ gem 'pg', '~> 1.1'
# Use Rubocop for linters
gem 'rubocop', '>= 1.0', '< 2.0'

# Use rack-cors for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# https:// github.com/cyu/rack-cors
gem 'rack-cors'

# Use for Devise Authentication
# https://github.com/heartcombo/devise
gem 'devise'

# JWT for Devise Authentication for API
# https://github.com/waiting-for-dev/devise-jwt
gem 'devise-jwt'

# Use CAnCanCan for Authorization
# https://github.com/CanCanCommunity/cancancan
gem 'cancancan'

# Use for hiding credentials
Expand Down Expand Up @@ -77,6 +87,8 @@ group :development do
gem 'web-console'
# Use bullet to fix N + 1 problems
gem 'bullet'
# Use letter_opener to open emails in the browser
gem 'letter_opener'
# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"

Expand Down
28 changes: 28 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,24 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-jwt (0.10.0)
devise (~> 4.0)
warden-jwt_auth (~> 0.6)
diff-lcs (1.5.0)
dotenv (2.8.1)
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
railties (>= 3.2)
dry-auto_inject (0.9.0)
dry-container (>= 0.3.4)
dry-configurable (0.16.1)
dry-core (~> 0.6)
zeitwerk (~> 2.6)
dry-container (0.11.0)
concurrent-ruby (~> 1.0)
dry-core (0.9.1)
concurrent-ruby (~> 1.0)
zeitwerk (~> 2.6)
erubi (1.11.0)
ffi (1.15.5-x64-mingw-ucrt)
globalid (1.0.0)
Expand All @@ -126,6 +139,11 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.6.2)
jwt (2.6.0)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand Down Expand Up @@ -160,6 +178,8 @@ GEM
nio4r (~> 2.0)
racc (1.6.0)
rack (2.2.4)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-test (2.0.2)
rack (>= 1.3)
rails (7.0.4)
Expand Down Expand Up @@ -269,6 +289,11 @@ GEM
uniform_notifier (1.16.0)
warden (1.2.9)
rack (>= 2.0.9)
warden-jwt_auth (0.7.0)
dry-auto_inject (~> 0.8)
dry-configurable (~> 0.13)
jwt (~> 2.1)
warden (~> 1.2)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -297,13 +322,16 @@ DEPENDENCIES
database_cleaner
debug
devise
devise-jwt
dotenv-rails
ffi
importmap-rails
jbuilder
letter_opener
pagy (~> 5.10)
pg (~> 1.1)
puma (~> 5.0)
rack-cors
rails (~> 7.0.4)
rails-controller-testing
rspec-rails
Expand Down
21 changes: 16 additions & 5 deletions app/controllers/api/v1/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ class Api::V1::ApplicationController < ActionController::API
include Response
include ExceptionHandler

before_action :restrict_access
# before_action :restrict_access

before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?

respond_to :json

private
protected

def restrict_access
api_key = ApiKey.find_by_access_token(params[:access_token])
head :unauthorized unless api_key
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) }
devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:email, :password) }
end

# private

# def restrict_access
# api_key = ApiKey.find_by_access_token(params[:access_token])
# head :unauthorized unless api_key
# end
end
2 changes: 1 addition & 1 deletion app/controllers/api/v1/comments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def set_author
end

def set_post
@post = set_author.posts.find(params[:post_id])
@post = Post.find(params[:post_id])
end

def set_comment
Expand Down
85 changes: 85 additions & 0 deletions app/controllers/api/v1/users/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
class Api::V1::Users::RegistrationsController < Devise::RegistrationsController
# CSRF token verification is not required for API requests as they are stateless.
# The token is generated on the client side and is not accessible to the server.
# So, we skip this verification.
skip_before_action :verify_authenticity_token

respond_to :json

private

def respond_with(resource, _opts = {})
p resource
resource.persisted? ? register_success : register_failed
end

def register_success
render json: {
status: 200,
message: 'Signed up sucessfully.'
}, status: :ok
end

def register_failed
render json: {
status: 422,
message: "Signed up failure. #{resource.errors.full_messages.to_sentence}"
}, status: :unprocessable_entity
end

# GET /resource/sign_up
# def new
# super
# end

# POST /resoure#
# def create
# super
# end

# GET /resource/edit
# def edit
# super
# end

# PUT /resource
# def update
# super
# end

# DELETE /resource
# def destroy
# super
# end

# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end

# protected

# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_up_params
# devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
# end

# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
# end

# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end

# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
85 changes: 85 additions & 0 deletions app/controllers/api/v1/users/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
class Api::V1::Users::SessionsController < Devise::SessionsController
# CSRF token verification is not required for API requests as they are stateless.
# The token is generated on the client side and is not accessible to the server.
# So, we skip this verification.
skip_before_action :verify_authenticity_token

respond_to :json

private

def respond_with(_resource, _opts = {})
current_user ? log_in_success : log_in_failure
end

def respond_to_on_destroy
if request.headers['Authorization'].present?
jwt_payload = JWT.decode(request.headers['Authorization'].split.last,
ENV.fetch('DEVISE_JWT_SECRET_KEY')).first

current_user = User.find(jwt_payload['sub'])

current_user ? log_out_success : log_out_failure
else
log_out_failure
end
end

def log_in_success
render json: {
status: {
code: 200,
message: 'Logged in sucessfully.',
data: current_user
}
}, status: :ok
end

def log_in_failure
render json: {
status: {
code: 401,
message: "Logged in failure. #{resource.errors.full_messages.to_sentence}",
data: current_user
}
}, status: :unauthorized
end

def log_out_success
render json: {
status: 200,
message: 'Logged out sucessfully.'
}, status: :ok
end

def log_out_failure
render json: {
status: 401,
message: 'Logged out failure.'
}, status: :unauthorized
end

# before_action :configure_sign_in_params, only: [:create]

# GET /resource/sign_in
# def new
# super
# end

# POST /resource/sign_in
# def create
# super
# end

# DELETE /resource/sign_out
# def destroy
# super
# end

# protected

# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_in_params
# devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
# end
end
4 changes: 2 additions & 2 deletions app/controllers/concerns/exception_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ module ExceptionHandler
extend ActiveSupport::Concern
included do
rescue_from ActiveRecord::RecordNotFound do |e|
json_response({ message: e.message }, :not_found)
json_response({ code: 404, message: e.message }, :not_found)
end

rescue_from ActiveRecord::RecordInvalid do |e|
json_response({ message: e.message }, :unprocessable_entity)
json_response({ code: 422, message: e.message }, :unprocessable_entity)
end
end
end
9 changes: 8 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
class User < ApplicationRecord
include Devise::JWT::RevocationStrategies::JTIMatcher
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
:recoverable, :rememberable, :validatable, :confirmable,
:jwt_authenticatable, jwt_revocation_strategy: self

validates :name, presence: true
validates :posts_counter, comparison: { greater_than_or_equal_to: 0 }, numericality: { only_integer: true }

has_many :posts, foreign_key: :author_id, dependent: :destroy
has_many :comments, foreign_key: :author_id, dependent: :destroy
has_many :likes, foreign_key: :author_id, dependent: :destroy

# def jwt_payload
# super
# end

# User::Roles
# The available roles
ROLES = %i[admin default].freeze
Expand Down
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ class Application < Rails::Application
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
config.session_store :cookie_store, key: '_interslice_session'
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options
end
end
3 changes: 3 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@

# Default URL options for the Devise mailer
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

# Letter Opener config for development environment
config.action_mailer.delivery_method = :letter_opener

# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
Expand Down
Loading