diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 212bea42e..d28c43323 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Extract tag name
id: tag
- run: echo "::set-output name=value::${GITHUB_REF##*/}"
+ run: echo "value=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v3
- name: Create Release
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f459af38..a9a4b93e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,12 @@ Unreleased
----------
* Make type param for webhooks route optional. This will fix a bug with CLI initiated webhooks.
-21.9.0 (January 16, 2023)
+21.10.0 (January 24, 2024)
+----------
+* Fix session deletion for users with customized session storage[#1773](https://github.com/Shopify/shopify_app/pull/1773)
+* Add configuration flag `check_session_expiry_date` to trigger a re-auth when the (user) session is expired. The session expiry date must be stored and retrieved for this flag to be effective. When the `UserSessionStorageWithScopes` concern is used, a DB migration can be generated with `rails generate shopify_app:user_model --skip` and should be applied before enabling that flag[#1757](https://github.com/Shopify/shopify_app/pull/1757)
+
+21.9.0 (January 16, 2024)
----------
* Fix `add_webhook` generator to create the webhook jobs under the correct directory[#1748](https://github.com/Shopify/shopify_app/pull/1748)
* Add support for metafield_namespaces in webhook registration [#1745](https://github.com/Shopify/shopify_app/pull/1745)
diff --git a/Gemfile.lock b/Gemfile.lock
index 1182a2da7..adc09cce4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- shopify_app (21.9.0)
+ shopify_app (21.10.0)
activeresource
addressable (~> 2.7)
browser_sniffer (~> 2.0)
diff --git a/docs/shopify_app/generators.md b/docs/shopify_app/generators.md
index e5400014a..2a03cfabc 100644
--- a/docs/shopify_app/generators.md
+++ b/docs/shopify_app/generators.md
@@ -68,13 +68,13 @@ Specify whether the app is an embedded app. Apps are embedded by default.
#### `$ rails generate shopify_app:shop_model`
-This generator creates a `Shop` model and a migration to store shop installation records. See [*Shop-based token strategy*](/docs/shopify_app/session-repository.md#shop-based-token-storage) to learn more.
+This generator creates a `Shop` model and a migration to store shop installation records. See [*Shop-based token strategy*](/docs/shopify_app/sessions.md#shop-offline-token-storage) to learn more.
---
#### `$ rails generate shopify_app:user_model`
-This generator creates a `User` model and a migration to store user records. See [*User-based token strategy*](/docs/shopify_app/session-repository.md#user-based-token-storage) to learn more.
+This generator creates a `User` model and a migration to store user records. See [*User-based token strategy*](/docs/shopify_app/sessions.md#user-online-token-storage) to learn more.
---
diff --git a/docs/shopify_app/sessions.md b/docs/shopify_app/sessions.md
index b5af10e16..8d4f156fd 100644
--- a/docs/shopify_app/sessions.md
+++ b/docs/shopify_app/sessions.md
@@ -4,23 +4,27 @@ Sessions are used to make contextual API calls for either a shop (offline sessio
#### Table of contents
-- [Sessions](#sessions-1)
- - [Types of session tokens](#types-of-session-tokens) - Shop (offline) v.s. User (online)
- - [Session token storage](#session-token-storage)
- - [Shop (offline) token storage](#shop-offline-token-storage)
- - [User (online) token storage](#user-online-token-storage)
- - [In-Memory Session Storage for Testing](#in-memory-session-storage-for-testing)
- - [Customizing Session Storage with `ShopifyApp::SessionRepository`](#customizing-session-storage-with-shopifyappsessionrepository)
- - [Loading Sessions](#loading-sessions)
+- [Sessions](#sessions)
+ - [Table of contents](#table-of-contents)
+ - [Sessions](#sessions-1)
+ - [Types of session tokens](#types-of-session-tokens)
+ - [Session token storage](#session-token-storage)
+ - [Shop (offline) token storage](#shop-offline-token-storage)
+ - [User (online) token storage](#user-online-token-storage)
+ - [In-memory Session Storage for testing](#in-memory-session-storage-for-testing)
+ - [Customizing Session Storage with `ShopifyApp::SessionRepository`](#customizing-session-storage-with-shopifyappsessionrepository)
+ - [⚠️ Custom Session Storage Requirements](#️--custom-session-storage-requirements)
+ - [Available `ActiveSupport::Concerns` that contains implementation of the above methods](#available-activesupportconcerns-that-contains-implementation-of-the-above-methods)
+ - [Loading Sessions](#loading-sessions)
- [Getting Sessions with Controller Concerns](#getting-sessions-with-controller-concerns)
- - [Shop session - "EnsureInstalled" ](#shop-sessions---ensureinstalled)
- - [User session - "EnsureHasSession" ](#user-sessions---ensurehassession)
- - [Getting Sessions from a Shop or User model record - "with_shopify_session"](#getting-sessions-from-a-shop-or-user-model-record---with_shopify_session)
-- [Access scopes](#access-scopes)
- - [`ShopifyApp::ShopSessionStorageWithScopes`](#shopifyappshopsessionstoragewithscopes)
- - [``ShopifyApp::UserSessionStorageWithScopes``](#shopifyappusersessionstoragewithscopes)
-- [Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
-- [Migrating from ShopifyApi::Auth::SessionStorage to ShopifyApp::SessionStorage](#migrating-from-shopifyapiauthsessionstorage-to-shopifyappsessionstorage)
+ - [**Shop Sessions - `EnsureInstalled`**](#shop-sessions---ensureinstalled)
+ - [User Sessions - `EnsureHasSession`](#user-sessions---ensurehassession)
+ - [Getting sessions from a Shop or User model record - 'with\_shopify\_session'](#getting-sessions-from-a-shop-or-user-model-record---with_shopify_session)
+ - [Access scopes](#access-scopes)
+ - [`ShopifyApp::ShopSessionStorageWithScopes`](#shopifyappshopsessionstoragewithscopes)
+ - [`ShopifyApp::UserSessionStorageWithScopes`](#shopifyappusersessionstoragewithscopes)
+ - [Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
+ - [Migrating from `ShopifyApi::Auth::SessionStorage` to `ShopifyApp::SessionStorage`](#migrating-from-shopifyapiauthsessionstorage-to-shopifyappsessionstorage)
## Sessions
#### Types of session tokens
@@ -103,6 +107,7 @@ The custom **Shop** repository must implement the following methods:
| `self.store(auth_session)` | `auth_session` (ShopifyAPI::Auth::Session) | - |
| `self.retrieve(id)` | `id` (String) | ShopifyAPI::Auth::Session |
| `self.retrieve_by_shopify_domain(shopify_domain)` | `shopify_domain` (String) | ShopifyAPI::Auth::Session |
+| `self.destroy_by_shopify_domain(shopify_domain)` | `shopify_domain` (String) | - |
The custom **User** repository must implement the following methods:
| Method | Parameters | Return Type |
@@ -110,6 +115,7 @@ The custom **User** repository must implement the following methods:
| `self.store(auth_session, user)` |
`auth_session` (ShopifyAPI::Auth::Session)
`user` (ShopifyAPI::Auth::AssociatedUser) | - |
| `self.retrieve(id)` | `id` (String) | `ShopifyAPI::Auth::Session` |
| `self.retrieve_by_shopify_user_id(user_id)` | `user_id` (String) | `ShopifyAPI::Auth::Session` |
+| `self.destroy_by_shopify_user_id(user_id)` | `user_id` (String) | - |
These methods are already implemented as a part of the `User` and `Shop` models generated from this gem's generator.
@@ -153,7 +159,7 @@ end
```
##### User Sessions - `EnsureHasSession`
-- [EnsureHasSession](https://github.com/Shopify/shopify_app/blob/main/app/controllers/concerns/shopify_app/ensure_has_session.rb) controller concern will load a user session via `current_shopify_session`. As part of loading this session, this concern will also ensure that the user session has the appropriate scopes needed for the application. If the user isn't found or has fewer permitted scopes than are required, they will be prompted to authorize the application.
+- [EnsureHasSession](https://github.com/Shopify/shopify_app/blob/main/app/controllers/concerns/shopify_app/ensure_has_session.rb) controller concern will load a user session via `current_shopify_session`. As part of loading this session, this concern will also ensure that the user session has the appropriate scopes needed for the application and that it is not expired (when `check_session_expiry_date` is enabled). If the user isn't found or has fewer permitted scopes than are required, they will be prompted to authorize the application.
- This controller concern should be used if you don't need your app to make calls on behalf of a user. With that in mind, there are a few other embedded concerns that are mixed in to ensure that embedding, CSRF, localization, and billing allow the action for the user.
- Example
```ruby
@@ -228,6 +234,9 @@ class User < ActiveRecord::Base
end
```
+## Expiry date
+When the configuration flag `check_session_expiry_date` is set to true, the user session expiry date will be checked to trigger a re-auth and get a fresh user token when it is expired. This requires the `ShopifyAPI::Auth::Session` `expires` attribute to be stored. When the `User` model includes the `UserSessionStorageWithScopes` concern, a DB migration can be generated with `rails generate shopify_app:user_model --skip` to add the `expires_at` attribute to the model.
+
## Migrating from shop-based to user-based token strategy
1. Run the `user_model` generator as [mentioned above](#user-online-token-storage).
diff --git a/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb b/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb
new file mode 100644
index 000000000..920d9e314
--- /dev/null
+++ b/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb
@@ -0,0 +1,5 @@
+class AddUserExpiresAtColumn < ActiveRecord::Migration[<%= rails_migration_version %>]
+ def change
+ add_column :users, :expires_at, :datetime
+ end
+end
diff --git a/lib/generators/shopify_app/user_model/user_model_generator.rb b/lib/generators/shopify_app/user_model/user_model_generator.rb
index 9b6ab5c43..4afc0cfe1 100644
--- a/lib/generators/shopify_app/user_model/user_model_generator.rb
+++ b/lib/generators/shopify_app/user_model/user_model_generator.rb
@@ -40,6 +40,26 @@ def create_scopes_storage_in_user_model
end
end
+ def create_expires_at_storage_in_user_model
+ expires_at_column_prompt = <<~PROMPT
+ It is highly recommended that apps record the User session expiry date. \
+ This will allow to check if the session has expired and re-authenticate \
+ without a first call to Shopify.
+
+ After running the migration, the `check_session_expiry_date` configuration can be enabled.
+
+ The following migration will add an `expires_at` column to the User model. \
+ Do you want to include this migration? [y/n]
+ PROMPT
+
+ if new_shopify_cli_app? || Rails.env.test? || yes?(expires_at_column_prompt)
+ migration_template(
+ "db/migrate/add_user_expires_at_column.erb",
+ "db/migrate/add_user_expires_at_column.rb",
+ )
+ end
+ end
+
def update_shopify_app_initializer
gsub_file("config/initializers/shopify_app.rb", "ShopifyApp::InMemoryUserSessionStore", "User")
end
diff --git a/lib/shopify_app/configuration.rb b/lib/shopify_app/configuration.rb
index 63e9c8a2d..27f6316a5 100644
--- a/lib/shopify_app/configuration.rb
+++ b/lib/shopify_app/configuration.rb
@@ -20,6 +20,7 @@ class Configuration
attr_accessor :api_version
attr_accessor :reauth_on_access_scope_changes
+ attr_accessor :check_session_expiry_date
attr_accessor :log_level
# customise urls
@@ -44,6 +45,9 @@ class Configuration
# takes a ShopifyApp::BillingConfiguration object
attr_accessor :billing
+ # Work in Progress: enables token exchange authentication flow
+ attr_accessor :wip_new_embedded_auth_strategy
+
def initialize
@root_url = "/"
@myshopify_domain = "myshopify.com"
@@ -118,6 +122,10 @@ def shop_access_scopes
def user_access_scopes
@user_access_scopes || scope
end
+
+ def use_new_embedded_auth_strategy?
+ wip_new_embedded_auth_strategy && embedded_app?
+ end
end
class BillingConfiguration
diff --git a/lib/shopify_app/controller_concerns/login_protection.rb b/lib/shopify_app/controller_concerns/login_protection.rb
index 61eba2145..ac73a44de 100644
--- a/lib/shopify_app/controller_concerns/login_protection.rb
+++ b/lib/shopify_app/controller_concerns/login_protection.rb
@@ -30,6 +30,12 @@ def activate_shopify_session
return redirect_to_login
end
+ if ShopifyApp.configuration.check_session_expiry_date && current_shopify_session.expired?
+ ShopifyApp::Logger.debug("Session expired, redirecting to login")
+ clear_shopify_session
+ return redirect_to_login
+ end
+
if ShopifyApp.configuration.reauth_on_access_scope_changes &&
!ShopifyApp.configuration.user_access_scopes_strategy.covers_scopes?(current_shopify_session)
clear_shopify_session
diff --git a/lib/shopify_app/session/session_repository.rb b/lib/shopify_app/session/session_repository.rb
index e02ff4771..d1652e7b1 100644
--- a/lib/shopify_app/session/session_repository.rb
+++ b/lib/shopify_app/session/session_repository.rb
@@ -23,6 +23,14 @@ def retrieve_user_session_by_shopify_user_id(user_id)
user_storage.retrieve_by_shopify_user_id(user_id)
end
+ def destroy_shop_session_by_domain(shopify_domain)
+ shop_storage.destroy_by_shopify_domain(shopify_domain)
+ end
+
+ def destroy_user_session_by_shopify_user_id(user_id)
+ user_storage.destroy_by_shopify_user_id(user_id)
+ end
+
def store_shop_session(session)
shop_storage.store(session)
end
@@ -73,18 +81,17 @@ def load_session(id)
def delete_session(id)
match = id.match(/^offline_(.*)/)
- record = if match
+ if match
domain = match[1]
ShopifyApp::Logger.debug("Destroying session by domain - domain: #{domain}")
- Shop.find_by(shopify_domain: match[1])
+ destroy_shop_session_by_domain(domain)
+
else
shopify_user_id = id.split("_").last
ShopifyApp::Logger.debug("Destroying session by user - user_id: #{shopify_user_id}")
- User.find_by(shopify_user_id: shopify_user_id)
+ destroy_user_session_by_shopify_user_id(shopify_user_id)
end
- record.destroy
-
true
end
diff --git a/lib/shopify_app/session/shop_session_storage.rb b/lib/shopify_app/session/shop_session_storage.rb
index a1cc27ef7..f3cc08171 100644
--- a/lib/shopify_app/session/shop_session_storage.rb
+++ b/lib/shopify_app/session/shop_session_storage.rb
@@ -27,6 +27,10 @@ def retrieve_by_shopify_domain(domain)
construct_session(shop)
end
+ def destroy_by_shopify_domain(domain)
+ destroy_by(shopify_domain: domain)
+ end
+
private
def construct_session(shop)
diff --git a/lib/shopify_app/session/shop_session_storage_with_scopes.rb b/lib/shopify_app/session/shop_session_storage_with_scopes.rb
index 5312608e5..24458e421 100644
--- a/lib/shopify_app/session/shop_session_storage_with_scopes.rb
+++ b/lib/shopify_app/session/shop_session_storage_with_scopes.rb
@@ -29,6 +29,10 @@ def retrieve_by_shopify_domain(domain)
construct_session(shop)
end
+ def destroy_by_shopify_domain(domain)
+ destroy_by(shopify_domain: domain)
+ end
+
private
def construct_session(shop)
diff --git a/lib/shopify_app/session/user_session_storage.rb b/lib/shopify_app/session/user_session_storage.rb
index 78a77d1af..1cba9b2bf 100644
--- a/lib/shopify_app/session/user_session_storage.rb
+++ b/lib/shopify_app/session/user_session_storage.rb
@@ -28,6 +28,10 @@ def retrieve_by_shopify_user_id(user_id)
construct_session(user)
end
+ def destroy_by_shopify_user_id(user_id)
+ destroy_by(shopify_user_id: user_id)
+ end
+
private
def construct_session(user)
diff --git a/lib/shopify_app/session/user_session_storage_with_scopes.rb b/lib/shopify_app/session/user_session_storage_with_scopes.rb
index 440226862..cf19a166d 100644
--- a/lib/shopify_app/session/user_session_storage_with_scopes.rb
+++ b/lib/shopify_app/session/user_session_storage_with_scopes.rb
@@ -15,6 +15,7 @@ def store(auth_session, user)
user.shopify_token = auth_session.access_token
user.shopify_domain = auth_session.shop
user.access_scopes = auth_session.scope.to_s
+ user.expires_at = auth_session.expires
user.save!
user.id
@@ -30,6 +31,10 @@ def retrieve_by_shopify_user_id(user_id)
construct_session(user)
end
+ def destroy_by_shopify_user_id(user_id)
+ destroy_by(shopify_user_id: user_id)
+ end
+
private
def construct_session(user)
@@ -52,6 +57,7 @@ def construct_session(user)
scope: user.access_scopes,
associated_user_scope: user.access_scopes,
associated_user: associated_user,
+ expires: user.expires_at,
)
end
end
@@ -67,5 +73,24 @@ def access_scopes
rescue NotImplementedError, NoMethodError
raise NotImplementedError, "#access_scopes= must be defined to hook into stored access scopes"
end
+
+ def expires_at=(expires_at)
+ super
+ rescue NotImplementedError, NoMethodError
+ if ShopifyApp.configuration.check_session_expiry_date
+ raise NotImplementedError,
+ "#expires_at= must be defined to handle storing the session expiry date"
+ end
+ end
+
+ def expires_at
+ super
+ rescue NotImplementedError, NoMethodError
+ if ShopifyApp.configuration.check_session_expiry_date
+ raise NotImplementedError, "#expires_at must be defined to check the session expiry date"
+ end
+
+ nil
+ end
end
end
diff --git a/lib/shopify_app/version.rb b/lib/shopify_app/version.rb
index 44ae5656b..1d0df6966 100644
--- a/lib/shopify_app/version.rb
+++ b/lib/shopify_app/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module ShopifyApp
- VERSION = "21.9.0"
+ VERSION = "21.10.0"
end
diff --git a/package.json b/package.json
index 55c9ac900..b2bc3cb36 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "shopify_app",
- "version": "21.9.0",
+ "version": "21.10.0",
"repository": "git@github.com:Shopify/shopify_app.git",
"author": "Shopify",
"license": "MIT",
diff --git a/test/generators/user_model_generator_test.rb b/test/generators/user_model_generator_test.rb
index b90ef3c02..35896f37e 100644
--- a/test/generators/user_model_generator_test.rb
+++ b/test/generators/user_model_generator_test.rb
@@ -35,7 +35,14 @@ class UserModelGeneratorTest < Rails::Generators::TestCase
end
end
- test "create User with access_scopes migration with --new-shopify-cli-app flag provided" do
+ test "create expires_at migration for User model" do
+ run_generator
+ assert_migration "db/migrate/add_user_expires_at_column.rb" do |migration|
+ assert_match "add_column :users, :expires_at, :datetime", migration
+ end
+ end
+
+ test "create User with all migrations with --new-shopify-cli-app flag provided" do
Rails.env = "mock_environment"
run_generator ["--new-shopify-cli-app"]
@@ -44,6 +51,9 @@ class UserModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/add_user_access_scopes_column.rb" do |migration|
assert_match "add_column :users, :access_scopes, :string", migration
end
+ assert_migration "db/migrate/add_user_expires_at_column.rb" do |migration|
+ assert_match "add_column :users, :expires_at, :datetime", migration
+ end
end
test "updates the shopify_app initializer to use User to store session" do
diff --git a/test/shopify_app/configuration_test.rb b/test/shopify_app/configuration_test.rb
index 9c6bd9d97..aaaddb9ce 100644
--- a/test/shopify_app/configuration_test.rb
+++ b/test/shopify_app/configuration_test.rb
@@ -228,4 +228,30 @@ class ConfigurationTest < ActiveSupport::TestCase
end
assert_equal "Invalid user access scopes strategy - expected a string", error.message
end
+
+ test "#use_new_embedded_auth_strategy? is true when wip_new_embedded_auth_strategy is on for embedded apps" do
+ ShopifyApp.configure do |config|
+ config.embedded_app = true
+ config.wip_new_embedded_auth_strategy = true
+ end
+
+ assert ShopifyApp.configuration.use_new_embedded_auth_strategy?
+ end
+
+ test "#use_new_embedded_auth_strategy? is false for non-embedded apps even if wip_new_embedded_auth_strategy is configured" do
+ ShopifyApp.configure do |config|
+ config.embedded_app = false
+ config.wip_new_embedded_auth_strategy = true
+ end
+
+ refute ShopifyApp.configuration.use_new_embedded_auth_strategy?
+ end
+
+ test "#use_new_embedded_auth_strategy? is false when wip_new_embedded_auth_strategy is off" do
+ ShopifyApp.configure do |config|
+ config.wip_new_embedded_auth_strategy = false
+ end
+
+ refute ShopifyApp.configuration.use_new_embedded_auth_strategy?
+ end
end
diff --git a/test/shopify_app/controller_concerns/login_protection_test.rb b/test/shopify_app/controller_concerns/login_protection_test.rb
index fbac19018..7f11ac758 100644
--- a/test/shopify_app/controller_concerns/login_protection_test.rb
+++ b/test/shopify_app/controller_concerns/login_protection_test.rb
@@ -426,6 +426,53 @@ class LoginProtectionControllerTest < ActionController::TestCase
end
end
+ test "#activate_shopify_session with an expired Shopify session redirects to the login url when check_session_expiry_date enabled" do
+ ShopifyApp.configuration.check_session_expiry_date = true
+
+ with_application_test_routes do
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = "cookie"
+ ShopifyApp::SessionRepository.expects(:load_session)
+ .returns(ShopifyAPI::Auth::Session.new(shop: "shop.myshopify.com", expires: 1.minute.ago))
+
+ get :index, params: { shop: "foobar" }
+
+ assert_redirected_to "/login?shop=foobar.myshopify.com"
+ assert_nil cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME]
+ end
+ end
+
+ test "#activate_shopify_session with an expired Shopify session, when the request is an XHR, returns an HTTP 401 when check_session_expiry_date enabled" do
+ ShopifyApp.configuration.check_session_expiry_date = true
+
+ with_application_test_routes do
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = "cookie"
+ ShopifyApp::SessionRepository.expects(:load_session)
+ .returns(ShopifyAPI::Auth::Session.new(shop: "shop.myshopify.com", expires: 1.minute.ago))
+
+ get :index, params: { shop: "foobar" }, xhr: true
+
+ assert_equal 401, response.status
+ assert_match "1", response.headers["X-Shopify-API-Request-Failure-Reauthorize"]
+ assert_match "/login?shop=foobar", response.headers["X-Shopify-API-Request-Failure-Reauthorize-Url"]
+ assert_nil cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME]
+ end
+ end
+
+ test "#activate_shopify_session with an expired Shopify session does not redirect to the login url when check_session_expiry_date disabled" do
+ ShopifyApp.configuration.check_session_expiry_date = false
+
+ with_application_test_routes do
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = "cookie"
+ ShopifyApp::SessionRepository.expects(:load_session)
+ .returns(ShopifyAPI::Auth::Session.new(shop: "shop.myshopify.com", expires: 1.minute.ago))
+ ::ShopifyAPI::Context.expects(:activate_session)
+
+ get :index, params: { shop: "foobar" }
+
+ assert_response :ok
+ end
+ end
+
test "#fullpage_redirect_to sends a post message to that shop in the shop param" do
with_application_test_routes do
example_shop = "shop.myshopify.com"
diff --git a/test/shopify_app/session/session_repository_test.rb b/test/shopify_app/session/session_repository_test.rb
index 1c18bce48..2ed23a37f 100644
--- a/test/shopify_app/session/session_repository_test.rb
+++ b/test/shopify_app/session/session_repository_test.rb
@@ -140,21 +140,21 @@ class SessionRepositoryTest < ActiveSupport::TestCase
end
test(".delete_session destroys a shop record") do
- shop = MockShopInstance.new(shopify_domain: "shop", shopify_token: "token")
+ SessionRepository.shop_storage = InMemoryShopSessionStore
+ mock_session_id = "offline_abra-shop"
- Shop.expects(:find_by).with(shopify_domain: "shop").returns(shop)
- shop.expects(:destroy)
+ InMemoryShopSessionStore.expects(:destroy_by_shopify_domain).with("abra-shop")
- SessionRepository.delete_session("offline_shop")
+ SessionRepository.delete_session(mock_session_id)
end
test(".delete_session destroys a user record") do
- user = MockUserInstance.new(shopify_domain: "shop", shopify_token: "token")
+ SessionRepository.user_storage = InMemoryUserSessionStore
+ mock_session_id = "test_shop.myshopify.com_1234"
- User.expects(:find_by).with(shopify_user_id: "1234").returns(user)
- user.expects(:destroy)
+ InMemoryUserSessionStore.expects(:destroy_by_shopify_user_id).with("1234")
- SessionRepository.delete_session("shop_1234")
+ SessionRepository.delete_session(mock_session_id)
end
private
diff --git a/test/shopify_app/session/shop_session_storage_test.rb b/test/shopify_app/session/shop_session_storage_test.rb
index cecad0696..02bfc6524 100644
--- a/test/shopify_app/session/shop_session_storage_test.rb
+++ b/test/shopify_app/session/shop_session_storage_test.rb
@@ -40,6 +40,12 @@ class ShopSessionStorageTest < ActiveSupport::TestCase
assert_equal expected_session.access_token, session.access_token
end
+ test ".destroy_by_shopify_domain destroys shop session records by JWT" do
+ ShopMockSessionStore.expects(:destroy_by).with(shopify_domain: TEST_SHOPIFY_DOMAIN)
+
+ ShopMockSessionStore.destroy_by_shopify_domain(TEST_SHOPIFY_DOMAIN)
+ end
+
test ".store can store shop session records" do
mock_shop_instance = MockShopInstance.new(id: 12345)
mock_shop_instance.stubs(:save!).returns(true)
diff --git a/test/shopify_app/session/shop_session_storage_with_scopes_test.rb b/test/shopify_app/session/shop_session_storage_with_scopes_test.rb
index 87f1e08a2..663b1a9aa 100644
--- a/test/shopify_app/session/shop_session_storage_with_scopes_test.rb
+++ b/test/shopify_app/session/shop_session_storage_with_scopes_test.rb
@@ -46,6 +46,12 @@ class ShopSessionStorageWithScopesTest < ActiveSupport::TestCase
assert_equal expected_session.scope, session.scope
end
+ test ".destroy_by_shopify_domain destroys shop session records by JWT" do
+ ShopMockSessionStoreWithScopes.expects(:destroy_by).with(shopify_domain: TEST_SHOPIFY_DOMAIN)
+
+ ShopMockSessionStoreWithScopes.destroy_by_shopify_domain(TEST_SHOPIFY_DOMAIN)
+ end
+
test ".store can store shop session records" do
mock_shop_instance = MockShopInstance.new(id: 12345)
mock_shop_instance.stubs(:save!).returns(true)
diff --git a/test/shopify_app/session/user_session_storage_test.rb b/test/shopify_app/session/user_session_storage_test.rb
index 7f399f361..6f1037209 100644
--- a/test/shopify_app/session/user_session_storage_test.rb
+++ b/test/shopify_app/session/user_session_storage_test.rb
@@ -46,6 +46,12 @@ class UserSessionStorageTest < ActiveSupport::TestCase
assert_equal expected_session.access_token, session.access_token
end
+ test ".destroy_by_shopify_user_id destroys user session by shopify_user_id" do
+ UserMockSessionStore.expects(:destroy_by).with(shopify_user_id: TEST_SHOPIFY_USER_ID)
+
+ UserMockSessionStore.destroy_by_shopify_user_id(TEST_SHOPIFY_USER_ID)
+ end
+
test ".store can store user session record" do
mock_user_instance = MockUserInstance.new(shopify_user_id: 100)
mock_user_instance.stubs(:save!).returns(true)
diff --git a/test/shopify_app/session/user_session_storage_with_scopes_test.rb b/test/shopify_app/session/user_session_storage_with_scopes_test.rb
index 1bb47188b..6cf6a09c1 100644
--- a/test/shopify_app/session/user_session_storage_with_scopes_test.rb
+++ b/test/shopify_app/session/user_session_storage_with_scopes_test.rb
@@ -12,6 +12,7 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
TEST_SHOPIFY_DOMAIN = "example.myshopify.com"
TEST_SHOPIFY_USER_TOKEN = "some-user-token-42"
TEST_MERCHANT_SCOPES = "read_orders, write_products"
+ TEST_EXPIRES_AT = Time.now
test ".retrieve returns user session by id" do
UserMockSessionStoreWithScopes.stubs(:find_by).returns(MockUserInstance.new(
@@ -19,6 +20,7 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
shopify_domain: TEST_SHOPIFY_DOMAIN,
shopify_token: TEST_SHOPIFY_USER_TOKEN,
scopes: TEST_MERCHANT_SCOPES,
+ expires_at: TEST_EXPIRES_AT,
))
session = UserMockSessionStoreWithScopes.retrieve(shopify_user_id: TEST_SHOPIFY_USER_ID)
@@ -26,6 +28,7 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
assert_equal TEST_SHOPIFY_DOMAIN, session.shop
assert_equal TEST_SHOPIFY_USER_TOKEN, session.access_token
assert_equal ShopifyAPI::Auth::AuthScopes.new(TEST_MERCHANT_SCOPES), session.scope
+ assert_equal TEST_EXPIRES_AT, session.expires
end
test ".retrieve_by_shopify_user_id returns user session by shopify_user_id" do
@@ -35,6 +38,7 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
shopify_token: TEST_SHOPIFY_USER_TOKEN,
api_version: ShopifyApp.configuration.api_version,
scopes: TEST_MERCHANT_SCOPES,
+ expires_at: TEST_EXPIRES_AT,
)
UserMockSessionStoreWithScopes.stubs(:find_by).with(shopify_user_id: TEST_SHOPIFY_USER_ID).returns(instance)
@@ -42,6 +46,7 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
shop: instance.shopify_domain,
access_token: instance.shopify_token,
scope: TEST_MERCHANT_SCOPES,
+ expires: TEST_EXPIRES_AT,
)
user_id = TEST_SHOPIFY_USER_ID
@@ -49,6 +54,13 @@ class UserSessionStorageWithScopesTest < ActiveSupport::TestCase
assert_equal expected_session.shop, session.shop
assert_equal expected_session.access_token, session.access_token
assert_equal expected_session.scope, session.scope
+ assert_equal expected_session.expires, session.expires
+ end
+
+ test ".destroy_by_shopify_user_id destroys user session by shopify_user_id" do
+ UserMockSessionStoreWithScopes.expects(:destroy_by).with(shopify_user_id: TEST_SHOPIFY_USER_ID)
+
+ UserMockSessionStoreWithScopes.destroy_by_shopify_user_id(TEST_SHOPIFY_USER_ID)
end
test ".store can store user session record" do
diff --git a/test/support/session_store_strategy_test_helpers.rb b/test/support/session_store_strategy_test_helpers.rb
index 622fd176c..ceebe937d 100644
--- a/test/support/session_store_strategy_test_helpers.rb
+++ b/test/support/session_store_strategy_test_helpers.rb
@@ -21,8 +21,8 @@ def initialize(
end
class MockUserInstance
- attr_reader :id, :shopify_user_id, :shopify_domain, :shopify_token, :api_version, :access_scopes
- attr_writer :shopify_token, :shopify_domain, :access_scopes
+ attr_reader :id, :shopify_user_id, :shopify_domain, :shopify_token, :api_version, :access_scopes, :expires_at
+ attr_writer :shopify_token, :shopify_domain, :access_scopes, :expires_at
def initialize(
id: 1,
@@ -30,7 +30,8 @@ def initialize(
shopify_domain: "example.myshopify.com",
shopify_token: "1234-user-token",
api_version: ShopifyApp.configuration.api_version,
- scopes: "read_products"
+ scopes: "read_products",
+ expires_at: nil
)
@id = id
@shopify_user_id = shopify_user_id
@@ -38,6 +39,7 @@ def initialize(
@shopify_token = shopify_token
@api_version = api_version
@access_scopes = scopes
+ @expires_at = expires_at
end
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ce758d969..31e5e7834 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -57,6 +57,8 @@ def mock_session(shop: "my-shop.myshopify.com", scope: ShopifyApp.configuration.
mock_session.stubs(:access_token).returns("a-new-user_token!")
mock_session.stubs(:scope).returns(ShopifyAPI::Auth::AuthScopes.new(scope))
mock_session.stubs(:shopify_session_id).returns(1)
+ mock_session.stubs(:expires).returns(nil)
+ mock_session.stubs(:expired?).returns(false)
mock_session
end