shopify_app 11.2.0 → 11.5.0

Sign up to get free protection for your applications and to get access to all the features.
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