Skip to content

Commit 75b9337

Browse files
authored
Merge pull request #6241 from xihai01/feature/6222
Feature/6222 - Endpoint /api/v1/users/sign_out revokes access token and refresh token on request
2 parents 9f91cac + 51864b9 commit 75b9337

10 files changed

Lines changed: 120 additions & 6 deletions

File tree

app/controllers/api/v1/base_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Api::V1::BaseController < ActionController::API
22
rescue_from ActiveRecord::RecordNotFound, with: :not_found
3-
before_action :authenticate_user!, except: [:create]
3+
before_action :authenticate_user!, except: [:create, :destroy]
44

55
def authenticate_user!
66
api_token, options = ActionController::HttpAuthentication::Token.token_and_options(request)

app/controllers/api/v1/users/sessions_controller.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ def create
88
end
99
end
1010

11+
def destroy
12+
# fetch access token from request header
13+
api_token = request.headers["Authorization"]&.split(" ")&.last
14+
# find user's api credentials by access token
15+
api_credential = ApiCredential.find_by(api_token_digest: Digest::SHA256.hexdigest(api_token))
16+
# set api and refresh tokens to nil; otherwise render 401
17+
if api_credential
18+
api_credential.revoke_api_token
19+
api_credential.revoke_refresh_token
20+
render json: {message: "Signed out successfully."}, status: 200
21+
else
22+
render json: {message: "An error occured when signing out."}, status: 401
23+
nil
24+
end
25+
end
26+
1127
private
1228

1329
def user_params

app/models/api_credential.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ def is_refresh_token_expired?
3737
refresh_token_expires_at < Time.current
3838
end
3939

40+
def revoke_api_token
41+
update_columns(api_token_digest: nil)
42+
end
43+
44+
def revoke_refresh_token
45+
update_columns(refresh_token_digest: nil)
46+
end
47+
4048
private
4149

4250
# Generate unique tokens and hashes them for secure db storage

config/routes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@
250250
namespace :v1 do
251251
namespace :users do
252252
post "sign_in", to: "sessions#create"
253-
# get 'sign_out', to: 'sessions#destroy'
253+
delete "sign_out", to: "sessions#destroy"
254254
end
255255
end
256256
end

db/seeds.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,4 @@ def create_org_related_data(db_populator, casa_org, options)
147147
Rails.logger.error { "Caught error during db seed emancipation_options_prune, continuing. Message: #{e}" }
148148
end
149149
load(Rails.root.join("db/seeds/placement_data.rb"))
150+
load(Rails.root.join("db/seeds/api_credential_data.rb"))

db/seeds/api_credential_data.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ApiCredential.destroy_all
2+
users = User.all
3+
4+
users.each do |user|
5+
ApiCredential.create!(user: user, api_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)), refresh_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)))
6+
end

spec/models/api_credential_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,22 @@
100100
expect(api_credential.refresh_token_digest).to eq(Digest::SHA256.hexdigest(refresh_token))
101101
end
102102
end
103+
104+
describe "#revoke_api_token" do
105+
it "sets api token to nil" do
106+
api_credential.return_new_api_token![:api_token]
107+
api_credential.revoke_api_token
108+
109+
expect(api_credential.api_token_digest).to be_nil
110+
end
111+
end
112+
113+
describe "#revoke_refresh_token" do
114+
it "sets refresh token to nil" do
115+
api_credential.return_new_refresh_token![:refresh_token]
116+
api_credential.revoke_refresh_token
117+
118+
expect(api_credential.refresh_token_digest).to be_nil
119+
end
120+
end
103121
end

spec/requests/api/v1/users/sessions_spec.rb

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
require "swagger_helper"
22

33
RSpec.describe "sessions API", type: :request do
4+
let(:casa_org) { create(:casa_org) }
5+
let(:volunteer) { create(:volunteer, casa_org: casa_org) }
6+
47
path "/api/v1/users/sign_in" do
58
post "Signs in a user" do
69
tags "Sessions"
@@ -15,9 +18,6 @@
1518
required: %w[email password]
1619
}
1720

18-
let(:casa_org) { create(:casa_org) }
19-
let(:volunteer) { create(:volunteer, casa_org: casa_org) }
20-
2121
response "201", "user signed in" do
2222
let(:user) { {email: volunteer.email, password: volunteer.password} }
2323
schema "$ref" => "#/components/schemas/login_success"
@@ -41,4 +41,34 @@
4141
end
4242
end
4343
end
44+
45+
path "/api/v1/users/sign_out" do
46+
delete "Signs out a user" do
47+
tags "Sessions"
48+
produces "application/json"
49+
parameter name: :authorization, in: :header, type: :string, required: true
50+
51+
let(:api_token) { create(:api_credential, user: volunteer).return_new_api_token![:api_token] }
52+
53+
response "200", "user signed out" do
54+
let(:authorization) { "Bearer #{api_token}" }
55+
schema "$ref" => "#/components/schemas/sign_out"
56+
run_test! do |response|
57+
expect(response.content_type).to eq("application/json; charset=utf-8")
58+
expect(response.body).to eq({message: "Signed out successfully."}.to_json)
59+
expect(response.status).to eq(200)
60+
end
61+
end
62+
63+
response "401", "unauthorized" do
64+
let(:authorization) { "Bearer foo" }
65+
schema "$ref" => "#/components/schemas/sign_out"
66+
run_test! do |response|
67+
expect(response.content_type).to eq("application/json; charset=utf-8")
68+
expect(response.body).to eq({message: "An error occured when signing out."}.to_json)
69+
expect(response.status).to eq(401)
70+
end
71+
end
72+
end
73+
end
4474
end

spec/swagger_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
properties: {
4343
message: {type: :string}
4444
}
45+
},
46+
sign_out: {
47+
type: :object,
48+
properties: {
49+
message: {type: :string}
50+
}
4551
}
4652
}
4753
},

swagger/v1/swagger.yaml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ components:
1919
type: string
2020
email:
2121
type: string
22-
api_token_expires_at:
22+
token_expires_at:
2323
type: datetime
2424
refresh_token_expires_at:
2525
type: datetime
@@ -28,6 +28,11 @@ components:
2828
properties:
2929
message:
3030
type: string
31+
sign_out:
32+
type: object
33+
properties:
34+
message:
35+
type: string
3136
paths:
3237
"/api/v1/users/sign_in":
3338
post:
@@ -61,6 +66,30 @@ paths:
6166
required:
6267
- email
6368
- password
69+
"/api/v1/users/sign_out":
70+
delete:
71+
summary: Signs out a user
72+
tags:
73+
- Sessions
74+
parameters:
75+
- name: Authorization
76+
in: header
77+
required: true
78+
schema:
79+
type: string
80+
responses:
81+
'200':
82+
description: user signed out
83+
content:
84+
application/json:
85+
schema:
86+
"$ref": "#/components/schemas/sign_out"
87+
'401':
88+
description: unauthorized
89+
content:
90+
application/json:
91+
schema:
92+
"$ref": "#/components/schemas/sign_out"
6493
servers:
6594
- url: https://{defaultHost}
6695
variables:

0 commit comments

Comments
 (0)