Skip to content

Commit

Permalink
feat: added support client credentials in shopify-api-js
Browse files Browse the repository at this point in the history
  • Loading branch information
fwaadahmad1 committed Feb 18, 2025
1 parent f21e275 commit 10e8848
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 2 deletions.
52 changes: 52 additions & 0 deletions lib/shopify_api/auth/client_credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# typed: strict
# frozen_string_literal: true

module ShopifyAPI
module Auth
module ClientCredentials
extend T::Sig

CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"

class << self
extend T::Sig

sig do
params(
shop: String,
).returns(ShopifyAPI::Auth::Session)
end
def client_credentials(shop:)
unless ShopifyAPI::Context.setup?
raise ShopifyAPI::Errors::ContextNotSetupError,
"ShopifyAPI::Context not setup, please call ShopifyAPI::Context.setup"
end

shop_session = ShopifyAPI::Auth::Session.new(shop: shop)
body = {
client_id: ShopifyAPI::Context.api_key,
client_secret: ShopifyAPI::Context.api_secret_key,
grant_type: CLIENT_CREDENTIALS_GRANT_TYPE,
}

client = Clients::HttpClient.new(session: shop_session, base_path: "/admin/oauth")
response =
client.request(
Clients::HttpRequest.new(
http_method: :post,
path: "access_token",
body: body,
body_type: "application/json",
),
)
response_hash = T.cast(response.body, T::Hash[String, T.untyped]).to_h

Session.from(
shop: shop,
access_token_response: Oauth::AccessTokenResponse.from_hash(response_hash),
)
end
end
end
end
end
5 changes: 4 additions & 1 deletion lib/shopify_api/auth/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,16 @@ def from(shop:, access_token_response:)

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

if access_token_response.expires_in
expires = Time.now + access_token_response.expires_in.to_i
end

new(
id: id,
shop: shop,
Expand Down
53 changes: 53 additions & 0 deletions test/auth/client_credentials_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# typed: false
# frozen_string_literal: true

require_relative "../test_helper"

module ShopifyAPITest
module Auth
class ClientCredentialsTest < Test::Unit::TestCase
def setup
super()

@stubbed_time_now = Time.now
@shop = "test-shop.myshopify.com"
@client_credentials_request = {
client_id: ShopifyAPI::Context.api_key,
client_secret: ShopifyAPI::Context.api_secret_key,
grant_type: "client_credentials",
}
@offline_token_response = {
access_token: SecureRandom.alphanumeric(10),
scope: "scope1,scope2",
expires_in: 1000,
}
end

def test_client_credentials_context_not_setup
modify_context(api_key: "", api_secret_key: "", host: "")

assert_raises(ShopifyAPI::Errors::ContextNotSetupError) do
ShopifyAPI::Auth::ClientCredentials.client_credentials(shop: @shop)
end
end

def test_client_credentials_offline_token
stub_request(:post, "https://#{@shop}/admin/oauth/access_token")
.with(body: @client_credentials_request)
.to_return(body: @offline_token_response.to_json, headers: { content_type: "application/json" })
expected_session = ShopifyAPI::Auth::Session.new(
id: "offline_#{@shop}",
shop: @shop,
access_token: @offline_token_response[:access_token],
scope: @offline_token_response[:scope],
is_online: false,
expires: @stubbed_time_now + @offline_token_response[:expires_in].to_i,
)

session = ShopifyAPI::Auth::ClientCredentials.client_credentials(shop: @shop)

assert_equal(expected_session, session)
end
end
end
end
26 changes: 25 additions & 1 deletion test/auth/session_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_temp_with_block_var
assert_equal(session, ShopifyAPI::Context.active_session)
end

def test_from_with_offline_access_token_response
def test_from_with_offline_access_token_response_with_no_expires_in
shop = "test-shop"
response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new(
access_token: "token",
Expand All @@ -103,6 +103,30 @@ def test_from_with_offline_access_token_response
assert_equal(expected_session, session)
end

def test_from_with_offline_access_token_response_with_expires_in
shop = "test-shop"
response = ShopifyAPI::Auth::Oauth::AccessTokenResponse.new(
access_token: "token",
scope: "scope1, scope2",
expires_in: 1000,
)

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: Time.now + response.expires_in,
shopify_session_id: response.session,
)

session = ShopifyAPI::Auth::Session.from(shop: shop, access_token_response: response)
assert_equal(expected_session, session)
end

def test_from_with_online_access_token_response
shop = "test-shop"
associated_user = ShopifyAPI::Auth::AssociatedUser.new(
Expand Down

0 comments on commit 10e8848

Please sign in to comment.