shopify_app 11.2.0 → 11.5.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -2
  3. data/CHANGELOG.md +30 -0
  4. data/README.md +116 -101
  5. data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
  6. data/app/controllers/shopify_app/callback_controller.rb +8 -2
  7. data/config/locales/de.yml +3 -3
  8. data/config/locales/nl.yml +7 -7
  9. data/config/locales/pt-BR.yml +5 -5
  10. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +39 -0
  11. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +66 -0
  12. data/lib/generators/shopify_app/install/install_generator.rb +20 -9
  13. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +5 -1
  14. data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
  15. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -0
  16. data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
  17. data/lib/generators/shopify_app/user_model/templates/user.rb +7 -0
  18. data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
  19. data/lib/generators/shopify_app/user_model/user_model_generator.rb +38 -0
  20. data/lib/shopify_app.rb +46 -29
  21. data/lib/shopify_app/configuration.rb +8 -0
  22. data/lib/shopify_app/controller_concerns/login_protection.rb +22 -3
  23. data/lib/shopify_app/controllers/extension_verification_controller.rb +18 -0
  24. data/lib/shopify_app/session/in_memory_session_store.rb +1 -1
  25. data/lib/shopify_app/session/session_repository.rb +2 -2
  26. data/lib/shopify_app/session/session_storage.rb +14 -15
  27. data/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +24 -0
  28. data/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +26 -0
  29. data/lib/shopify_app/version.rb +1 -1
  30. data/package-lock.json +1218 -1221
  31. data/package.json +1 -2
  32. data/service.yml +1 -1
  33. data/shopify_app.gemspec +5 -2
  34. data/yarn.lock +14 -14
  35. metadata +56 -4
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MarketingActivitiesController < ExtensionVerificationController
4
+ def preload_form_data
5
+ preload_data = {
6
+ "form_data": {
7
+ "budget": {
8
+ "currency": "USD",
9
+ }
10
+ }
11
+ }
12
+ render(json: preload_data, status: :ok)
13
+ end
14
+
15
+ def update
16
+ render(json: {}, status: :accepted)
17
+ end
18
+
19
+ def pause
20
+ render(json: {}, status: :accepted)
21
+ end
22
+
23
+ def resume
24
+ render(json: {}, status: :accepted)
25
+ end
26
+
27
+ def delete
28
+ render(json: {}, status: :accepted)
29
+ end
30
+
31
+ def preview
32
+ placeholder_img = "https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_small.png"
33
+ preview_response = {
34
+ "desktop": {
35
+ "preview_url": placeholder_img,
36
+ "content_type": "text/html",
37
+ "width": 360,
38
+ "height": 200
39
+ },
40
+ "mobile": {
41
+ "preview_url": placeholder_img,
42
+ "content_type": "text/html",
43
+ "width": 360,
44
+ "height": 200
45
+ }
46
+ }
47
+ render(json: preview_response, status: :ok)
48
+ end
49
+
50
+ def create
51
+ render(json: {}, status: :ok)
52
+ end
53
+
54
+ def republish
55
+ render(json: {}, status: :accepted)
56
+ end
57
+
58
+ def errors
59
+ request_id = params[:request_id]
60
+ message = params[:message]
61
+
62
+ Rails.logger.info("[Marketing Activity App Error Feedback] Request id: #{request_id}, message: #{message}")
63
+
64
+ render(json: {}, status: :ok)
65
+ end
66
+ end
@@ -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'])
@@ -40,12 +36,19 @@ module ShopifyApp
40
36
  end
41
37
 
42
38
  def create_embedded_app_layout
43
- if embedded_app?
44
- copy_file 'embedded_app.html.erb', 'app/views/layouts/embedded_app.html.erb'
39
+ return unless embedded_app?
40
+
41
+ copy_file 'embedded_app.html.erb', 'app/views/layouts/embedded_app.html.erb'
42
+ copy_file '_flash_messages.html.erb', 'app/views/layouts/_flash_messages.html.erb'
43
+
44
+ if ShopifyApp.use_webpacker?
45
+ copy_file('shopify_app.js', 'app/javascript/shopify_app/shopify_app.js')
46
+ copy_file('flash_messages.js', 'app/javascript/shopify_app/flash_messages.js')
47
+ copy_file('shopify_app_index.js', 'app/javascript/shopify_app/index.js')
48
+ append_to_file('app/javascript/packs/application.js', 'require("shopify_app")')
49
+ else
45
50
  copy_file('shopify_app.js', 'app/assets/javascripts/shopify_app.js')
46
- copy_file '_flash_messages.html.erb', 'app/views/layouts/_flash_messages.html.erb'
47
- copy_file('flash_messages.js',
48
- 'app/assets/javascripts/flash_messages.js')
51
+ copy_file('flash_messages.js', 'app/assets/javascripts/flash_messages.js')
49
52
  end
50
53
  end
51
54
 
@@ -57,6 +60,14 @@ module ShopifyApp
57
60
  route "mount ShopifyApp::Engine, at: '/'"
58
61
  end
59
62
 
63
+ def insert_hosts_into_development_config
64
+ inject_into_file(
65
+ 'config/environments/development.rb',
66
+ " config.hosts = (config.hosts rescue []) << /\\h+.ngrok.io/\n",
67
+ after: "Rails.application.configure do\n"
68
+ )
69
+ end
70
+
60
71
  private
61
72
 
62
73
  def embedded_app?
@@ -5,7 +5,11 @@
5
5
  <% application_name = ShopifyApp.configuration.application_name %>
6
6
  <title><%= application_name %></title>
7
7
  <%= stylesheet_link_tag 'application' %>
8
- <%= javascript_include_tag 'application', "data-turbolinks-track" => true %>
8
+ <% if ShopifyApp.use_webpacker? %>
9
+ <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
10
+ <% else %>
11
+ <%= javascript_include_tag 'application', "data-turbolinks-track" => true %>
12
+ <% end %>
9
13
  <%= csrf_meta_tags %>
10
14
  </head>
11
15
 
@@ -0,0 +1,2 @@
1
+ require('./shopify_app')
2
+ require('./flash_messages')
@@ -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
@@ -4,32 +4,49 @@ require 'shopify_app/version'
4
4
  require 'shopify_api'
5
5
  require 'omniauth-shopify-oauth2'
6
6
 
7
- # config
8
- require 'shopify_app/configuration'
9
-
10
- # engine
11
- require 'shopify_app/engine'
12
-
13
- # utils
14
- require 'shopify_app/utils'
15
-
16
- # controller concerns
17
- require 'shopify_app/controller_concerns/localization'
18
- require 'shopify_app/controller_concerns/itp'
19
- require 'shopify_app/controller_concerns/login_protection'
20
- require 'shopify_app/controller_concerns/embedded_app'
21
- require 'shopify_app/controller_concerns/webhook_verification'
22
- require 'shopify_app/controller_concerns/app_proxy_verification'
23
-
24
- # jobs
25
- require 'shopify_app/jobs/webhooks_manager_job'
26
- require 'shopify_app/jobs/scripttags_manager_job'
27
-
28
- # managers
29
- require 'shopify_app/managers/webhooks_manager'
30
- require 'shopify_app/managers/scripttags_manager'
31
-
32
- # session
33
- require 'shopify_app/session/session_storage'
34
- require 'shopify_app/session/session_repository'
35
- require 'shopify_app/session/in_memory_session_store'
7
+ module ShopifyApp
8
+ def self.rails6?
9
+ Rails::VERSION::MAJOR >= 6
10
+ end
11
+
12
+ def self.use_webpacker?
13
+ rails6? &&
14
+ defined?(Webpacker) == 'constant' &&
15
+ !configuration.disable_webpacker
16
+ end
17
+
18
+ # config
19
+ require 'shopify_app/configuration'
20
+
21
+ # engine
22
+ require 'shopify_app/engine'
23
+
24
+ # utils
25
+ require 'shopify_app/utils'
26
+
27
+ # controllers
28
+ require 'shopify_app/controllers/extension_verification_controller'
29
+
30
+ # controller concerns
31
+ require 'shopify_app/controller_concerns/localization'
32
+ require 'shopify_app/controller_concerns/itp'
33
+ require 'shopify_app/controller_concerns/login_protection'
34
+ require 'shopify_app/controller_concerns/embedded_app'
35
+ require 'shopify_app/controller_concerns/webhook_verification'
36
+ require 'shopify_app/controller_concerns/app_proxy_verification'
37
+
38
+ # jobs
39
+ require 'shopify_app/jobs/webhooks_manager_job'
40
+ require 'shopify_app/jobs/scripttags_manager_job'
41
+
42
+ # managers
43
+ require 'shopify_app/managers/webhooks_manager'
44
+ require 'shopify_app/managers/scripttags_manager'
45
+
46
+ # session
47
+ require 'shopify_app/session/storage_strategies/shop_storage_strategy'
48
+ require 'shopify_app/session/storage_strategies/user_storage_strategy'
49
+ require 'shopify_app/session/session_storage'
50
+ require 'shopify_app/session/session_repository'
51
+ require 'shopify_app/session/in_memory_session_store'
52
+ end
@@ -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
@@ -28,6 +30,9 @@ module ShopifyApp
28
30
  # configure myshopify domain for local shopify development
29
31
  attr_accessor :myshopify_domain
30
32
 
33
+ # ability to have webpacker installed but not used in this gem and the generators
34
+ attr_accessor :disable_webpacker
35
+
31
36
  # allow namespacing webhook jobs
32
37
  attr_accessor :webhook_jobs_namespace
33
38
 
@@ -36,6 +41,8 @@ module ShopifyApp
36
41
  @myshopify_domain = 'myshopify.com'
37
42
  @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
38
43
  @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
44
+ @per_user_tokens = false
45
+ @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
39
46
  end
40
47
 
41
48
  def login_url
@@ -59,6 +66,7 @@ module ShopifyApp
59
66
  def has_scripttags?
60
67
  scripttags.present?
61
68
  end
69
+
62
70
  end
63
71
 
64
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)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ExtensionVerificationController < ActionController::Base
4
+ protect_from_forgery with: :null_session
5
+ before_action :verify_request
6
+
7
+ private
8
+
9
+ def verify_request
10
+ hmac_header = request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
11
+ request_body = request.body.read
12
+ secret = ShopifyApp.configuration.secret
13
+ digest = OpenSSL::Digest.new('sha256')
14
+
15
+ expected_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, request_body))
16
+ head(:unauthorized) unless ActiveSupport::SecurityUtils.secure_compare(expected_hmac, hmac_header)
17
+ end
18
+ end
@@ -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