Skip to content

Commit 61bf37a

Browse files
authored
Merge branch 'main' into zl/add_expiring_token_support
2 parents 447b66c + ffa5573 commit 61bf37a

File tree

5 files changed

+89
-19
lines changed

5 files changed

+89
-19
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ jobs:
1111
strategy:
1212
matrix:
1313
version:
14-
- 3.0
15-
- 3.1
1614
- 3.2
1715
- 3.3
16+
- 3.4
1817
steps:
1918
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
2019
- name: Set up Ruby ${{ matrix.version }}

BREAKING_CHANGES_FOR_V16.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Breaking change notice for version 16.0.0
2+
3+
## Minimum Ruby Version Requirement
4+
5+
The minimum required Ruby version has been updated from 3.0 to 3.2.
6+
7+
### Why this change?
8+
9+
Ruby 3.0 and 3.1 have reached End of Life (EOL).
10+
11+
### Migration Guide
12+
13+
If you're currently using Ruby 3.0 or 3.1, you'll need to upgrade to Ruby 3.2 or higher before upgrading to shopify-api-ruby v16.0.0.
14+
15+
**Note:** Ruby 3.2+ includes performance improvements and new features. Most applications should not require code changes beyond updating the Ruby version itself.
16+
## Removal of `Session#serialize` and `Session.deserialize` methods
17+
18+
The `Session#serialize` and `Session.deserialize` methods have been removed due to a security vulnerability. The `deserialize` method used `Oj.load` without safe mode, which allows instantiation of arbitrary Ruby objects.
19+
20+
These methods were originally created for session persistence when the library handled session storage. After session storage was deprecated in v12.3.0, applications became responsible for their own session persistence, making these methods unnecessary for their original purpose.
21+
22+
### Why this change?
23+
24+
**No impact on most applications:** The `shopify_app gem` stores individual session attributes in database columns and reconstructs sessions using `Session.new()`, which is the recommended pattern.
25+
26+
## Migration Guide
27+
28+
If your application was using `Session#serialize` and `Session.deserialize` for session persistence, you'll need to refactor to store individual session attributes and reconstruct sessions using `Session.new()`.
29+
30+
### Previous implementation (removed in v16.0.0)
31+
32+
```ruby
33+
# Storing a session
34+
session = ShopifyAPI::Auth::Session.new(
35+
shop: "example.myshopify.com",
36+
access_token: "shpat_xxxxx",
37+
scope: "read_products,write_orders"
38+
)
39+
40+
serialized_data = session.serialize
41+
# Store serialized_data in Redis, database, etc.
42+
redis.set("session:#{session.id}", serialized_data)
43+
44+
# Retrieving a session
45+
serialized_data = redis.get("session:#{session_id}")
46+
session = ShopifyAPI::Auth::Session.deserialize(serialized_data)
47+
```
48+
49+
### New implementation (required in v16.0.0)
50+
51+
Store individual session attributes and reconstruct using `Session.new()`:
52+
53+
## Reference: shopify_app gem implementation
54+
55+
The [shopify_app gem](https://github.com/Shopify/shopify_app) provides a reference implementation of session storage that follows these best practices:
56+
57+
**Shop Session Storage** ([source](https://github.com/Shopify/shopify_app/blob/main/lib/shopify_app/session/shop_session_storage.rb)):
58+
```ruby
59+
# Stores attributes in database columns
60+
def store(auth_session)
61+
shop = find_or_initialize_by(shopify_domain: auth_session.shop)
62+
shop.shopify_token = auth_session.access_token
63+
shop.save!
64+
end
65+
66+
# Reconstructs using Session.new()
67+
def retrieve(id)
68+
shop = find_by(id: id)
69+
return unless shop
70+
71+
ShopifyAPI::Auth::Session.new(
72+
shop: shop.shopify_domain,
73+
access_token: shop.shopify_token
74+
)
75+
end
76+
```

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Note: For changes to the API, see https://shopify.dev/changelog?filter=api
44
## Unreleased
5+
- ⚠️ [Breaking] Minimum required Ruby version is now 3.2. Ruby 3.0 and 3.1 are no longer supported.
6+
- ⚠️ [Breaking] Removed `Session#serialize` and `Session.deserialize` methods due to security concerns (RCE vulnerability via `Oj.load`). These methods were not used internally by the library. If your application relies on session serialization, use `Session.new()` to reconstruct sessions from stored attributes instead.
57

68
- Add support for expiring offline access tokens with refresh tokens. See [OAuth documentation](docs/usage/oauth.md#expiring-offline-access-tokens) for details.
79
- Add `ShopifyAPI::Auth::TokenExchange.migrate_to_expiring_token` method to migrate existing non-expiring offline tokens to expiring tokens. See [migration documentation](docs/usage/oauth.md#migrating-non-expiring-tokens-to-expiring-tokens) for details.

lib/shopify_api/auth/session.rb

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,29 +138,22 @@ def from(shop:, access_token_response:)
138138
refresh_token_expires: refresh_token_expires,
139139
)
140140
end
141-
142-
sig { params(str: String).returns(Session) }
143-
def deserialize(str)
144-
Oj.load(str)
145-
end
146141
end
147142

148143
sig { params(other: Session).returns(Session) }
149144
def copy_attributes_from(other)
150-
JSON.parse(other.serialize).keys.each do |key|
151-
next if key.include?("^")
152-
153-
variable_name = "@#{key}"
154-
instance_variable_set(variable_name, other.instance_variable_get(variable_name))
155-
end
145+
@shop = other.shop
146+
@state = other.state
147+
@access_token = other.access_token
148+
@scope = other.scope
149+
@associated_user_scope = other.associated_user_scope
150+
@expires = other.expires
151+
@associated_user = other.associated_user
152+
@is_online = other.online?
153+
@shopify_session_id = other.shopify_session_id
156154
self
157155
end
158156

159-
sig { returns(String) }
160-
def serialize
161-
Oj.dump(self)
162-
end
163-
164157
alias_method :eql?, :==
165158
sig { params(other: T.nilable(Session)).returns(T::Boolean) }
166159
def ==(other)

shopify_api.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
3030

3131
s.license = "MIT"
3232

33-
s.required_ruby_version = ">= 3.0"
33+
s.required_ruby_version = ">= 3.2"
3434

3535
s.add_runtime_dependency("activesupport")
3636
s.add_runtime_dependency("concurrent-ruby")

0 commit comments

Comments
 (0)