ruby_shopify_app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.babelrc +5 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/ISSUE_TEMPLATE/bug-report.md +63 -0
- data/.github/ISSUE_TEMPLATE/config.yml +1 -0
- data/.github/ISSUE_TEMPLATE/feature-request.md +33 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.github/probots.yml +2 -0
- data/.github/workflows/build.yml +40 -0
- data/.github/workflows/release.yml +24 -0
- data/.github/workflows/rubocop.yml +22 -0
- data/.gitignore +14 -0
- data/.nvmrc +1 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG-OLD.md +643 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +81 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +280 -0
- data/LICENSE +19 -0
- data/README.md +132 -0
- data/Rakefile +7 -0
- data/SECURITY.md +59 -0
- data/app/assets/images/storage_access.svg +1 -0
- data/app/assets/javascripts/shopify_app/app_bridge_2.0.12.js +10 -0
- data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +22 -0
- data/app/assets/javascripts/shopify_app/enable_cookies.js +3 -0
- data/app/assets/javascripts/shopify_app/itp_helper.js +40 -0
- data/app/assets/javascripts/shopify_app/partition_cookies.js +8 -0
- data/app/assets/javascripts/shopify_app/post_redirect.js +9 -0
- data/app/assets/javascripts/shopify_app/redirect.js +31 -0
- data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
- data/app/assets/javascripts/shopify_app/storage_access.js +148 -0
- data/app/assets/javascripts/shopify_app/storage_access_redirect.js +17 -0
- data/app/assets/javascripts/shopify_app/top_level.js +2 -0
- data/app/assets/javascripts/shopify_app/top_level_interaction.js +11 -0
- data/app/controllers/concerns/shopify_app/authenticated.rb +16 -0
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +39 -0
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +40 -0
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +32 -0
- data/app/controllers/shopify_app/authenticated_controller.rb +8 -0
- data/app/controllers/shopify_app/callback_controller.rb +195 -0
- data/app/controllers/shopify_app/extension_verification_controller.rb +15 -0
- data/app/controllers/shopify_app/sessions_controller.rb +202 -0
- data/app/controllers/shopify_app/webhooks_controller.rb +36 -0
- data/app/views/shopify_app/partials/_button_styles.html.erb +109 -0
- data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
- data/app/views/shopify_app/partials/_empty_state_styles.html.erb +98 -0
- data/app/views/shopify_app/partials/_form_styles.html.erb +56 -0
- data/app/views/shopify_app/partials/_layout_styles.html.erb +182 -0
- data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +70 -0
- data/app/views/shopify_app/sessions/new.html.erb +51 -0
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +68 -0
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +63 -0
- data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +13 -0
- data/app/views/shopify_app/shared/redirect.html.erb +23 -0
- data/config/locales/cs.yml +23 -0
- data/config/locales/da.yml +20 -0
- data/config/locales/de.yml +22 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +22 -0
- data/config/locales/fi.yml +20 -0
- data/config/locales/fr.yml +23 -0
- data/config/locales/it.yml +21 -0
- data/config/locales/ja.yml +17 -0
- data/config/locales/ko.yml +19 -0
- data/config/locales/nb.yml +21 -0
- data/config/locales/nl.yml +21 -0
- data/config/locales/pl.yml +21 -0
- data/config/locales/pt-BR.yml +21 -0
- data/config/locales/pt-PT.yml +22 -0
- data/config/locales/sv.yml +21 -0
- data/config/locales/th.yml +20 -0
- data/config/locales/tr.yml +22 -0
- data/config/locales/vi.yml +22 -0
- data/config/locales/zh-CN.yml +16 -0
- data/config/locales/zh-TW.yml +16 -0
- data/config/routes.rb +23 -0
- data/docs/Quickstart.md +31 -0
- data/docs/Releasing.md +21 -0
- data/docs/Troubleshooting.md +159 -0
- data/docs/Upgrading.md +132 -0
- data/docs/shopify_app/authentication.md +124 -0
- data/docs/shopify_app/engine.md +82 -0
- data/docs/shopify_app/generators.md +127 -0
- data/docs/shopify_app/handling-access-scopes-changes.md +24 -0
- data/docs/shopify_app/script-tags.md +28 -0
- data/docs/shopify_app/session-repository.md +88 -0
- data/docs/shopify_app/testing.md +38 -0
- data/docs/shopify_app/webhooks.md +72 -0
- data/images/app-proxy-screenshot.png +0 -0
- data/karma.conf.js +44 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +47 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +11 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +40 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +69 -0
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +13 -0
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +26 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +8 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +11 -0
- data/lib/generators/shopify_app/app_proxy_controller/templates/index.html.erb +19 -0
- data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +15 -0
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +5 -0
- data/lib/generators/shopify_app/controllers/controllers_generator.rb +30 -0
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +53 -0
- data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +18 -0
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +75 -0
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +12 -0
- data/lib/generators/shopify_app/install/install_generator.rb +121 -0
- data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +3 -0
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +44 -0
- data/lib/generators/shopify_app/install/templates/flash_messages.js +24 -0
- data/lib/generators/shopify_app/install/templates/omniauth.rb +4 -0
- data/lib/generators/shopify_app/install/templates/session_store.rb +4 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.js +15 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +25 -0
- data/lib/generators/shopify_app/install/templates/shopify_app_importmap.js +13 -0
- data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +8 -0
- data/lib/generators/shopify_app/install/templates/user_agent.rb +6 -0
- data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +19 -0
- data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +8 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +16 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +17 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +42 -0
- data/lib/generators/shopify_app/routes/routes_generator.rb +32 -0
- data/lib/generators/shopify_app/routes/templates/routes.rb +12 -0
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +70 -0
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +5 -0
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +8 -0
- data/lib/generators/shopify_app/shop_model/templates/shops.yml +3 -0
- data/lib/generators/shopify_app/shopify_app_generator.rb +18 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +5 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
- data/lib/generators/shopify_app/user_model/templates/user.rb +8 -0
- data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +70 -0
- data/lib/generators/shopify_app/views/views_generator.rb +30 -0
- data/lib/shopify_app/access_scopes/noop_strategy.rb +13 -0
- data/lib/shopify_app/access_scopes/shop_strategy.rb +24 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +41 -0
- data/lib/shopify_app/configuration.rb +119 -0
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +38 -0
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
- data/lib/shopify_app/controller_concerns/embedded_app.rb +20 -0
- data/lib/shopify_app/controller_concerns/itp.rb +45 -0
- data/lib/shopify_app/controller_concerns/localization.rb +23 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +259 -0
- data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +23 -0
- data/lib/shopify_app/engine.rb +47 -0
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +16 -0
- data/lib/shopify_app/jobs/webhooks_manager_job.rb +16 -0
- data/lib/shopify_app/managers/scripttags_manager.rb +78 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +62 -0
- data/lib/shopify_app/middleware/jwt_middleware.rb +43 -0
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +34 -0
- data/lib/shopify_app/omniauth/omniauth_configuration.rb +64 -0
- data/lib/shopify_app/session/in_memory_session_store.rb +31 -0
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +16 -0
- data/lib/shopify_app/session/in_memory_user_session_store.rb +16 -0
- data/lib/shopify_app/session/jwt.rb +67 -0
- data/lib/shopify_app/session/null_user_session_store.rb +22 -0
- data/lib/shopify_app/session/session_repository.rb +56 -0
- data/lib/shopify_app/session/session_storage.rb +20 -0
- data/lib/shopify_app/session/shop_session_storage.rb +42 -0
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/session/user_session_storage.rb +42 -0
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/test_helpers/all.rb +2 -0
- data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
- data/lib/shopify_app/utils.rb +37 -0
- data/lib/shopify_app/version.rb +4 -0
- data/lib/shopify_app.rb +80 -0
- data/package.json +27 -0
- data/service.yml +4 -0
- data/shipit.rubygems.yml +4 -0
- data/shopify_app.gemspec +39 -0
- data/translation.yml +7 -0
- data/webpack.config.js +24 -0
- data/yarn.lock +5230 -0
- metadata +465 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
class InMemoryShopSessionStore < InMemorySessionStore
|
|
4
|
+
class << self
|
|
5
|
+
def store(session, *args)
|
|
6
|
+
id = super
|
|
7
|
+
repo[session.domain] = session
|
|
8
|
+
id
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def retrieve_by_shopify_domain(shopify_domain)
|
|
12
|
+
repo[shopify_domain]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
class InMemoryUserSessionStore < InMemorySessionStore
|
|
4
|
+
class << self
|
|
5
|
+
def store(session, user)
|
|
6
|
+
id = super
|
|
7
|
+
repo[user.shopify_user_id] = session
|
|
8
|
+
id
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def retrieve_by_shopify_user_id(user_id)
|
|
12
|
+
repo[user_id]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
class JWT
|
|
4
|
+
class InvalidDestinationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class MismatchedHostsError < StandardError; end
|
|
7
|
+
|
|
8
|
+
class InvalidAudienceError < StandardError; end
|
|
9
|
+
|
|
10
|
+
WARN_EXCEPTIONS = [
|
|
11
|
+
::JWT::DecodeError,
|
|
12
|
+
::JWT::ExpiredSignature,
|
|
13
|
+
::JWT::ImmatureSignature,
|
|
14
|
+
::JWT::VerificationError,
|
|
15
|
+
InvalidAudienceError,
|
|
16
|
+
InvalidDestinationError,
|
|
17
|
+
MismatchedHostsError,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def initialize(token)
|
|
21
|
+
@token = token
|
|
22
|
+
set_payload
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def shopify_domain
|
|
26
|
+
@payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def shopify_user_id
|
|
30
|
+
@payload['sub'].to_i if @payload && @payload['sub']
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def expire_at
|
|
34
|
+
@payload['exp'].to_i if @payload && @payload['exp']
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def set_payload
|
|
40
|
+
payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
|
|
41
|
+
@payload = validate_payload(payload)
|
|
42
|
+
rescue *WARN_EXCEPTIONS => error
|
|
43
|
+
Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_token_data(secret, old_secret)
|
|
48
|
+
::JWT.decode(@token, secret, true, { algorithm: 'HS256' })
|
|
49
|
+
rescue ::JWT::VerificationError
|
|
50
|
+
raise unless old_secret
|
|
51
|
+
|
|
52
|
+
::JWT.decode(@token, old_secret, true, { algorithm: 'HS256' })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_payload(payload)
|
|
56
|
+
dest_host = ShopifyApp::Utils.sanitize_shop_domain(payload['dest'])
|
|
57
|
+
iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload['iss'])
|
|
58
|
+
api_key = ShopifyApp.configuration.api_key
|
|
59
|
+
|
|
60
|
+
raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload['aud'] == api_key
|
|
61
|
+
raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
|
|
62
|
+
raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
|
|
63
|
+
|
|
64
|
+
payload
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
class NullUserSessionStore
|
|
4
|
+
class << self
|
|
5
|
+
def retrieve(_)
|
|
6
|
+
nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def store(_, _)
|
|
10
|
+
raise SessionRepository::ConfigurationError, 'user_storage is not configured'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def retrieve_by_shopify_user_id(_)
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def blank?
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
class SessionRepository
|
|
4
|
+
class ConfigurationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
attr_writer :shop_storage
|
|
8
|
+
|
|
9
|
+
attr_writer :user_storage
|
|
10
|
+
|
|
11
|
+
def retrieve_shop_session(id)
|
|
12
|
+
shop_storage.retrieve(id)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def retrieve_user_session(id)
|
|
16
|
+
user_storage.retrieve(id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retrieve_shop_session_by_shopify_domain(shopify_domain)
|
|
20
|
+
shop_storage.retrieve_by_shopify_domain(shopify_domain)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def retrieve_user_session_by_shopify_user_id(user_id)
|
|
24
|
+
user_storage.retrieve_by_shopify_user_id(user_id)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def store_shop_session(session)
|
|
28
|
+
shop_storage.store(session)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def store_user_session(session, user)
|
|
32
|
+
user_storage.store(session, user)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def shop_storage
|
|
36
|
+
load_shop_storage || raise(ConfigurationError, "ShopifySessionRepository.shop_storage is not configured!")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def user_storage
|
|
40
|
+
load_user_storage
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def load_shop_storage
|
|
46
|
+
return unless @shop_storage
|
|
47
|
+
@shop_storage.respond_to?(:safe_constantize) ? @shop_storage.safe_constantize : @shop_storage
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_user_storage
|
|
51
|
+
return NullUserSessionStore unless @user_storage
|
|
52
|
+
@user_storage.respond_to?(:safe_constantize) ? @user_storage.safe_constantize : @user_storage
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module SessionStorage
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
validates :shopify_token, presence: true
|
|
8
|
+
validates :api_version, presence: true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def with_shopify_session(&block)
|
|
12
|
+
ShopifyAPI::Session.temp(
|
|
13
|
+
domain: shopify_domain,
|
|
14
|
+
token: shopify_token,
|
|
15
|
+
api_version: api_version,
|
|
16
|
+
&block
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module ShopSessionStorage
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include ::ShopifyApp::SessionStorage
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def store(auth_session, *_args)
|
|
13
|
+
shop = find_or_initialize_by(shopify_domain: auth_session.domain)
|
|
14
|
+
shop.shopify_token = auth_session.token
|
|
15
|
+
shop.save!
|
|
16
|
+
shop.id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retrieve(id)
|
|
20
|
+
shop = find_by(id: id)
|
|
21
|
+
construct_session(shop)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def retrieve_by_shopify_domain(domain)
|
|
25
|
+
shop = find_by(shopify_domain: domain)
|
|
26
|
+
construct_session(shop)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def construct_session(shop)
|
|
32
|
+
return unless shop
|
|
33
|
+
|
|
34
|
+
ShopifyAPI::Session.new(
|
|
35
|
+
domain: shop.shopify_domain,
|
|
36
|
+
token: shop.shopify_token,
|
|
37
|
+
api_version: shop.api_version,
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyApp
|
|
4
|
+
module ShopSessionStorageWithScopes
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
include ::ShopifyApp::SessionStorage
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_methods do
|
|
13
|
+
def store(auth_session, *_args)
|
|
14
|
+
shop = find_or_initialize_by(shopify_domain: auth_session.domain)
|
|
15
|
+
shop.shopify_token = auth_session.token
|
|
16
|
+
shop.access_scopes = auth_session.access_scopes
|
|
17
|
+
|
|
18
|
+
shop.save!
|
|
19
|
+
shop.id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def retrieve(id)
|
|
23
|
+
shop = find_by(id: id)
|
|
24
|
+
construct_session(shop)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def retrieve_by_shopify_domain(domain)
|
|
28
|
+
shop = find_by(shopify_domain: domain)
|
|
29
|
+
construct_session(shop)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def construct_session(shop)
|
|
35
|
+
return unless shop
|
|
36
|
+
|
|
37
|
+
ShopifyAPI::Session.new(
|
|
38
|
+
domain: shop.shopify_domain,
|
|
39
|
+
token: shop.shopify_token,
|
|
40
|
+
api_version: shop.api_version,
|
|
41
|
+
access_scopes: shop.access_scopes
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def access_scopes=(scopes)
|
|
47
|
+
super(scopes)
|
|
48
|
+
rescue NotImplementedError, NoMethodError
|
|
49
|
+
raise NotImplementedError, "#access_scopes= must be defined to handle storing access scopes: #{scopes}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def access_scopes
|
|
53
|
+
super
|
|
54
|
+
rescue NotImplementedError, NoMethodError
|
|
55
|
+
raise NotImplementedError, "#access_scopes= must be defined to hook into stored access scopes"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module UserSessionStorage
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include ::ShopifyApp::SessionStorage
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
validates :shopify_domain, presence: true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def store(auth_session, user)
|
|
13
|
+
user = find_or_initialize_by(shopify_user_id: user[:id])
|
|
14
|
+
user.shopify_token = auth_session.token
|
|
15
|
+
user.shopify_domain = auth_session.domain
|
|
16
|
+
user.save!
|
|
17
|
+
user.id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def retrieve(id)
|
|
21
|
+
user = find_by(id: id)
|
|
22
|
+
construct_session(user)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def retrieve_by_shopify_user_id(user_id)
|
|
26
|
+
user = find_by(shopify_user_id: user_id)
|
|
27
|
+
construct_session(user)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def construct_session(user)
|
|
33
|
+
return unless user
|
|
34
|
+
ShopifyAPI::Session.new(
|
|
35
|
+
domain: user.shopify_domain,
|
|
36
|
+
token: user.shopify_token,
|
|
37
|
+
api_version: user.api_version,
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module UserSessionStorageWithScopes
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include ::ShopifyApp::SessionStorage
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
validates :shopify_domain, presence: true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def store(auth_session, user)
|
|
13
|
+
user = find_or_initialize_by(shopify_user_id: user[:id])
|
|
14
|
+
user.shopify_token = auth_session.token
|
|
15
|
+
user.shopify_domain = auth_session.domain
|
|
16
|
+
user.access_scopes = auth_session.access_scopes
|
|
17
|
+
|
|
18
|
+
user.save!
|
|
19
|
+
user.id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def retrieve(id)
|
|
23
|
+
user = find_by(id: id)
|
|
24
|
+
construct_session(user)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def retrieve_by_shopify_user_id(user_id)
|
|
28
|
+
user = find_by(shopify_user_id: user_id)
|
|
29
|
+
construct_session(user)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def construct_session(user)
|
|
35
|
+
return unless user
|
|
36
|
+
|
|
37
|
+
ShopifyAPI::Session.new(
|
|
38
|
+
domain: user.shopify_domain,
|
|
39
|
+
token: user.shopify_token,
|
|
40
|
+
api_version: user.api_version,
|
|
41
|
+
access_scopes: user.access_scopes
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def access_scopes=(scopes)
|
|
47
|
+
super(scopes)
|
|
48
|
+
rescue NotImplementedError, NoMethodError
|
|
49
|
+
raise NotImplementedError, "#access_scopes= must be defined to handle storing access scopes: #{scopes}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def access_scopes
|
|
53
|
+
super
|
|
54
|
+
rescue NotImplementedError, NoMethodError
|
|
55
|
+
raise NotImplementedError, "#access_scopes= must be defined to hook into stored access scopes"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module TestHelpers
|
|
4
|
+
module WebhookVerificationHelper
|
|
5
|
+
def authorized_webhook_verification_headers!(params = {})
|
|
6
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
7
|
+
secret = ShopifyApp.configuration.secret
|
|
8
|
+
valid_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, secret, params.to_query)).strip
|
|
9
|
+
@request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = valid_hmac
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def unauthorized_webhook_verification_headers!
|
|
13
|
+
@request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = "invalid_hmac"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module ShopifyApp
|
|
3
|
+
module Utils
|
|
4
|
+
def self.sanitize_shop_domain(shop_domain)
|
|
5
|
+
myshopify_domain = ShopifyApp.configuration.myshopify_domain
|
|
6
|
+
name = shop_domain.to_s.downcase.strip
|
|
7
|
+
name += ".#{myshopify_domain}" if !name.include?(myshopify_domain.to_s) && !name.include?(".")
|
|
8
|
+
name.sub!(%r|https?://|, '')
|
|
9
|
+
|
|
10
|
+
u = URI("http://#{name}")
|
|
11
|
+
u.host if u.host&.match(/^[a-z0-9][a-z0-9\-]*[a-z0-9]\.#{Regexp.escape(myshopify_domain)}$/)
|
|
12
|
+
rescue URI::InvalidURIError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.fetch_known_api_versions
|
|
17
|
+
Rails.logger.info("[ShopifyAPI::ApiVersion] Fetching known Admin API Versions from Shopify...")
|
|
18
|
+
ShopifyAPI::ApiVersion.fetch_known_versions
|
|
19
|
+
Rails.logger.info("[ShopifyAPI::ApiVersion] Known API Versions: #{ShopifyAPI::ApiVersion.versions.keys}")
|
|
20
|
+
rescue ActiveResource::ConnectionError
|
|
21
|
+
logger.error("[ShopifyAPI::ApiVersion] Unable to fetch api_versions from Shopify")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.shop_login_url(shop:, host:, return_to:)
|
|
25
|
+
return ShopifyApp.configuration.login_url unless shop
|
|
26
|
+
url = URI(ShopifyApp.configuration.login_url)
|
|
27
|
+
|
|
28
|
+
url.query = URI.encode_www_form(
|
|
29
|
+
shop: shop,
|
|
30
|
+
host: host,
|
|
31
|
+
return_to: return_to,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
url.to_s
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/shopify_app.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'shopify_app/version'
|
|
3
|
+
|
|
4
|
+
# deps
|
|
5
|
+
require 'ruby_shopify_api'
|
|
6
|
+
require 'omniauth/rails_csrf_protection'
|
|
7
|
+
require 'omniauth-shopify-oauth2'
|
|
8
|
+
require 'redirect_safely'
|
|
9
|
+
|
|
10
|
+
module ShopifyApp
|
|
11
|
+
def self.rails6?
|
|
12
|
+
Rails::VERSION::MAJOR >= 6
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.rails7?
|
|
16
|
+
Rails::VERSION::MAJOR >= 7
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.use_importmap?
|
|
20
|
+
rails7? && File.exist?("config/importmap.rb")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.use_webpacker?
|
|
24
|
+
rails6? &&
|
|
25
|
+
defined?(Webpacker) == 'constant' &&
|
|
26
|
+
!configuration.disable_webpacker
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# config
|
|
30
|
+
require 'shopify_app/configuration'
|
|
31
|
+
|
|
32
|
+
# engine
|
|
33
|
+
require 'shopify_app/engine'
|
|
34
|
+
|
|
35
|
+
# utils
|
|
36
|
+
require 'shopify_app/utils'
|
|
37
|
+
|
|
38
|
+
# controller concerns
|
|
39
|
+
require 'shopify_app/controller_concerns/csrf_protection'
|
|
40
|
+
require 'shopify_app/controller_concerns/localization'
|
|
41
|
+
require 'shopify_app/controller_concerns/itp'
|
|
42
|
+
require 'shopify_app/controller_concerns/login_protection'
|
|
43
|
+
require 'shopify_app/controller_concerns/embedded_app'
|
|
44
|
+
require 'shopify_app/controller_concerns/payload_verification'
|
|
45
|
+
require 'shopify_app/controller_concerns/app_proxy_verification'
|
|
46
|
+
require 'shopify_app/controller_concerns/webhook_verification'
|
|
47
|
+
|
|
48
|
+
# jobs
|
|
49
|
+
require 'shopify_app/jobs/webhooks_manager_job'
|
|
50
|
+
require 'shopify_app/jobs/scripttags_manager_job'
|
|
51
|
+
|
|
52
|
+
# managers
|
|
53
|
+
require 'shopify_app/managers/webhooks_manager'
|
|
54
|
+
require 'shopify_app/managers/scripttags_manager'
|
|
55
|
+
|
|
56
|
+
# middleware
|
|
57
|
+
require 'shopify_app/middleware/jwt_middleware'
|
|
58
|
+
require 'shopify_app/middleware/same_site_cookie_middleware'
|
|
59
|
+
|
|
60
|
+
# session
|
|
61
|
+
require 'shopify_app/session/in_memory_session_store'
|
|
62
|
+
require 'shopify_app/session/in_memory_shop_session_store'
|
|
63
|
+
require 'shopify_app/session/in_memory_user_session_store'
|
|
64
|
+
require 'shopify_app/session/jwt'
|
|
65
|
+
require 'shopify_app/session/null_user_session_store'
|
|
66
|
+
require 'shopify_app/session/session_repository'
|
|
67
|
+
require 'shopify_app/session/session_storage'
|
|
68
|
+
require 'shopify_app/session/shop_session_storage'
|
|
69
|
+
require 'shopify_app/session/shop_session_storage_with_scopes'
|
|
70
|
+
require 'shopify_app/session/user_session_storage'
|
|
71
|
+
require 'shopify_app/session/user_session_storage_with_scopes'
|
|
72
|
+
|
|
73
|
+
# access scopes strategies
|
|
74
|
+
require 'shopify_app/access_scopes/shop_strategy'
|
|
75
|
+
require 'shopify_app/access_scopes/user_strategy'
|
|
76
|
+
require 'shopify_app/access_scopes/noop_strategy'
|
|
77
|
+
|
|
78
|
+
# omniauth_configuration
|
|
79
|
+
require 'shopify_app/omniauth/omniauth_configuration'
|
|
80
|
+
end
|
data/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shopify_app",
|
|
3
|
+
"version": "18.1.2",
|
|
4
|
+
"repository": "git@github.com:Shopify/shopify_app.git",
|
|
5
|
+
"author": "Shopify",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"dependencies": {},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"babel-loader": "^8.0.6",
|
|
10
|
+
"babel-preset-shopify": "^21.0.0",
|
|
11
|
+
"chai": "^4.1.2",
|
|
12
|
+
"karma": "^5.2.1",
|
|
13
|
+
"karma-chai-sinon": "^0.1.5",
|
|
14
|
+
"karma-chrome-launcher": "^3.1.0",
|
|
15
|
+
"karma-cli": "^2.0.0",
|
|
16
|
+
"karma-mocha": "^2.0.1",
|
|
17
|
+
"karma-mocha-clean-reporter": "^0.0.1",
|
|
18
|
+
"karma-webpack": "^4.0.2",
|
|
19
|
+
"mocha": "^8.1.3",
|
|
20
|
+
"sinon": "^9.0.3",
|
|
21
|
+
"sinon-chai": "^3.2.0",
|
|
22
|
+
"webpack": "^4.44.1"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "./node_modules/.bin/karma start --browsers ChromeHeadless --single-run"
|
|
26
|
+
}
|
|
27
|
+
}
|
data/service.yml
ADDED
data/shipit.rubygems.yml
ADDED
data/shopify_app.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
$LOAD_PATH.push(File.expand_path('../lib', __FILE__))
|
|
3
|
+
require "shopify_app/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "ruby_shopify_app"
|
|
7
|
+
s.version = ShopifyApp::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Hopper Gee"]
|
|
10
|
+
s.email = ["hopper.gee@hey.com"]
|
|
11
|
+
s.summary = 'This gem is used to get quickly started with the Shopify API'
|
|
12
|
+
|
|
13
|
+
s.required_ruby_version = ">= 2.6"
|
|
14
|
+
|
|
15
|
+
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
16
|
+
|
|
17
|
+
s.add_runtime_dependency('browser_sniffer', '~> 1.4.0')
|
|
18
|
+
s.add_runtime_dependency('omniauth-rails_csrf_protection')
|
|
19
|
+
s.add_runtime_dependency('rails', '> 5.2.1')
|
|
20
|
+
s.add_runtime_dependency('ruby_shopify_api', '~> 1.0')
|
|
21
|
+
s.add_runtime_dependency('omniauth-shopify-oauth2', '~> 2.3')
|
|
22
|
+
s.add_runtime_dependency('jwt', '>= 2.2.3')
|
|
23
|
+
s.add_runtime_dependency('redirect_safely', '~> 1.0')
|
|
24
|
+
|
|
25
|
+
s.add_development_dependency('rake')
|
|
26
|
+
s.add_development_dependency('byebug')
|
|
27
|
+
s.add_development_dependency('pry')
|
|
28
|
+
s.add_development_dependency('pry-nav')
|
|
29
|
+
s.add_development_dependency('pry-stack_explorer')
|
|
30
|
+
s.add_development_dependency('rb-readline')
|
|
31
|
+
s.add_development_dependency('sqlite3', '~> 1.4')
|
|
32
|
+
s.add_development_dependency('minitest')
|
|
33
|
+
s.add_development_dependency('mocha')
|
|
34
|
+
s.add_development_dependency('webmock')
|
|
35
|
+
|
|
36
|
+
s.files = %x(git ls-files).split("\n").reject { |f| f.match(%r{^(test|example)/}) }
|
|
37
|
+
s.test_files = %x(git ls-files -- {test}/*).split("\n")
|
|
38
|
+
s.require_paths = ["lib"]
|
|
39
|
+
end
|
data/translation.yml
ADDED
data/webpack.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var webpack = require('webpack');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
mode: 'development',
|
|
7
|
+
entry: 'test/javascripts/test.js',
|
|
8
|
+
output: {
|
|
9
|
+
path: path.resolve(__dirname, 'build'),
|
|
10
|
+
filename: 'test.bundle.js'
|
|
11
|
+
},
|
|
12
|
+
module: {
|
|
13
|
+
loaders: [
|
|
14
|
+
{
|
|
15
|
+
test: /\.js$/,
|
|
16
|
+
loader: 'babel-loader',
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
stats: {
|
|
21
|
+
colors: true
|
|
22
|
+
},
|
|
23
|
+
devtool: 'source-map'
|
|
24
|
+
};
|