Skip to content

Commit ede9e67

Browse files
authored
Merge pull request rubyforgood#6216 from 7riumph/feature/6207
Feature/6207 Endpoint api/v1/sign_in now outputs randomized tokens in response upon sign-in
2 parents 5e2a32e + 778758e commit ede9e67

22 files changed

+305
-35
lines changed

.allow_skipping_tests

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ models/application_record.rb
2626
models/concerns/CasaCase/validations.rb
2727
models/concerns/by_organization_scope.rb
2828
models/concerns/roles.rb
29+
models/concerns/api.rb
2930
models/fund_request.rb
3031
notifications/base_notifier.rb
3132
notifications/delivery_methods/sms.rb
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
class Api::V1::SessionBlueprint < Blueprinter::Base
2-
identifier :id
2+
field :user do |user|
3+
{
4+
id: user.id,
5+
display_name: user.display_name,
6+
email: user.email,
7+
refresh_token_expires_at: user.api_credential&.refresh_token_expires_at,
8+
token_expires_at: user.api_credential&.token_expires_at
9+
}
10+
end
311

4-
fields :id, :display_name, :email, :token
12+
field :api_token do |user|
13+
token = user.api_credential
14+
token.return_new_api_token![:api_token]
15+
end
16+
17+
field :refresh_token do |user|
18+
token = user.api_credential
19+
token.return_new_refresh_token![:refresh_token]
20+
end
521
end

app/controllers/api/v1/base_controller.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ class Api::V1::BaseController < ActionController::API
33
before_action :authenticate_user!, except: [:create]
44

55
def authenticate_user!
6-
token, options = ActionController::HttpAuthentication::Token.token_and_options(request)
6+
api_token, options = ActionController::HttpAuthentication::Token.token_and_options(request)
77
user = User.find_by(email: options[:email])
8-
if user && token && ActiveSupport::SecurityUtils.secure_compare(user.token, token)
8+
if user && api_token && ActiveSupport::SecurityUtils.secure_compare(user.api_credential.api_token_digest, Digest::SHA256.hexdigest(api_token))
99
@current_user = user
1010
else
11-
render json: {message: "Wrong password or email"}, status: 401
11+
render json: {message: "Incorrect email or password."}, status: 401
1212
end
1313
end
1414

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ def create
44
if @user
55
render json: Api::V1::SessionBlueprint.render(@user), status: 201
66
else
7-
render json: {message: "Wrong password or email"}, status: 401
7+
render json: {message: "Incorrect email or password."}, status: 401
88
end
99
end
1010

app/models/api_credential.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
require "digest"
2+
3+
class ApiCredential < ApplicationRecord
4+
belongs_to :user
5+
6+
before_save :generate_api_token
7+
before_save :generate_refresh_token
8+
9+
# Securely confirm/deny that Hash in db is same as current users token Hash
10+
def authenticate_api_token(api_token)
11+
Digest::SHA256.hexdigest(api_token) == api_token_digest
12+
end
13+
14+
def authenticate_refresh_token(refresh_token)
15+
Digest::SHA256.hexdigest(refresh_token) == refresh_token_digest
16+
end
17+
18+
# Securely generate and then return new tokens
19+
def return_new_api_token!
20+
new_token = generate_api_token
21+
update_column(:api_token_digest, api_token_digest)
22+
{api_token: new_token}
23+
end
24+
25+
def return_new_refresh_token!
26+
new_token = generate_refresh_token
27+
update_column(:refresh_token_digest, refresh_token_digest)
28+
{refresh_token: new_token}
29+
end
30+
31+
# Verifying token has or has not expired
32+
def is_api_token_expired?
33+
token_expires_at < Time.current
34+
end
35+
36+
def is_refresh_token_expired?
37+
refresh_token_expires_at < Time.current
38+
end
39+
40+
private
41+
42+
# Generate unique tokens and hashes them for secure db storage
43+
def generate_api_token
44+
new_api_token = SecureRandom.hex(18)
45+
self.api_token_digest = Digest::SHA256.hexdigest(new_api_token)
46+
new_api_token
47+
end
48+
49+
def generate_refresh_token
50+
new_refresh_token = SecureRandom.hex(18)
51+
self.refresh_token_digest = Digest::SHA256.hexdigest(new_refresh_token)
52+
new_refresh_token
53+
end
54+
end
55+
56+
# == Schema Information
57+
#
58+
# Table name: api_credentials
59+
#
60+
# id :bigint not null, primary key
61+
# api_token_digest :string
62+
# refresh_token_digest :string
63+
# refresh_token_expires_at :datetime
64+
# token_expires_at :datetime
65+
# created_at :datetime not null
66+
# updated_at :datetime not null
67+
# user_id :bigint not null
68+
#
69+
# Indexes
70+
#
71+
# index_api_credentials_on_api_token_digest (api_token_digest) UNIQUE WHERE (api_token_digest IS NOT NULL)
72+
# index_api_credentials_on_refresh_token_digest (refresh_token_digest) UNIQUE WHERE (refresh_token_digest IS NOT NULL)
73+
# index_api_credentials_on_user_id (user_id)
74+
#
75+
# Foreign Keys
76+
#
77+
# fk_rails_... (user_id => users.id)
78+
#

app/models/casa_admin.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ def change_to_supervisor!
5151
# reset_password_sent_at :datetime
5252
# reset_password_token :string
5353
# sign_in_count :integer default(0), not null
54-
# token :string
5554
# type :string
5655
# unconfirmed_email :string
5756
# created_at :datetime not null

app/models/concerns/api.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Api
2+
extend ActiveSupport::Concern
3+
included do
4+
has_one :api_credential, dependent: :destroy
5+
after_create :initialize_api_credentials
6+
end
7+
8+
private
9+
10+
def initialize_api_credentials
11+
create_api_credential unless api_credential
12+
end
13+
end

app/models/supervisor.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ def recently_unassigned_volunteers
8484
# reset_password_sent_at :datetime
8585
# reset_password_token :string
8686
# sign_in_count :integer default(0), not null
87-
# token :string
8887
# type :string
8988
# unconfirmed_email :string
9089
# created_at :datetime not null

app/models/user.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
# model for all user roles: volunteer supervisor casa_admin inactive
44
class User < ApplicationRecord
55
include Roles
6+
include Api
67
include ByOrganizationScope
78
include DateHelper
89

910
before_save :normalize_phone_number
1011
after_create :skip_email_confirmation_upon_creation
1112
after_create :create_preference_set
1213
before_update :record_previous_email
13-
has_secure_token :token, length: 36
1414

1515
validates_with UserValidator
1616

@@ -217,7 +217,6 @@ def normalize_phone_number
217217
# reset_password_sent_at :datetime
218218
# reset_password_token :string
219219
# sign_in_count :integer default(0), not null
220-
# token :string
221220
# type :string
222221
# unconfirmed_email :string
223222
# created_at :datetime not null

app/models/volunteer.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ def cases_where_contact_made_in_days(num_days = CONTACT_MADE_IN_DAYS_NUM)
179179
# reset_password_sent_at :datetime
180180
# reset_password_token :string
181181
# sign_in_count :integer default(0), not null
182-
# token :string
183182
# type :string
184183
# unconfirmed_email :string
185184
# created_at :datetime not null

0 commit comments

Comments
 (0)