shopify_app 13.0.0 → 18.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug-report.md +63 -0
  4. data/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. data/.github/ISSUE_TEMPLATE/feature-request.md +33 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  7. data/.github/workflows/build.yml +41 -0
  8. data/.github/workflows/release.yml +24 -0
  9. data/.github/workflows/rubocop.yml +22 -0
  10. data/.gitignore +0 -2
  11. data/.rubocop.yml +14 -6
  12. data/CHANGELOG.md +158 -0
  13. data/CONTRIBUTING.md +76 -0
  14. data/Gemfile +5 -0
  15. data/Gemfile.lock +268 -0
  16. data/README.md +73 -534
  17. data/Rakefile +1 -0
  18. data/SECURITY.md +59 -0
  19. data/app/assets/images/storage_access.svg +1 -2
  20. data/app/assets/javascripts/shopify_app/post_redirect.js +9 -0
  21. data/app/assets/javascripts/shopify_app/storage_access.js +2 -1
  22. data/app/assets/javascripts/shopify_app/top_level_interaction.js +1 -1
  23. data/app/controllers/concerns/shopify_app/authenticated.rb +1 -0
  24. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +26 -0
  25. data/app/controllers/concerns/shopify_app/require_known_shop.rb +39 -0
  26. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +32 -0
  27. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  28. data/app/controllers/shopify_app/callback_controller.rb +104 -18
  29. data/app/controllers/shopify_app/extension_verification_controller.rb +2 -7
  30. data/app/controllers/shopify_app/sessions_controller.rb +26 -10
  31. data/app/controllers/shopify_app/webhooks_controller.rb +6 -5
  32. data/app/views/shopify_app/partials/_button_styles.html.erb +41 -36
  33. data/app/views/shopify_app/partials/_card_styles.html.erb +3 -3
  34. data/app/views/shopify_app/partials/_empty_state_styles.html.erb +28 -59
  35. data/app/views/shopify_app/partials/_form_styles.html.erb +56 -0
  36. data/app/views/shopify_app/partials/_layout_styles.html.erb +16 -1
  37. data/app/views/shopify_app/partials/_typography_styles.html.erb +6 -6
  38. data/app/views/shopify_app/sessions/enable_cookies.html.erb +2 -7
  39. data/app/views/shopify_app/sessions/new.html.erb +38 -110
  40. data/app/views/shopify_app/sessions/request_storage_access.html.erb +1 -1
  41. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +21 -22
  42. data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +13 -0
  43. data/config/locales/de.yml +11 -11
  44. data/config/locales/fi.yml +1 -1
  45. data/config/locales/nl.yml +8 -8
  46. data/config/locales/th.yml +4 -4
  47. data/config/locales/vi.yml +22 -0
  48. data/config/locales/zh-CN.yml +1 -1
  49. data/config/routes.rb +1 -0
  50. data/docs/Quickstart.md +15 -87
  51. data/docs/Releasing.md +18 -14
  52. data/docs/Troubleshooting.md +147 -4
  53. data/docs/Upgrading.md +126 -0
  54. data/docs/shopify_app/authentication.md +124 -0
  55. data/docs/shopify_app/engine.md +82 -0
  56. data/docs/shopify_app/generators.md +127 -0
  57. data/docs/shopify_app/handling-access-scopes-changes.md +14 -0
  58. data/docs/shopify_app/script-tags.md +28 -0
  59. data/docs/shopify_app/session-repository.md +88 -0
  60. data/docs/shopify_app/testing.md +38 -0
  61. data/docs/shopify_app/webhooks.md +72 -0
  62. data/karma.conf.js +1 -1
  63. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +5 -3
  64. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  65. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +2 -1
  66. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +4 -4
  67. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +5 -4
  68. data/lib/generators/shopify_app/add_webhook/templates/{webhook_job.rb → webhook_job.rb.tt} +5 -0
  69. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +4 -3
  70. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +3 -3
  71. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +10 -9
  72. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +1 -1
  73. data/lib/generators/shopify_app/controllers/controllers_generator.rb +2 -1
  74. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +31 -3
  75. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +10 -0
  76. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +66 -16
  77. data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +12 -0
  78. data/lib/generators/shopify_app/install/install_generator.rb +46 -11
  79. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +3 -2
  80. data/lib/generators/shopify_app/install/templates/flash_messages.js +0 -2
  81. data/lib/generators/shopify_app/install/templates/omniauth.rb +3 -1
  82. data/lib/generators/shopify_app/install/templates/shopify_app.js +1 -1
  83. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +25 -0
  84. data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +8 -0
  85. data/lib/generators/shopify_app/install/templates/user_agent.rb +2 -1
  86. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +19 -0
  87. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +8 -0
  88. data/lib/generators/shopify_app/routes/routes_generator.rb +1 -0
  89. data/lib/generators/shopify_app/routes/templates/routes.rb +10 -9
  90. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +39 -7
  91. data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +5 -0
  92. data/lib/generators/shopify_app/shop_model/templates/shop.rb +2 -1
  93. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  94. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +5 -0
  95. data/lib/generators/shopify_app/user_model/templates/user.rb +2 -1
  96. data/lib/generators/shopify_app/user_model/user_model_generator.rb +39 -7
  97. data/lib/generators/shopify_app/views/views_generator.rb +2 -1
  98. data/lib/shopify_app/access_scopes/noop_strategy.rb +13 -0
  99. data/lib/shopify_app/access_scopes/shop_strategy.rb +24 -0
  100. data/lib/shopify_app/access_scopes/user_strategy.rb +41 -0
  101. data/lib/shopify_app/configuration.rb +40 -8
  102. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -3
  103. data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
  104. data/lib/shopify_app/controller_concerns/embedded_app.rb +3 -2
  105. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  106. data/lib/shopify_app/controller_concerns/login_protection.rb +81 -16
  107. data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
  108. data/lib/shopify_app/controller_concerns/webhook_verification.rb +3 -18
  109. data/lib/shopify_app/engine.rb +27 -0
  110. data/lib/shopify_app/jobs/scripttags_manager_job.rb +1 -1
  111. data/lib/shopify_app/jobs/webhooks_manager_job.rb +1 -1
  112. data/lib/shopify_app/managers/scripttags_manager.rb +4 -3
  113. data/lib/shopify_app/managers/webhooks_manager.rb +4 -3
  114. data/lib/shopify_app/middleware/jwt_middleware.rb +42 -0
  115. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +3 -2
  116. data/lib/shopify_app/omniauth/omniauth_configuration.rb +64 -0
  117. data/lib/shopify_app/session/in_memory_session_store.rb +7 -3
  118. data/lib/shopify_app/session/in_memory_shop_session_store.rb +12 -0
  119. data/lib/shopify_app/session/in_memory_user_session_store.rb +12 -0
  120. data/lib/shopify_app/session/jwt.rb +63 -0
  121. data/lib/shopify_app/session/null_user_session_store.rb +22 -0
  122. data/lib/shopify_app/session/session_repository.rb +13 -16
  123. data/lib/shopify_app/session/session_storage.rb +1 -0
  124. data/lib/shopify_app/session/shop_session_storage.rb +21 -9
  125. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +58 -0
  126. data/lib/shopify_app/session/user_session_storage.rb +19 -8
  127. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +58 -0
  128. data/lib/shopify_app/test_helpers/all.rb +2 -0
  129. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
  130. data/lib/shopify_app/utils.rb +18 -5
  131. data/lib/shopify_app/version.rb +2 -1
  132. data/lib/shopify_app.rb +23 -5
  133. data/package.json +7 -8
  134. data/service.yml +1 -4
  135. data/shopify_app.gemspec +15 -9
  136. data/translation.yml +1 -1
  137. data/yarn.lock +2140 -2188
  138. metadata +107 -19
  139. data/.github/ISSUE_TEMPLATE.md +0 -14
  140. data/.travis.yml +0 -27
  141. data/docs/install-on-dev-shop.png +0 -0
  142. data/docs/test-your-app.png +0 -0
  143. data/lib/generators/shopify_app/install/templates/shopify_app.rb +0 -15
  144. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +0 -20
  145. data/package-lock.json +0 -7245
@@ -0,0 +1,82 @@
1
+ # Engine
2
+
3
+ #### Table of contents
4
+
5
+ [Customize the app login URL](#customize-the-app-login-url)
6
+
7
+ [Mount the Shopify App engine at nested routes](#mount-the-shopify-app-engine-at-nested-routes)
8
+
9
+ [Verify HTTP requests sent via an app proxy](#verify-http-requests-sent-via-an-app-proxy)
10
+ * [Recommended usage of `ShopifyApp::AppProxyVerification`](#recommended-usage-of-shopifyappappproxyverification)
11
+
12
+ ## Customize the app login URL
13
+
14
+ While you can customize the login view by creating a `/app/views/shopify_app/sessions/new.html.erb` file, you may also want to customize the URL entirely. You can modify your `shopify_app.rb` initializer to provide a custom `login_url` e.g.:
15
+
16
+ ```ruby
17
+ ShopifyApp.configure do |config|
18
+ config.login_url = 'https://my.domain.com/nested/login'
19
+ end
20
+ ```
21
+
22
+ ## Mount the Shopify App engine at nested routes
23
+
24
+ The engine may also be mounted at a nested route, for example:
25
+
26
+ ```ruby
27
+ mount ShopifyApp::Engine, at: '/nested'
28
+ ```
29
+
30
+ This will create the Shopify engine routes under the specified subpath. You'll also need to make some updates to your `shopify_app.rb` and `omniauth.rb` initializers. First, update the shopify_app initializer to include a custom `root_url` e.g.:
31
+
32
+ ```ruby
33
+ ShopifyApp.configure do |config|
34
+ config.root_url = '/nested'
35
+ end
36
+ ```
37
+
38
+ then update the omniauth initializer to include a custom `callback_path` e.g.:
39
+
40
+ ```ruby
41
+ provider :shopify,
42
+ ShopifyApp.configuration.api_key,
43
+ ShopifyApp.configuration.secret,
44
+ scope: ShopifyApp.configuration.scope,
45
+ callback_path: '/nested/auth/shopify/callback'
46
+ ```
47
+
48
+ You may also need to change your `config/routes.rb` to render a view for `/nested`, since this is what will be rendered in the Shopify Admin of any shops that have installed your app. The engine itself doesn't have a view for this, so you'll need something like this:
49
+
50
+ ```ruby
51
+ # config/routes.rb
52
+ Rails.application.routes.draw do
53
+ root :to => 'something_else#index'
54
+ get "/nested", to: "home#index"
55
+ mount ShopifyApp::Engine, at: '/nested'
56
+ end
57
+ ```
58
+
59
+ Finally, note that if you do this, to add your app to a store, you must navigate to `/nested` in order to render the `Enter your shop domain to log in or install this app.` UI.
60
+
61
+ ## Verify HTTP requests sent via an app proxy
62
+
63
+ See [`ShopifyApp::AppProxyVerification`](/lib/shopify_app/controller_concerns/app_proxy_verification.rb).
64
+
65
+ The engine provides a mixin for verifying incoming HTTP requests sent via an App Proxy. Any controller that `include`s `ShopifyApp::AppProxyVerification` will verify that each request has a valid `signature` query parameter that is calculated using the other query parameters and the app's shared secret.
66
+
67
+ ### Recommended usage of `ShopifyApp::AppProxyVerification`
68
+
69
+ The App Proxy Controller Generator automatically adds the mixin to the generated app_proxy_controller.rb
70
+ Additional controllers for resources within the App_Proxy namespace, will need to include the mixin like so:
71
+
72
+ ```ruby
73
+ # app/controllers/app_proxy/reviews_controller.rb
74
+ class ReviewsController < ApplicationController
75
+ include ShopifyApp::AppProxyVerification
76
+ # ...
77
+ end
78
+ ```
79
+
80
+ Create your app proxy URL in the [Shopify Partners dashboard](https://partners.shopify.com/organizations), making sure to point it to `https://your_app_website.com/app_proxy`.
81
+
82
+ ![Creating an App Proxy](/images/app-proxy-screenshot.png)
@@ -0,0 +1,127 @@
1
+ # Generators
2
+
3
+ > Listed below are the generators currently offered by the Shopify App gem. To learn more about how this gem creates an embedded app, start with the [default Shopify App generator](#-environment-rails-generate-shopify_app).
4
+
5
+ ---
6
+
7
+ #### `$ [environment] rails generate shopify_app`
8
+
9
+ The default generator will run the [`install`](#-rails-generate-shopify_appinstall-flags), [`shop_model`](#-rails-generate-shopify_appshop_model), [`authenticated_controller`](#-rails-generate-shopify_appauthenticated_controller), and [`home_controller`](#-rails-generate-shopify_apphome_controller-flags) generators. This is the recommended way to start a new app from scratch.
10
+
11
+ ##### Environment
12
+
13
+ ###### `SHOPIFY_APP_DISABLE_WEBPACKER`
14
+
15
+ **[Optional]** Specify this environment variable if your app uses sprockets with Rails 6 or to generate a Shopify App without webpacker.
16
+
17
+ *Example:*
18
+
19
+ Run:
20
+
21
+ ```terminal
22
+ $ SHOPIFY_APP_DISABLE_WEBPACKER=1 rails generate shopify_app
23
+ ```
24
+
25
+ Add the following code to your ShopifyApp configuration block:
26
+
27
+ ```ruby
28
+ ShopifyApp.configure do |config|
29
+ config.disable_webpacker = true
30
+ end
31
+ ```
32
+
33
+ ---
34
+
35
+ #### `$ rails generate shopify_app:install [flags]`
36
+
37
+ This generator adds ShopifyApp and the required initializers to the host Rails application. You can update any of these settings later.
38
+
39
+ ##### Flags
40
+
41
+ ###### `--application_name`
42
+
43
+ The name of your app. The flag can be supplied with or without double-quotes.
44
+
45
+ *Example:* `--application_name "My Shopify App"`
46
+
47
+ ###### `--scope`
48
+
49
+ The [OAuth access scope(s)](https://shopify.dev/docs/admin-api/access-scopes) required by your app. Delimit multiple access scopes using a comma-space. The flag can be supplied with or without double-quotes.
50
+
51
+ *Example:* `--scope read_products, write_orders`
52
+
53
+ *Example:* `--scope "read_products, write_orders"`
54
+
55
+ ###### `--embedded`
56
+
57
+ Specify whether the app is an embedded app. Apps are embedded by default.
58
+
59
+ *Example:* `--embedded false`
60
+
61
+ ###### `--with-cookie-authentication`
62
+
63
+ **[Not recommended]** Specify whether the app uses cookies to authenticate. By default, the app is configured to use [session tokens](https://shopify.dev/concepts/apps/building-embedded-apps-using-session-tokens).
64
+
65
+ *Example:* `--with-cookie-authentication true`
66
+
67
+ ---
68
+
69
+ #### `$ rails generate shopify_app:shop_model`
70
+
71
+ This generator creates a `Shop` model and a migration to store shop installation records. See [*Shop-based token strategy*](/docs/shopify_app/session-repository.md#shop-based-token-storage) to learn more.
72
+
73
+ ---
74
+
75
+ #### `$ rails generate shopify_app:user_model`
76
+
77
+ This generator creates a `User` model and a migration to store user records. See [*User-based token strategy*](/docs/shopify_app/session-repository.md#user-based-token-storage) to learn more.
78
+
79
+ ---
80
+
81
+ #### `$ rails generate shopify_app:authenticated_controller`
82
+
83
+ This generator creates a sample authenticated controller. By default, inheriting from this controller protects your app controllers using session tokens. See [*Authentication*](/docs/shopify_app/authentication.md) to learn more.
84
+
85
+ ---
86
+
87
+ #### `$ rails generate shopify_app:home_controller [flags]`
88
+
89
+ This generator creates a sample home controller with a view that displays a shop's products. This generator also runs the [`products_controller`](#-rails-generate-shopify_appproducts_controller) generator.
90
+
91
+ ##### Flags
92
+
93
+ ###### `--with-cookie-authentication`
94
+
95
+ **[Not recommended]** This flag generates an authenticated home controller. Use this flag only if your app uses cookies to authenticate. By default, the home controller is unauthenticated.
96
+
97
+ ---
98
+
99
+ #### `$ rails generate shopify_app:products_controller`
100
+
101
+ This generator creates a sample products API controller to fetch products using the Shopify REST API.
102
+
103
+ ---
104
+
105
+ #### `$ rails generate shopify_app:add_after_authenticate_job`
106
+
107
+ **[Optional]** This generator creates a skeleton job that runs after the OAuth authorization flow. See [*Run jobs after the OAuth flow*](/docs/shopify_app/authentication.md#run-jobs-after-the-oauth-flow) for more information.
108
+
109
+ ---
110
+
111
+ #### `$ rails generate shopify_app:app_proxy_controller`
112
+
113
+ **[Optional]** This generator creates the app proxy controller to handle proxy requests to the app from your shop storefront. It also modifies 'config/routes.rb' with a namespace route and creates a sample view that displays current shop information using the LiquidAPI. See [*Verify HTTP requests sent via an app proxy*](/docs/shopify_app/engine.md#verify-http-requests-sent-via-an-app-proxy) for more information.
114
+
115
+ ---
116
+
117
+ #### `$ rails generate shopify_app:marketing_activity_extension`
118
+
119
+ **[Optional]** This generator creates a controller with the endpoints required to build a [marketing activities extension](https://shopify.dev/docs/marketing-activities). The extension is generated with the base URL `/marketing_activities`. This URL would need to be configured in the Shopify Partners dashboard.
120
+
121
+ ---
122
+
123
+ #### `$ rails generate shopify_app:controllers`
124
+
125
+ **[Optional]** This generator is for your convenience. Run this generator if you would like to override code that is part of the Shopify App Rails engine.
126
+
127
+ *Example:* The Shopify App Rails engine provides a sample [`SessionsController`](/app/controllers/shopify_app/sessions_controller.rb). Running this generator copies this controller to your app so you can begin extending it. Routes and views follow the same pattern.
@@ -0,0 +1,14 @@
1
+ # Handling changes in access scopes
2
+ The Shopify App gem provides handling changes to scopes for both shop/offline and user/online tokens. To enable your app to login via OAuth on scope changes, you can set the following configuration flag in your `config/initializers/shopify_app.rb`:
3
+ ```ruby
4
+ config.reauth_on_access_scope_changes = true
5
+ ```
6
+
7
+ ## ShopAccessScopesVerification
8
+ The `ShopifyApp::ShopAccessScopesVerification` concern helps merchants grant new access scopes requested by the app. The concern compares the current access scopes granted by the shop and compares them with the scopes requested by the app. If there is a mismatch in configuration, the merchant is redirected to login via OAuth and grant the net new scopes.
9
+
10
+ To activate the `ShopAccessScopesVerification` for a controller add `include ShopifyApp::ShopAccessScopesVerification`:
11
+ ```ruby
12
+ class HomeController < AuthenticatedController
13
+ include ShopifyApp::ShopAccessScopesVerification
14
+ ```
@@ -0,0 +1,28 @@
1
+ # ScriptTags
2
+
3
+ #### Table of contents
4
+
5
+ [Manage ScriptTags using the Shopify App initializer](#manage-scripttags-using-the-shopify-app-initializer)
6
+
7
+ ## Manage ScriptTags using the Shopify App initializer
8
+
9
+ As with webhooks, ShopifyApp can manage your app's [ScriptTags](https://shopify-dev-staging.shopifycloud.com/docs/admin-api/graphql/reference/online-store/scripttag) for you by setting which scripttags you require in the initializer:
10
+
11
+ ```ruby
12
+ ShopifyApp.configure do |config|
13
+ config.scripttags = [
14
+ {event:'onload', src: 'https://my-shopifyapp.herokuapp.com/fancy.js'},
15
+ {event:'onload', src: ->(domain) { dynamic_tag_url(domain) } }
16
+ ]
17
+ end
18
+ ```
19
+
20
+ You also need to have write_script_tags permission in the config scope in order to add script tags automatically:
21
+
22
+ ```ruby
23
+ config.scope = '... , write_script_tags'
24
+ ```
25
+
26
+ Scripttags are created in the same way as the [Webhooks](/docs/shopify_app/webhooks.md), with a background job which will create the required scripttags.
27
+
28
+ If `src` responds to `call` its return value will be used as the scripttag's source. It will be called on scripttag creation and deletion.
@@ -0,0 +1,88 @@
1
+ # Session repository
2
+
3
+ #### Table of contents
4
+
5
+ [`ShopifyApp::SessionRepository`](#shopifyappsessionrepository)
6
+ * [Shop-based token storage](#shop-based-token-storage)
7
+ * [User-based token storage](#user-based-token-storage)
8
+
9
+ [Access scopes](#access-scopes)
10
+ * [`ShopifyApp::ShopSessionStorageWithScopes`](#shopifyappshopsessionstoragewithscopes)
11
+ * [``ShopifyApp::UserSessionStorageWithScopes``](#shopifyappusersessionstoragewithscopes)
12
+
13
+ [Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
14
+
15
+ ## ShopifyApp::SessionRepository
16
+
17
+ `ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are stored and retrieved for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(auth_session, *args)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. These methods are already implemented as part of the `ShopifyApp::SessionStorage` concern but can be overridden for custom implementation.
18
+
19
+ ### Shop-based token storage
20
+
21
+ Storing tokens on the store model means that any user login associated with the store will have equal access levels to whatever the original user granted the app.
22
+ ```sh
23
+ $ rails generate shopify_app:shop_model
24
+ ```
25
+ This will generate a shop model which will be the storage for the tokens necessary for authentication.
26
+
27
+ ### User-based token storage
28
+
29
+ A more granular control over the level of access per user on an app might be necessary, to which the shop-based token strategy is not sufficient. Shopify supports a user-based token storage strategy where a unique token to each user can be managed. Shop tokens must still be maintained if you are running background jobs so that you can make use of them when necessary.
30
+ ```sh
31
+ $ rails generate shopify_app:shop_model
32
+ $ rails generate shopify_app:user_model
33
+ ```
34
+ This will generate a shop model and user model, which will be the storage for the tokens necessary for authentication.
35
+
36
+ The current Shopify user will be stored in the rails session at `session[:shopify_user]`
37
+
38
+ Read more about Online vs. Offline access [here](https://help.shopify.com/api/getting-started/authentication/oauth).
39
+
40
+ ## Access scopes
41
+
42
+ If you want to customize how access scopes are stored for shops and users, you can implement the `access_scopes` getters and setters in the models that include `ShopifyApp::ShopSessionStorageWithScopes` and `ShopifyApp::UserSessionStorageWithScopes` as shown:
43
+
44
+ ### `ShopifyApp::ShopSessionStorageWithScopes`
45
+ ```ruby
46
+ class Shop < ActiveRecord::Base
47
+ include ShopifyApp::ShopSessionStorageWithScopes
48
+
49
+ def access_scopes=(scopes)
50
+ # Store access scopes
51
+ end
52
+ def access_scopes
53
+ # Find access scopes
54
+ end
55
+ end
56
+ ```
57
+
58
+ ### `ShopifyApp::UserSessionStorageWithScopes`
59
+ ```ruby
60
+ class User < ActiveRecord::Base
61
+ include ShopifyApp::UserSessionStorageWithScopes
62
+
63
+ def access_scopes=(scopes)
64
+ # Store access scopes
65
+ end
66
+ def access_scopes
67
+ # Find access scopes
68
+ end
69
+ end
70
+ ```
71
+ ## Migrating from shop-based to user-based token strategy
72
+
73
+ 1. Run the `user_model` generator as mentioned above.
74
+ 2. Ensure that both your `Shop` model and `User` model includes the necessary concerns `ShopifyApp::ShopSessionStorage` and `ShopifyApp::UserSessionStorage`.
75
+ 3. Make changes to 2 initializer files as shown below:
76
+ ```ruby
77
+ # In the `omniauth.rb` initializer:
78
+ provider :shopify,
79
+ ...
80
+ setup: lambda { |env|
81
+ configuration = ShopifyApp::OmniAuthConfiguration.new(env['omniauth.strategy'], Rack::Request.new(env))
82
+ configuration.build_options
83
+ }
84
+
85
+ # In the `shopify_app.rb` initializer:
86
+ config.shop_session_repository = {YOUR_SHOP_MODEL_CLASS}
87
+ config.user_session_repository = {YOUR_USER_MODEL_CLASS}
88
+ ```
@@ -0,0 +1,38 @@
1
+ # Testing
2
+
3
+ #### Table of contents
4
+
5
+ [Using test helpers inside your application](#using-test-helpers-inside-your-application)
6
+
7
+ [Testing an embedded app outside the Shopify admin](#testing-an-embedded-app-outside-the-shopify-admin)
8
+
9
+ ## Using test helpers inside your application
10
+
11
+ A test helper that will allow you to test `ShopifyApp::WebhookVerification` in the controller from your app, to use this test, you need to `require` it directly inside your app `test/controllers/webhook_verification_test.rb`.
12
+
13
+ ```ruby
14
+ require 'test_helper'
15
+ require 'action_controller'
16
+ require 'action_controller/base'
17
+ require 'shopify_app/test_helpers/webhook_verification_helper'
18
+ ```
19
+
20
+ Or you can require in your `test/test_helper.rb`.
21
+
22
+ ```ruby
23
+ ENV['RAILS_ENV'] ||= 'test'
24
+ require_relative '../config/environment'
25
+ require 'rails/test_help'
26
+ require 'byebug'
27
+ require 'shopify_app/test_helpers/all'
28
+ ```
29
+
30
+ With `lib/shopify_app/test_helpers/all'` more tests can be added and will only need to be required in once in your library.
31
+
32
+ ## Testing an embedded app outside the Shopify admin
33
+
34
+ By default, loading your embedded app will redirect to the Shopify admin, with the app view loaded in an `iframe`. If you need to load your app outside of the Shopify admin (e.g., for performance testing), you can change `forceRedirect: true` to `false` in `ShopifyApp.init` block in the `embedded_app` view. To keep the redirect on in production but off in your `development` and `test` environments, you can use:
35
+
36
+ ```javascript
37
+ forceRedirect: <%= Rails.env.development? || Rails.env.test? ? 'false' : 'true' %>
38
+ ```
@@ -0,0 +1,72 @@
1
+ # Webhooks
2
+
3
+ #### Table of contents
4
+
5
+ [Manage webhooks using `ShopifyApp::WebhooksManager`](#manage-webhooks-using-shopifyappwebhooksmanager)
6
+
7
+ ## Manage webhooks using `ShopifyApp::WebhooksManager`
8
+
9
+ See [`ShopifyApp::WebhooksManager`](/lib/shopify_app/managers/webhooks_manager.rb)
10
+ ShopifyApp can manage your app's webhooks for you if you set which webhooks you require in the initializer:
11
+
12
+ ```ruby
13
+ ShopifyApp.configure do |config|
14
+ config.webhooks = [
15
+ {topic: 'carts/update', address: 'https://example-app.com/webhooks/carts_update'}
16
+ ]
17
+ end
18
+ ```
19
+
20
+ When the [OAuth callback](/docs/shopify_app/authentication.md#oauth-callback) is completed successfully, ShopifyApp will queue a background job which will ensure all the specified webhooks exist for that shop. Because this runs on every OAuth callback, it means your app will always have the webhooks it needs even if the user uninstalls and re-installs the app.
21
+
22
+ ShopifyApp also provides a [WebhooksController](/app/controllers/shopify_app/webhooks_controller.rb) that receives webhooks and queues a job based on the received topic. For example, if you register the webhook from above, then all you need to do is create a job called `CartsUpdateJob`. The job will be queued with 2 params: `shop_domain` and `webhook` (which is the webhook body).
23
+
24
+ If you would like to namespace your jobs, you may set `webhook_jobs_namespace` in the config. For example, if your app handles webhooks from other ecommerce applications as well, and you want Shopify cart update webhooks to be processed by a job living in `jobs/shopify/webhooks/carts_update_job.rb` rather than `jobs/carts_update_job.rb`):
25
+
26
+ ```ruby
27
+ ShopifyApp.configure do |config|
28
+ config.webhook_jobs_namespace = 'shopify/webhooks'
29
+ end
30
+ ```
31
+
32
+ If you are only interested in particular fields, you can optionally filter the data sent by Shopify by specifying the `fields` parameter in `config/webhooks`. Note that you will still receive a webhook request from Shopify every time the resource is updated, but only the specified fields will be sent.
33
+
34
+ ```ruby
35
+ ShopifyApp.configure do |config|
36
+ config.webhooks = [
37
+ {topic: 'products/update', address: 'https://example-app.com/webhooks/products_update', fields: ['title', 'vendor']}
38
+ ]
39
+ end
40
+ ```
41
+
42
+ If you'd rather implement your own controller then you'll want to use the [`ShopifyApp::WebhookVerification`](/lib/shopify_app/controller_concerns/webhook_verification.rb) module to verify your webhooks, example:
43
+
44
+ ```ruby
45
+ class CustomWebhooksController < ApplicationController
46
+ include ShopifyApp::WebhookVerification
47
+
48
+ def carts_update
49
+ params.permit!
50
+ SomeJob.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
51
+ head :no_content
52
+ end
53
+
54
+ private
55
+
56
+ def webhook_params
57
+ params.except(:controller, :action, :type)
58
+ end
59
+ end
60
+ ```
61
+
62
+ The module skips the `verify_authenticity_token` before_action and adds an action to verify that the webhook came from Shopify. You can now add a post route to your application, pointing to the controller and action to accept the webhook data from Shopify.
63
+
64
+ The WebhooksManager uses ActiveJob. If ActiveJob is not configured then by default Rails will run the jobs inline. However, it is highly recommended to configure a proper background processing queue like Sidekiq or Resque in production.
65
+
66
+ ShopifyApp can create webhooks for you using the `add_webhook` generator. This will add the new webhook to your config and create the required job class for you.
67
+
68
+ ```
69
+ rails g shopify_app:add_webhook -t carts/update -a https://example.com/webhooks/carts_update
70
+ ```
71
+
72
+ Where `-t` is the topic and `-a` is the address the webhook should be sent to.