shopify_app 11.4.0 → 11.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '097ed8c5a4e2712571c38ab110dba580e6c80dd6af95d04414d489e16366653f'
4
- data.tar.gz: 7f95f2cd5100b9952cef6a40b68cb778bfa4be6753f9693f7c6ff6df18615014
3
+ metadata.gz: 0ba7612b4db7acb35c0fa91209e7b4421f90cefa6b95d52b433218e9be5a8703
4
+ data.tar.gz: 9db8d74d1f2bebd2b4fe8856bb8c2d0a3e7108cfb5a940521a70ca67b1f6e1fe
5
5
  SHA512:
6
- metadata.gz: '08c2495dac07fc430e8583c196e06af804905b3de7d6e7d7eed4bcc29a131d635caacc82f4a0619b7f7170be77d2069df6602379d413eb1f273a64dd9c79486d'
7
- data.tar.gz: 3a559f4426151c91fd16eb1125a8b910a68ac4abe1b6601a5234a1e3c91969d4227eca31acae7a27171c0c0004cd8af1e17f6e9aebac60957119f4f211d895b4
6
+ metadata.gz: 5b73cda40e6980ab59c5f5d40bcb09c93cfeb7e0fcfd8ff8cd953509e1d43eb45073f80e987aea8e7f585c28f50328613e5956d4d1de9e264908e3fc0265d670
7
+ data.tar.gz: 349bbdfea89e30efd696f50572d09af4e421caa8a3ce89f539e5b1a05b956c0b35af1651482fc20bfdfc7d451874ffebecf15ed4ee86bb9bb885cab358a1d707
@@ -6,8 +6,6 @@ before_script:
6
6
  - "sudo chown root /opt/google/chrome/chrome-sandbox"
7
7
  - "sudo chmod 4755 /opt/google/chrome/chrome-sandbox"
8
8
  language: ruby
9
- before_install:
10
- - gem update --system
11
9
  cache:
12
10
  bundler: true
13
11
  directories:
@@ -1,3 +1,8 @@
1
+ 11.5.0
2
+ -----
3
+ * Modularizes durable session storage
4
+ * Introduces per-user token support and user session management
5
+
1
6
  11.4.0
2
7
  -----
3
8
  * Remove `dotenv-rails` dependency. [#835](https://github.com/Shopify/shopify_app/pull/835)
data/README.md CHANGED
@@ -12,61 +12,35 @@ Shopify Application Rails engine and generator
12
12
 
13
13
  Table of Contents
14
14
  -----------------
15
- * [**Description**](#description)
16
- * [**Quickstart**](#quickstart)
17
- * [**Becoming a Shopify App Developer**](#becoming-a-shopify-app-developer)
18
- * [**App Tunneling**](#app-tunneling)
19
- * [**Installation**](#installation)
20
- * [Rails Compatibility](#rails-compatibility)
21
- * [**Generators**](#generators)
22
- * [Default Generator](#default-generator)
23
- * [Install Generator](#install-generator)
24
- * [Shop Model Generator](#shop-model-generator)
25
- * [Home Controller Generator](#home-controller-generator)
26
- * [App Proxy Controller Generator](#app-proxy-controller-generator)
27
- * [Controllers, Routes and Views](#controllers-routes-and-views)
28
- * [**Mounting the Engine**](#mounting-the-engine)
29
- * [**WebhooksManager**](#webhooksmanager)
30
- * [**ScripttagsManager**](#scripttagsmanager)
31
- * [**AfterAuthenticate Job**](#afterauthenticate-job)
32
- * [**ShopifyApp::SessionRepository**](#shopifyappsessionrepository)
33
- * [**Authenticated**](#authenticated)
34
- * [**AppProxyVerification**](#appproxyverification)
35
- * [Recommended Usage](#recommended-usage)
36
- * [**Upgrading from 8.6 to 9.0.0**](#upgrading-from-86-to-900)
37
- * [**Troubleshooting**](#troubleshooting)
38
- * [Generator shopify_app:install hangs](#generator-shopify_appinstall-hangs)
39
- * [**Testing an embedded app outside the Shopify admin**](#testing-an-embedded-app-outside-the-shopify-admin)
40
- * [**Questions or problems?**](#questions-or-problems)
41
-
42
-
43
- Description
15
+ - [Introduction](#introduction)
16
+ - [Becoming a Shopify App Developer](#becoming-a-shopify-app-developer)
17
+ - [Installation](#installation)
18
+ - [Generators](#generators)
19
+ - [Mounting the Engine](#mounting-the-engine)
20
+ - [Authentication](#authentication)
21
+ - [WebhooksManager](#webhooksmanager)
22
+ - [ScripttagsManager](#scripttagsmanager)
23
+ - [RotateShopifyTokenJob](#rotateshopifytokenjob)
24
+ - [App Tunneling](#app-tunneling)
25
+ - [AppProxyVerification](#appproxyverification)
26
+ - [Troubleshooting](#troubleshooting)
27
+ - [Testing an embedded app outside the Shopify admin](#testing-an-embedded-app-outside-the-shopify-admin)
28
+ - [Questions or problems?](#questions-or-problems-)
29
+ - [Rails 6 Compatibility](#rails-6-compatibility)
30
+ - [Upgrading from 8.6 to 9.0.0](#upgrading-from-86-to-900)
31
+
32
+ Introduction
44
33
  -----------
45
34
  This gem includes a Rails Engine and generators for writing Rails applications using the Shopify API. The Engine provides a SessionsController and all the required code for authenticating with a shop via Oauth (other authentication methods are not supported).
46
35
 
47
36
  *Note: It's recommended to use this on a new Rails project, so that the generator won't overwrite/delete some of your files.*
48
37
 
49
-
50
- Quickstart
51
- ----------
52
-
53
38
  Check out this screencast on how to create and deploy a new Shopify App to Heroku in 5 minutes:
54
39
 
55
40
  [https://www.youtube.com/watch?v=yGxeoAHlQOg](https://www.youtube.com/watch?v=yGxeoAHlQOg)
56
41
 
57
42
  Or if you prefer text instructions the steps in the video are written out [here](https://github.com/Shopify/shopify_app/blob/master/docs/Quickstart.md)
58
43
 
59
- App Tunneling
60
- -------------
61
-
62
- Your local app needs to be accessible from the public Internet in order to install it on a shop, use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks. Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), [Hookbin](https://hookbin.com/), etc.
63
-
64
- For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port:
65
-
66
- ```sh
67
- ngrok http 3000
68
- ```
69
-
70
44
  Becoming a Shopify App Developer
71
45
  --------------------------------
72
46
  If you don't have a Shopify Partner account yet head over to http://shopify.com/partners to create one, you'll need it before you can start developing apps.
@@ -106,7 +80,7 @@ The default generator will run the `install`, `shop`, and `home_controller` gene
106
80
  $ rails generate shopify_app
107
81
  ```
108
82
 
109
- After running the generator, you will need to run `rake db:migrate` to add tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting localhost.
83
+ After running the generator, you will need to run `rails db:migrate` to add tables to your database. You can start your app with `bundle exec rails server` and install your app by visiting localhost.
110
84
 
111
85
  ### API Keys
112
86
 
@@ -143,17 +117,6 @@ The generator adds ShopifyApp and the required initializers to the host Rails ap
143
117
  After running the `install` generator, you can start your app with `bundle exec rails server` and install your app by visiting localhost.
144
118
 
145
119
 
146
- ### Shop Model Generator
147
-
148
- ```sh
149
- $ rails generate shopify_app:shop_model
150
- ```
151
-
152
- The `install` generator doesn't create any database tables or models for you. If you are starting a new app its quite likely that you will want a shops table and model to store the tokens when your app is installed (most of our internally developed apps do!). This generator creates a shop model and a migration. This model includes the `ShopifyApp::SessionStorage` concern which adds two methods to make it compatible as a `SessionRepository`. After running this generator you'll notice the `session_repository` in your `config/initializers/shopify_app.rb` will be set to the `Shop` model. This means that internally ShopifyApp will try and load tokens from this model.
153
-
154
- *Note that you will need to run rake db:migrate after this generator*
155
-
156
-
157
120
  ### Home Controller Generator
158
121
 
159
122
  ```sh
@@ -171,6 +134,13 @@ $ rails generate shopify_app:app_proxy_controller
171
134
 
172
135
  This optional generator, not included with the default generator, creates the app proxy controller to handle proxy requests to the app from your shop storefront, modifies 'config/routes.rb' with a namespace route, and an example view which displays current shop information using the LiquidAPI
173
136
 
137
+ ### Marketing Extension Generator
138
+
139
+ ```sh
140
+ $ rails generate shopify_app:add_marketing_activity_extension
141
+ ```
142
+
143
+ This will create a controller with the endpoints required to build a [marketing activities extension](https://help.shopify.com/en/api/embedded-apps/app-extensions/shopify-admin/marketing-activities). The extension will be generated with a base url at `/marketing_activities`, which should also be configured in partners.
174
144
 
175
145
  ### Controllers, Routes and Views
176
146
 
@@ -238,21 +208,82 @@ ShopifyApp.configure do |config|
238
208
  end
239
209
  ```
240
210
 
241
- Per User Authentication
242
- -----------------------
243
- To enable per user authentication you need to update the `omniauth.rb` initializer:
211
+ Authentication
212
+ --------------
213
+
214
+ ### ShopifyApp::SessionRepository
215
+
216
+ `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)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. See either the `ShopifyApp::InMemorySessionStore` class or the `ShopifyApp::SessionStorage` concern for details.
217
+
218
+ If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. For multi-server environments, implement one of the following token-storage strategies.
219
+
220
+ #### Shop-based token storage
221
+ Storing tokens on the store model means that any user login associated to the store will have equal access levels to whatever the original user granted the app.
222
+ ```sh
223
+ $ rails generate shopify_app:shop_model
224
+ ```
225
+ This will generate a shop model which will be the storage for the tokens necessary for authentication.
226
+
227
+ #### User-based token storage
228
+ A more granular control over 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.
229
+ ```sh
230
+ $ rails generate shopify_app:user_model
231
+ ```
232
+ This will generate a user model which will be the storage for the tokens necessary for authentication.
233
+
234
+ The current Shopify user will be stored in the rails session at `session[:shopify_user]`
235
+
236
+ This will change the type of token that Shopify returns and it will only be valid for a short time. Read more about `Online access` [here](https://help.shopify.com/api/getting-started/authentication/oauth). Note that this means you won't be able to use this token to respond to Webhooks.
237
+
238
+ #### Migrating from shop-based to user-based token strategy
239
+ After running the generator, ensure that configuration settings are successfully changed:
244
240
 
245
241
  ```ruby
242
+ # In the `omniauth.rb` initializer:
246
243
  provider :shopify,
247
244
  ShopifyApp.configuration.api_key,
248
245
  ShopifyApp.configuration.secret,
249
246
  scope: ShopifyApp.configuration.scope,
250
247
  per_user_permissions: true
248
+
249
+ # In the `shopify_app.rb` initializer:
250
+ config.session_repository = User
251
+ config.per_user_tokens = true
251
252
  ```
252
253
 
253
- The current Shopify user will be stored in the rails session at `session[:shopify_user]`
254
+ ### Authenticated
254
255
 
255
- This will change the type of token that Shopify returns and it will only be valid for a short time. Read more about `Online access` [here](https://help.shopify.com/api/getting-started/authentication/oauth). Note that this means you won't be able to use this token to respond to Webhooks.
256
+ The engine provides a `ShopifyApp::Authenticated` concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit.
257
+
258
+ For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`.
259
+
260
+ ### AfterAuthenticate Job
261
+
262
+ If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing (note that we already provide support for automatically creating Webhooks and Scripttags). To configure the after authenticate job update your initializer as follows:
263
+
264
+ ```ruby
265
+ ShopifyApp.configure do |config|
266
+ config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob" }
267
+ end
268
+ ```
269
+
270
+ The job can be configured as either a class or a class name string.
271
+
272
+ If you need the job to run synchronously add the `inline` flag:
273
+
274
+ ```ruby
275
+ ShopifyApp.configure do |config|
276
+ config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
277
+ end
278
+ ```
279
+
280
+ We've also provided a generator which creates a skeleton job and updates the initializer for you:
281
+
282
+ ```
283
+ bin/rails g shopify_app:add_after_authenticate_job
284
+ ```
285
+
286
+ If you want to perform that action only once, e.g. send a welcome email to the user when they install the app, you should make sure that this action is idempotent, meaning that it won't have an impact if run multiple times.
256
287
 
257
288
 
258
289
  WebhooksManager
@@ -346,36 +377,6 @@ Scripttags are created in the same way as the Webhooks, with a background job wh
346
377
 
347
378
  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.
348
379
 
349
- AfterAuthenticate Job
350
- ---------------------
351
-
352
- If your app needs to perform specific actions after the user is authenticated successfully (i.e. every time a new session is created), ShopifyApp can queue or run a job of your choosing (note that we already provide support for automatically creating Webhooks and Scripttags). To configure the after authenticate job update your initializer as follows:
353
-
354
- ```ruby
355
- ShopifyApp.configure do |config|
356
- config.after_authenticate_job = { job: "Shopify::AfterAuthenticateJob" }
357
- end
358
- ```
359
-
360
- The job can be configured as either a class or a class name string.
361
-
362
- If you need the job to run synchronously add the `inline` flag:
363
-
364
- ```ruby
365
- ShopifyApp.configure do |config|
366
- config.after_authenticate_job = { job: Shopify::AfterAuthenticateJob, inline: true }
367
- end
368
- ```
369
-
370
- We've also provided a generator which creates a skeleton job and updates the initializer for you:
371
-
372
- ```
373
- bin/rails g shopify_app:add_after_authenticate_job
374
- ```
375
-
376
- If you want to perform that action only once, e.g. send a welcome email to the user when they install the app, you should make sure that this action is idempotent, meaning that it won't have an impact if run multiple times.
377
-
378
-
379
380
  RotateShopifyTokenJob
380
381
  ---------------------
381
382
 
@@ -402,19 +403,16 @@ The generated rake task will be found at `lib/tasks/shopify/rotate_shopify_token
402
403
  strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
403
404
  ```
404
405
 
405
- ShopifyApp::SessionRepository
406
- -----------------------------
407
-
408
- `ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are retrieved and stored 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(shopify_session)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. See either the `ShopifyApp::InMemorySessionStore` class or the `ShopifyApp::SessionStorage` concern for examples.
409
-
410
- If you only run the install generator then by default you will have an in memory store but it **won't work** on multi-server environments including Heroku. If you ran all the generators including the shop_model generator then the `Shop` model itself will be the `SessionRepository`. If you look at the implementation of the generated shop model you'll see that this gem provides a concern for the `SessionRepository`. You can use this concern on any model that responds to `shopify_domain`, `shopify_token` and `api_version`.
411
-
412
- Authenticated
406
+ App Tunneling
413
407
  -------------
414
408
 
415
- The engine provides a `ShopifyApp::Authenticated` concern which should be included in any controller that is intended to be behind Shopify OAuth. It adds `before_action`s to ensure that the user is authenticated and will redirect to the Shopify login page if not. It is best practice to include this concern in a base controller inheriting from your `ApplicationController`, from which all controllers that require Shopify authentication inherit.
409
+ Your local app needs to be accessible from the public Internet in order to install it on a shop, use the [App Proxy Controller](#app-proxy-controller-generator) or receive Webhooks. Use a tunneling service like [ngrok](https://ngrok.com/), [Forward](https://forwardhq.com/), [Beeceptor](https://beeceptor.com/), [Mockbin](http://mockbin.org/), [Hookbin](https://hookbin.com/), etc.
416
410
 
417
- For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`.
411
+ For example with [ngrok](https://ngrok.com/), run this command to set up proxying to Rails' default port:
412
+
413
+ ```sh
414
+ ngrok http 3000
415
+ ```
418
416
 
419
417
  AppProxyVerification
420
418
  --------------------
@@ -458,7 +456,7 @@ Questions or problems?
458
456
  - [Read the docs!](https://help.shopify.com/api/guides)
459
457
 
460
458
  Rails 6 Compatibility
461
- ---------------------------
459
+ ---------------------
462
460
 
463
461
  ### Disable Webpacker
464
462
  If you are using sprockets in rails 6 or want to generate a shopify_app without webpacker run the install task by running
@@ -8,7 +8,7 @@ module ShopifyApp
8
8
  include ShopifyApp::Localization
9
9
  include ShopifyApp::LoginProtection
10
10
  include ShopifyApp::EmbeddedApp
11
- before_action :login_again_if_different_shop
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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  nl:
3
- logged_out: je bent afgemeld
3
+ logged_out: u bent afgemeld
4
4
  could_not_log_in: Kon niet aanmelden bij Shopify-winkel
5
5
  invalid_shop_url: Ongeldig winkeldomein
6
6
  enable_cookies_heading: Schakel cookies in van %{app}
@@ -11,10 +11,6 @@ module ShopifyApp
11
11
  class_option :embedded, type: :string, default: 'true'
12
12
  class_option :api_version, type: :string, default: nil
13
13
 
14
- def add_dotenv_gem
15
- gem('dotenv-rails', group: [:test, :development])
16
- end
17
-
18
14
  def create_shopify_app_initializer
19
15
  @application_name = format_array_argument(options['application_name'])
20
16
  @scope = format_array_argument(options['scope'])
@@ -4,6 +4,7 @@ provider :shopify,
4
4
  ShopifyApp.configuration.api_key,
5
5
  ShopifyApp.configuration.secret,
6
6
  scope: ShopifyApp.configuration.scope,
7
+ per_user_permissions: ShopifyApp.configuration.per_user_tokens,
7
8
  setup: lambda { |env|
8
9
  strategy = env['omniauth.strategy']
9
10
 
@@ -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,7 @@
1
+ class User < ActiveRecord::Base
2
+ include ShopifyApp::SessionStorage
3
+
4
+ def api_version
5
+ ShopifyApp.configuration.api_version
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ regular_user:
2
+ shopify_domain: 'regular-shop.myshopify.com'
3
+ shopify_token: 'token'
4
+ shopify_user_id: 1
@@ -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
@@ -44,6 +44,8 @@ module ShopifyApp
44
44
  require 'shopify_app/managers/scripttags_manager'
45
45
 
46
46
  # session
47
+ require 'shopify_app/session/storage_strategies/shop_storage_strategy'
48
+ require 'shopify_app/session/storage_strategies/user_storage_strategy'
47
49
  require 'shopify_app/session/session_storage'
48
50
  require 'shopify_app/session/session_repository'
49
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_accessor :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
@@ -39,6 +41,7 @@ module ShopifyApp
39
41
  @myshopify_domain = 'myshopify.com'
40
42
  @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
41
43
  @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
44
+ @per_user_tokens = false
42
45
  @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
43
46
  end
44
47
 
@@ -63,6 +66,7 @@ module ShopifyApp
63
66
  def has_scripttags?
64
67
  scripttags.present?
65
68
  end
69
+
66
70
  end
67
71
 
68
72
  def self.configuration
@@ -27,12 +27,30 @@ module ShopifyApp
27
27
  end
28
28
 
29
29
  def shop_session
30
- return unless session[:shopify]
31
- @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify])
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 login_again_if_different_shop
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
@@ -60,6 +78,7 @@ module ShopifyApp
60
78
  session[:shopify] = nil
61
79
  session[:shopify_domain] = nil
62
80
  session[:shopify_user] = nil
81
+ session[:user_session] = nil
63
82
  end
64
83
 
65
84
  def login_url_with_optional_shop(top_level: false)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ExtensionVerificationController < ActionController::Base
4
+ protect_from_forgery with: :null_session
4
5
  before_action :verify_request
5
6
 
6
7
  private
@@ -6,7 +6,7 @@ module ShopifyApp
6
6
  repo[id]
7
7
  end
8
8
 
9
- def self.store(session)
9
+ def self.store(session, *args)
10
10
  id = SecureRandom.uuid
11
11
  repo[id] = session
12
12
  id
@@ -15,8 +15,8 @@ module ShopifyApp
15
15
  storage.retrieve(id)
16
16
  end
17
17
 
18
- def store(session)
19
- storage.store(session)
18
+ def store(session, *args)
19
+ storage.store(session, *args)
20
20
  end
21
21
 
22
22
  def storage
@@ -3,9 +3,12 @@ module ShopifyApp
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
7
6
  validates :shopify_token, presence: true
8
7
  validates :api_version, presence: true
8
+ validates :shopify_domain, presence: true,
9
+ if: Proc.new {|_| ShopifyApp.configuration.per_user_tokens? }
10
+ validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false },
11
+ if: Proc.new {|_| !ShopifyApp.configuration.per_user_tokens? }
9
12
  end
10
13
 
11
14
  def with_shopify_session(&block)
@@ -18,23 +21,19 @@ module ShopifyApp
18
21
  end
19
22
 
20
23
  class_methods do
21
- def store(session)
22
- shop = find_or_initialize_by(shopify_domain: session.domain)
23
- shop.shopify_token = session.token
24
- shop.save!
25
- shop.id
24
+
25
+ def strategy_klass
26
+ ShopifyApp.configuration.per_user_tokens? ?
27
+ ShopifyApp::SessionStorage::UserStorageStrategy :
28
+ ShopifyApp::SessionStorage::ShopStorageStrategy
26
29
  end
27
30
 
28
- def retrieve(id)
29
- return unless id
31
+ def store(auth_session, user: nil)
32
+ strategy_klass.store(auth_session, user)
33
+ end
30
34
 
31
- if shop = self.find_by(id: id)
32
- ShopifyAPI::Session.new(
33
- domain: shop.shopify_domain,
34
- token: shop.shopify_token,
35
- api_version: shop.api_version
36
- )
37
- end
35
+ def retrieve(id)
36
+ strategy_klass.retrieve(id)
38
37
  end
39
38
  end
40
39
  end
@@ -0,0 +1,24 @@
1
+ module ShopifyApp
2
+ module SessionStorage
3
+ class ShopStorageStrategy
4
+
5
+ def self.store(auth_session, *args)
6
+ shop = Shop.find_or_initialize_by(shopify_domain: auth_session.domain)
7
+ shop.shopify_token = auth_session.token
8
+ shop.save!
9
+ shop.id
10
+ end
11
+
12
+ def self.retrieve(id)
13
+ return unless id
14
+ if shop = Shop.find_by(id: id)
15
+ ShopifyAPI::Session.new(
16
+ domain: shop.shopify_domain,
17
+ token: shop.shopify_token,
18
+ api_version: shop.api_version
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module ShopifyApp
2
+ module SessionStorage
3
+ class UserStorageStrategy
4
+
5
+ def self.store(auth_session, user)
6
+ user = User.find_or_initialize_by(shopify_user_id: user[:id])
7
+ user.shopify_token = auth_session.token
8
+ user.shopify_domain = auth_session.domain
9
+ user.save!
10
+ user.id
11
+ end
12
+
13
+ def self.retrieve(id)
14
+ return unless id
15
+ if user = User.find_by(shopify_user_id: id)
16
+ ShopifyAPI::Session.new(
17
+ domain: user.shopify_domain,
18
+ token: user.shopify_token,
19
+ api_version: user.api_version
20
+ )
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module ShopifyApp
2
- VERSION = '11.4.0'.freeze
2
+ VERSION = '11.5.0'.freeze
3
3
  end
@@ -5909,12 +5909,6 @@
5909
5909
  "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
5910
5910
  "dev": true
5911
5911
  },
5912
- "serialize-javascript": {
5913
- "version": "1.9.1",
5914
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
5915
- "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
5916
- "dev": true
5917
- },
5918
5912
  "set-blocking": {
5919
5913
  "version": "2.0.0",
5920
5914
  "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -6497,31 +6491,6 @@
6497
6491
  }
6498
6492
  }
6499
6493
  },
6500
- "terser-webpack-plugin": {
6501
- "version": "1.4.1",
6502
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
6503
- "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
6504
- "dev": true,
6505
- "requires": {
6506
- "cacache": "12.0.3",
6507
- "find-cache-dir": "2.1.0",
6508
- "is-wsl": "1.1.0",
6509
- "schema-utils": "1.0.0",
6510
- "serialize-javascript": "1.9.1",
6511
- "source-map": "0.6.1",
6512
- "terser": "4.3.1",
6513
- "webpack-sources": "1.4.3",
6514
- "worker-farm": "1.7.0"
6515
- },
6516
- "dependencies": {
6517
- "source-map": {
6518
- "version": "0.6.1",
6519
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
6520
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
6521
- "dev": true
6522
- }
6523
- }
6524
- },
6525
6494
  "through2": {
6526
6495
  "version": "2.0.5",
6527
6496
  "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@@ -6878,9 +6847,9 @@
6878
6847
  }
6879
6848
  },
6880
6849
  "webpack": {
6881
- "version": "4.40.2",
6882
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.40.2.tgz",
6883
- "integrity": "sha512-5nIvteTDCUws2DVvP9Qe+JPla7kWPPIDFZv55To7IycHWZ+Z5qBdaBYPyuXWdhggTufZkQwfIK+5rKQTVovm2A==",
6850
+ "version": "4.41.3",
6851
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.3.tgz",
6852
+ "integrity": "sha512-EcNzP9jGoxpQAXq1VOoTet0ik7/VVU1MovIfcUSAjLowc7GhcQku/sOXALvq5nPpSei2HF6VRhibeJSC3i/Law==",
6884
6853
  "dev": true,
6885
6854
  "requires": {
6886
6855
  "@webassemblyjs/ast": "1.8.5",
@@ -6903,7 +6872,7 @@
6903
6872
  "node-libs-browser": "2.2.1",
6904
6873
  "schema-utils": "1.0.0",
6905
6874
  "tapable": "1.1.3",
6906
- "terser-webpack-plugin": "1.4.1",
6875
+ "terser-webpack-plugin": "1.4.3",
6907
6876
  "watchpack": "1.6.0",
6908
6877
  "webpack-sources": "1.4.3"
6909
6878
  },
@@ -6925,6 +6894,35 @@
6925
6894
  "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
6926
6895
  "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
6927
6896
  "dev": true
6897
+ },
6898
+ "serialize-javascript": {
6899
+ "version": "2.1.2",
6900
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
6901
+ "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
6902
+ "dev": true
6903
+ },
6904
+ "source-map": {
6905
+ "version": "0.6.1",
6906
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
6907
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
6908
+ "dev": true
6909
+ },
6910
+ "terser-webpack-plugin": {
6911
+ "version": "1.4.3",
6912
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
6913
+ "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
6914
+ "dev": true,
6915
+ "requires": {
6916
+ "cacache": "12.0.3",
6917
+ "find-cache-dir": "2.1.0",
6918
+ "is-wsl": "1.1.0",
6919
+ "schema-utils": "1.0.0",
6920
+ "serialize-javascript": "2.1.2",
6921
+ "source-map": "0.6.1",
6922
+ "terser": "4.3.1",
6923
+ "webpack-sources": "1.4.3",
6924
+ "worker-farm": "1.7.0"
6925
+ }
6928
6926
  }
6929
6927
  }
6930
6928
  },
@@ -19,7 +19,7 @@
19
19
  "mocha-debug": "^0.0.1",
20
20
  "sinon": "^7.4.2",
21
21
  "sinon-chai": "^3.2.0",
22
- "webpack": "^4.40.2"
22
+ "webpack": "^4.41.3"
23
23
  },
24
24
  "scripts": {
25
25
  "test": "./node_modules/.bin/karma start --browsers ChromeHeadless --single-run"
@@ -2,6 +2,6 @@ audience: partner
2
2
  classification: library
3
3
  org_line: App & Partner Platform
4
4
  owners:
5
- - Shopify/app-partner-dev-tools-education
5
+ - Shopify/platform-dev-tools-education
6
6
  slack_channels:
7
7
  - dev-tools-education
@@ -18,6 +18,9 @@ Gem::Specification.new do |s|
18
18
  s.add_development_dependency('rake')
19
19
  s.add_development_dependency('byebug')
20
20
  s.add_development_dependency('pry')
21
+ s.add_development_dependency('pry-nav')
22
+ s.add_development_dependency('pry-stack_explorer')
23
+ s.add_development_dependency('rb-readline')
21
24
  s.add_development_dependency('sqlite3', '~> 1.4')
22
25
  s.add_development_dependency('minitest')
23
26
  s.add_development_dependency('mocha')
@@ -26,4 +29,4 @@ Gem::Specification.new do |s|
26
29
  s.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(test|example)/}) }
27
30
  s.test_files = `git ls-files -- {test}/*`.split("\n")
28
31
  s.require_paths = ["lib"]
29
- end
32
+ end
data/yarn.lock CHANGED
@@ -4360,10 +4360,10 @@ semver@^6.3.0:
4360
4360
  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
4361
4361
  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
4362
4362
 
4363
- serialize-javascript@^1.7.0:
4364
- version "1.9.1"
4365
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
4366
- integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
4363
+ serialize-javascript@^2.1.2:
4364
+ version "2.1.2"
4365
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
4366
+ integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
4367
4367
 
4368
4368
  set-blocking@^2.0.0, set-blocking@~2.0.0:
4369
4369
  version "2.0.0"
@@ -4769,16 +4769,16 @@ tar@^4:
4769
4769
  safe-buffer "^5.1.2"
4770
4770
  yallist "^3.0.3"
4771
4771
 
4772
- terser-webpack-plugin@^1.4.1:
4773
- version "1.4.1"
4774
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
4775
- integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
4772
+ terser-webpack-plugin@^1.4.3:
4773
+ version "1.4.3"
4774
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c"
4775
+ integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
4776
4776
  dependencies:
4777
4777
  cacache "^12.0.2"
4778
4778
  find-cache-dir "^2.1.0"
4779
4779
  is-wsl "^1.1.0"
4780
4780
  schema-utils "^1.0.0"
4781
- serialize-javascript "^1.7.0"
4781
+ serialize-javascript "^2.1.2"
4782
4782
  source-map "^0.6.1"
4783
4783
  terser "^4.1.2"
4784
4784
  webpack-sources "^1.4.0"
@@ -5083,10 +5083,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
5083
5083
  source-list-map "^2.0.0"
5084
5084
  source-map "~0.6.1"
5085
5085
 
5086
- webpack@^4.40.2:
5087
- version "4.40.2"
5088
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.40.2.tgz#d21433d250f900bf0facbabe8f50d585b2dc30a7"
5089
- integrity sha512-5nIvteTDCUws2DVvP9Qe+JPla7kWPPIDFZv55To7IycHWZ+Z5qBdaBYPyuXWdhggTufZkQwfIK+5rKQTVovm2A==
5086
+ webpack@^4.41.3:
5087
+ version "4.41.3"
5088
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.3.tgz#cb7592c43080337dbc9be9e98fc6478eb3981026"
5089
+ integrity sha512-EcNzP9jGoxpQAXq1VOoTet0ik7/VVU1MovIfcUSAjLowc7GhcQku/sOXALvq5nPpSei2HF6VRhibeJSC3i/Law==
5090
5090
  dependencies:
5091
5091
  "@webassemblyjs/ast" "1.8.5"
5092
5092
  "@webassemblyjs/helper-module-context" "1.8.5"
@@ -5108,7 +5108,7 @@ webpack@^4.40.2:
5108
5108
  node-libs-browser "^2.2.1"
5109
5109
  schema-utils "^1.0.0"
5110
5110
  tapable "^1.1.3"
5111
- terser-webpack-plugin "^1.4.1"
5111
+ terser-webpack-plugin "^1.4.3"
5112
5112
  watchpack "^1.6.0"
5113
5113
  webpack-sources "^1.4.1"
5114
5114
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_app
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.4.0
4
+ version: 11.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-10 00:00:00.000000000 Z
11
+ date: 2020-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: browser_sniffer
@@ -108,6 +108,48 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-nav
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry-stack_explorer
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rb-readline
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
111
153
  - !ruby/object:Gem::Dependency
112
154
  name: sqlite3
113
155
  requirement: !ruby/object:Gem::Requirement
@@ -275,6 +317,10 @@ files:
275
317
  - lib/generators/shopify_app/shop_model/templates/shop.rb
276
318
  - lib/generators/shopify_app/shop_model/templates/shops.yml
277
319
  - lib/generators/shopify_app/shopify_app_generator.rb
320
+ - lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb
321
+ - lib/generators/shopify_app/user_model/templates/user.rb
322
+ - lib/generators/shopify_app/user_model/templates/users.yml
323
+ - lib/generators/shopify_app/user_model/user_model_generator.rb
278
324
  - lib/generators/shopify_app/views/views_generator.rb
279
325
  - lib/shopify_app.rb
280
326
  - lib/shopify_app/configuration.rb
@@ -293,6 +339,8 @@ files:
293
339
  - lib/shopify_app/session/in_memory_session_store.rb
294
340
  - lib/shopify_app/session/session_repository.rb
295
341
  - lib/shopify_app/session/session_storage.rb
342
+ - lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb
343
+ - lib/shopify_app/session/storage_strategies/user_storage_strategy.rb
296
344
  - lib/shopify_app/utils.rb
297
345
  - lib/shopify_app/version.rb
298
346
  - package-lock.json