duodealer_app 1.0.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 +7 -0
- data/.!66854!duodealer_app.gemspec +0 -0
- data/.babelrc +5 -0
- data/.gitignore +16 -0
- data/.nvmrc +1 -0
- data/.rubocop.yml +263 -0
- data/.ruby-version +1 -0
- data/.travis.yml +27 -0
- data/Gemfile +8 -0
- data/LICENSE +19 -0
- data/README.md +553 -0
- data/Rakefile +6 -0
- data/app/assets/images/storage_access.svg +2 -0
- data/app/assets/javascripts/duodealer_app/enable_cookies.js +3 -0
- data/app/assets/javascripts/duodealer_app/itp_helper.js +40 -0
- data/app/assets/javascripts/duodealer_app/partition_cookies.js +8 -0
- data/app/assets/javascripts/duodealer_app/redirect.js +33 -0
- data/app/assets/javascripts/duodealer_app/request_storage_access.js +3 -0
- data/app/assets/javascripts/duodealer_app/storage_access.js +153 -0
- data/app/assets/javascripts/duodealer_app/storage_access_redirect.js +17 -0
- data/app/assets/javascripts/duodealer_app/top_level.js +2 -0
- data/app/assets/javascripts/duodealer_app/top_level_interaction.js +11 -0
- data/app/controllers/concerns/duodealer_app/authenticated.rb +15 -0
- data/app/controllers/concerns/duodealer_app/authenticated.rb-e +15 -0
- data/app/controllers/duodealer_app/authenticated_controller.rb +9 -0
- data/app/controllers/duodealer_app/authenticated_controller.rb-e +9 -0
- data/app/controllers/duodealer_app/callback_controller.rb +104 -0
- data/app/controllers/duodealer_app/callback_controller.rb-e +104 -0
- data/app/controllers/duodealer_app/extension_verification_controller.rb +19 -0
- data/app/controllers/duodealer_app/extension_verification_controller.rb-e +19 -0
- data/app/controllers/duodealer_app/sessions_controller.rb +159 -0
- data/app/controllers/duodealer_app/sessions_controller.rb-e +159 -0
- data/app/controllers/duodealer_app/webhooks_controller.rb +37 -0
- data/app/controllers/duodealer_app/webhooks_controller.rb-e +37 -0
- data/app/views/duodealer_app/partials/_button_styles.html.erb +104 -0
- data/app/views/duodealer_app/partials/_button_styles.html.erb-e +104 -0
- data/app/views/duodealer_app/partials/_card_styles.html.erb +33 -0
- data/app/views/duodealer_app/partials/_card_styles.html.erb-e +33 -0
- data/app/views/duodealer_app/partials/_empty_state_styles.html.erb +129 -0
- data/app/views/duodealer_app/partials/_empty_state_styles.html.erb-e +129 -0
- data/app/views/duodealer_app/partials/_layout_styles.html.erb +167 -0
- data/app/views/duodealer_app/partials/_layout_styles.html.erb-e +167 -0
- data/app/views/duodealer_app/partials/_typography_styles.html.erb +35 -0
- data/app/views/duodealer_app/partials/_typography_styles.html.erb-e +35 -0
- data/app/views/duodealer_app/sessions/enable_cookies.html.erb +75 -0
- data/app/views/duodealer_app/sessions/enable_cookies.html.erb-e +75 -0
- data/app/views/duodealer_app/sessions/new.html.erb +123 -0
- data/app/views/duodealer_app/sessions/new.html.erb-e +123 -0
- data/app/views/duodealer_app/sessions/request_storage_access.html.erb +68 -0
- data/app/views/duodealer_app/sessions/request_storage_access.html.erb-e +68 -0
- data/app/views/duodealer_app/sessions/top_level_interaction.html.erb +64 -0
- data/app/views/duodealer_app/sessions/top_level_interaction.html.erb-e +64 -0
- data/app/views/duodealer_app/shared/redirect.html.erb +23 -0
- data/app/views/duodealer_app/shared/redirect.html.erb-e +23 -0
- data/config/locales/cs.yml +23 -0
- data/config/locales/da.yml +20 -0
- data/config/locales/de.yml +22 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +22 -0
- data/config/locales/fi.yml +20 -0
- data/config/locales/fr.yml +23 -0
- data/config/locales/hi.yml +23 -0
- data/config/locales/it.yml +21 -0
- data/config/locales/ja.yml +17 -0
- data/config/locales/ko.yml +19 -0
- data/config/locales/ms.yml +22 -0
- data/config/locales/nb.yml +21 -0
- data/config/locales/nl.yml +21 -0
- data/config/locales/pl.yml +21 -0
- data/config/locales/pt-BR.yml +21 -0
- data/config/locales/pt-PT.yml +22 -0
- data/config/locales/sv.yml +21 -0
- data/config/locales/th.yml +20 -0
- data/config/locales/tr.yml +22 -0
- data/config/locales/zh-CN.yml +16 -0
- data/config/locales/zh-TW.yml +16 -0
- data/config/routes.rb +22 -0
- data/docs/.!20385!test-your-app.png +0 -0
- data/docs/.!20388!install-on-dev-shop.png +0 -0
- data/docs/.!62511!test-your-app.png +0 -0
- data/docs/.!62512!install-on-dev-shop.png +0 -0
- data/docs/.!62763!test-your-app.png +0 -0
- data/docs/.!62765!install-on-dev-shop.png +0 -0
- data/docs/.!63018!test-your-app.png +0 -0
- data/docs/.!63020!install-on-dev-shop.png +0 -0
- data/docs/.!63289!test-your-app.png +0 -0
- data/docs/.!63291!install-on-dev-shop.png +0 -0
- data/docs/.!63562!test-your-app.png +0 -0
- data/docs/.!63564!install-on-dev-shop.png +0 -0
- data/docs/.!63872!test-your-app.png +0 -0
- data/docs/.!63874!install-on-dev-shop.png +0 -0
- data/docs/.!64151!test-your-app.png +0 -0
- data/docs/.!64153!install-on-dev-shop.png +0 -0
- data/docs/.!64428!test-your-app.png +0 -0
- data/docs/.!64431!install-on-dev-shop.png +0 -0
- data/docs/.!64737!test-your-app.png +0 -0
- data/docs/.!64740!install-on-dev-shop.png +0 -0
- data/docs/.!65025!test-your-app.png +0 -0
- data/docs/.!65028!install-on-dev-shop.png +0 -0
- data/docs/.!65324!test-your-app.png +0 -0
- data/docs/.!65327!install-on-dev-shop.png +0 -0
- data/docs/.!65626!test-your-app.png +0 -0
- data/docs/.!65629!install-on-dev-shop.png +0 -0
- data/docs/.!65942!test-your-app.png +0 -0
- data/docs/.!65945!install-on-dev-shop.png +0 -0
- data/docs/.!66760!test-your-app.png +0 -0
- data/docs/.!66763!install-on-dev-shop.png +0 -0
- data/docs/.!67028!test-your-app.png +0 -0
- data/docs/.!67031!install-on-dev-shop.png +0 -0
- data/docs/.!67657!test-your-app.png +0 -0
- data/docs/.!67660!install-on-dev-shop.png +0 -0
- data/docs/.!68031!test-your-app.png +0 -0
- data/docs/.!68034!install-on-dev-shop.png +0 -0
- data/docs/.!68363!test-your-app.png +0 -0
- data/docs/.!68366!install-on-dev-shop.png +0 -0
- data/docs/Quickstart.md +103 -0
- data/docs/Releasing.md +17 -0
- data/docs/Troubleshooting.md +16 -0
- data/docs/install-on-dev-shop.png +0 -0
- data/docs/test-your-app.png +0 -0
- data/duodealer_app.gemspec +34 -0
- data/images/.!20334!app-proxy-screenshot.png +0 -0
- data/images/.!62504!app-proxy-screenshot.png +0 -0
- data/images/.!62754!app-proxy-screenshot.png +0 -0
- data/images/.!63008!app-proxy-screenshot.png +0 -0
- data/images/.!63277!app-proxy-screenshot.png +0 -0
- data/images/.!63548!app-proxy-screenshot.png +0 -0
- data/images/.!63855!app-proxy-screenshot.png +0 -0
- data/images/.!64132!app-proxy-screenshot.png +0 -0
- data/images/.!64407!app-proxy-screenshot.png +0 -0
- data/images/.!64714!app-proxy-screenshot.png +0 -0
- data/images/.!65000!app-proxy-screenshot.png +0 -0
- data/images/.!65296!app-proxy-screenshot.png +0 -0
- data/images/.!65594!app-proxy-screenshot.png +0 -0
- data/images/.!65908!app-proxy-screenshot.png +0 -0
- data/images/.!66724!app-proxy-screenshot.png +0 -0
- data/images/.!66989!app-proxy-screenshot.png +0 -0
- data/images/.!67614!app-proxy-screenshot.png +0 -0
- data/images/.!67986!app-proxy-screenshot.png +0 -0
- data/images/.!68314!app-proxy-screenshot.png +0 -0
- data/images/app-proxy-screenshot.png +0 -0
- data/karma.conf.js +44 -0
- data/lib/duodealer_app.rb +54 -0
- data/lib/duodealer_app/configuration.rb +85 -0
- data/lib/duodealer_app/controller_concerns/app_proxy_verification.rb +39 -0
- data/lib/duodealer_app/controller_concerns/embedded_app.rb +20 -0
- data/lib/duodealer_app/controller_concerns/itp.rb +44 -0
- data/lib/duodealer_app/controller_concerns/localization.rb +23 -0
- data/lib/duodealer_app/controller_concerns/login_protection.rb +180 -0
- data/lib/duodealer_app/controller_concerns/webhook_verification.rb +39 -0
- data/lib/duodealer_app/engine.rb +22 -0
- data/lib/duodealer_app/jobs/scripttags_manager_job.rb +17 -0
- data/lib/duodealer_app/jobs/webhooks_manager_job.rb +17 -0
- data/lib/duodealer_app/managers/scripttags_manager.rb +78 -0
- data/lib/duodealer_app/managers/webhooks_manager.rb +62 -0
- data/lib/duodealer_app/middleware/same_site_cookie_middleware.rb +69 -0
- data/lib/duodealer_app/session/in_memory_session_store.rb +29 -0
- data/lib/duodealer_app/session/session_repository.rb +33 -0
- data/lib/duodealer_app/session/session_storage.rb +31 -0
- data/lib/duodealer_app/session/storage_strategies/shop_storage_strategy.rb +25 -0
- data/lib/duodealer_app/session/storage_strategies/user_storage_strategy.rb +26 -0
- data/lib/duodealer_app/utils.rb +24 -0
- data/lib/duodealer_app/version.rb +5 -0
- data/lib/generators/duodealer_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +46 -0
- data/lib/generators/duodealer_app/add_after_authenticate_job/templates/after_authenticate_job.rb +10 -0
- data/lib/generators/duodealer_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +40 -0
- data/lib/generators/duodealer_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
- data/lib/generators/duodealer_app/add_webhook/add_webhook_generator.rb +69 -0
- data/lib/generators/duodealer_app/add_webhook/templates/webhook_job.rb +8 -0
- data/lib/generators/duodealer_app/app_proxy_controller/app_proxy_controller_generator.rb +27 -0
- data/lib/generators/duodealer_app/app_proxy_controller/templates/app_proxy_controller.rb +9 -0
- data/lib/generators/duodealer_app/app_proxy_controller/templates/app_proxy_route.rb +10 -0
- data/lib/generators/duodealer_app/app_proxy_controller/templates/index.html.erb +19 -0
- data/lib/generators/duodealer_app/authenticated_controller/authenticated_controller_generator.rb +15 -0
- data/lib/generators/duodealer_app/authenticated_controller/templates/authenticated_controller.rb +5 -0
- data/lib/generators/duodealer_app/controllers/controllers_generator.rb +30 -0
- data/lib/generators/duodealer_app/duodealer_app_generator.rb +19 -0
- data/lib/generators/duodealer_app/home_controller/home_controller_generator.rb +27 -0
- data/lib/generators/duodealer_app/home_controller/templates/home_controller.rb +8 -0
- data/lib/generators/duodealer_app/home_controller/templates/index.html.erb +21 -0
- data/lib/generators/duodealer_app/install/install_generator.rb +83 -0
- data/lib/generators/duodealer_app/install/templates/_flash_messages.html.erb +3 -0
- data/lib/generators/duodealer_app/install/templates/duodealer_app.js +15 -0
- data/lib/generators/duodealer_app/install/templates/duodealer_app.rb +15 -0
- data/lib/generators/duodealer_app/install/templates/duodealer_app_index.js +2 -0
- data/lib/generators/duodealer_app/install/templates/duodealer_provider.rb +20 -0
- data/lib/generators/duodealer_app/install/templates/embedded_app.html.erb +41 -0
- data/lib/generators/duodealer_app/install/templates/flash_messages.js +26 -0
- data/lib/generators/duodealer_app/install/templates/omniauth.rb +2 -0
- data/lib/generators/duodealer_app/install/templates/session_store.rb +4 -0
- data/lib/generators/duodealer_app/install/templates/user_agent.rb +5 -0
- data/lib/generators/duodealer_app/rotate_duodealer_token_job/rotate_duodealer_token_job_generator.rb +16 -0
- data/lib/generators/duodealer_app/rotate_duodealer_token_job/templates/rotate_duodealer_token.rake +17 -0
- data/lib/generators/duodealer_app/rotate_duodealer_token_job/templates/rotate_duodealer_token_job.rb +42 -0
- data/lib/generators/duodealer_app/routes/routes_generator.rb +32 -0
- data/lib/generators/duodealer_app/routes/templates/routes.rb +11 -0
- data/lib/generators/duodealer_app/shop_model/shop_model_generator.rb +39 -0
- data/lib/generators/duodealer_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
- data/lib/generators/duodealer_app/shop_model/templates/shop.rb +7 -0
- data/lib/generators/duodealer_app/shop_model/templates/shops.yml +3 -0
- data/lib/generators/duodealer_app/user_model/templates/db/migrate/create_users.erb +16 -0
- data/lib/generators/duodealer_app/user_model/templates/user.rb +7 -0
- data/lib/generators/duodealer_app/user_model/templates/users.yml +4 -0
- data/lib/generators/duodealer_app/user_model/user_model_generator.rb +39 -0
- data/lib/generators/duodealer_app/views/views_generator.rb +30 -0
- data/package-lock.json +7224 -0
- data/package.json +28 -0
- data/shipit.rubygems.yml +4 -0
- data/translation.yml +7 -0
- data/webpack.config.js +24 -0
- data/yarn.lock +5263 -0
- metadata +447 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
module AppProxyVerification
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
skip_before_action :verify_authenticity_token, raise: false
|
|
9
|
+
before_action :verify_proxy_request
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def verify_proxy_request
|
|
13
|
+
return head :forbidden unless query_string_valid?(request.query_string)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def query_string_valid?(query_string)
|
|
18
|
+
query_hash = Rack::Utils.parse_query(query_string)
|
|
19
|
+
|
|
20
|
+
signature = query_hash.delete("signature")
|
|
21
|
+
return false if signature.nil?
|
|
22
|
+
|
|
23
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
|
24
|
+
calculated_signature(query_hash),
|
|
25
|
+
signature
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def calculated_signature(query_hash_without_signature)
|
|
30
|
+
sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join
|
|
31
|
+
|
|
32
|
+
OpenSSL::HMAC.hexdigest(
|
|
33
|
+
OpenSSL::Digest.new("sha256"),
|
|
34
|
+
DuodealerApp.configuration.secret,
|
|
35
|
+
sorted_params
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
module EmbeddedApp
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
if DuodealerApp.configuration.embedded_app?
|
|
9
|
+
after_action :set_esdk_headers
|
|
10
|
+
layout "embedded_app"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def set_esdk_headers
|
|
16
|
+
response.set_header("P3P", 'CP="Not used"')
|
|
17
|
+
response.headers.except!("X-Frame-Options")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
# Cookie management helpers required for ITP implementation
|
|
5
|
+
module Itp
|
|
6
|
+
private
|
|
7
|
+
def set_test_cookie
|
|
8
|
+
return unless DuodealerApp.configuration.embedded_app?
|
|
9
|
+
return unless user_agent_can_partition_cookies
|
|
10
|
+
|
|
11
|
+
session["duodealer.cookies_persist"] = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set_top_level_oauth_cookie
|
|
15
|
+
session["duodealer.top_level_oauth"] = true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clear_top_level_oauth_cookie
|
|
19
|
+
session.delete("duodealer.top_level_oauth")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def user_agent_is_mobile
|
|
23
|
+
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
|
24
|
+
|
|
25
|
+
user_agent[:name].to_s.match(/Duo\sDealer\sMobile/)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def user_agent_is_pos
|
|
29
|
+
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
|
30
|
+
|
|
31
|
+
user_agent[:name].to_s.match(/Duo\sDealer\sPOS/)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def user_agent_can_partition_cookies
|
|
35
|
+
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
|
36
|
+
|
|
37
|
+
is_safari = user_agent[:name].to_s.match(/Safari/)
|
|
38
|
+
|
|
39
|
+
return false unless is_safari
|
|
40
|
+
|
|
41
|
+
user_agent[:version].to_s.match(/12\.0/)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
module Localization
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
before_action :set_locale
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def set_locale
|
|
13
|
+
if params[:locale]
|
|
14
|
+
session[:locale] = params[:locale]
|
|
15
|
+
else
|
|
16
|
+
session[:locale] ||= I18n.default_locale
|
|
17
|
+
end
|
|
18
|
+
I18n.locale = session[:locale]
|
|
19
|
+
rescue I18n::InvalidLocale
|
|
20
|
+
I18n.locale = I18n.default_locale
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "browser_sniffer"
|
|
4
|
+
|
|
5
|
+
module DuodealerApp
|
|
6
|
+
module LoginProtection
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
include DuodealerApp::Itp
|
|
9
|
+
|
|
10
|
+
class DuodealerDomainNotFound < StandardError; end
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
after_action :set_test_cookie
|
|
14
|
+
rescue_from ActiveResource::UnauthorizedAccess, with: :close_session
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def duodealer_session
|
|
18
|
+
return redirect_to_login unless shop_session
|
|
19
|
+
clear_top_level_oauth_cookie
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
DuodealerAPI::Base.activate_session(shop_session)
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
DuodealerAPI::Base.clear_session
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def shop_session
|
|
30
|
+
if DuodealerApp.configuration.per_user_tokens?
|
|
31
|
+
return unless session[:duodealer_user]
|
|
32
|
+
@shop_session ||= DuodealerApp::SessionRepository.retrieve(session[:duodealer_user]["id"])
|
|
33
|
+
else
|
|
34
|
+
return unless session[:duodealer]
|
|
35
|
+
@shop_session ||= DuodealerApp::SessionRepository.retrieve(session[:duodealer])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def login_again_if_different_user_or_shop
|
|
40
|
+
if DuodealerApp.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
|
+
|
|
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
|
|
54
|
+
clear_shop_session
|
|
55
|
+
redirect_to_login
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
def redirect_to_login
|
|
61
|
+
if request.xhr?
|
|
62
|
+
head :unauthorized
|
|
63
|
+
else
|
|
64
|
+
if request.get?
|
|
65
|
+
path = request.path
|
|
66
|
+
query = sanitized_params.to_query
|
|
67
|
+
else
|
|
68
|
+
referer = URI(request.referer || "/")
|
|
69
|
+
path = referer.path
|
|
70
|
+
query = "#{referer.query}&#{sanitized_params.to_query}"
|
|
71
|
+
end
|
|
72
|
+
session[:return_to] = "#{path}?#{query}"
|
|
73
|
+
redirect_to(login_url_with_optional_shop)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def close_session
|
|
78
|
+
clear_shop_session
|
|
79
|
+
redirect_to(login_url_with_optional_shop)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def clear_shop_session
|
|
83
|
+
session[:duodealer] = nil
|
|
84
|
+
session[:duodealer_domain] = nil
|
|
85
|
+
session[:duodealer_user] = nil
|
|
86
|
+
session[:user_session] = nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def login_url_with_optional_shop(top_level: false)
|
|
90
|
+
url = DuodealerApp.configuration.login_url
|
|
91
|
+
|
|
92
|
+
query_params = login_url_params(top_level: top_level)
|
|
93
|
+
|
|
94
|
+
url = "#{url}?#{query_params.to_query}" if query_params.present?
|
|
95
|
+
url
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def login_url_params(top_level:)
|
|
99
|
+
query_params = {}
|
|
100
|
+
query_params[:shop] = sanitized_params[:shop] if params[:shop].present?
|
|
101
|
+
|
|
102
|
+
return_to = session[:return_to] || params[:return_to]
|
|
103
|
+
|
|
104
|
+
if return_to.present? && return_to_param_required?
|
|
105
|
+
query_params[:return_to] = return_to
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
has_referer_shop_name = referer_sanitized_shop_name.present?
|
|
109
|
+
|
|
110
|
+
if has_referer_shop_name
|
|
111
|
+
query_params[:shop] ||= referer_sanitized_shop_name
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
query_params[:top_level] = true if top_level
|
|
115
|
+
query_params
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def return_to_param_required?
|
|
119
|
+
native_params = %i[shop hmac timestamp locale protocol return_to]
|
|
120
|
+
request.path != "/" || sanitized_params.except(*native_params).any?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def fullpage_redirect_to(url)
|
|
124
|
+
if DuodealerApp.configuration.embedded_app?
|
|
125
|
+
render "duodealer_app/shared/redirect", layout: false, locals: { url: url, current_duodealer_domain: current_duodealer_domain }
|
|
126
|
+
else
|
|
127
|
+
redirect_to url
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def current_duodealer_domain
|
|
132
|
+
duodealer_domain = sanitized_shop_name || session[:duodealer_domain]
|
|
133
|
+
return duodealer_domain if duodealer_domain.present?
|
|
134
|
+
|
|
135
|
+
raise DuodealerDomainNotFound
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def sanitized_shop_name
|
|
139
|
+
@sanitized_shop_name ||= sanitize_shop_param(params)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def referer_sanitized_shop_name
|
|
143
|
+
return if request.referer.blank?
|
|
144
|
+
|
|
145
|
+
@referer_sanitized_shop_name ||= begin
|
|
146
|
+
referer_uri = URI(request.referer)
|
|
147
|
+
query_params = Rack::Utils.parse_query(referer_uri.query)
|
|
148
|
+
|
|
149
|
+
sanitize_shop_param(query_params.with_indifferent_access)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def sanitize_shop_param(params)
|
|
154
|
+
return if params[:shop].blank?
|
|
155
|
+
DuodealerApp::Utils.sanitize_shop_domain(params[:shop])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def sanitized_params
|
|
159
|
+
request.query_parameters.clone.tap do |query_params|
|
|
160
|
+
if params[:shop].is_a?(String)
|
|
161
|
+
query_params[:shop] = sanitize_shop_param(params)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def return_address
|
|
167
|
+
session.delete(:return_to) || DuodealerApp.configuration.root_url
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def return_address_with_params(params)
|
|
171
|
+
uri = URI(return_address)
|
|
172
|
+
uri.query = CGI.parse(uri.query.to_s)
|
|
173
|
+
.symbolize_keys
|
|
174
|
+
.transform_values { |v| v.one? ? v.first : v }
|
|
175
|
+
.merge(params)
|
|
176
|
+
.to_query
|
|
177
|
+
uri.to_s
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
module WebhookVerification
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
skip_before_action :verify_authenticity_token, raise: false
|
|
9
|
+
before_action :verify_request
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def verify_request
|
|
14
|
+
data = request.raw_post
|
|
15
|
+
return head :unauthorized unless hmac_valid?(data)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hmac_valid?(data)
|
|
19
|
+
secrets = [DuodealerApp.configuration.secret, DuodealerApp.configuration.old_secret].reject(&:blank?)
|
|
20
|
+
|
|
21
|
+
secrets.any? do |secret|
|
|
22
|
+
digest = OpenSSL::Digest.new("sha256")
|
|
23
|
+
|
|
24
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
|
25
|
+
duodealer_hmac,
|
|
26
|
+
Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, data))
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def shop_domain
|
|
32
|
+
request.headers["HTTP_X_DUODEALER_SHOP_DOMAIN"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def duodealer_hmac
|
|
36
|
+
request.headers["HTTP_X_DUODEALER_HMAC_SHA256"]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
class Engine < Rails::Engine
|
|
5
|
+
engine_name "duodealer_app"
|
|
6
|
+
isolate_namespace DuodealerApp
|
|
7
|
+
|
|
8
|
+
initializer "duodealer_app.assets.precompile" do |app|
|
|
9
|
+
app.config.assets.precompile += %w[
|
|
10
|
+
duodealer_app/redirect.js
|
|
11
|
+
duodealer_app/top_level.js
|
|
12
|
+
duodealer_app/enable_cookies.js
|
|
13
|
+
duodealer_app/request_storage_access.js
|
|
14
|
+
storage_access.svg
|
|
15
|
+
]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "duodealer_app.middleware" do |app|
|
|
19
|
+
app.config.middleware.insert_after(::Rack::Runtime, DuodealerApp::SameSiteCookieMiddleware)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
class ScripttagsManagerJob < ActiveJob::Base
|
|
5
|
+
queue_as do
|
|
6
|
+
DuodealerApp.configuration.scripttags_manager_queue_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def perform(shop_domain:, shop_token:, scripttags:)
|
|
10
|
+
api_version = DuodealerApp.configuration.api_version
|
|
11
|
+
DuodealerAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
|
|
12
|
+
manager = ScripttagsManager.new(scripttags, shop_domain)
|
|
13
|
+
manager.create_scripttags
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
class WebhooksManagerJob < ActiveJob::Base
|
|
5
|
+
queue_as do
|
|
6
|
+
DuodealerApp.configuration.webhooks_manager_queue_name
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def perform(shop_domain:, shop_token:, webhooks:)
|
|
10
|
+
api_version = DuodealerApp.configuration.api_version
|
|
11
|
+
DuodealerAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
|
|
12
|
+
manager = WebhooksManager.new(webhooks)
|
|
13
|
+
manager.create_webhooks
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuodealerApp
|
|
4
|
+
class ScripttagsManager
|
|
5
|
+
class CreationFailed < StandardError; end
|
|
6
|
+
|
|
7
|
+
def self.queue(shop_domain, shop_token, scripttags)
|
|
8
|
+
DuodealerApp::ScripttagsManagerJob.perform_later(
|
|
9
|
+
shop_domain: shop_domain,
|
|
10
|
+
shop_token: shop_token,
|
|
11
|
+
# Procs cannot be serialized so we interpolate now, if necessary
|
|
12
|
+
scripttags: build_src(scripttags, shop_domain)
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.build_src(scripttags, domain)
|
|
17
|
+
scripttags.map do |tag|
|
|
18
|
+
next tag unless tag[:src].respond_to?(:call)
|
|
19
|
+
tag = tag.dup
|
|
20
|
+
tag[:src] = tag[:src].call(domain)
|
|
21
|
+
tag
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :required_scripttags, :shop_domain
|
|
26
|
+
|
|
27
|
+
def initialize(scripttags, shop_domain)
|
|
28
|
+
@required_scripttags = scripttags
|
|
29
|
+
@shop_domain = shop_domain
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def recreate_scripttags!
|
|
33
|
+
destroy_scripttags
|
|
34
|
+
create_scripttags
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_scripttags
|
|
38
|
+
return if required_scripttags.blank?
|
|
39
|
+
|
|
40
|
+
expanded_scripttags.each do |scripttag|
|
|
41
|
+
create_scripttag(scripttag) unless scripttag_exists?(scripttag[:src])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy_scripttags
|
|
46
|
+
scripttags = expanded_scripttags
|
|
47
|
+
DuodealerAPI::ScriptTag.all.each do |tag|
|
|
48
|
+
DuodealerAPI::ScriptTag.delete(tag.id) if is_required_scripttag?(scripttags, tag)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@current_scripttags = nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
def expanded_scripttags
|
|
56
|
+
self.class.build_src(required_scripttags, shop_domain)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def is_required_scripttag?(scripttags, tag)
|
|
60
|
+
scripttags.map { |w| w[:src] }.include? tag.src
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def create_scripttag(attributes)
|
|
64
|
+
attributes.reverse_merge!(format: "json")
|
|
65
|
+
scripttag = DuodealerAPI::ScriptTag.create(attributes)
|
|
66
|
+
raise CreationFailed, scripttag.errors.full_messages.to_sentence unless scripttag.persisted?
|
|
67
|
+
scripttag
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def scripttag_exists?(src)
|
|
71
|
+
current_scripttags[src]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def current_scripttags
|
|
75
|
+
@current_scripttags ||= DuodealerAPI::ScriptTag.all.index_by(&:src)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|