shopify_app 11.3.2 → 11.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -2
- data/CHANGELOG.md +24 -0
- data/README.md +100 -102
- data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +8 -2
- data/app/controllers/shopify_app/extension_verification_controller.rb +20 -0
- data/config/locales/nl.yml +7 -7
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +39 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +66 -0
- data/lib/generators/shopify_app/install/install_generator.rb +0 -4
- data/lib/generators/shopify_app/install/templates/shopify_app.rb +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
- data/lib/generators/shopify_app/user_model/templates/user.rb +7 -0
- data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +38 -0
- data/lib/shopify_app.rb +5 -0
- data/lib/shopify_app/configuration.rb +13 -8
- data/lib/shopify_app/controller_concerns/login_protection.rb +22 -3
- data/lib/shopify_app/engine.rb +4 -0
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +60 -0
- data/lib/shopify_app/session/in_memory_session_store.rb +1 -1
- data/lib/shopify_app/session/session_repository.rb +2 -2
- data/lib/shopify_app/session/session_storage.rb +14 -15
- data/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +24 -0
- data/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +26 -0
- data/lib/shopify_app/version.rb +1 -1
- data/package-lock.json +33 -35
- data/package.json +3 -2
- data/service.yml +1 -1
- data/shopify_app.gemspec +4 -2
- data/yarn.lock +14 -14
- metadata +49 -11
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MarketingActivitiesController < ShopifyApp::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'])
|
@@ -8,7 +8,7 @@ ShopifyApp.configure do |config|
|
|
8
8
|
config.embedded_app = <%= embedded_app? %>
|
9
9
|
config.after_authenticate_job = false
|
10
10
|
config.api_version = "<%= @api_version %>"
|
11
|
-
config.session_repository = ShopifyApp::InMemorySessionStore
|
11
|
+
config.session_repository = 'ShopifyApp::InMemorySessionStore'
|
12
12
|
end
|
13
13
|
|
14
14
|
# ShopifyApp::Utils.fetch_known_api_versions # Uncomment to fetch known api versions from shopify servers on boot
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateUsers < ActiveRecord::Migration[<%= rails_migration_version %>]
|
2
|
+
def self.up
|
3
|
+
create_table :users do |t|
|
4
|
+
t.bigint :shopify_user_id, null: false
|
5
|
+
t.string :shopify_domain, null: false
|
6
|
+
t.string :shopify_token, null: false
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :users, :shopify_user_id, unique: true
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :users
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
|
4
|
+
module ShopifyApp
|
5
|
+
module Generators
|
6
|
+
class UserModelGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
def create_user_model
|
11
|
+
copy_file 'user.rb', 'app/models/user.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_user_migration
|
15
|
+
migration_template 'db/migrate/create_users.erb', 'db/migrate/create_users.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_shopify_app_initializer
|
19
|
+
gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'User'
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_user_fixtures
|
23
|
+
copy_file 'users.yml', 'test/fixtures/users.yml'
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def rails_migration_version
|
29
|
+
Rails.version.match(/\d\.\d/)[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
# for generating a timestamp when using `create_migration`
|
33
|
+
def self.next_migration_number(dir)
|
34
|
+
ActiveRecord::Generators::Base.next_migration_number(dir)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/shopify_app.rb
CHANGED
@@ -40,7 +40,12 @@ module ShopifyApp
|
|
40
40
|
require 'shopify_app/managers/webhooks_manager'
|
41
41
|
require 'shopify_app/managers/scripttags_manager'
|
42
42
|
|
43
|
+
# middleware
|
44
|
+
require 'shopify_app/middleware/same_site_cookie_middleware'
|
45
|
+
|
43
46
|
# session
|
47
|
+
require 'shopify_app/session/storage_strategies/shop_storage_strategy'
|
48
|
+
require 'shopify_app/session/storage_strategies/user_storage_strategy'
|
44
49
|
require 'shopify_app/session/session_storage'
|
45
50
|
require 'shopify_app/session/session_repository'
|
46
51
|
require 'shopify_app/session/in_memory_session_store'
|
@@ -14,7 +14,9 @@ module ShopifyApp
|
|
14
14
|
attr_accessor :webhooks
|
15
15
|
attr_accessor :scripttags
|
16
16
|
attr_accessor :after_authenticate_job
|
17
|
-
|
17
|
+
attr_reader :session_repository
|
18
|
+
attr_accessor :per_user_tokens
|
19
|
+
alias_method :per_user_tokens?, :per_user_tokens
|
18
20
|
attr_accessor :api_version
|
19
21
|
|
20
22
|
# customise urls
|
@@ -34,11 +36,15 @@ module ShopifyApp
|
|
34
36
|
# allow namespacing webhook jobs
|
35
37
|
attr_accessor :webhook_jobs_namespace
|
36
38
|
|
39
|
+
# allow enabling of same site none on cookies
|
40
|
+
attr_accessor :enable_same_site_none
|
41
|
+
|
37
42
|
def initialize
|
38
43
|
@root_url = '/'
|
39
44
|
@myshopify_domain = 'myshopify.com'
|
40
45
|
@scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
|
41
46
|
@webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
|
47
|
+
@per_user_tokens = false
|
42
48
|
@disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
|
43
49
|
end
|
44
50
|
|
@@ -47,13 +53,8 @@ module ShopifyApp
|
|
47
53
|
end
|
48
54
|
|
49
55
|
def session_repository=(klass)
|
50
|
-
|
51
|
-
|
52
|
-
else
|
53
|
-
ActiveSupport::Reloader.to_prepare do
|
54
|
-
ShopifyApp::SessionRepository.storage = klass
|
55
|
-
end
|
56
|
-
end
|
56
|
+
@session_repository = klass
|
57
|
+
ShopifyApp::SessionRepository.storage = klass
|
57
58
|
end
|
58
59
|
|
59
60
|
def has_webhooks?
|
@@ -63,6 +64,10 @@ module ShopifyApp
|
|
63
64
|
def has_scripttags?
|
64
65
|
scripttags.present?
|
65
66
|
end
|
67
|
+
|
68
|
+
def enable_same_site_none
|
69
|
+
@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none
|
70
|
+
end
|
66
71
|
end
|
67
72
|
|
68
73
|
def self.configuration
|
@@ -27,12 +27,30 @@ module ShopifyApp
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def shop_session
|
30
|
-
|
31
|
-
|
30
|
+
if ShopifyApp.configuration.per_user_tokens?
|
31
|
+
return unless session[:shopify_user]
|
32
|
+
@shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify_user]['id'])
|
33
|
+
else
|
34
|
+
return unless session[:shopify]
|
35
|
+
@shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify])
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
39
|
+
def login_again_if_different_user_or_shop
|
40
|
+
if ShopifyApp.configuration.per_user_tokens?
|
41
|
+
valid_session_data = session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
|
42
|
+
sessions_do_not_match = session[:user_session] != params[:session] # current user is different from stored user
|
43
|
+
|
44
|
+
if valid_session_data && sessions_do_not_match
|
45
|
+
clear_session = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
35
49
|
if shop_session && params[:shop] && params[:shop].is_a?(String) && (shop_session.domain != params[:shop])
|
50
|
+
clear_session = true
|
51
|
+
end
|
52
|
+
|
53
|
+
if clear_session
|
36
54
|
clear_shop_session
|
37
55
|
redirect_to_login
|
38
56
|
end
|
@@ -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)
|
data/lib/shopify_app/engine.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
module ShopifyApp
|
2
|
+
class SameSiteCookieMiddleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
_status, headers, _body = @app.call(env)
|
9
|
+
ensure
|
10
|
+
user_agent = env['HTTP_USER_AGENT']
|
11
|
+
|
12
|
+
if headers && headers['Set-Cookie'] && !SameSiteCookieMiddleware.same_site_none_incompatible?(user_agent) &&
|
13
|
+
ShopifyApp.configuration.enable_same_site_none
|
14
|
+
|
15
|
+
cookies = headers['Set-Cookie'].split("\n").compact
|
16
|
+
|
17
|
+
cookies.each do |cookie|
|
18
|
+
unless cookie.include?("; SameSite")
|
19
|
+
headers['Set-Cookie'] = headers['Set-Cookie'].gsub("#{cookie}", "#{cookie}; secure; SameSite=None")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.same_site_none_incompatible?(user_agent)
|
26
|
+
sniffer = BrowserSniffer.new(user_agent)
|
27
|
+
|
28
|
+
webkit_same_site_bug?(sniffer) || drops_unrecognized_same_site_cookies?(sniffer)
|
29
|
+
rescue
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.webkit_same_site_bug?(sniffer)
|
34
|
+
(sniffer.os == :ios && sniffer.os_version.match?(/^([0-9]|1[12])[\.\_]/)) ||
|
35
|
+
(sniffer.os == :mac && sniffer.browser == :safari && sniffer.os_version.match?(/^10[\.\_]14/))
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.drops_unrecognized_same_site_cookies?(sniffer)
|
39
|
+
(chromium_based?(sniffer) && sniffer.major_browser_version >= 51 && sniffer.major_browser_version <= 66) ||
|
40
|
+
(uc_browser?(sniffer) && !uc_browser_version_at_least?(sniffer: sniffer, major: 12, minor: 13, build: 2))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.chromium_based?(sniffer)
|
44
|
+
sniffer.browser_name.downcase.match?(/chrom(e|ium)/)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.uc_browser?(sniffer)
|
48
|
+
sniffer.user_agent.downcase.match?(/uc\s?browser/)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.uc_browser_version_at_least?(sniffer:, major:, minor:, build:)
|
52
|
+
digits = sniffer.browser_version.split('.').map(&:to_i)
|
53
|
+
return false unless digits.count >= 3
|
54
|
+
|
55
|
+
return digits[0] > major if digits[0] != major
|
56
|
+
return digits[1] > minor if digits[1] != minor
|
57
|
+
digits[2] >= build
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
29
|
-
|
31
|
+
def store(auth_session, user: nil)
|
32
|
+
strategy_klass.store(auth_session, user)
|
33
|
+
end
|
30
34
|
|
31
|
-
|
32
|
-
|
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
|