Skip to content

Commit 5463b38

Browse files
committed
Token exchange expiring offline token based on configuration
1 parent fb73526 commit 5463b38

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

lib/shopify_api/auth/token_exchange.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ def exchange_token(shop:, session_token:, requested_token_type:)
4949
requested_token_type: requested_token_type.serialize,
5050
}
5151

52+
if requested_token_type == RequestedTokenType::OFFLINE_ACCESS_TOKEN
53+
body.merge!({ expiring: ShopifyAPI::Context.expiring_offline_access_tokens ? 1 : 0 })
54+
end
55+
5256
client = Clients::HttpClient.new(session: shop_session, base_path: "/admin/oauth")
5357
response = begin
5458
client.request(

test/auth/token_exchange_test.rb

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,34 @@ def setup
2323
sid: "abc123",
2424
}
2525
@session_token = JWT.encode(@jwt_payload, ShopifyAPI::Context.api_secret_key, "HS256")
26-
@token_exchange_request = {
26+
base_offline_token_exchange_request = {
2727
client_id: ShopifyAPI::Context.api_key,
2828
client_secret: ShopifyAPI::Context.api_secret_key,
2929
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
3030
subject_token_type: "urn:ietf:params:oauth:token-type:id_token",
3131
subject_token: @session_token,
3232
requested_token_type: "urn:shopify:params:oauth:token-type:offline-access-token",
3333
}
34+
@non_expiring_offline_token_exchange_request = base_offline_token_exchange_request.merge({ expiring: 0 })
35+
@expiring_offline_token_exchange_request = base_offline_token_exchange_request.merge({ expiring: 1 })
36+
37+
@online_token_exchange_request = base_offline_token_exchange_request.merge(
38+
{ requested_token_type: "urn:shopify:params:oauth:token-type:online-access-token" },
39+
)
40+
3441
@offline_token_response = {
3542
access_token: SecureRandom.alphanumeric(10),
3643
scope: "scope1,scope2",
3744
session: SecureRandom.alphanumeric(10),
3845
}
46+
@expiring_offline_token_response = @offline_token_response.merge(
47+
{
48+
expires_in: 2000,
49+
refresh_token: SecureRandom.alphanumeric(10),
50+
refresh_token_expires_in: 4000,
51+
},
52+
)
53+
3954
@online_token_response = {
4055
access_token: SecureRandom.alphanumeric(10),
4156
scope: "scope1,scope2",
@@ -117,7 +132,7 @@ def test_exchange_token_invalid_session_token
117132
def test_exchange_token_rejected_session_token
118133
modify_context(is_embedded: true)
119134
stub_request(:post, "https://#{@shop}/admin/oauth/access_token")
120-
.with(body: @token_exchange_request)
135+
.with(body: @non_expiring_offline_token_exchange_request)
121136
.to_return(
122137
status: 400,
123138
body: { error: "invalid_subject_token" }.to_json,
@@ -134,9 +149,9 @@ def test_exchange_token_rejected_session_token
134149
end
135150

136151
def test_exchange_token_offline_token
137-
modify_context(is_embedded: true)
152+
modify_context(is_embedded: true, expiring_offline_access_tokens: false)
138153
stub_request(:post, "https://#{@shop}/admin/oauth/access_token")
139-
.with(body: @token_exchange_request)
154+
.with(body: @non_expiring_offline_token_exchange_request)
140155
.to_return(body: @offline_token_response.to_json, headers: { content_type: "application/json" })
141156
expected_session = ShopifyAPI::Auth::Session.new(
142157
id: "offline_#{@shop}",
@@ -146,6 +161,8 @@ def test_exchange_token_offline_token
146161
is_online: false,
147162
expires: nil,
148163
shopify_session_id: @offline_token_response[:session],
164+
refresh_token: nil,
165+
refresh_token_expires: nil,
149166
)
150167

151168
session = ShopifyAPI::Auth::TokenExchange.exchange_token(
@@ -157,12 +174,38 @@ def test_exchange_token_offline_token
157174
assert_equal(expected_session, session)
158175
end
159176

177+
def test_exchange_token_expiring_offline_token
178+
modify_context(is_embedded: true, expiring_offline_access_tokens: true)
179+
stub_request(:post, "https://#{@shop}/admin/oauth/access_token")
180+
.with(body: @expiring_offline_token_exchange_request)
181+
.to_return(body: @expiring_offline_token_response.to_json, headers: { content_type: "application/json" })
182+
expected_session = ShopifyAPI::Auth::Session.new(
183+
id: "offline_#{@shop}",
184+
shop: @shop,
185+
access_token: @expiring_offline_token_response[:access_token],
186+
scope: @expiring_offline_token_response[:scope],
187+
is_online: false,
188+
expires: @stubbed_time_now + @expiring_offline_token_response[:expires_in].to_i,
189+
shopify_session_id: @expiring_offline_token_response[:session],
190+
refresh_token: @expiring_offline_token_response[:refresh_token],
191+
refresh_token_expires: @stubbed_time_now + @expiring_offline_token_response[:refresh_token_expires_in].to_i,
192+
)
193+
194+
session = Time.stub(:now, @stubbed_time_now) do
195+
ShopifyAPI::Auth::TokenExchange.exchange_token(
196+
shop: @shop,
197+
session_token: @session_token,
198+
requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN,
199+
)
200+
end
201+
202+
assert_equal(expected_session, session)
203+
end
204+
160205
def test_exchange_token_online_token
161206
modify_context(is_embedded: true)
162207
stub_request(:post, "https://#{@shop}/admin/oauth/access_token")
163-
.with(body: @token_exchange_request.dup.tap do |h|
164-
h[:requested_token_type] = "urn:shopify:params:oauth:token-type:online-access-token"
165-
end)
208+
.with(body: @online_token_exchange_request)
166209
.to_return(body: @online_token_response.to_json, headers: { content_type: "application/json" })
167210
expected_session = ShopifyAPI::Auth::Session.new(
168211
id: "#{@shop}_#{@online_token_response[:associated_user][:id]}",

test/test_helper.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def setup
5353
old_api_secret_key: T.nilable(String),
5454
response_as_struct: T.nilable(T::Boolean),
5555
api_host: T.nilable(String),
56+
expiring_offline_access_tokens: T.nilable(T::Boolean),
5657
).void
5758
end
5859
def modify_context(
@@ -68,7 +69,8 @@ def modify_context(
6869
user_agent_prefix: nil,
6970
old_api_secret_key: nil,
7071
response_as_struct: nil,
71-
api_host: nil
72+
api_host: nil,
73+
expiring_offline_access_tokens: nil
7274
)
7375
ShopifyAPI::Context.setup(
7476
api_key: api_key ? api_key : ShopifyAPI::Context.api_key,
@@ -85,6 +87,7 @@ def modify_context(
8587
log_level: :off,
8688
response_as_struct: response_as_struct || ShopifyAPI::Context.response_as_struct,
8789
api_host: api_host || ShopifyAPI::Context.api_host,
90+
expiring_offline_access_tokens: expiring_offline_access_tokens || ShopifyAPI::Context.expiring_offline_access_tokens,
8891
)
8992
end
9093
end

0 commit comments

Comments
 (0)