From 00c051149b5904316b6d830644ed82a70aaf4cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gae=CC=88tan=20Bzodek?= Date: Tue, 2 Jan 2024 14:38:54 +0100 Subject: [PATCH] Extract session creation from access token response --- lib/shopify_api/auth/oauth.rb | 35 +--------- .../auth/oauth/access_token_response.rb | 37 +++++++++++ lib/shopify_api/auth/session.rb | 26 ++++++++ lib/shopify_api/auth/token_exchange.rb | 34 +--------- test/auth/oauth/access_token_response_test.rb | 43 +++++++++++++ test/auth/session_test.rb | 64 +++++++++++++++++++ test/auth/token_exchange_test.rb | 4 ++ 7 files changed, 177 insertions(+), 66 deletions(-) create mode 100644 lib/shopify_api/auth/oauth/access_token_response.rb create mode 100644 test/auth/oauth/access_token_response_test.rb diff --git a/lib/shopify_api/auth/oauth.rb b/lib/shopify_api/auth/oauth.rb index 8046d7b38..1739e405d 100644 --- a/lib/shopify_api/auth/oauth.rb +++ b/lib/shopify_api/auth/oauth.rb @@ -89,7 +89,8 @@ def validate_auth_callback(cookies:, auth_query:) end session_params = T.cast(response.body, T::Hash[String, T.untyped]).to_h - session = create_new_session(session_params, auth_query.shop) + session = Session.for(shop: auth_query.shop, + access_token_response: Oauth::AccessTokenResponse.from_hash(session_params)) cookie = if Context.embedded? SessionCookie.new( @@ -105,38 +106,6 @@ def validate_auth_callback(cookies:, auth_query:) { session: session, cookie: cookie } end - - private - - sig { params(session_params: T::Hash[String, T.untyped], shop: String).returns(Session) } - def create_new_session(session_params, shop) - session_params = session_params.to_h { |k, v| [k.to_sym, v] } - - scope = session_params[:scope] - - is_online = !session_params[:associated_user].nil? - - if is_online - associated_user = AssociatedUser.new(session_params[:associated_user].to_h { |k, v| [k.to_sym, v] }) - expires = Time.now + session_params[:expires_in].to_i - associated_user_scope = session_params[:associated_user_scope] - id = "#{shop}_#{associated_user.id}" - else - id = "offline_#{shop}" - end - - Session.new( - id: id, - shop: shop, - access_token: session_params[:access_token], - scope: scope, - is_online: is_online, - associated_user_scope: associated_user_scope, - associated_user: associated_user, - expires: expires, - shopify_session_id: session_params[:session], - ) - end end end end diff --git a/lib/shopify_api/auth/oauth/access_token_response.rb b/lib/shopify_api/auth/oauth/access_token_response.rb new file mode 100644 index 000000000..504cf3557 --- /dev/null +++ b/lib/shopify_api/auth/oauth/access_token_response.rb @@ -0,0 +1,37 @@ +# typed: strict +# frozen_string_literal: true + +module ShopifyAPI + module Auth + module Oauth + class AccessTokenResponse < T::Struct + extend T::Sig + + const :access_token, String + const :scope, String + const :session, T.nilable(String) + const :expires_in, T.nilable(Integer) + const :associated_user, T.nilable(AssociatedUser) + const :associated_user_scope, T.nilable(String) + + sig { returns(T::Boolean) } + def online_token? + !associated_user.nil? + end + + alias_method :eql?, :== + sig { params(other: T.nilable(AccessTokenResponse)).returns(T::Boolean) } + def ==(other) + return false unless other + + access_token == other.access_token && + scope == other.scope && + session == other.session && + expires_in == other.expires_in && + associated_user == other.associated_user && + associated_user_scope == other.associated_user_scope + end + end + end + end +end diff --git a/lib/shopify_api/auth/session.rb b/lib/shopify_api/auth/session.rb index b58ba8522..81a85b239 100644 --- a/lib/shopify_api/auth/session.rb +++ b/lib/shopify_api/auth/session.rb @@ -89,6 +89,32 @@ def temp(shop:, access_token:, &blk) end end + sig { params(shop: String, access_token_response: Oauth::AccessTokenResponse).returns(Session) } + def for(shop:, access_token_response:) + is_online = access_token_response.online_token? + + if is_online + associated_user = T.must(access_token_response.associated_user) + expires = Time.now + access_token_response.expires_in.to_i + associated_user_scope = access_token_response.associated_user_scope + id = "#{shop}_#{associated_user.id}" + else + id = "offline_#{shop}" + end + + new( + id: id, + shop: shop, + access_token: access_token_response.access_token, + scope: access_token_response.scope, + is_online: is_online, + associated_user_scope: associated_user_scope, + associated_user: associated_user, + expires: expires, + shopify_session_id: access_token_response.session, + ) + end + sig { params(str: String).returns(Session) } def deserialize(str) Oj.load(str) diff --git a/lib/shopify_api/auth/token_exchange.rb b/lib/shopify_api/auth/token_exchange.rb index 4f9ef458f..6307c6c10 100644 --- a/lib/shopify_api/auth/token_exchange.rb +++ b/lib/shopify_api/auth/token_exchange.rb @@ -68,42 +68,10 @@ def exchange_token(shop:, session_token:, requested_token_type:) end session_params = T.cast(response.body, T::Hash[String, T.untyped]).to_h - session = create_new_session(session_params, shop) + session = Session.for(shop: shop, access_token_response: Oauth::AccessTokenResponse.from_hash(session_params)) session end - - private - - sig { params(session_params: T::Hash[String, T.untyped], shop: String).returns(Session) } - def create_new_session(session_params, shop) - session_params = session_params.to_h { |k, v| [k.to_sym, v] } - - scope = session_params[:scope] - - is_online = !session_params[:associated_user].nil? - - if is_online - associated_user = AssociatedUser.new(session_params[:associated_user].to_h { |k, v| [k.to_sym, v] }) - expires = Time.now + session_params[:expires_in].to_i - associated_user_scope = session_params[:associated_user_scope] - id = "#{shop}_#{associated_user.id}" - else - id = "offline_#{shop}" - end - - Session.new( - id: id, - shop: shop, - access_token: session_params[:access_token], - scope: scope, - is_online: is_online, - associated_user_scope: associated_user_scope, - associated_user: associated_user, - expires: expires, - shopify_session_id: session_params[:session], - ) - end end end end diff --git a/test/auth/oauth/access_token_response_test.rb b/test/auth/oauth/access_token_response_test.rb new file mode 100644 index 000000000..ad2bdbcb6 --- /dev/null +++ b/test/auth/oauth/access_token_response_test.rb @@ -0,0 +1,43 @@ +# typed: true +# frozen_string_literal: true + +require_relative "../../test_helper" + +module ShopifyAPITest + module Auth + module Oauth + class AccessTokenResponseTest < Test::Unit::TestCase + def test_online_token_is_false_when_no_associated_user + token_response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new( + access_token: "token", + scope: "scope1, scope2", + ) + + assert(!token_response.online_token?) + end + + def test_online_token_is_true_when_associated_user_is_present + associated_user = ShopifyAPI::Auth::AssociatedUser.new( + id: 902541635, + first_name: "first", + last_name: "last", + email: "firstlast@example.com", + email_verified: true, + account_owner: true, + locale: "en", + collaborator: false, + ) + token_response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new( + access_token: "token", + scope: "scope1, scope2", + expires_in: 1000, + associated_user_scope: "scope1", + associated_user: associated_user, + ) + + assert(token_response.online_token?) + end + end + end + end +end diff --git a/test/auth/session_test.rb b/test/auth/session_test.rb index 3f479d63e..f8fa30946 100644 --- a/test/auth/session_test.rb +++ b/test/auth/session_test.rb @@ -79,6 +79,70 @@ def test_temp_with_block_var assert_equal(session, ShopifyAPI::Context.active_session) end + def test_for_with_offline_access_token_response + shop = "test-shop" + response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new( + access_token: "token", + scope: "scope1, scope2", + session: "session", + ) + expected_session = ShopifyAPI::Auth::Session.new( + id: "offline_#{shop}", + shop: shop, + access_token: response.access_token, + scope: response.scope, + is_online: false, + associated_user_scope: nil, + associated_user: nil, + expires: nil, + shopify_session_id: response.session, + ) + + session = ShopifyAPI::Auth::Session.for(shop: shop, access_token_response: response) + + assert_equal(expected_session, session) + end + + def test_for_with_online_access_token_response + shop = "test-shop" + associated_user = ShopifyAPI::Auth::AssociatedUser.new( + id: 902541635, + first_name: "first", + last_name: "last", + email: "firstlast@example.com", + email_verified: true, + account_owner: true, + locale: "en", + collaborator: false, + ) + response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new( + access_token: "token", + scope: "scope1, scope2", + session: "session", + expires_in: 1000, + associated_user: associated_user, + associated_user_scope: "scope1", + ) + time_now = Time.now + expected_session = ShopifyAPI::Auth::Session.new( + id: "#{shop}_#{associated_user.id}", + shop: shop, + access_token: response.access_token, + scope: response.scope, + is_online: false, + associated_user_scope: response.associated_user_scope, + associated_user: associated_user, + expires: time_now + response.expires_in, + shopify_session_id: response.session, + ) + + session = Time.stub(:now, time_now) do + ShopifyAPI::Auth::Session.for(shop: shop, access_token_response: response) + end + + assert_equal(expected_session, session) + end + def teardown ShopifyAPI::Context.deactivate_session end diff --git a/test/auth/token_exchange_test.rb b/test/auth/token_exchange_test.rb index 6e12e806c..3fa643326 100644 --- a/test/auth/token_exchange_test.rb +++ b/test/auth/token_exchange_test.rb @@ -34,6 +34,7 @@ def setup @offline_token_response = { access_token: SecureRandom.alphanumeric(10), scope: "scope1,scope2", + session: SecureRandom.alphanumeric(10), } @online_token_response = { access_token: SecureRandom.alphanumeric(10), @@ -50,6 +51,7 @@ def setup locale: "en", collaborator: false, }, + session: SecureRandom.alphanumeric(10), } @expected_associated_user = ShopifyAPI::Auth::AssociatedUser.new( @@ -143,6 +145,7 @@ def test_exchange_token_offline_token scope: @offline_token_response[:scope], is_online: false, expires: nil, + shopify_session_id: @offline_token_response[:session], ) session = ShopifyAPI::Auth::TokenExchange.exchange_token( @@ -169,6 +172,7 @@ def test_exchange_token_online_token associated_user_scope: @online_token_response[:associated_user_scope], expires: @stubbed_time_now + @online_token_response[:expires_in].to_i, associated_user: @expected_associated_user, + shopify_session_id: @online_token_response[:session], ) session = Time.stub(:now, @stubbed_time_now) do