shopify_app 17.0.1 → 17.1.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 +4 -4
- 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 +17 -1
- data/.github/workflows/release.yml +6 -2
- data/CHANGELOG.md +21 -0
- data/CONTRIBUTING.md +76 -0
- data/Gemfile.lock +63 -63
- data/README.md +72 -593
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +4 -0
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +32 -0
- data/app/controllers/shopify_app/callback_controller.rb +35 -4
- data/app/controllers/shopify_app/sessions_controller.rb +13 -4
- data/docs/Quickstart.md +15 -77
- data/docs/Upgrading.md +110 -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 +8 -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/lib/generators/shopify_app/home_controller/templates/home_controller.rb +2 -0
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -0
- data/lib/generators/shopify_app/install/install_generator.rb +30 -1
- data/lib/generators/shopify_app/install/templates/omniauth.rb +1 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +20 -14
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +8 -0
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +27 -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/shop.rb +1 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +1 -1
- 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/user.rb +1 -1
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +27 -0
- data/lib/shopify_app.rb +10 -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 +22 -0
- data/lib/shopify_app/omniauth/omniauth_configuration.rb +64 -0
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +9 -7
- data/lib/shopify_app/session/in_memory_user_session_store.rb +9 -7
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +58 -0
- data/lib/shopify_app/utils.rb +12 -0
- data/lib/shopify_app/version.rb +1 -1
- data/package.json +1 -1
- data/shopify_app.gemspec +1 -1
- data/yarn.lock +70 -101
- metadata +27 -8
- data/.github/ISSUE_TEMPLATE.md +0 -19
- data/docs/install-on-dev-shop.png +0 -0
- data/docs/test-your-app.png +0 -0
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +0 -20
@@ -30,9 +30,11 @@ module ShopifyApp
|
|
30
30
|
copy_file('omniauth.rb', 'config/initializers/omniauth.rb')
|
31
31
|
end
|
32
32
|
|
33
|
+
return if !Rails.env.test? && shopify_provider_exists?
|
34
|
+
|
33
35
|
inject_into_file(
|
34
36
|
'config/initializers/omniauth.rb',
|
35
|
-
|
37
|
+
shopify_provider_template,
|
36
38
|
after: "Rails.application.config.middleware.use(OmniAuth::Builder) do\n"
|
37
39
|
)
|
38
40
|
end
|
@@ -72,6 +74,33 @@ module ShopifyApp
|
|
72
74
|
|
73
75
|
private
|
74
76
|
|
77
|
+
def shopify_provider_exists?
|
78
|
+
File.open("config/initializers/omniauth.rb") do |file|
|
79
|
+
file.each_line do |line|
|
80
|
+
if line =~ /provider :shopify/
|
81
|
+
puts "\e[33m#{omniauth_warning}\e[0m"
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def omniauth_warning
|
90
|
+
<<~OMNIAUTH
|
91
|
+
\n[WARNING] The Shopify App generator attempted to add the following Shopify Omniauth \
|
92
|
+
provider 'config/initializers/omniauth.rb':
|
93
|
+
|
94
|
+
\e[0m#{shopify_provider_template}\e[33m
|
95
|
+
|
96
|
+
Consider updating 'config/initializers/omniauth.rb' to match the configuration above.
|
97
|
+
OMNIAUTH
|
98
|
+
end
|
99
|
+
|
100
|
+
def shopify_provider_template
|
101
|
+
File.read(File.expand_path(find_in_source_paths('shopify_provider.rb.tt')))
|
102
|
+
end
|
103
|
+
|
75
104
|
def embedded_app?
|
76
105
|
options['embedded'] == 'true'
|
77
106
|
end
|
@@ -1,17 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
ShopifyApp.configure do |config|
|
2
|
+
config.application_name = "<%= @application_name %>"
|
3
|
+
config.old_secret = "<%= @old_secret %>"
|
4
|
+
config.scope = "<%= @scope %>" # Consult this page for more scope options:
|
5
|
+
# https://help.shopify.com/en/api/getting-started/authentication/oauth/scopes
|
6
|
+
config.embedded_app = <%= embedded_app? %>
|
7
|
+
config.after_authenticate_job = false
|
8
|
+
config.api_version = "<%= @api_version %>"
|
9
|
+
config.shop_session_repository = 'Shop'
|
10
|
+
|
11
|
+
config.reauth_on_access_scope_changes = true
|
12
|
+
|
13
|
+
config.allow_jwt_authentication = <%= !with_cookie_authentication? %>
|
14
|
+
config.allow_cookie_authentication = <%= with_cookie_authentication? %>
|
15
|
+
|
16
|
+
config.api_key = ENV.fetch('SHOPIFY_API_KEY', '').presence
|
17
|
+
config.secret = ENV.fetch('SHOPIFY_API_SECRET', '').presence
|
18
|
+
if defined? Rails::Server
|
19
|
+
raise('Missing SHOPIFY_API_KEY. See https://github.com/Shopify/shopify_app#requirements') unless config.api_key
|
20
|
+
raise('Missing SHOPIFY_API_SECRET. See https://github.com/Shopify/shopify_app#requirements') unless config.secret
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
provider :shopify,
|
2
|
+
ShopifyApp.configuration.api_key,
|
3
|
+
ShopifyApp.configuration.secret,
|
4
|
+
scope: ShopifyApp.configuration.scope,
|
5
|
+
setup: lambda { |env|
|
6
|
+
configuration = ShopifyApp::OmniAuthConfiguration.new(env['omniauth.strategy'], Rack::Request.new(env))
|
7
|
+
configuration.build_options
|
8
|
+
}
|
@@ -8,6 +8,8 @@ module ShopifyApp
|
|
8
8
|
include Rails::Generators::Migration
|
9
9
|
source_root File.expand_path('../templates', __FILE__)
|
10
10
|
|
11
|
+
class_option :new_shopify_cli_app, type: :boolean, default: false
|
12
|
+
|
11
13
|
def create_shop_model
|
12
14
|
copy_file('shop.rb', 'app/models/shop.rb')
|
13
15
|
end
|
@@ -16,6 +18,27 @@ module ShopifyApp
|
|
16
18
|
migration_template('db/migrate/create_shops.erb', 'db/migrate/create_shops.rb')
|
17
19
|
end
|
18
20
|
|
21
|
+
def create_shop_with_access_scopes_migration
|
22
|
+
scopes_column_prompt = <<~PROMPT
|
23
|
+
It is highly recommended that apps record the access scopes granted by \
|
24
|
+
merchants during app installation. See app/models/shop.rb to modify how \
|
25
|
+
access scopes are stored and retrieved.
|
26
|
+
|
27
|
+
[WARNING] You will need to update the access_scopes accessors in the Shop model \
|
28
|
+
to allow shopify_app to store and retrieve scopes when going through OAuth.
|
29
|
+
|
30
|
+
The following migration will add an `access_scopes` column to the Shop model. \
|
31
|
+
Do you want to include this migration? [y/n]
|
32
|
+
PROMPT
|
33
|
+
|
34
|
+
if new_shopify_cli_app? || Rails.env.test? || yes?(scopes_column_prompt)
|
35
|
+
migration_template(
|
36
|
+
'db/migrate/add_shop_access_scopes_column.erb',
|
37
|
+
'db/migrate/add_shop_access_scopes_column.rb'
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
19
42
|
def update_shopify_app_initializer
|
20
43
|
gsub_file('config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryShopSessionStore', 'Shop')
|
21
44
|
end
|
@@ -26,6 +49,10 @@ module ShopifyApp
|
|
26
49
|
|
27
50
|
private
|
28
51
|
|
52
|
+
def new_shopify_cli_app?
|
53
|
+
options['new_shopify_cli_app']
|
54
|
+
end
|
55
|
+
|
29
56
|
def rails_migration_version
|
30
57
|
Rails.version.match(/\d\.\d/)[0]
|
31
58
|
end
|
@@ -9,7 +9,7 @@ module ShopifyApp
|
|
9
9
|
|
10
10
|
def run_all_generators
|
11
11
|
generate("shopify_app:install #{@opts.join(' ')}")
|
12
|
-
generate("shopify_app:shop_model")
|
12
|
+
generate("shopify_app:shop_model #{@opts.join(' ')}")
|
13
13
|
generate("shopify_app:authenticated_controller")
|
14
14
|
generate("shopify_app:home_controller #{@opts.join(' ')}")
|
15
15
|
end
|
@@ -8,6 +8,8 @@ module ShopifyApp
|
|
8
8
|
include Rails::Generators::Migration
|
9
9
|
source_root File.expand_path('../templates', __FILE__)
|
10
10
|
|
11
|
+
class_option :new_shopify_cli_app, type: :boolean, default: false
|
12
|
+
|
11
13
|
def create_user_model
|
12
14
|
copy_file('user.rb', 'app/models/user.rb')
|
13
15
|
end
|
@@ -16,6 +18,27 @@ module ShopifyApp
|
|
16
18
|
migration_template('db/migrate/create_users.erb', 'db/migrate/create_users.rb')
|
17
19
|
end
|
18
20
|
|
21
|
+
def create_scopes_storage_in_user_model
|
22
|
+
scopes_column_prompt = <<~PROMPT
|
23
|
+
It is highly recommended that apps record the access scopes granted by \
|
24
|
+
merchants during app installation. See app/models/user.rb to modify how \
|
25
|
+
access scopes are stored and retrieved.
|
26
|
+
|
27
|
+
[WARNING] You will need to update the access_scopes accessors in the User model \
|
28
|
+
to allow shopify_app to store and retrieve scopes when going through OAuth.
|
29
|
+
|
30
|
+
The following migration will add an `access_scopes` column to the User model. \
|
31
|
+
Do you want to include this migration? [y/n]
|
32
|
+
PROMPT
|
33
|
+
|
34
|
+
if new_shopify_cli_app? || Rails.env.test? || yes?(scopes_column_prompt)
|
35
|
+
migration_template(
|
36
|
+
'db/migrate/add_user_access_scopes_column.erb',
|
37
|
+
'db/migrate/add_user_access_scopes_column.rb'
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
19
42
|
def update_shopify_app_initializer
|
20
43
|
gsub_file('config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryUserSessionStore', 'User')
|
21
44
|
end
|
@@ -26,6 +49,10 @@ module ShopifyApp
|
|
26
49
|
|
27
50
|
private
|
28
51
|
|
52
|
+
def new_shopify_cli_app?
|
53
|
+
options['new_shopify_cli_app']
|
54
|
+
end
|
55
|
+
|
29
56
|
def rails_migration_version
|
30
57
|
Rails.version.match(/\d\.\d/)[0]
|
31
58
|
end
|
data/lib/shopify_app.rb
CHANGED
@@ -57,5 +57,15 @@ module ShopifyApp
|
|
57
57
|
require 'shopify_app/session/session_repository'
|
58
58
|
require 'shopify_app/session/session_storage'
|
59
59
|
require 'shopify_app/session/shop_session_storage'
|
60
|
+
require 'shopify_app/session/shop_session_storage_with_scopes'
|
60
61
|
require 'shopify_app/session/user_session_storage'
|
62
|
+
require 'shopify_app/session/user_session_storage_with_scopes'
|
63
|
+
|
64
|
+
# access scopes strategies
|
65
|
+
require 'shopify_app/access_scopes/shop_strategy'
|
66
|
+
require 'shopify_app/access_scopes/user_strategy'
|
67
|
+
require 'shopify_app/access_scopes/noop_strategy'
|
68
|
+
|
69
|
+
# omniauth_configuration
|
70
|
+
require 'shopify_app/omniauth/omniauth_configuration'
|
61
71
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
module AccessScopes
|
5
|
+
class ShopStrategy
|
6
|
+
class << self
|
7
|
+
def update_access_scopes?(shop_domain)
|
8
|
+
shop_access_scopes = shop_access_scopes(shop_domain)
|
9
|
+
configuration_access_scopes != shop_access_scopes
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def shop_access_scopes(shop_domain)
|
15
|
+
ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(shop_domain)&.access_scopes
|
16
|
+
end
|
17
|
+
|
18
|
+
def configuration_access_scopes
|
19
|
+
ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.shop_access_scopes)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
module AccessScopes
|
5
|
+
class UserStrategy
|
6
|
+
class InvalidInput < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def update_access_scopes?(user_id: nil, shopify_user_id: nil)
|
10
|
+
return update_access_scopes_for_user_id?(user_id) if user_id
|
11
|
+
return update_access_scopes_for_shopify_user_id?(shopify_user_id) if shopify_user_id
|
12
|
+
raise(InvalidInput, '#update_access_scopes? requires user_id or shopify_user_id parameter inputs')
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def update_access_scopes_for_user_id?(user_id)
|
18
|
+
user_access_scopes = user_access_scopes_by_user_id(user_id)
|
19
|
+
configuration_access_scopes != user_access_scopes
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_access_scopes_for_shopify_user_id?(shopify_user_id)
|
23
|
+
user_access_scopes = user_access_scopes_by_shopify_user_id(shopify_user_id)
|
24
|
+
configuration_access_scopes != user_access_scopes
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_access_scopes_by_user_id(user_id)
|
28
|
+
ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.access_scopes
|
29
|
+
end
|
30
|
+
|
31
|
+
def user_access_scopes_by_shopify_user_id(shopify_user_id)
|
32
|
+
ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(shopify_user_id)&.access_scopes
|
33
|
+
end
|
34
|
+
|
35
|
+
def configuration_access_scopes
|
36
|
+
ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.user_access_scopes)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -9,6 +9,8 @@ module ShopifyApp
|
|
9
9
|
attr_accessor :secret
|
10
10
|
attr_accessor :old_secret
|
11
11
|
attr_accessor :scope
|
12
|
+
attr_writer :shop_access_scopes
|
13
|
+
attr_writer :user_access_scopes
|
12
14
|
attr_accessor :embedded_app
|
13
15
|
alias_method :embedded_app?, :embedded_app
|
14
16
|
attr_accessor :webhooks
|
@@ -16,6 +18,8 @@ module ShopifyApp
|
|
16
18
|
attr_accessor :after_authenticate_job
|
17
19
|
attr_accessor :api_version
|
18
20
|
|
21
|
+
attr_accessor :reauth_on_access_scope_changes
|
22
|
+
|
19
23
|
# customise urls
|
20
24
|
attr_accessor :root_url
|
21
25
|
attr_writer :login_url
|
@@ -70,6 +74,16 @@ module ShopifyApp
|
|
70
74
|
ShopifyApp::SessionRepository.shop_storage
|
71
75
|
end
|
72
76
|
|
77
|
+
def shop_access_scopes_strategy
|
78
|
+
return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
|
79
|
+
ShopifyApp::AccessScopes::ShopStrategy
|
80
|
+
end
|
81
|
+
|
82
|
+
def user_access_scopes_strategy
|
83
|
+
return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
|
84
|
+
ShopifyApp::AccessScopes::UserStrategy
|
85
|
+
end
|
86
|
+
|
73
87
|
def has_webhooks?
|
74
88
|
webhooks.present?
|
75
89
|
end
|
@@ -81,6 +95,14 @@ module ShopifyApp
|
|
81
95
|
def enable_same_site_none
|
82
96
|
!Rails.env.test? && (@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none)
|
83
97
|
end
|
98
|
+
|
99
|
+
def shop_access_scopes
|
100
|
+
@shop_access_scopes || scope
|
101
|
+
end
|
102
|
+
|
103
|
+
def user_access_scopes
|
104
|
+
@user_access_scopes || scope
|
105
|
+
end
|
84
106
|
end
|
85
107
|
|
86
108
|
def self.configuration
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
class OmniAuthConfiguration
|
5
|
+
attr_reader :strategy, :request
|
6
|
+
attr_writer :client_options_site, :scopes, :per_user_permissions
|
7
|
+
|
8
|
+
def initialize(strategy, request)
|
9
|
+
@strategy = strategy
|
10
|
+
@request = request
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_options
|
14
|
+
strategy.options[:client_options][:site] = client_options_site
|
15
|
+
strategy.options[:scope] = scopes
|
16
|
+
strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
|
17
|
+
strategy.options[:per_user_permissions] = request_online_tokens?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def request_online_tokens?
|
23
|
+
return @per_user_permissions unless @per_user_permissions.nil?
|
24
|
+
default_request_online_tokens?
|
25
|
+
end
|
26
|
+
|
27
|
+
def scopes
|
28
|
+
@scopes || default_scopes
|
29
|
+
end
|
30
|
+
|
31
|
+
def client_options_site
|
32
|
+
@client_options_site || default_client_options_site
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_scopes
|
36
|
+
if request_online_tokens?
|
37
|
+
ShopifyApp.configuration.user_access_scopes
|
38
|
+
else
|
39
|
+
ShopifyApp.configuration.shop_access_scopes
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_client_options_site
|
44
|
+
return '' unless shop_domain.present?
|
45
|
+
"https://#{shopify_auth_params[:shop]}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_request_online_tokens?
|
49
|
+
strategy.session[:user_tokens] && !update_shop_scopes?
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_shop_scopes?
|
53
|
+
ShopifyApp.configuration.shop_access_scopes_strategy.update_access_scopes?(shop_domain)
|
54
|
+
end
|
55
|
+
|
56
|
+
def shop_domain
|
57
|
+
request.params['shop'] || (shopify_auth_params && shopify_auth_params['shop'])
|
58
|
+
end
|
59
|
+
|
60
|
+
def shopify_auth_params
|
61
|
+
strategy.session['shopify.omniauth_params']&.with_indifferent_access
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|