shopify_app 12.0.7 → 13.2.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/.rubocop.yml +12 -6
- data/.travis.yml +4 -3
- data/CHANGELOG.md +33 -0
- data/Gemfile +3 -0
- data/README.md +98 -42
- data/Rakefile +1 -0
- data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
- data/app/controllers/shopify_app/callback_controller.rb +50 -17
- data/app/controllers/shopify_app/sessions_controller.rb +36 -10
- data/app/controllers/shopify_app/webhooks_controller.rb +6 -5
- data/config/locales/fi.yml +1 -1
- data/config/locales/nl.yml +7 -7
- data/config/routes.rb +1 -0
- data/docs/Releasing.md +1 -0
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +5 -3
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +2 -1
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +4 -4
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +5 -4
- data/lib/generators/shopify_app/add_webhook/templates/{webhook_job.rb → webhook_job.rb.tt} +5 -0
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +4 -3
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +3 -3
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +10 -9
- data/lib/generators/shopify_app/controllers/controllers_generator.rb +1 -0
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +4 -3
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/install/install_generator.rb +10 -9
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
- data/lib/generators/shopify_app/install/templates/omniauth.rb +2 -1
- data/lib/generators/shopify_app/install/templates/{shopify_app.rb → shopify_app.rb.tt} +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -1
- data/lib/generators/shopify_app/install/templates/user_agent.rb +2 -1
- data/lib/generators/shopify_app/routes/routes_generator.rb +1 -0
- data/lib/generators/shopify_app/routes/templates/routes.rb +10 -9
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +12 -7
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +2 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
- data/lib/generators/shopify_app/user_model/templates/user.rb +2 -1
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +12 -7
- data/lib/generators/shopify_app/views/views_generator.rb +1 -0
- data/lib/shopify_app.rb +11 -4
- data/lib/shopify_app/configuration.rb +21 -11
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -2
- data/lib/shopify_app/controller_concerns/embedded_app.rb +3 -2
- data/lib/shopify_app/controller_concerns/localization.rb +1 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +71 -29
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
- data/lib/shopify_app/engine.rb +5 -0
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +1 -1
- data/lib/shopify_app/jobs/webhooks_manager_job.rb +1 -1
- data/lib/shopify_app/managers/scripttags_manager.rb +4 -3
- data/lib/shopify_app/managers/webhooks_manager.rb +4 -3
- data/lib/shopify_app/middleware/jwt_middleware.rb +41 -0
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +2 -1
- data/lib/shopify_app/session/in_memory_session_store.rb +7 -3
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +14 -0
- data/lib/shopify_app/session/in_memory_user_session_store.rb +14 -0
- data/lib/shopify_app/session/jwt.rb +61 -0
- data/lib/shopify_app/session/null_user_session_store.rb +22 -0
- data/lib/shopify_app/session/session_repository.rb +36 -14
- data/lib/shopify_app/session/session_storage.rb +1 -10
- data/lib/shopify_app/session/shop_session_storage.rb +42 -0
- data/lib/shopify_app/session/user_session_storage.rb +42 -0
- data/lib/shopify_app/test_helpers/all.rb +2 -0
- data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
- data/lib/shopify_app/utils.rb +6 -5
- data/lib/shopify_app/version.rb +2 -1
- data/package-lock.json +1231 -1210
- data/package.json +1 -1
- data/shopify_app.gemspec +13 -8
- data/yarn.lock +3 -3
- metadata +50 -14
- data/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +0 -23
- data/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +0 -24
| @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              module WebhookVerification
         | 
| 3 4 | 
             
                extend ActiveSupport::Concern
         | 
| @@ -11,7 +12,7 @@ module ShopifyApp | |
| 11 12 |  | 
| 12 13 | 
             
                def verify_request
         | 
| 13 14 | 
             
                  data = request.raw_post
         | 
| 14 | 
            -
                  return head | 
| 15 | 
            +
                  return head(:unauthorized) unless hmac_valid?(data)
         | 
| 15 16 | 
             
                end
         | 
| 16 17 |  | 
| 17 18 | 
             
                def hmac_valid?(data)
         | 
    
        data/lib/shopify_app/engine.rb
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              class Engine < Rails::Engine
         | 
| 3 4 | 
             
                engine_name 'shopify_app'
         | 
| @@ -15,6 +16,10 @@ module ShopifyApp | |
| 15 16 |  | 
| 16 17 | 
             
                initializer "shopify_app.middleware" do |app|
         | 
| 17 18 | 
             
                  app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::SameSiteCookieMiddleware)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  if ShopifyApp.configuration.allow_jwt_authentication
         | 
| 21 | 
            +
                    app.config.middleware.insert_after(ShopifyApp::SameSiteCookieMiddleware, ShopifyApp::JWTMiddleware)
         | 
| 22 | 
            +
                  end
         | 
| 18 23 | 
             
                end
         | 
| 19 24 | 
             
              end
         | 
| 20 25 | 
             
            end
         | 
| @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              class ScripttagsManager
         | 
| 3 4 | 
             
                class CreationFailed < StandardError; end
         | 
| @@ -43,7 +44,7 @@ module ShopifyApp | |
| 43 44 | 
             
                def destroy_scripttags
         | 
| 44 45 | 
             
                  scripttags = expanded_scripttags
         | 
| 45 46 | 
             
                  ShopifyAPI::ScriptTag.all.each do |tag|
         | 
| 46 | 
            -
                    ShopifyAPI::ScriptTag.delete(tag.id) if  | 
| 47 | 
            +
                    ShopifyAPI::ScriptTag.delete(tag.id) if required_scripttag?(scripttags, tag)
         | 
| 47 48 | 
             
                  end
         | 
| 48 49 |  | 
| 49 50 | 
             
                  @current_scripttags = nil
         | 
| @@ -55,8 +56,8 @@ module ShopifyApp | |
| 55 56 | 
             
                  self.class.build_src(required_scripttags, shop_domain)
         | 
| 56 57 | 
             
                end
         | 
| 57 58 |  | 
| 58 | 
            -
                def  | 
| 59 | 
            -
                  scripttags.map{ |w| w[:src] }.include? | 
| 59 | 
            +
                def required_scripttag?(scripttags, tag)
         | 
| 60 | 
            +
                  scripttags.map { |w| w[:src] }.include?(tag.src)
         | 
| 60 61 | 
             
                end
         | 
| 61 62 |  | 
| 62 63 | 
             
                def create_scripttag(attributes)
         | 
| @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              class WebhooksManager
         | 
| 3 4 | 
             
                class CreationFailed < StandardError; end
         | 
| @@ -31,7 +32,7 @@ module ShopifyApp | |
| 31 32 |  | 
| 32 33 | 
             
                def destroy_webhooks
         | 
| 33 34 | 
             
                  ShopifyAPI::Webhook.all.to_a.each do |webhook|
         | 
| 34 | 
            -
                    ShopifyAPI::Webhook.delete(webhook.id) if  | 
| 35 | 
            +
                    ShopifyAPI::Webhook.delete(webhook.id) if required_webhook?(webhook)
         | 
| 35 36 | 
             
                  end
         | 
| 36 37 |  | 
| 37 38 | 
             
                  @current_webhooks = nil
         | 
| @@ -39,8 +40,8 @@ module ShopifyApp | |
| 39 40 |  | 
| 40 41 | 
             
                private
         | 
| 41 42 |  | 
| 42 | 
            -
                def  | 
| 43 | 
            -
                  required_webhooks.map{ |w| w[:address] }.include? | 
| 43 | 
            +
                def required_webhook?(webhook)
         | 
| 44 | 
            +
                  required_webhooks.map { |w| w[:address] }.include?(webhook.address)
         | 
| 44 45 | 
             
                end
         | 
| 45 46 |  | 
| 46 47 | 
             
                def create_webhook(attributes)
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module ShopifyApp
         | 
| 2 | 
            +
              class JWTMiddleware
         | 
| 3 | 
            +
                TOKEN_REGEX = /^Bearer\s+(.*?)$/
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(app)
         | 
| 6 | 
            +
                  @app = app
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def call(env)
         | 
| 10 | 
            +
                  return call_next(env) unless authorization_header(env)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  token = extract_token(env)
         | 
| 13 | 
            +
                  return call_next(env) unless token
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  set_env_variables(token, env)
         | 
| 16 | 
            +
                  call_next(env)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def call_next(env)
         | 
| 22 | 
            +
                  @app.call(env)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def authorization_header(env)
         | 
| 26 | 
            +
                  env['HTTP_AUTHORIZATION']
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def extract_token(env)
         | 
| 30 | 
            +
                  match = authorization_header(env).match(TOKEN_REGEX)
         | 
| 31 | 
            +
                  match && match[1]
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def set_env_variables(token, env)
         | 
| 35 | 
            +
                  jwt = ShopifyApp::JWT.new(token)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  env['jwt.shopify_domain'] = jwt.shopify_domain
         | 
| 38 | 
            +
                  env['jwt.shopify_user_id'] = jwt.shopify_user_id
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              class SameSiteCookieMiddleware
         | 
| 3 4 | 
             
                COOKIE_SEPARATOR = "\n"
         | 
| @@ -19,7 +20,7 @@ module ShopifyApp | |
| 19 20 | 
             
                      .split(COOKIE_SEPARATOR)
         | 
| 20 21 | 
             
                      .compact
         | 
| 21 22 | 
             
                      .map do |cookie|
         | 
| 22 | 
            -
                        cookie << '; Secure'  | 
| 23 | 
            +
                        cookie << '; Secure' unless cookie =~ /;\s*secure/i
         | 
| 23 24 | 
             
                        cookie << '; SameSite=None' unless cookie =~ /;\s*samesite=/i
         | 
| 24 25 | 
             
                        cookie
         | 
| 25 26 | 
             
                      end
         | 
| @@ -1,4 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 3 | 
            +
              # rubocop:disable Style/ClassVars
         | 
| 4 | 
            +
              # Class var repo is needed here in order to share data between the 2 child classes.
         | 
| 2 5 | 
             
              class InMemorySessionStore
         | 
| 3 6 | 
             
                class EnvironmentError < StandardError; end
         | 
| 4 7 |  | 
| @@ -6,7 +9,7 @@ module ShopifyApp | |
| 6 9 | 
             
                  repo[id]
         | 
| 7 10 | 
             
                end
         | 
| 8 11 |  | 
| 9 | 
            -
                def self.store(session, * | 
| 12 | 
            +
                def self.store(session, *_args)
         | 
| 10 13 | 
             
                  id = SecureRandom.uuid
         | 
| 11 14 | 
             
                  repo[id] = session
         | 
| 12 15 | 
             
                  id
         | 
| @@ -18,10 +21,11 @@ module ShopifyApp | |
| 18 21 |  | 
| 19 22 | 
             
                def self.repo
         | 
| 20 23 | 
             
                  if Rails.env.production?
         | 
| 21 | 
            -
                    raise EnvironmentError | 
| 22 | 
            -
                      Please initialize ShopifyApp with a model that can store and retrieve sessions" | 
| 24 | 
            +
                    raise EnvironmentError, "Cannot use InMemorySessionStore in a Production environment. \
         | 
| 25 | 
            +
                      Please initialize ShopifyApp with a model that can store and retrieve sessions"
         | 
| 23 26 | 
             
                  end
         | 
| 24 27 | 
             
                  @@repo ||= {}
         | 
| 25 28 | 
             
                end
         | 
| 26 29 | 
             
              end
         | 
| 30 | 
            +
              # rubocop:enable Style/ClassVars
         | 
| 27 31 | 
             
            end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              class InMemoryShopSessionStore < InMemorySessionStore
         | 
| 4 | 
            +
                def self.store(session, *args)
         | 
| 5 | 
            +
                  id = super
         | 
| 6 | 
            +
                  repo[session.domain] = session
         | 
| 7 | 
            +
                  id
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def self.retrieve_by_shopify_domain(shopify_domain)
         | 
| 11 | 
            +
                  repo[shopify_domain]
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              class InMemoryUserSessionStore < InMemorySessionStore
         | 
| 4 | 
            +
                def self.store(session, user)
         | 
| 5 | 
            +
                  id = super
         | 
| 6 | 
            +
                  repo[user.shopify_user_id] = session
         | 
| 7 | 
            +
                  id
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def self.retrieve_by_shopify_user_id(user_id)
         | 
| 11 | 
            +
                  repo[user_id]
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              class JWT
         | 
| 4 | 
            +
                class InvalidDestinationError < StandardError; end
         | 
| 5 | 
            +
                class MismatchedHostsError < StandardError; end
         | 
| 6 | 
            +
                class InvalidAudienceError < StandardError; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                WARN_EXCEPTIONS = [
         | 
| 9 | 
            +
                  ::JWT::DecodeError,
         | 
| 10 | 
            +
                  ::JWT::ExpiredSignature,
         | 
| 11 | 
            +
                  ::JWT::ImmatureSignature,
         | 
| 12 | 
            +
                  ::JWT::VerificationError,
         | 
| 13 | 
            +
                  InvalidAudienceError,
         | 
| 14 | 
            +
                  InvalidDestinationError,
         | 
| 15 | 
            +
                  MismatchedHostsError,
         | 
| 16 | 
            +
                ]
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def initialize(token)
         | 
| 19 | 
            +
                  @token = token
         | 
| 20 | 
            +
                  set_payload
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def shopify_domain
         | 
| 24 | 
            +
                  @payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def shopify_user_id
         | 
| 28 | 
            +
                  @payload && @payload['sub']
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def set_payload
         | 
| 34 | 
            +
                  payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
         | 
| 35 | 
            +
                  @payload = validate_payload(payload)
         | 
| 36 | 
            +
                rescue *WARN_EXCEPTIONS => error
         | 
| 37 | 
            +
                  Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
         | 
| 38 | 
            +
                  nil
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def parse_token_data(secret, old_secret)
         | 
| 42 | 
            +
                  ::JWT.decode(@token, secret, true, { algorithm: 'HS256' })
         | 
| 43 | 
            +
                rescue ::JWT::VerificationError
         | 
| 44 | 
            +
                  raise unless old_secret
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  ::JWT.decode(@token, old_secret, true, { algorithm: 'HS256' })
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def validate_payload(payload)
         | 
| 50 | 
            +
                  dest_host = ShopifyApp::Utils.sanitize_shop_domain(payload['dest'])
         | 
| 51 | 
            +
                  iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload['iss'])
         | 
| 52 | 
            +
                  api_key = ShopifyApp.configuration.api_key
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload['aud'] == api_key
         | 
| 55 | 
            +
                  raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
         | 
| 56 | 
            +
                  raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  payload
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              class NullUserSessionStore
         | 
| 4 | 
            +
                class << self
         | 
| 5 | 
            +
                  def retrieve(_)
         | 
| 6 | 
            +
                    nil
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def store(_, _)
         | 
| 10 | 
            +
                    raise SessionRepository::ConfigurationError, 'user_storage is not configured'
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def retrieve_by_shopify_user_id(_)
         | 
| 14 | 
            +
                    nil
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def blank?
         | 
| 18 | 
            +
                    true
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -1,33 +1,55 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              class SessionRepository
         | 
| 3 4 | 
             
                class ConfigurationError < StandardError; end
         | 
| 4 5 |  | 
| 5 6 | 
             
                class << self
         | 
| 6 | 
            -
                   | 
| 7 | 
            -
                    @storage = storage
         | 
| 7 | 
            +
                  attr_writer :shop_storage
         | 
| 8 8 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 9 | 
            +
                  attr_writer :user_storage
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def retrieve_shop_session(id)
         | 
| 12 | 
            +
                    shop_storage.retrieve(id)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def retrieve_user_session(id)
         | 
| 16 | 
            +
                    user_storage.retrieve(id)
         | 
| 12 17 | 
             
                  end
         | 
| 13 18 |  | 
| 14 | 
            -
                  def  | 
| 15 | 
            -
                     | 
| 19 | 
            +
                  def retrieve_shop_session_by_shopify_domain(shopify_domain)
         | 
| 20 | 
            +
                    shop_storage.retrieve_by_shopify_domain(shopify_domain)
         | 
| 16 21 | 
             
                  end
         | 
| 17 22 |  | 
| 18 | 
            -
                  def  | 
| 19 | 
            -
                     | 
| 23 | 
            +
                  def retrieve_user_session_by_shopify_user_id(user_id)
         | 
| 24 | 
            +
                    user_storage.retrieve_by_shopify_user_id(user_id)
         | 
| 20 25 | 
             
                  end
         | 
| 21 26 |  | 
| 22 | 
            -
                  def  | 
| 23 | 
            -
                     | 
| 27 | 
            +
                  def store_shop_session(session)
         | 
| 28 | 
            +
                    shop_storage.store(session)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def store_user_session(session, user)
         | 
| 32 | 
            +
                    user_storage.store(session, user)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def shop_storage
         | 
| 36 | 
            +
                    load_shop_storage || raise(ConfigurationError, "ShopifySessionRepository.shop_storage is not configured!")
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def user_storage
         | 
| 40 | 
            +
                    load_user_storage
         | 
| 24 41 | 
             
                  end
         | 
| 25 42 |  | 
| 26 43 | 
             
                  private
         | 
| 27 44 |  | 
| 28 | 
            -
                  def  | 
| 29 | 
            -
                    return unless @ | 
| 30 | 
            -
                    @ | 
| 45 | 
            +
                  def load_shop_storage
         | 
| 46 | 
            +
                    return unless @shop_storage
         | 
| 47 | 
            +
                    @shop_storage.respond_to?(:safe_constantize) ? @shop_storage.safe_constantize : @shop_storage
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def load_user_storage
         | 
| 51 | 
            +
                    return NullUserSessionStore unless @user_storage
         | 
| 52 | 
            +
                    @user_storage.respond_to?(:safe_constantize) ? @user_storage.safe_constantize : @user_storage
         | 
| 31 53 | 
             
                  end
         | 
| 32 54 | 
             
                end
         | 
| 33 55 | 
             
              end
         | 
| @@ -1,20 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 | 
             
            module ShopifyApp
         | 
| 2 3 | 
             
              module SessionStorage
         | 
| 3 4 | 
             
                extend ActiveSupport::Concern
         | 
| 4 5 |  | 
| 5 6 | 
             
                included do
         | 
| 6 | 
            -
                  if ShopifyApp.configuration.per_user_tokens?
         | 
| 7 | 
            -
                    extend ShopifyApp::SessionStorage::UserStorageStrategy
         | 
| 8 | 
            -
                  else
         | 
| 9 | 
            -
                    extend ShopifyApp::SessionStorage::ShopStorageStrategy
         | 
| 10 | 
            -
                  end
         | 
| 11 | 
            -
             | 
| 12 7 | 
             
                  validates :shopify_token, presence: true
         | 
| 13 8 | 
             
                  validates :api_version, presence: true
         | 
| 14 | 
            -
                  validates :shopify_domain, presence: true,
         | 
| 15 | 
            -
                    if: Proc.new {|_| ShopifyApp.configuration.per_user_tokens? }
         | 
| 16 | 
            -
                  validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false },
         | 
| 17 | 
            -
                    if: Proc.new {|_| !ShopifyApp.configuration.per_user_tokens? }
         | 
| 18 9 | 
             
                end
         | 
| 19 10 |  | 
| 20 11 | 
             
                def with_shopify_session(&block)
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              module ShopSessionStorage
         | 
| 4 | 
            +
                extend ActiveSupport::Concern
         | 
| 5 | 
            +
                include ::ShopifyApp::SessionStorage
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                included do
         | 
| 8 | 
            +
                  validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class_methods do
         | 
| 12 | 
            +
                  def store(auth_session, *_args)
         | 
| 13 | 
            +
                    shop = find_or_initialize_by(shopify_domain: auth_session.domain)
         | 
| 14 | 
            +
                    shop.shopify_token = auth_session.token
         | 
| 15 | 
            +
                    shop.save!
         | 
| 16 | 
            +
                    shop.id
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def retrieve(id)
         | 
| 20 | 
            +
                    shop = find_by(id: id)
         | 
| 21 | 
            +
                    construct_session(shop)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def retrieve_by_shopify_domain(domain)
         | 
| 25 | 
            +
                    shop = find_by(shopify_domain: domain)
         | 
| 26 | 
            +
                    construct_session(shop)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def construct_session(shop)
         | 
| 32 | 
            +
                    return unless shop
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    ShopifyAPI::Session.new(
         | 
| 35 | 
            +
                      domain: shop.shopify_domain,
         | 
| 36 | 
            +
                      token: shop.shopify_token,
         | 
| 37 | 
            +
                      api_version: shop.api_version,
         | 
| 38 | 
            +
                    )
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ShopifyApp
         | 
| 3 | 
            +
              module UserSessionStorage
         | 
| 4 | 
            +
                extend ActiveSupport::Concern
         | 
| 5 | 
            +
                include ::ShopifyApp::SessionStorage
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                included do
         | 
| 8 | 
            +
                  validates :shopify_domain, presence: true
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class_methods do
         | 
| 12 | 
            +
                  def store(auth_session, user)
         | 
| 13 | 
            +
                    user = find_or_initialize_by(shopify_user_id: user[:id])
         | 
| 14 | 
            +
                    user.shopify_token = auth_session.token
         | 
| 15 | 
            +
                    user.shopify_domain = auth_session.domain
         | 
| 16 | 
            +
                    user.save!
         | 
| 17 | 
            +
                    user.id
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def retrieve(id)
         | 
| 21 | 
            +
                    user = find_by(id: id)
         | 
| 22 | 
            +
                    construct_session(user)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def retrieve_by_shopify_user_id(user_id)
         | 
| 26 | 
            +
                    user = find_by(shopify_user_id: user_id)
         | 
| 27 | 
            +
                    construct_session(user)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def construct_session(user)
         | 
| 33 | 
            +
                    return unless user
         | 
| 34 | 
            +
                    ShopifyAPI::Session.new(
         | 
| 35 | 
            +
                      domain: user.shopify_domain,
         | 
| 36 | 
            +
                      token: user.shopify_token,
         | 
| 37 | 
            +
                      api_version: user.api_version,
         | 
| 38 | 
            +
                    )
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         |