shopify_app 11.3.2 → 11.7.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.
- 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
|