Skip to content

Commit 03b7a9a

Browse files
committed
Expire user session when access token expires
In a previous commit we setup to have the AddAddressesToBolt be called asynchronously via a Job. However, when the first access_token expires then new one doesn't have write access anymore so the Job fails. To solve this we are adding a check for the expiration time and signing out the user, to force them to get a fresh token with write access. To improve the UX, we are moving this step to before delivery (to avoid signing out a user right at the end of the checkout flow).
1 parent d622b7f commit 03b7a9a

4 files changed

Lines changed: 99 additions & 51 deletions

File tree

app/decorators/controllers/solidus_bolt/spree_checkout_controller/add_addresses_to_bolt.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
module SolidusBolt
44
module SpreeCheckoutController
55
module AddAddressesToBolt
6-
def finalize_order
7-
if session[:bolt_access_token] && current_order.payments.last&.source_type == "SolidusBolt::PaymentSource"
6+
def before_delivery
7+
if session_refreshed_or_expired?
8+
Spree::UserLastUrlStorer.new(self).store_location
9+
sign_out(spree_current_user)
10+
elsif session[:bolt_access_token]
811
spree_current_user.addresses.each do |address|
912
SolidusBolt::AddAddressJob.perform_later(
1013
order: current_order,
11-
access_token: SolidusBolt::Users::RefreshAccessTokenService.call(session: session),
14+
access_token: session[:bolt_access_token],
1215
address: address
1316
)
1417
end
@@ -18,6 +21,13 @@ def finalize_order
1821
end
1922

2023
Spree::CheckoutController.prepend self
24+
25+
private
26+
27+
def session_refreshed_or_expired?
28+
session[:bolt_access_token] &&
29+
(session[:bolt_scope] == 'bolt.account.view' || Time.now.utc >= session[:bolt_expiration_time])
30+
end
2131
end
2232
end
2333
end

app/decorators/omniauth/strategies/bolt_decorator.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def callback_phase
88
session[:bolt_expiration_time] = @expiration_time
99
session[:bolt_refresh_token] = @refresh_token
1010
session[:bolt_refresh_token_scope] = @refresh_token_scope
11+
session[:bolt_scope] = @scope
1112
end
1213
end
1314

app/services/solidus_bolt/users/refresh_access_token_service.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def refresh_access_token
2626
session[:bolt_refresh_token] = response['refresh_token']
2727
session[:bolt_refresh_token_scope] = response['refresh_token_scope']
2828
session[:bolt_access_token] = response['access_token']
29+
session[:bolt_scope] = response['scope']
2930
end
3031

3132
def build_options

spec/requests/spree/checkout_controller_spec.rb

Lines changed: 84 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -33,85 +33,121 @@
3333
end
3434
end
3535

36-
describe "GET /checkout/payment" do
37-
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
38-
39-
before { allow(SolidusBolt::Users::SyncPaymentSourcesService).to receive(:call) }
40-
41-
it 'returns a successful response' do
42-
get '/checkout/payment'
43-
44-
expect(response.status).to eq 200
45-
end
46-
47-
it 'calls the service to sync payment sources' do
48-
get '/checkout/payment'
49-
50-
expect(SolidusBolt::Users::SyncPaymentSourcesService).to have_received(:call)
36+
describe 'GET /checkout/delivery' do
37+
subject(:deliver_order) { get '/checkout/delivery' }
38+
39+
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:delivery) }
40+
let(:bolt_expiration_time) { Time.now.utc - 15.minutes }
41+
let(:access_token) { 'access_token' }
42+
let(:bolt_scope) { 'bolt.account.view openid' }
43+
let!(:session) do
44+
{ bolt_access_token: access_token, bolt_expiration_time: bolt_expiration_time, bolt_scope: bolt_scope }
5145
end
52-
end
53-
54-
describe 'PATCH /checkout/update/confirm' do
55-
subject(:confirm_order) { patch '/checkout/update/confirm' }
56-
57-
let(:order) { FactoryBot.create(:order_with_totals) }
58-
let(:payment) { create(:payment, amount: order.total, order: order) }
59-
let(:session) { { bolt_access_token: access_token } }
60-
let(:access_token) { nil }
6146

6247
before do
63-
# use test preparation from solidusio/solidus/frontend/spec/controllers/spree/checkout_controller_spec.rb
64-
# because Spree::TestingSupport::OrderWalkthrough.up_to(:confirm) doesn't work
6548
order.update! user: user
66-
order.update(state: 'confirm')
67-
payment
68-
order.create_proposed_shipments
69-
order.payments.reload
7049

71-
# request calls Gateway#authorize - need to stub it to test our action
72-
allow(SolidusBolt::Transactions::AuthorizeService).to(receive(:call).and_return({
73-
'transaction' => { 'from_credit_card' => { 'id' => 'CreditCardId' } }
74-
}))
50+
allow(SolidusBolt::Users::SyncPaymentSourcesService).to receive(:call)
7551

7652
# rubocop:disable RSpec/AnyInstance
7753
allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(session)
7854
# rubocop:enable RSpec/AnyInstance
7955
end
8056

81-
it 'redirects to completion route' do
82-
confirm_order
83-
expect(response).to redirect_to spree.order_path(order)
57+
context 'without bolt session' do
58+
let(:access_token) { nil }
59+
60+
it 'returns a successful response' do
61+
deliver_order
62+
63+
expect(response.status).to eq 200
64+
end
65+
66+
it 'skips the job call to add addresses' do
67+
expect { deliver_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
68+
end
8469
end
8570

86-
context 'with logged in Bolt user and Bolt payment' do
87-
let(:access_token) { 'accesstoken' }
88-
let(:payment) { create(:bolt_payment, amount: order.total, order: order) }
71+
context 'with valid bolt_access token' do
72+
it 'returns a successful response' do
73+
deliver_order
8974

90-
before { allow(SolidusBolt::Users::RefreshAccessTokenService).to receive(:call).and_return(access_token) }
75+
expect(response.status).to eq 200
76+
end
9177

9278
it 'calls the job to add addresses' do
93-
expect { confirm_order }.to(have_enqueued_job(SolidusBolt::AddAddressJob).twice.with { |hash|
79+
expect { deliver_order }.to(have_enqueued_job(SolidusBolt::AddAddressJob).twice.with { |hash|
9480
expect(hash[:order]).to eq(order)
9581
expect(hash[:access_token]).to eq(access_token)
9682
expect(user.addresses).to include(hash[:address])
9783
})
9884
end
9985
end
10086

101-
context 'with logged in Bolt user' do
102-
let(:access_token) { 'accesstoken' }
87+
context 'with expired bolt_access token' do
88+
include Devise::Test::IntegrationHelpers
89+
90+
let(:bolt_expiration_time) { Time.now.utc + 15.minutes }
91+
92+
before { sign_in(order.user) }
93+
94+
it 'stores the sign in location' do
95+
deliver_order
96+
97+
expect(session[:spree_user_return_to]).to eq('/checkout/delivery')
98+
end
99+
100+
it 'signs out the user' do
101+
deliver_order
102+
103+
expect(controller.spree_user_signed_in?).to be false
104+
end
103105

104106
it 'skips the job call to add addresses' do
105-
expect { confirm_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
107+
expect { deliver_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
106108
end
107109
end
108110

109-
context 'with Bolt payment' do
110-
let(:payment) { create(:bolt_payment, amount: order.total, order: order) }
111+
context 'with refreshed access_token' do
112+
include Devise::Test::IntegrationHelpers
113+
114+
let(:bolt_scope) { 'bolt.account.view' }
115+
116+
before { sign_in(order.user) }
117+
118+
it 'stores the sign in location' do
119+
deliver_order
120+
121+
expect(session[:spree_user_return_to]).to eq('/checkout/delivery')
122+
end
123+
124+
it 'signs out the user' do
125+
deliver_order
126+
127+
expect(controller.spree_user_signed_in?).to be false
128+
end
111129

112130
it 'skips the job call to add addresses' do
113-
expect { confirm_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
131+
expect { deliver_order }.not_to have_enqueued_job(SolidusBolt::AddAddressJob)
114132
end
115133
end
116134
end
135+
136+
describe "GET /checkout/payment" do
137+
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
138+
139+
before { allow(SolidusBolt::Users::SyncPaymentSourcesService).to receive(:call) }
140+
141+
it 'returns a successful response' do
142+
get '/checkout/payment'
143+
144+
expect(response.status).to eq 200
145+
end
146+
147+
it 'calls the service to sync payment sources' do
148+
get '/checkout/payment'
149+
150+
expect(SolidusBolt::Users::SyncPaymentSourcesService).to have_received(:call)
151+
end
152+
end
117153
end

0 commit comments

Comments
 (0)