shopify_app 11.6.0 → 12.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +122 -115
- data/app/assets/javascripts/shopify_app/itp_helper.js +6 -6
- data/app/assets/javascripts/shopify_app/storage_access.js +31 -2
- data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +8 -2
- data/app/controllers/shopify_app/extension_verification_controller.rb +20 -0
- data/config/locales/pt-BR.yml +1 -1
- data/docs/Quickstart.md +44 -16
- data/docs/install-on-dev-shop.png +0 -0
- data/docs/test-your-app.png +0 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +2 -6
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +0 -6
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +2 -2
- data/lib/generators/shopify_app/install/templates/flash_messages.js +11 -2
- data/lib/generators/shopify_app/install/templates/shopify_app.js +9 -3
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -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 +7 -0
- data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +38 -0
- data/lib/shopify_app.rb +2 -3
- data/lib/shopify_app/configuration.rb +4 -1
- data/lib/shopify_app/controller_concerns/login_protection.rb +29 -4
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +5 -5
- data/lib/shopify_app/session/in_memory_session_store.rb +1 -1
- data/lib/shopify_app/session/session_repository.rb +2 -2
- data/lib/shopify_app/session/session_storage.rb +10 -22
- data/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +23 -0
- data/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +24 -0
- data/lib/shopify_app/version.rb +1 -1
- data/package.json +2 -2
- data/service.yml +1 -1
- data/shopify_app.gemspec +5 -2
- metadata +55 -6
- data/lib/generators/shopify_app/home_controller/templates/shopify_app_ready_script.html.erb +0 -7
- data/lib/shopify_app/controllers/extension_verification_controller.rb +0 -18
@@ -8,7 +8,7 @@ module ShopifyApp
|
|
8
8
|
include ShopifyApp::Localization
|
9
9
|
include ShopifyApp::LoginProtection
|
10
10
|
include ShopifyApp::EmbeddedApp
|
11
|
-
before_action :
|
11
|
+
before_action :login_again_if_different_user_or_shop
|
12
12
|
around_action :shopify_session
|
13
13
|
end
|
14
14
|
end
|
@@ -55,10 +55,16 @@ module ShopifyApp
|
|
55
55
|
token: token,
|
56
56
|
api_version: ShopifyApp.configuration.api_version
|
57
57
|
)
|
58
|
-
|
59
|
-
session[:shopify] = ShopifyApp::SessionRepository.store(session_store)
|
58
|
+
session[:shopify] = ShopifyApp::SessionRepository.store(session_store, user: associated_user)
|
60
59
|
session[:shopify_domain] = shop_name
|
61
60
|
session[:shopify_user] = associated_user
|
61
|
+
|
62
|
+
if ShopifyApp.configuration.per_user_tokens?
|
63
|
+
# Adds the user_session to the session to determine if the logged in user has changed
|
64
|
+
user_session = auth_hash&.extra&.session
|
65
|
+
raise IndexError, "Missing user session signature" if user_session.nil?
|
66
|
+
session[:user_session] = user_session
|
67
|
+
end
|
62
68
|
end
|
63
69
|
|
64
70
|
def install_webhooks
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
class ExtensionVerificationController < ActionController::Base
|
5
|
+
protect_from_forgery with: :null_session
|
6
|
+
before_action :verify_request
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def verify_request
|
11
|
+
hmac_header = request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
|
12
|
+
request_body = request.body.read
|
13
|
+
secret = ShopifyApp.configuration.secret
|
14
|
+
digest = OpenSSL::Digest.new('sha256')
|
15
|
+
|
16
|
+
expected_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, request_body))
|
17
|
+
head(:unauthorized) unless ActiveSupport::SecurityUtils.secure_compare(expected_hmac, hmac_header)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/config/locales/pt-BR.yml
CHANGED
@@ -4,7 +4,7 @@ pt-BR:
|
|
4
4
|
could_not_log_in: Não foi possível fazer login na Shopify store
|
5
5
|
invalid_shop_url: Domínio de loja inválido
|
6
6
|
enable_cookies_heading: Habilitar cookies de %{app}
|
7
|
-
enable_cookies_body: Você
|
7
|
+
enable_cookies_body: Você precisa habilitar manualmente os cookies neste navegador
|
8
8
|
para usar %{app} dentro da Shopify.
|
9
9
|
enable_cookies_footer: Os cookies permitem que o app o autentique armazenando temporariamente
|
10
10
|
suas preferências e dados pessoais. Eles expiram depois de 30 dias.
|
data/docs/Quickstart.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
Quickstart
|
2
2
|
==========
|
3
3
|
|
4
|
-
|
4
|
+
Get started building and deploying a new Shopify App to Heroku in just a few minutes. This guide assumes you have Ruby/Rails installed on your computer already; if you haven't done that already start with [this guide.](https://guides.rubyonrails.org/v5.0/getting_started.html#installing-rails)
|
5
5
|
|
6
6
|
1. New Rails App (with postgres)
|
7
7
|
--------------------------------
|
8
8
|
|
9
|
+
To create a new Rails app and use this generator, open your terminal and run the following commands:
|
10
|
+
|
9
11
|
```sh
|
10
12
|
$ rails new test-app --database=postgresql
|
11
13
|
$ cd test-app
|
@@ -17,43 +19,51 @@ $ git commit -m 'new rails app'
|
|
17
19
|
2. Create a new Heroku app
|
18
20
|
--------------------------
|
19
21
|
|
20
|
-
The next step is to create a new
|
22
|
+
The next step is to create a new Heroku app to host your application. If you haven't got a Heroku account yet, create a free account [here](https://www.heroku.com/).
|
23
|
+
|
24
|
+
Head to the Heroku dashboard and create a new app, or run the following commands with the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli#download-and-install) installed, substituting `name` for the name of your own app:
|
21
25
|
|
22
|
-
|
26
|
+
CLI:
|
23
27
|
```sh
|
24
28
|
$ heroku create name
|
25
29
|
$ heroku git:remote -a name
|
26
30
|
```
|
27
31
|
|
28
|
-
|
32
|
+
Once you have created an app on Heroku, we need to let Git know where the Heroku server is so we can deploy to it later. Copy the app's name from your Heroku dashboard and substitute `appname.git` with the name you chose earlier:
|
29
33
|
|
30
34
|
web:
|
31
35
|
```sh
|
32
36
|
# https://dashboard.heroku.com/new
|
33
|
-
$ git remote add heroku git@heroku.com:
|
37
|
+
$ git remote add heroku git@heroku.com:appname.git
|
34
38
|
```
|
35
39
|
|
36
|
-
3. Create a new App in the
|
40
|
+
3. Create a new App in the Shopify Partner dashboard
|
37
41
|
-----------------------------------------
|
42
|
+
* Create a Shopify app in the [Partners dashboard](https://partner.shopify.com). For this tutorial, you can choose either a public or custom app, but you can [learn about App Types here.](https://help.shopify.com/en/manual/apps/app-types)
|
38
43
|
[https://app.shopify.com/services/partners/api_clients](https://app.shopify.com/services/partners/api_clients)
|
39
|
-
*
|
40
|
-
*
|
41
|
-
*
|
42
|
-
|
44
|
+
* Set the callback url to `https://<appname>.herokuapp.com/`
|
45
|
+
* Choose an embedded app
|
46
|
+
* Set the app's `redirect_uri` to `https://<appname>.herokuapp.com/auth/shopify/callback`
|
43
47
|
|
44
|
-
4. Add ShopifyApp to
|
48
|
+
4. Add ShopifyApp to Gemfile
|
45
49
|
----------------------------
|
50
|
+
|
51
|
+
Run these commands to add the `shopify_app` Gem to your app:
|
52
|
+
|
46
53
|
```sh
|
47
54
|
$ echo "gem 'shopify_app'" >> Gemfile
|
48
55
|
$ bundle install
|
49
56
|
```
|
50
57
|
|
51
|
-
Note
|
58
|
+
**Note:** we recommend using the latest version of Shopify Gem. Check the [Git tags](https://github.com/Shopify/shopify_app/tags) to see the latest release version and then add it to your Gemfile e.g `gem 'shopify_app', '~> 7.0.0'`
|
52
59
|
|
53
60
|
5. Run the ShopifyApp generator
|
54
61
|
-------------------------------
|
62
|
+
|
63
|
+
Generate the code for your app by running these commands:
|
64
|
+
|
55
65
|
```sh
|
56
|
-
#
|
66
|
+
# Use the keys from your app you created in the partners area
|
57
67
|
$ rails generate shopify_app --api_key <shopify_api_key> --secret <shopify_api_secret>
|
58
68
|
$ git add .
|
59
69
|
$ git commit -m 'generated shopify app'
|
@@ -61,10 +71,12 @@ $ git commit -m 'generated shopify app'
|
|
61
71
|
|
62
72
|
If you forget to set your keys or redirect uri above, you will find them in the shopify_app initializer at: `/config/initializers/shopify_app.rb`.
|
63
73
|
|
64
|
-
We recommend adding a gem or utilizing
|
74
|
+
We recommend adding a gem or utilizing environment variables (`.env`) to handle your keys before releasing your app. [Learn more about using environment variables.](https://www.honeybadger.io/blog/ruby-guide-environment-variables/)
|
65
75
|
|
66
|
-
6. Deploy
|
76
|
+
6. Deploy your app
|
67
77
|
---------
|
78
|
+
|
79
|
+
Once you've generated your app, push it into your Heroku environment to see it up and running:
|
68
80
|
```sh
|
69
81
|
$ git push heroku
|
70
82
|
$ heroku run rake db:migrate
|
@@ -72,4 +84,20 @@ $ heroku run rake db:migrate
|
|
72
84
|
|
73
85
|
7. Install the App!
|
74
86
|
-------------------
|
75
|
-
|
87
|
+
|
88
|
+
Ensure you have created a [development store](https://help.shopify.com/en/api/getting-started/making-your-first-request#create-a-development-store) using the Shopify Partner Dashboard. If you don't already have one, [create one by following these instructions](https://help.shopify.com/en/api/getting-started/making-your-first-request#create-a-development-store).
|
89
|
+
|
90
|
+
##### Note: The following step will cause your store to become `transfer-disabled.` Read more about store transfer and why it's important [here](https://help.shopify.com/en/api/guides/store-transfers#transfer-disabled-stores). This is an irreversible change, so be sure you don't plan to transfer this store to a merchant.
|
91
|
+
|
92
|
+
Install the app onto your new development store using the Partner Dashboard. Log in to your account, visit the apps page, click the app you created earlier, and looking for the `Test your app` instructions where you can select a store to install your app on.
|
93
|
+
|
94
|
+
![Installing an app on the partners dashboard dropdown](/docs/install-on-dev-shop.png)
|
95
|
+
|
96
|
+
### OR
|
97
|
+
|
98
|
+
![Installing an app on the partners dashboard card](/docs/test-your-app.png)
|
99
|
+
|
100
|
+
8. Great work!
|
101
|
+
-------------------
|
102
|
+
|
103
|
+
You're done creating your first app on Shopify. Keep going and learn more by [diving into our full documentation](https://help.shopify.com/en/api/getting-started), or join our [community of developers.](https://community.shopify.com/c/Shopify-Apps/bd-p/shopify-apps)
|
Binary file
|
Binary file
|
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class MarketingActivitiesController < ExtensionVerificationController
|
3
|
+
class MarketingActivitiesController < ShopifyApp::ExtensionVerificationController
|
4
4
|
def preload_form_data
|
5
5
|
preload_data = {
|
6
|
-
"form_data": {
|
7
|
-
"budget": {
|
8
|
-
"currency": "USD",
|
9
|
-
}
|
10
|
-
}
|
6
|
+
"form_data": {}
|
11
7
|
}
|
12
8
|
render(json: preload_data, status: :ok)
|
13
9
|
end
|
@@ -11,12 +11,6 @@ module ShopifyApp
|
|
11
11
|
|
12
12
|
def create_home_index_view
|
13
13
|
copy_file 'index.html.erb', 'app/views/home/index.html.erb'
|
14
|
-
if embedded_app?
|
15
|
-
prepend_to_file(
|
16
|
-
'app/views/home/index.html.erb',
|
17
|
-
File.read(File.expand_path(find_in_source_paths('shopify_app_ready_script.html.erb')))
|
18
|
-
)
|
19
|
-
end
|
20
14
|
end
|
21
15
|
|
22
16
|
def add_home_index_route
|
@@ -24,11 +24,11 @@
|
|
24
24
|
|
25
25
|
<%= render 'layouts/flash_messages' %>
|
26
26
|
|
27
|
-
<script src="https://
|
27
|
+
<script src="https://unpkg.com/@shopify/app-bridge"></script>
|
28
28
|
|
29
29
|
<%= content_tag(:div, nil, id: 'shopify-app-init', data: {
|
30
30
|
api_key: ShopifyApp.configuration.api_key,
|
31
|
-
shop_origin: (
|
31
|
+
shop_origin: (@shop_session.domain if @shop_session),
|
32
32
|
debug: Rails.env.development?
|
33
33
|
} ) %>
|
34
34
|
|
@@ -4,12 +4,21 @@ if (!document.documentElement.hasAttribute("data-turbolinks-preview")) {
|
|
4
4
|
document.addEventListener(eventName, function flash() {
|
5
5
|
var flashData = JSON.parse(document.getElementById('shopify-app-flash').dataset.flash);
|
6
6
|
|
7
|
+
var Toast = window['app-bridge'].actions.Toast;
|
8
|
+
|
7
9
|
if (flashData.notice) {
|
8
|
-
|
10
|
+
Toast.create(app, {
|
11
|
+
message: flashData.notice,
|
12
|
+
duration: 5000,
|
13
|
+
}).dispatch(Toast.Action.SHOW);
|
9
14
|
}
|
10
15
|
|
11
16
|
if (flashData.error) {
|
12
|
-
|
17
|
+
Toast.create(app, {
|
18
|
+
message: flashData.error,
|
19
|
+
duration: 5000,
|
20
|
+
isError: true,
|
21
|
+
}).dispatch(Toast.Action.SHOW);
|
13
22
|
}
|
14
23
|
|
15
24
|
document.removeEventListener(eventName, flash)
|
@@ -1,9 +1,15 @@
|
|
1
1
|
document.addEventListener('DOMContentLoaded', () => {
|
2
2
|
var data = document.getElementById('shopify-app-init').dataset;
|
3
|
-
|
3
|
+
var AppBridge = window['app-bridge'];
|
4
|
+
var createApp = AppBridge.default;
|
5
|
+
window.app = createApp({
|
4
6
|
apiKey: data.apiKey,
|
5
7
|
shopOrigin: data.shopOrigin,
|
6
|
-
|
7
|
-
|
8
|
+
});
|
9
|
+
|
10
|
+
var actions = AppBridge.actions;
|
11
|
+
var TitleBar = actions.TitleBar;
|
12
|
+
TitleBar.create(app, {
|
13
|
+
title: data.page,
|
8
14
|
});
|
9
15
|
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateUsers < ActiveRecord::Migration[<%= rails_migration_version %>]
|
2
|
+
def self.up
|
3
|
+
create_table :users do |t|
|
4
|
+
t.bigint :shopify_user_id, null: false
|
5
|
+
t.string :shopify_domain, null: false
|
6
|
+
t.string :shopify_token, null: false
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :users, :shopify_user_id, unique: true
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :users
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
|
4
|
+
module ShopifyApp
|
5
|
+
module Generators
|
6
|
+
class UserModelGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
def create_user_model
|
11
|
+
copy_file 'user.rb', 'app/models/user.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_user_migration
|
15
|
+
migration_template 'db/migrate/create_users.erb', 'db/migrate/create_users.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_shopify_app_initializer
|
19
|
+
gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'User'
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_user_fixtures
|
23
|
+
copy_file 'users.yml', 'test/fixtures/users.yml'
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def rails_migration_version
|
29
|
+
Rails.version.match(/\d\.\d/)[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
# for generating a timestamp when using `create_migration`
|
33
|
+
def self.next_migration_number(dir)
|
34
|
+
ActiveRecord::Generators::Base.next_migration_number(dir)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/shopify_app.rb
CHANGED
@@ -24,9 +24,6 @@ module ShopifyApp
|
|
24
24
|
# utils
|
25
25
|
require 'shopify_app/utils'
|
26
26
|
|
27
|
-
# controllers
|
28
|
-
require 'shopify_app/controllers/extension_verification_controller'
|
29
|
-
|
30
27
|
# controller concerns
|
31
28
|
require 'shopify_app/controller_concerns/localization'
|
32
29
|
require 'shopify_app/controller_concerns/itp'
|
@@ -47,6 +44,8 @@ module ShopifyApp
|
|
47
44
|
require 'shopify_app/middleware/same_site_cookie_middleware'
|
48
45
|
|
49
46
|
# session
|
47
|
+
require 'shopify_app/session/storage_strategies/shop_storage_strategy'
|
48
|
+
require 'shopify_app/session/storage_strategies/user_storage_strategy'
|
50
49
|
require 'shopify_app/session/session_storage'
|
51
50
|
require 'shopify_app/session/session_repository'
|
52
51
|
require 'shopify_app/session/in_memory_session_store'
|
@@ -15,6 +15,8 @@ module ShopifyApp
|
|
15
15
|
attr_accessor :scripttags
|
16
16
|
attr_accessor :after_authenticate_job
|
17
17
|
attr_reader :session_repository
|
18
|
+
attr_accessor :per_user_tokens
|
19
|
+
alias_method :per_user_tokens?, :per_user_tokens
|
18
20
|
attr_accessor :api_version
|
19
21
|
|
20
22
|
# customise urls
|
@@ -42,6 +44,7 @@ module ShopifyApp
|
|
42
44
|
@myshopify_domain = 'myshopify.com'
|
43
45
|
@scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
|
44
46
|
@webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
|
47
|
+
@per_user_tokens = false
|
45
48
|
@disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
|
46
49
|
end
|
47
50
|
|
@@ -63,7 +66,7 @@ module ShopifyApp
|
|
63
66
|
end
|
64
67
|
|
65
68
|
def enable_same_site_none
|
66
|
-
@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none
|
69
|
+
!Rails.env.test? && (@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none)
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
@@ -27,12 +27,30 @@ module ShopifyApp
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def shop_session
|
30
|
-
|
31
|
-
|
30
|
+
if ShopifyApp.configuration.per_user_tokens?
|
31
|
+
return unless session[:shopify_user]
|
32
|
+
@shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify_user]['id'])
|
33
|
+
else
|
34
|
+
return unless session[:shopify]
|
35
|
+
@shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify])
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
39
|
+
def login_again_if_different_user_or_shop
|
40
|
+
if ShopifyApp.configuration.per_user_tokens?
|
41
|
+
valid_session_data = session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
|
42
|
+
sessions_do_not_match = session[:user_session] != params[:session] # current user is different from stored user
|
43
|
+
|
44
|
+
if valid_session_data && sessions_do_not_match
|
45
|
+
clear_session = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
35
49
|
if shop_session && params[:shop] && params[:shop].is_a?(String) && (shop_session.domain != params[:shop])
|
50
|
+
clear_session = true
|
51
|
+
end
|
52
|
+
|
53
|
+
if clear_session
|
36
54
|
clear_shop_session
|
37
55
|
redirect_to_login
|
38
56
|
end
|
@@ -45,8 +63,14 @@ module ShopifyApp
|
|
45
63
|
head :unauthorized
|
46
64
|
else
|
47
65
|
if request.get?
|
48
|
-
|
66
|
+
path = request.path
|
67
|
+
query = sanitized_params.to_query
|
68
|
+
else
|
69
|
+
referer = URI(request.referer || "/")
|
70
|
+
path = referer.path
|
71
|
+
query = "#{referer.query}&#{sanitized_params.to_query}"
|
49
72
|
end
|
73
|
+
session[:return_to] = "#{path}?#{query}"
|
50
74
|
redirect_to(login_url_with_optional_shop)
|
51
75
|
end
|
52
76
|
end
|
@@ -60,6 +84,7 @@ module ShopifyApp
|
|
60
84
|
session[:shopify] = nil
|
61
85
|
session[:shopify_domain] = nil
|
62
86
|
session[:shopify_user] = nil
|
87
|
+
session[:user_session] = nil
|
63
88
|
end
|
64
89
|
|
65
90
|
def login_url_with_optional_shop(top_level: false)
|