shopify_app 21.2.0 → 21.3.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/.github/workflows/build.yml +7 -8
- data/.github/workflows/stale.yml +1 -0
- data/.spin/rails/prepare-application +8 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +106 -91
- data/README.md +19 -15
- data/SECURITY.md +1 -1
- data/app/controllers/concerns/shopify_app/authenticated.rb +4 -9
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +3 -2
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +19 -0
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +62 -0
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +3 -38
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +64 -27
- data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
- data/app/controllers/shopify_app/sessions_controller.rb +11 -2
- data/config/locales/ja.yml +1 -1
- data/docs/Troubleshooting.md +38 -2
- data/docs/Upgrading.md +40 -32
- data/docs/shopify_app/controller-concerns.md +48 -0
- data/docs/shopify_app/logging.md +21 -0
- data/docs/shopify_app/webhooks.md +13 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb +23 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +1 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +2 -1
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +2 -1
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +8 -2
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
- data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +5 -0
- data/lib/shopify_app/configuration.rb +11 -0
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +3 -0
- data/lib/shopify_app/controller_concerns/itp.rb +5 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +52 -13
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +4 -1
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
- data/lib/shopify_app/logger.rb +28 -0
- data/lib/shopify_app/managers/scripttags_manager.rb +1 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +6 -0
- data/lib/shopify_app/session/jwt.rb +1 -1
- data/lib/shopify_app/session/session_repository.rb +15 -4
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +2 -0
- data/shopify_app.gemspec +2 -1
- data/yarn.lock +5 -5
- metadata +30 -4
| @@ -8,6 +8,7 @@ module ShopifyApp | |
| 8 8 | 
             
                  source_root File.expand_path("../templates", __FILE__)
         | 
| 9 9 |  | 
| 10 10 | 
             
                  def generate_app_extension
         | 
| 11 | 
            +
                    ShopifyApp::Logger.deprecated("MarketingActivitiesController will be removed in an upcoming version", "22.0.0")
         | 
| 11 12 | 
             
                    template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
         | 
| 12 13 | 
             
                    generate_routes
         | 
| 13 14 | 
             
                  end
         | 
| @@ -55,7 +55,8 @@ class MarketingActivitiesController < ShopifyApp::ExtensionVerificationControlle | |
| 55 55 | 
             
                request_id = params[:request_id]
         | 
| 56 56 | 
             
                message = params[:message]
         | 
| 57 57 |  | 
| 58 | 
            -
                 | 
| 58 | 
            +
                ShopifyApp::Logger.info("[Marketing Activity App Error Feedback]"\
         | 
| 59 | 
            +
                  "Request id: #{request_id}, message: #{message}")
         | 
| 59 60 |  | 
| 60 61 | 
             
                render(json: {}, status: :ok)
         | 
| 61 62 | 
             
              end
         | 
| @@ -12,7 +12,8 @@ class <%= @job_class_name %> < ActiveJob::Base | |
| 12 12 |  | 
| 13 13 | 
             
                if shop.nil?
         | 
| 14 14 | 
             
                  logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
         | 
| 15 | 
            -
                   | 
| 15 | 
            +
                  
         | 
| 16 | 
            +
                  raise ActiveRecord::RecordNotFound, "Shop Not Found"
         | 
| 16 17 | 
             
                end
         | 
| 17 18 |  | 
| 18 19 | 
             
                shop.with_shopify_session do
         | 
| @@ -24,7 +24,7 @@ | |
| 24 24 | 
             
                      });
         | 
| 25 25 |  | 
| 26 26 | 
             
                      var fetchProducts = function() {
         | 
| 27 | 
            -
                        var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
         | 
| 27 | 
            +
                        var headers = new Headers({ "Content-Type": "text/javascript", "Authorization": "Bearer " + window.sessionToken });
         | 
| 28 28 | 
             
                        return fetch("/products", { headers })
         | 
| 29 29 | 
             
                          .then(response => response.json())
         | 
| 30 30 | 
             
                          .then(data => {
         | 
| @@ -7,8 +7,14 @@ ShopifyApp.configure do |config| | |
| 7 7 | 
             
              config.after_authenticate_job = false
         | 
| 8 8 | 
             
              config.api_version = "<%= @api_version %>"
         | 
| 9 9 | 
             
              config.shop_session_repository = 'Shop'
         | 
| 10 | 
            -
             | 
| 10 | 
            +
              config.log_level = :info
         | 
| 11 11 | 
             
              config.reauth_on_access_scope_changes = true
         | 
| 12 | 
            +
              config.webhooks = [
         | 
| 13 | 
            +
                { topic: "app/uninstalled", address: "webhooks/app_uninstalled"},
         | 
| 14 | 
            +
                { topic: "customers/data_request", address: "webhooks/customers_data_request" },
         | 
| 15 | 
            +
                { topic: "customer/redact", address: "webhooks/customers_redact"},
         | 
| 16 | 
            +
                { topic: "shop/redact", address: "webhooks/shop_redact"}
         | 
| 17 | 
            +
              ]
         | 
| 12 18 |  | 
| 13 19 | 
             
              config.api_key = ENV.fetch('SHOPIFY_API_KEY', '').presence
         | 
| 14 20 | 
             
              config.secret = ENV.fetch('SHOPIFY_API_SECRET', '').presence
         | 
| @@ -42,7 +48,7 @@ Rails.application.config.after_initialize do | |
| 42 48 | 
             
                  scope: ShopifyApp.configuration.scope,
         | 
| 43 49 | 
             
                  is_private: !ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', '').empty?,
         | 
| 44 50 | 
             
                  is_embedded: ShopifyApp.configuration.embedded_app,
         | 
| 45 | 
            -
                   | 
| 51 | 
            +
                  log_level: :info,
         | 
| 46 52 | 
             
                  logger: Rails.logger,
         | 
| 47 53 | 
             
                  private_shop: ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', nil),
         | 
| 48 54 | 
             
                  user_agent_prefix: "ShopifyApp/#{ShopifyApp::VERSION}"
         | 
| @@ -9,6 +9,8 @@ module ShopifyApp | |
| 9 9 | 
             
                  end
         | 
| 10 10 |  | 
| 11 11 | 
             
                  def run_all_generators
         | 
| 12 | 
            +
                    generate("shopify_app:add_app_uninstalled_job")
         | 
| 13 | 
            +
                    generate("shopify_app:add_gdpr_jobs")
         | 
| 12 14 | 
             
                    generate("shopify_app:install #{@opts.join(" ")}")
         | 
| 13 15 | 
             
                    generate("shopify_app:shop_model #{@opts.join(" ")}")
         | 
| 14 16 | 
             
                    generate("shopify_app:authenticated_controller")
         | 
| @@ -12,6 +12,11 @@ module ShopifyApp | |
| 12 12 | 
             
                        "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
         | 
| 13 13 | 
             
                    end
         | 
| 14 14 |  | 
| 15 | 
            +
                    def covers_scopes?(current_shopify_session)
         | 
| 16 | 
            +
                      # NOTE: this not Ruby's `covers?` method, it is defined in ShopifyAPI::Auth::AuthScopes
         | 
| 17 | 
            +
                      current_shopify_session.scope.to_a.empty? || current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 15 20 | 
             
                    private
         | 
| 16 21 |  | 
| 17 22 | 
             
                    def update_access_scopes_for_user_id?(user_id)
         | 
| @@ -20,6 +20,7 @@ module ShopifyApp | |
| 20 20 | 
             
                attr_accessor :api_version
         | 
| 21 21 |  | 
| 22 22 | 
             
                attr_accessor :reauth_on_access_scope_changes
         | 
| 23 | 
            +
                attr_accessor :log_level
         | 
| 23 24 |  | 
| 24 25 | 
             
                # customise urls
         | 
| 25 26 | 
             
                attr_accessor :root_url
         | 
| @@ -82,7 +83,17 @@ module ShopifyApp | |
| 82 83 | 
             
                  ShopifyApp::AccessScopes::ShopStrategy
         | 
| 83 84 | 
             
                end
         | 
| 84 85 |  | 
| 86 | 
            +
                def user_access_scopes_strategy=(class_name)
         | 
| 87 | 
            +
                  unless class_name.is_a?(String)
         | 
| 88 | 
            +
                    raise ConfigurationError, "Invalid user access scopes strategy - expected a string"
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  @user_access_scopes_strategy = class_name.safe_constantize
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 85 94 | 
             
                def user_access_scopes_strategy
         | 
| 95 | 
            +
                  return @user_access_scopes_strategy if @user_access_scopes_strategy
         | 
| 96 | 
            +
             | 
| 86 97 | 
             
                  return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
         | 
| 87 98 |  | 
| 88 99 | 
             
                  ShopifyApp::AccessScopes::UserStrategy
         | 
| @@ -28,6 +28,7 @@ module ShopifyApp | |
| 28 28 | 
             
                  unless has_payment
         | 
| 29 29 | 
             
                    if request.xhr?
         | 
| 30 30 | 
             
                      add_top_level_redirection_headers(url: confirmation_url, ignore_response_code: true)
         | 
| 31 | 
            +
                      ShopifyApp::Logger.debug("Responding with 401 unauthorized")
         | 
| 31 32 | 
             
                      head(:unauthorized)
         | 
| 32 33 | 
             
                    else
         | 
| 33 34 | 
             
                      redirect_to(confirmation_url, allow_other_host: true)
         | 
| @@ -55,6 +56,7 @@ module ShopifyApp | |
| 55 56 | 
             
                end
         | 
| 56 57 |  | 
| 57 58 | 
             
                def has_subscription?(session)
         | 
| 59 | 
            +
                  ShopifyApp::Logger.debug("Checking if shop has subscription")
         | 
| 58 60 | 
             
                  response = run_query(session: session, query: RECURRING_PURCHASES_QUERY)
         | 
| 59 61 | 
             
                  subscriptions = response.body["data"]["currentAppInstallation"]["activeSubscriptions"]
         | 
| 60 62 |  | 
| @@ -70,6 +72,7 @@ module ShopifyApp | |
| 70 72 | 
             
                end
         | 
| 71 73 |  | 
| 72 74 | 
             
                def has_one_time_payment?(session)
         | 
| 75 | 
            +
                  ShopifyApp::Logger.debug("Checking if has one time payment")
         | 
| 73 76 | 
             
                  purchases = nil
         | 
| 74 77 | 
             
                  end_cursor = nil
         | 
| 75 78 |  | 
| @@ -3,6 +3,11 @@ | |
| 3 3 | 
             
            module ShopifyApp
         | 
| 4 4 | 
             
              # Cookie management helpers required for ITP implementation
         | 
| 5 5 | 
             
              module Itp
         | 
| 6 | 
            +
                extend ActiveSupport::Concern
         | 
| 7 | 
            +
                included do
         | 
| 8 | 
            +
                  ShopifyApp::Logger.deprecated("Itp will be removed in an upcoming version", "22.0.0")
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 6 11 | 
             
                private
         | 
| 7 12 |  | 
| 8 13 | 
             
                def set_test_cookie
         | 
| @@ -5,11 +5,17 @@ require "browser_sniffer" | |
| 5 5 | 
             
            module ShopifyApp
         | 
| 6 6 | 
             
              module LoginProtection
         | 
| 7 7 | 
             
                extend ActiveSupport::Concern
         | 
| 8 | 
            -
                include ShopifyApp::Itp
         | 
| 9 8 | 
             
                include ShopifyApp::SanitizedParams
         | 
| 10 9 |  | 
| 11 10 | 
             
                included do
         | 
| 12 | 
            -
                   | 
| 11 | 
            +
                  if ancestors.include?(ShopifyApp::RequireKnownShop || ShopifyApp::EnsureInstalled)
         | 
| 12 | 
            +
                    message = <<~EOS
         | 
| 13 | 
            +
                      We detected the use of incompatible concerns (RequireKnownShop/EnsureInstalled and LoginProtection) in #{name},
         | 
| 14 | 
            +
                      which may lead to unpredictable behavior. In a future release of this library this will raise an error.
         | 
| 15 | 
            +
                    EOS
         | 
| 16 | 
            +
                    ShopifyApp::Logger.deprecated(message, "22.0.0")
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 13 19 | 
             
                  rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
         | 
| 14 20 | 
             
                end
         | 
| 15 21 |  | 
| @@ -18,20 +24,21 @@ module ShopifyApp | |
| 18 24 | 
             
                def activate_shopify_session
         | 
| 19 25 | 
             
                  if current_shopify_session.blank?
         | 
| 20 26 | 
             
                    signal_access_token_required
         | 
| 27 | 
            +
                    ShopifyApp::Logger.debug("No session found, redirecting to login")
         | 
| 21 28 | 
             
                    return redirect_to_login
         | 
| 22 29 | 
             
                  end
         | 
| 23 30 |  | 
| 24 | 
            -
                  unless  | 
| 25 | 
            -
                      current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
         | 
| 26 | 
            -
             | 
| 31 | 
            +
                  unless ShopifyApp.configuration.user_access_scopes_strategy.covers_scopes?(current_shopify_session)
         | 
| 27 32 | 
             
                    clear_shopify_session
         | 
| 28 33 | 
             
                    return redirect_to_login
         | 
| 29 34 | 
             
                  end
         | 
| 30 35 |  | 
| 31 36 | 
             
                  begin
         | 
| 37 | 
            +
                    ShopifyApp::Logger.debug("Activating Shopify session")
         | 
| 32 38 | 
             
                    ShopifyAPI::Context.activate_session(current_shopify_session)
         | 
| 33 39 | 
             
                    yield
         | 
| 34 40 | 
             
                  ensure
         | 
| 41 | 
            +
                    ShopifyApp::Logger.debug("Deactivating session")
         | 
| 35 42 | 
             
                    ShopifyAPI::Context.deactivate_session
         | 
| 36 43 | 
             
                  end
         | 
| 37 44 | 
             
                end
         | 
| @@ -39,14 +46,16 @@ module ShopifyApp | |
| 39 46 | 
             
                def current_shopify_session
         | 
| 40 47 | 
             
                  @current_shopify_session ||= begin
         | 
| 41 48 | 
             
                    cookie_name = ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME
         | 
| 42 | 
            -
                     | 
| 49 | 
            +
                    load_current_session(
         | 
| 43 50 | 
             
                      auth_header: request.headers["HTTP_AUTHORIZATION"],
         | 
| 44 51 | 
             
                      cookies: { cookie_name => cookies.encrypted[cookie_name] },
         | 
| 45 | 
            -
                      is_online:  | 
| 52 | 
            +
                      is_online: online_token_configured?,
         | 
| 46 53 | 
             
                    )
         | 
| 47 54 | 
             
                  rescue ShopifyAPI::Errors::CookieNotFoundError
         | 
| 55 | 
            +
                    ShopifyApp::Logger.warn("No cookies have been found - cookie name: #{cookie_name}")
         | 
| 48 56 | 
             
                    nil
         | 
| 49 57 | 
             
                  rescue ShopifyAPI::Errors::InvalidJwtTokenError
         | 
| 58 | 
            +
                    ShopifyApp::Logger.warn("Invalid JWT token for current Shopify session")
         | 
| 50 59 | 
             
                    nil
         | 
| 51 60 | 
             
                  end
         | 
| 52 61 | 
             
                end
         | 
| @@ -54,6 +63,7 @@ module ShopifyApp | |
| 54 63 | 
             
                def login_again_if_different_user_or_shop
         | 
| 55 64 | 
             
                  return unless session_id_conflicts_with_params || session_shop_conflicts_with_params
         | 
| 56 65 |  | 
| 66 | 
            +
                  ShopifyApp::Logger.debug("Clearing session and redirecting to login")
         | 
| 57 67 | 
             
                  clear_shopify_session
         | 
| 58 68 | 
             
                  redirect_to_login
         | 
| 59 69 | 
             
                end
         | 
| @@ -71,10 +81,13 @@ module ShopifyApp | |
| 71 81 |  | 
| 72 82 | 
             
                def add_top_level_redirection_headers(url: nil, ignore_response_code: false)
         | 
| 73 83 | 
             
                  if request.xhr? && (ignore_response_code || response.code.to_i == 401)
         | 
| 84 | 
            +
                    ShopifyApp::Logger.debug("Adding top level redirection headers")
         | 
| 74 85 | 
             
                    # Make sure the shop is set in the redirection URL
         | 
| 75 86 | 
             
                    unless params[:shop]
         | 
| 87 | 
            +
                      ShopifyApp::Logger.debug("Setting current shop session")
         | 
| 76 88 | 
             
                      params[:shop] = if current_shopify_session
         | 
| 77 89 | 
             
                        current_shopify_session.shop
         | 
| 90 | 
            +
             | 
| 78 91 | 
             
                      elsif (matches = request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/))
         | 
| 79 92 | 
             
                        jwt_payload = ShopifyAPI::Auth::JwtPayload.new(T.must(matches[1]))
         | 
| 80 93 | 
             
                        jwt_payload.shop
         | 
| @@ -83,6 +96,7 @@ module ShopifyApp | |
| 83 96 |  | 
| 84 97 | 
             
                    url ||= login_url_with_optional_shop
         | 
| 85 98 |  | 
| 99 | 
            +
                    ShopifyApp::Logger.debug("Setting Reauthorize-Url to #{url}")
         | 
| 86 100 | 
             
                    response.set_header("X-Shopify-API-Request-Failure-Reauthorize", "1")
         | 
| 87 101 | 
             
                    response.set_header("X-Shopify-API-Request-Failure-Reauthorize-Url", url)
         | 
| 88 102 | 
             
                  end
         | 
| @@ -103,8 +117,9 @@ module ShopifyApp | |
| 103 117 | 
             
                end
         | 
| 104 118 |  | 
| 105 119 | 
             
                def redirect_to_login
         | 
| 106 | 
            -
                  if  | 
| 120 | 
            +
                  if requested_by_javascript?
         | 
| 107 121 | 
             
                    add_top_level_redirection_headers(ignore_response_code: true)
         | 
| 122 | 
            +
                    ShopifyApp::Logger.debug("Login redirect request is a XHR")
         | 
| 108 123 | 
             
                    head(:unauthorized)
         | 
| 109 124 | 
             
                  else
         | 
| 110 125 | 
             
                    if request.get?
         | 
| @@ -117,12 +132,15 @@ module ShopifyApp | |
| 117 132 | 
             
                      query = query.merge(sanitized_params).to_query
         | 
| 118 133 | 
             
                    end
         | 
| 119 134 | 
             
                    session[:return_to] = query.blank? ? path.to_s : "#{path}?#{query}"
         | 
| 135 | 
            +
                    ShopifyApp::Logger.debug("Redirecting to #{login_url_with_optional_shop}")
         | 
| 120 136 | 
             
                    redirect_to(login_url_with_optional_shop)
         | 
| 121 137 | 
             
                  end
         | 
| 122 138 | 
             
                end
         | 
| 123 139 |  | 
| 124 140 | 
             
                def close_session
         | 
| 125 141 | 
             
                  clear_shopify_session
         | 
| 142 | 
            +
                  ShopifyApp::Logger.debug("Closing session")
         | 
| 143 | 
            +
                  ShopifyApp::Logger.debug("Redirecting to #{login_url_with_optional_shop}")
         | 
| 126 144 | 
             
                  redirect_to(login_url_with_optional_shop)
         | 
| 127 145 | 
             
                end
         | 
| 128 146 |  | 
| @@ -182,6 +200,8 @@ module ShopifyApp | |
| 182 200 |  | 
| 183 201 | 
             
                def fullpage_redirect_to(url)
         | 
| 184 202 | 
             
                  if ShopifyApp.configuration.embedded_app?
         | 
| 203 | 
            +
                    raise ::ShopifyApp::ShopifyDomainNotFound if current_shopify_domain.nil?
         | 
| 204 | 
            +
             | 
| 185 205 | 
             
                    render("shopify_app/shared/redirect", layout: false,
         | 
| 186 206 | 
             
                      locals: { url: url, current_shopify_domain: current_shopify_domain })
         | 
| 187 207 | 
             
                  else
         | 
| @@ -191,13 +211,13 @@ module ShopifyApp | |
| 191 211 |  | 
| 192 212 | 
             
                def current_shopify_domain
         | 
| 193 213 | 
             
                  shopify_domain = sanitized_shop_name || current_shopify_session&.shop
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                   | 
| 196 | 
            -
             | 
| 197 | 
            -
                  raise ::ShopifyApp::ShopifyDomainNotFound
         | 
| 214 | 
            +
                  ShopifyApp::Logger.info("Installed store  - #{shopify_domain} deduced from user session")
         | 
| 215 | 
            +
                  shopify_domain
         | 
| 198 216 | 
             
                end
         | 
| 199 217 |  | 
| 200 218 | 
             
                def return_address
         | 
| 219 | 
            +
                  return base_return_address if current_shopify_domain.nil?
         | 
| 220 | 
            +
             | 
| 201 221 | 
             
                  return_address_with_params(shop: current_shopify_domain, host: host)
         | 
| 202 222 | 
             
                rescue ::ShopifyApp::ShopifyDomainNotFound, ::ShopifyApp::ShopifyHostNotFound
         | 
| 203 223 | 
             
                  base_return_address
         | 
| @@ -232,11 +252,30 @@ module ShopifyApp | |
| 232 252 | 
             
                  ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(sanitize_shop_param(params))
         | 
| 233 253 | 
             
                end
         | 
| 234 254 |  | 
| 255 | 
            +
                def online_token_configured?
         | 
| 256 | 
            +
                  !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
         | 
| 257 | 
            +
                end
         | 
| 258 | 
            +
             | 
| 235 259 | 
             
                def user_session_expected?
         | 
| 236 260 | 
             
                  return false if shop_session.nil?
         | 
| 237 261 | 
             
                  return false if ShopifyApp.configuration.shop_access_scopes_strategy.update_access_scopes?(shop_session.shop)
         | 
| 238 262 |  | 
| 239 | 
            -
                   | 
| 263 | 
            +
                  online_token_configured?
         | 
| 264 | 
            +
                end
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                def load_current_session(auth_header: nil, cookies: nil, is_online: false)
         | 
| 267 | 
            +
                  return ShopifyAPI::Context.load_private_session if ShopifyAPI::Context.private?
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                  session_id = ShopifyAPI::Utils::SessionUtils.current_session_id(auth_header, cookies, is_online)
         | 
| 270 | 
            +
                  return nil unless session_id
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                  ShopifyApp::SessionRepository.load_session(session_id)
         | 
| 273 | 
            +
                end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                def requested_by_javascript?
         | 
| 276 | 
            +
                  request.xhr? ||
         | 
| 277 | 
            +
                    request.content_type == "text/javascript" ||
         | 
| 278 | 
            +
                    request.content_type == "application/javascript"
         | 
| 240 279 | 
             
                end
         | 
| 241 280 | 
             
              end
         | 
| 242 281 | 
             
            end
         | 
| @@ -16,7 +16,10 @@ module ShopifyApp | |
| 16 16 |  | 
| 17 17 | 
             
                def redirect_for_embedded
         | 
| 18 18 | 
             
                  # Don't actually redirect if we're already in the redirect route - we want the request to reach the FE
         | 
| 19 | 
            -
                   | 
| 19 | 
            +
                  unless request.path == ShopifyApp.configuration.embedded_redirect_url
         | 
| 20 | 
            +
                    ShopifyApp::Logger.debug("Redirecting to #{redirect_uri_for_embedded}")
         | 
| 21 | 
            +
                    redirect_to(redirect_uri_for_embedded)
         | 
| 22 | 
            +
                  end
         | 
| 20 23 | 
             
                end
         | 
| 21 24 |  | 
| 22 25 | 
             
                def redirect_uri_for_embedded
         | 
| @@ -14,7 +14,10 @@ module ShopifyApp | |
| 14 14 |  | 
| 15 15 | 
             
                def verify_request
         | 
| 16 16 | 
             
                  data = request.raw_post
         | 
| 17 | 
            -
                   | 
| 17 | 
            +
                  unless hmac_valid?(data)
         | 
| 18 | 
            +
                    ShopifyApp::Logger.debug("Webhook verification failed - HMAC invalid")
         | 
| 19 | 
            +
                    head(:unauthorized)
         | 
| 20 | 
            +
                  end
         | 
| 18 21 | 
             
                end
         | 
| 19 22 |  | 
| 20 23 | 
             
                def shop_domain
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ShopifyApp
         | 
| 4 | 
            +
              class Logger < ShopifyAPI::Logger
         | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
                  def deprecated(message, version)
         | 
| 7 | 
            +
                    return unless enabled_for_log_level?(:warn)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    raise ShopifyAPI::Errors::FeatureDeprecatedError unless valid_version(version)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    ActiveSupport::Deprecation.warn("[#{version}] #{context(:warn)} #{message}")
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def context(log_level)
         | 
| 17 | 
            +
                    current_shop = ShopifyAPI::Context.active_session&.shop || "Shop Not Found"
         | 
| 18 | 
            +
                    "[ ShopifyApp | #{log_level.to_s.upcase} | #{current_shop} ]"
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def valid_version(version)
         | 
| 22 | 
            +
                    current_version = Gem::Version.create(ShopifyApp::VERSION)
         | 
| 23 | 
            +
                    deprecate_version = Gem::Version.create(version)
         | 
| 24 | 
            +
                    current_version < deprecate_version
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -24,6 +24,7 @@ module ShopifyApp | |
| 24 24 | 
             
                attr_reader :required_scripttags, :shop_domain
         | 
| 25 25 |  | 
| 26 26 | 
             
                def initialize(scripttags, shop_domain)
         | 
| 27 | 
            +
                  ShopifyApp::Logger.deprecated("The ScripttagsManager will become deprecated in an upcoming version", "22.0.0")
         | 
| 27 28 | 
             
                  @required_scripttags = scripttags
         | 
| 28 29 | 
             
                  @shop_domain = shop_domain
         | 
| 29 30 | 
             
                end
         | 
| @@ -15,6 +15,8 @@ module ShopifyApp | |
| 15 15 | 
             
                  def create_webhooks(session:)
         | 
| 16 16 | 
             
                    return unless ShopifyApp.configuration.has_webhooks?
         | 
| 17 17 |  | 
| 18 | 
            +
                    ShopifyApp::Logger.debug("Creating webhooks #{ShopifyApp.configuration.webhooks}")
         | 
| 19 | 
            +
             | 
| 18 20 | 
             
                    ShopifyAPI::Webhooks::Registry.register_all(session: session)
         | 
| 19 21 | 
             
                  end
         | 
| 20 22 |  | 
| @@ -23,12 +25,15 @@ module ShopifyApp | |
| 23 25 | 
             
                    return unless ShopifyApp.configuration.has_webhooks?
         | 
| 24 26 |  | 
| 25 27 | 
             
                    add_registrations
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    ShopifyApp::Logger.debug("Recreating webhooks")
         | 
| 26 30 | 
             
                    ShopifyAPI::Webhooks::Registry.register_all(session: session)
         | 
| 27 31 | 
             
                  end
         | 
| 28 32 |  | 
| 29 33 | 
             
                  def destroy_webhooks
         | 
| 30 34 | 
             
                    return unless ShopifyApp.configuration.has_webhooks?
         | 
| 31 35 |  | 
| 36 | 
            +
                    ShopifyApp::Logger.debug("Destroying webhooks")
         | 
| 32 37 | 
             
                    ShopifyApp.configuration.webhooks.each do |attributes|
         | 
| 33 38 | 
             
                      ShopifyAPI::Webhooks::Registry.unregister(topic: attributes[:topic])
         | 
| 34 39 | 
             
                    end
         | 
| @@ -37,6 +42,7 @@ module ShopifyApp | |
| 37 42 | 
             
                  def add_registrations
         | 
| 38 43 | 
             
                    return unless ShopifyApp.configuration.has_webhooks?
         | 
| 39 44 |  | 
| 45 | 
            +
                    ShopifyApp::Logger.debug("Adding registrations to webhooks")
         | 
| 40 46 | 
             
                    ShopifyApp.configuration.webhooks.each do |attributes|
         | 
| 41 47 | 
             
                      webhook_path = path(attributes)
         | 
| 42 48 |  | 
| @@ -35,7 +35,7 @@ module ShopifyApp | |
| 35 35 | 
             
                  payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
         | 
| 36 36 | 
             
                  @payload = validate_payload(payload)
         | 
| 37 37 | 
             
                rescue *WARN_EXCEPTIONS => error
         | 
| 38 | 
            -
                   | 
| 38 | 
            +
                  ShopifyApp::Logger.warn("Failed to validate JWT: [#{error.class}] #{error}")
         | 
| 39 39 | 
             
                  nil
         | 
| 40 40 | 
             
                end
         | 
| 41 41 |  | 
| @@ -45,8 +45,11 @@ module ShopifyApp | |
| 45 45 | 
             
                  # ShopifyAPI::Auth::SessionStorage override
         | 
| 46 46 | 
             
                  def store_session(session)
         | 
| 47 47 | 
             
                    if session.online?
         | 
| 48 | 
            -
                       | 
| 48 | 
            +
                      user = session.associated_user
         | 
| 49 | 
            +
                      ShopifyApp::Logger.debug("Storing online user session - session: #{session.id}")
         | 
| 50 | 
            +
                      user_storage.store(session, user)
         | 
| 49 51 | 
             
                    else
         | 
| 52 | 
            +
                      ShopifyApp::Logger.debug("Storing offline store session - session: #{session.id}")
         | 
| 50 53 | 
             
                      shop_storage.store(session)
         | 
| 51 54 | 
             
                    end
         | 
| 52 55 | 
             
                  end
         | 
| @@ -55,9 +58,13 @@ module ShopifyApp | |
| 55 58 | 
             
                  def load_session(id)
         | 
| 56 59 | 
             
                    match = id.match(/^offline_(.*)/)
         | 
| 57 60 | 
             
                    if match
         | 
| 58 | 
            -
                       | 
| 61 | 
            +
                      domain = match[1]
         | 
| 62 | 
            +
                      ShopifyApp::Logger.debug("Loading session by domain - domain: #{domain}")
         | 
| 63 | 
            +
                      retrieve_shop_session_by_shopify_domain(domain)
         | 
| 59 64 | 
             
                    else
         | 
| 60 | 
            -
                       | 
| 65 | 
            +
                      user = id.split("_").last
         | 
| 66 | 
            +
                      ShopifyApp::Logger.debug("Loading session by user_id - user: #{user}")
         | 
| 67 | 
            +
                      retrieve_user_session_by_shopify_user_id(user)
         | 
| 61 68 | 
             
                    end
         | 
| 62 69 | 
             
                  end
         | 
| 63 70 |  | 
| @@ -66,9 +73,13 @@ module ShopifyApp | |
| 66 73 | 
             
                    match = id.match(/^offline_(.*)/)
         | 
| 67 74 |  | 
| 68 75 | 
             
                    record = if match
         | 
| 76 | 
            +
                      domain = match[1]
         | 
| 77 | 
            +
                      ShopifyApp::Logger.debug("Destroying session by domain - domain: #{domain}")
         | 
| 69 78 | 
             
                      Shop.find_by(shopify_domain: match[1])
         | 
| 70 79 | 
             
                    else
         | 
| 71 | 
            -
                       | 
| 80 | 
            +
                      shopify_user_id = id.split("_").last
         | 
| 81 | 
            +
                      ShopifyApp::Logger.debug("Destroying session by user - user_id: #{shopify_user_id}")
         | 
| 82 | 
            +
                      User.find_by(shopify_user_id: shopify_user_id)
         | 
| 72 83 | 
             
                    end
         | 
| 73 84 |  | 
| 74 85 | 
             
                    record.destroy
         | 
    
        data/lib/shopify_app/version.rb
    CHANGED
    
    
    
        data/lib/shopify_app.rb
    CHANGED
    
    
    
        data/shopify_app.gemspec
    CHANGED
    
    | @@ -19,7 +19,7 @@ Gem::Specification.new do |s| | |
| 19 19 | 
             
              s.add_runtime_dependency("jwt", ">= 2.2.3")
         | 
| 20 20 | 
             
              s.add_runtime_dependency("rails", "> 5.2.1")
         | 
| 21 21 | 
             
              s.add_runtime_dependency("redirect_safely", "~> 1.0")
         | 
| 22 | 
            -
              s.add_runtime_dependency("shopify_api", "~> 12. | 
| 22 | 
            +
              s.add_runtime_dependency("shopify_api", "~> 12.3")
         | 
| 23 23 | 
             
              s.add_runtime_dependency("sprockets-rails", ">= 2.0.0")
         | 
| 24 24 |  | 
| 25 25 | 
             
              s.add_development_dependency("byebug")
         | 
| @@ -30,6 +30,7 @@ Gem::Specification.new do |s| | |
| 30 30 | 
             
              s.add_development_dependency("pry-stack_explorer")
         | 
| 31 31 | 
             
              s.add_development_dependency("rake")
         | 
| 32 32 | 
             
              s.add_development_dependency("rb-readline")
         | 
| 33 | 
            +
              s.add_development_dependency("ruby-lsp")
         | 
| 33 34 | 
             
              s.add_development_dependency("sqlite3", "~> 1.4")
         | 
| 34 35 | 
             
              s.add_development_dependency("webmock")
         | 
| 35 36 |  | 
    
        data/yarn.lock
    CHANGED
    
    | @@ -3095,7 +3095,7 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: | |
| 3095 3095 | 
             
            isarray@2.0.1:
         | 
| 3096 3096 | 
             
              version "2.0.1"
         | 
| 3097 3097 | 
             
              resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
         | 
| 3098 | 
            -
              integrity  | 
| 3098 | 
            +
              integrity sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==
         | 
| 3099 3099 |  | 
| 3100 3100 | 
             
            isarray@^2.0.5:
         | 
| 3101 3101 | 
             
              version "2.0.5"
         | 
| @@ -3601,7 +3601,7 @@ move-concurrently@^1.0.1: | |
| 3601 3601 | 
             
            ms@2.0.0:
         | 
| 3602 3602 | 
             
              version "2.0.0"
         | 
| 3603 3603 | 
             
              resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
         | 
| 3604 | 
            -
              integrity  | 
| 3604 | 
            +
              integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
         | 
| 3605 3605 |  | 
| 3606 3606 | 
             
            ms@2.1.2:
         | 
| 3607 3607 | 
             
              version "2.1.2"
         | 
| @@ -4448,9 +4448,9 @@ socket.io-client@2.4.0: | |
| 4448 4448 | 
             
                to-array "0.1.4"
         | 
| 4449 4449 |  | 
| 4450 4450 | 
             
            socket.io-parser@~3.3.0:
         | 
| 4451 | 
            -
              version "3.3. | 
| 4452 | 
            -
              resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3. | 
| 4453 | 
            -
              integrity sha512- | 
| 4451 | 
            +
              version "3.3.3"
         | 
| 4452 | 
            +
              resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.3.tgz#3a8b84823eba87f3f7624e64a8aaab6d6318a72f"
         | 
| 4453 | 
            +
              integrity sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==
         | 
| 4454 4454 | 
             
              dependencies:
         | 
| 4455 4455 | 
             
                component-emitter "~1.3.0"
         | 
| 4456 4456 | 
             
                debug "~3.1.0"
         |