Skip to content

Commit 6351c3b

Browse files
authored
Merge pull request #14 from tuanle03/clr-10-create-api-session
[CLR-10] Create Sessions API
2 parents 075e54c + 778c656 commit 6351c3b

18 files changed

+213
-36
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ gem 'grape-swagger'
5454

5555
gem 'grape_logging'
5656

57+
gem 'faker'
58+
5759
# Use Sass to process CSS
5860
# gem "sassc-rails"
5961

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ GEM
127127
factory_bot_rails (6.4.2)
128128
factory_bot (~> 6.4)
129129
railties (>= 5.0.0)
130+
faker (3.2.2)
131+
i18n (>= 1.8.11, < 2)
130132
globalid (1.2.1)
131133
activesupport (>= 6.1)
132134
grape (2.0.0)
@@ -315,6 +317,7 @@ DEPENDENCIES
315317
devise
316318
devise-jwt
317319
factory_bot_rails
320+
faker
318321
grape
319322
grape-swagger
320323
grape_logging

app/api/application_api.rb

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,14 @@
44
require 'grape-swagger'
55
require 'grape_logging'
66

7-
# Grape::Validations.register_validator('length', Validators::LengthValidator)
8-
# Grape::Validations.register_validator('numeric', Validators::NumericValidator)
9-
# Grape::Validations.register_validator('gte_date', Validators::GteDateValidator)
10-
11-
# All the API module are defined in this file.
127
class ApplicationAPI < Grape::API
138
format :json
14-
# error_formatter :json
15-
# formatter :json, JsonFormatter
16-
# default_error_formatter :json
179

1810
helpers ::Helpers::AuthenticationHelper
19-
20-
before do
21-
process_token
11+
helpers do
12+
def unauthorized_error!
13+
error!('Unauthorized', 401)
14+
end
2215
end
2316

2417
rescue_from Grape::Exceptions::ValidationErrors do |e|
Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
module Helpers::AuthenticationHelper
22
extend Grape::API::Helpers
33

4-
def process_token
5-
return unless request.headers['Authorization']
6-
7-
payload = JWT.decode(request.headers['Authorization'].split(' ')[1], Rails.application.credentials.devise_jwt_secret_key).first
8-
@current_user_id = payload['user_id']
9-
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
10-
head :unauthorized
11-
end
12-
13-
def authenticate_user!(options = {})
14-
head :unauthorized unless signed_in?
4+
def authenticate_user!
5+
error!('Unauthorized. Invalid or expired token.', 401) unless current_user
156
end
167

178
def current_user
18-
@current_user ||= super || User.find(@current_user_id)
9+
@current_user ||=
10+
begin
11+
token = request.headers['Authorization'].split(' ').last
12+
User.find_by(id: JWT.decode(token, Rails.application.credentials.devise_jwt_secret_key)[0]['user_id']) if token
13+
rescue JWT::DecodeError
14+
nil
15+
end
1916
end
2017

21-
def signed_in?
22-
@current_user_id.present?
23-
end
2418
end

app/api/web/feedbacks_api.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ class Web::FeedbacksAPI < Grape::API
44
desc 'Create feedback'
55
params do
66
requires :content, type: String
7-
requires :user_id, type: Integer
7+
requires :post_id, type: Integer
88
end
99
post do
10+
authenticate_user!
1011
Feedback.create!({
1112
content: params[:content],
12-
user_id: params[:user_id]
13+
user_id: current_user.id,
14+
post_id: params[:post_id]
1315
})
1416
end
1517

app/api/web/sessions_api.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module API
2+
class Web::SessionsAPI < Grape::API
3+
namespace :session do
4+
desc 'Sign in and retrieve JWT token'
5+
params do
6+
requires :email, type: String, desc: 'User email'
7+
requires :password, type: String, desc: 'User password'
8+
end
9+
post 'sign_in' do
10+
user = User.find_for_database_authentication(email: params[:email])
11+
if user && user.valid_password?(params[:password])
12+
token = user.generate_jwt
13+
status 200
14+
{
15+
success: true,
16+
token: token,
17+
}
18+
else
19+
status 401
20+
{
21+
success: false,
22+
message: 'Invalid email or password',
23+
}
24+
end
25+
end
26+
27+
desc 'Sign out and revoke JWT token'
28+
delete 'sign_out' do
29+
authenticate_user!
30+
current_user.revoke_jwt(request.headers['Authorization'])
31+
status 200
32+
{
33+
success: true,
34+
message: 'Signed out successfully',
35+
}
36+
end
37+
end
38+
end
39+
end

app/api/web_api.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
class WebAPI < Grape::API
2+
format :json
3+
prefix 'web'
4+
5+
helpers ::Helpers::AuthenticationHelper
26

37
mount Web::FeedbacksAPI
8+
mount Web::SessionsAPI
49

510
add_swagger_documentation(
611
format: :json,

app/models/allowlisted_jwt.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class AllowlistedJwt < ApplicationRecord
2+
end

app/models/user.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
class User < ApplicationRecord
2-
# Include default devise modules. Others available are:
3-
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
2+
include Devise::JWT::RevocationStrategies::Allowlist
3+
4+
serialize :revoked_tokens, Array
5+
46
devise :database_authenticatable, :registerable,
57
:recoverable, :rememberable, :validatable,
6-
:jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null
8+
:jwt_authenticatable, jwt_revocation_strategy: self
79

810
def generate_jwt
911
payload = { user_id: id, exp: 1.day.from_now.to_i }
1012
JWT.encode(payload, Rails.application.credentials.devise_jwt_secret_key)
1113
end
14+
15+
def decode_jwt(token)
16+
JWT.decode(token, Rails.application.credentials.devise_jwt_secret_key)
17+
end
18+
19+
def revoke_jwt(token)
20+
self.revoked_tokens ||= []
21+
self.revoked_tokens << token
22+
self.revoked_tokens.uniq!
23+
save
24+
end
25+
26+
def jwt_revoked?(token)
27+
revoked_tokens.include?(token)
28+
end
1229
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class UserRegistrationWithEmailService
2+
attr_reader :email, :token
3+
4+
def initialize(email)
5+
@email = email
6+
end
7+
8+
def register
9+
user = User.find_or_initialize_by email: email
10+
if user.save
11+
@token, payload = Warden::JWTAuth::UserEncoder.new.call(user, :user, nil)
12+
user.on_jwt_dispatch(@token, payload)
13+
end
14+
15+
user
16+
end
17+
end

0 commit comments

Comments
 (0)