@spree/docs 0.1.67 → 0.1.69
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.
- package/dist/api-reference/store-api/rate-limitting.md +0 -1
- package/dist/api-reference/store.yaml +340 -288
- package/dist/developer/admin/authentication.md +1 -1
- package/dist/developer/contributing/creating-an-extension.md +1 -1
- package/dist/developer/customization/authentication.md +2 -2
- package/dist/developer/customization/checkout.md +1 -1
- package/dist/developer/customization/v4/checkout.md +1 -1
- package/dist/developer/how-to/custom-api-authentication.md +301 -0
- package/dist/developer/upgrades/4.1-to-4.2.md +1 -1
- package/dist/developer/upgrades/5.2-to-5.3.md +2 -2
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ This will tell Spree to use your `AdminUser` model for the admin panel. You will
|
|
|
21
21
|
include Spree::UserMethods
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
In your `config/initializers/routes.rb` file, you will need define devise routes for the 2nd model:
|
|
24
|
+
In your `config/initializers/routes.rb` file, you will need to define devise routes for the 2nd model:
|
|
25
25
|
|
|
26
26
|
```ruby
|
|
27
27
|
Spree::Core::Engine.routes.prepend_routes do
|
|
@@ -70,7 +70,7 @@ Once you have added the gem, it's time to bundle:
|
|
|
70
70
|
bundle install
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Finally, let's run the `spree_simple_sales` install generator to copy over the migration we just created
|
|
73
|
+
Finally, let's run the `spree_simple_sales` install generator to copy over the migration we just created. Answer **yes** if prompted to run migrations:
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
# context: Your Spree store's app root (i.e. Rails.root); not the extension's root path.
|
|
@@ -24,7 +24,7 @@ Now, run the generator to set up Spree integration with Devise:
|
|
|
24
24
|
bin/rails g spree:authentication:devise
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
This will create
|
|
27
|
+
This will create a new file in `lib/spree/authentication_helpers.rb` that serves as a bridge between Spree and your existing authentication system routes. You can then use this file to customize the routes to your liking. It should automatically pick up standard Devise routes.
|
|
28
28
|
|
|
29
29
|
Secondly, this generator will add necessary modules to your `User` model.
|
|
30
30
|
|
|
@@ -68,7 +68,7 @@ Now, run the generator to set up Spree integration with your custom authenticati
|
|
|
68
68
|
bin/rails g spree:authentication:custom
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
This will create
|
|
71
|
+
This will create a new file in `lib/spree/authentication_helpers.rb` that serves as a bridge between Spree and your existing authentication system routes. You will need to customize this file to fit your needs.
|
|
72
72
|
|
|
73
73
|
Secondly, this generator will add necessary modules to your `User` model.
|
|
74
74
|
|
|
@@ -10,7 +10,7 @@ The Spree checkout process has been designed for maximum flexibility. It's been
|
|
|
10
10
|
|
|
11
11
|
## The Checkout Flow DSL
|
|
12
12
|
|
|
13
|
-
Spree comes with a new checkout DSL that allows you succinctly define the different steps of your checkout. This new DSL allows you to customize _just_ the checkout flow, while maintaining the unrelated admin states, such as "canceled" and "resumed", that an order can transition to. Ultimately, it provides a shorter syntax compared with overriding the entire state machine for the `Spree::Order` class.
|
|
13
|
+
Spree comes with a new checkout DSL that allows you to succinctly define the different steps of your checkout. This new DSL allows you to customize _just_ the checkout flow, while maintaining the unrelated admin states, such as "canceled" and "resumed", that an order can transition to. Ultimately, it provides a shorter syntax compared with overriding the entire state machine for the `Spree::Order` class.
|
|
14
14
|
|
|
15
15
|
The default checkout flow for Spree is defined like this, adequately demonstrating the abilities of this new system:
|
|
16
16
|
|
|
@@ -10,7 +10,7 @@ The Spree checkout process has been designed for maximum flexibility. It's been
|
|
|
10
10
|
|
|
11
11
|
## The Checkout Flow DSL
|
|
12
12
|
|
|
13
|
-
Spree comes with a new checkout DSL that allows you succinctly define the different steps of your checkout. This new DSL allows you to customize _just_ the checkout flow, while maintaining the unrelated admin states, such as "canceled" and "resumed", that an order can transition to. Ultimately, it provides a shorter syntax compared with overriding the entire state machine for the `Spree::Order` class.
|
|
13
|
+
Spree comes with a new checkout DSL that allows you to succinctly define the different steps of your checkout. This new DSL allows you to customize _just_ the checkout flow, while maintaining the unrelated admin states, such as "canceled" and "resumed", that an order can transition to. Ultimately, it provides a shorter syntax compared with overriding the entire state machine for the `Spree::Order` class.
|
|
14
14
|
|
|
15
15
|
The default checkout flow for Spree is defined like this, adequately demonstrating the abilities of this new system:
|
|
16
16
|
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Integrate a Third-Party Identity Provider
|
|
3
|
+
description: Step-by-step guide to plugging a custom identity provider (Auth0, Okta, Firebase, Cognito, or any JWT issuer) into the Spree Store API.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { Since } from '/snippets/since.mdx';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Spree's Store API ships with a pluggable authentication system. You register a **strategy class** for a named provider, and the existing login endpoints will dispatch to it — no controller patching, no route overrides, no fork.
|
|
12
|
+
|
|
13
|
+
By the end of this guide you'll have:
|
|
14
|
+
|
|
15
|
+
- A custom strategy that verifies a third-party JWT against a JWKS endpoint
|
|
16
|
+
- A user account auto-provisioned on first login, reused on subsequent logins
|
|
17
|
+
- A standard **Spree-issued JWT + refresh token** returned to the client
|
|
18
|
+
- All Store API endpoints (cart, checkout, account) protected by Spree's own JWT — the third-party token is only used at the exchange step
|
|
19
|
+
|
|
20
|
+
> **INFO:** The same pattern works for Admin API integrations via `admin_authentication_strategies`. Examples in this guide target the Store API; substitute `Spree.admin_user_class` and the admin endpoints where noted.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Client → POST /api/v3/store/auth/login
|
|
26
|
+
{ provider: "my_idp", token: "<third-party JWT>" }
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
AuthController looks up the strategy by `provider` key
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
YourStrategy#authenticate
|
|
33
|
+
• verifies the JWT against the IdP's JWKS
|
|
34
|
+
• finds or creates a Spree::UserIdentity → Spree user
|
|
35
|
+
• returns success(user) or failure(message)
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
Spree issues its own JWT (HS256, iss=spree, aud=store_api)
|
|
39
|
+
+ a rotatable RefreshToken
|
|
40
|
+
│
|
|
41
|
+
▼
|
|
42
|
+
Client → subsequent calls with `Authorization: Bearer <Spree JWT>`
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The third-party JWT proves identity **once, at login**. After that, the client uses the Spree JWT for everything, and `/auth/refresh` rotates it via Spree's own refresh-token mechanism. Your existing CanCanCan rules, `current_user`, and serializer params just work.
|
|
46
|
+
|
|
47
|
+
## Step 1: Create the Strategy Class
|
|
48
|
+
|
|
49
|
+
Subclass `Spree::Authentication::Strategies::BaseStrategy` and implement two methods: `provider` (a string identifier) and `authenticate` (returns a `Spree::ServiceModule::Result`).
|
|
50
|
+
|
|
51
|
+
```ruby app/models/my_app/auth/external_jwt_strategy.rb
|
|
52
|
+
module MyApp
|
|
53
|
+
module Auth
|
|
54
|
+
class ExternalJwtStrategy < Spree::Authentication::Strategies::BaseStrategy
|
|
55
|
+
PROVIDER = 'external_idp'.freeze
|
|
56
|
+
|
|
57
|
+
def provider
|
|
58
|
+
PROVIDER
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def authenticate
|
|
62
|
+
token = params[:token] || extract_bearer
|
|
63
|
+
return failure(I18n.t('spree.api.unauthorized')) if token.blank?
|
|
64
|
+
|
|
65
|
+
payload = verify_with_jwks(token)
|
|
66
|
+
|
|
67
|
+
user = find_or_create_user_from_oauth(
|
|
68
|
+
provider: PROVIDER,
|
|
69
|
+
uid: payload.fetch('sub'),
|
|
70
|
+
info: {
|
|
71
|
+
email: payload['email'],
|
|
72
|
+
first_name: payload['given_name'],
|
|
73
|
+
last_name: payload['family_name']
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
success(user)
|
|
78
|
+
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::InvalidIssuerError, JWT::InvalidAudError, KeyError => e
|
|
79
|
+
failure(e.message)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def verify_with_jwks(token)
|
|
85
|
+
jwks_loader = ->(opts) { jwks(force: opts[:invalidate]) }
|
|
86
|
+
|
|
87
|
+
JWT.decode(
|
|
88
|
+
token, nil, true,
|
|
89
|
+
algorithms: ['RS256'],
|
|
90
|
+
iss: ENV.fetch('EXTERNAL_IDP_ISSUER'),
|
|
91
|
+
aud: ENV.fetch('EXTERNAL_IDP_AUDIENCE'),
|
|
92
|
+
verify_iss: true,
|
|
93
|
+
verify_aud: true,
|
|
94
|
+
jwks: jwks_loader
|
|
95
|
+
).first
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def jwks(force: false)
|
|
99
|
+
Rails.cache.fetch('external_idp:jwks', expires_in: 1.hour, force: force) do
|
|
100
|
+
uri = URI(ENV.fetch('EXTERNAL_IDP_JWKS_URL'))
|
|
101
|
+
JSON.parse(Net::HTTP.get(uri))
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def extract_bearer
|
|
106
|
+
header = request_env['HTTP_AUTHORIZATION'].to_s
|
|
107
|
+
header.start_with?('Bearer ') ? header.split(' ', 2).last : nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### What the base class gives you
|
|
115
|
+
|
|
116
|
+
`Spree::Authentication::Strategies::BaseStrategy` (in `spree_core`) exposes a few helpers so your subclass stays small:
|
|
117
|
+
|
|
118
|
+
| Helper | Purpose |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `success(user)` | Wrap a user in a successful `ServiceModule::Result` |
|
|
121
|
+
| `failure(message)` | Wrap an error message in a failed result |
|
|
122
|
+
| `find_user_by_email(email)` | Lookup against `Spree.user_class` |
|
|
123
|
+
| `find_or_create_user_from_oauth(provider:, uid:, info:, tokens: {})` | Calls `Spree::UserIdentity.find_or_create_from_oauth` with the right `user_class` |
|
|
124
|
+
| `params`, `request_env`, `user_class` | Reader access to the controller-supplied inputs |
|
|
125
|
+
|
|
126
|
+
`find_or_create_user_from_oauth` returns the **user**, not the identity. It creates the `Spree::UserIdentity` row on first login (mapping `provider + uid → user`) and reuses it on subsequent logins — so repeat sign-ins land on the same Spree customer.
|
|
127
|
+
|
|
128
|
+
## Step 2: Register the Strategy
|
|
129
|
+
|
|
130
|
+
Add the strategy to `Spree.store_authentication_strategies` in an initializer. The key you choose here is what clients will send as `provider` in the login payload.
|
|
131
|
+
|
|
132
|
+
```ruby config/initializers/spree.rb
|
|
133
|
+
Rails.application.config.after_initialize do
|
|
134
|
+
Spree.store_authentication_strategies.add(:external_idp, MyApp::Auth::ExternalJwtStrategy)
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`Spree.store_authentication_strategies` is a `Spree::Authentication::StrategyRegistry`. The full API:
|
|
139
|
+
|
|
140
|
+
| Method | Purpose |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `add(key, strategy_class)` | Register a strategy. Overwrites any existing entry under the same key — that's how you swap the built-in `:email` strategy. |
|
|
143
|
+
| `remove(key)` | Unregister a strategy. Idempotent (returns `nil` if the key was never registered). |
|
|
144
|
+
| `[key]` | Look up a registered class. |
|
|
145
|
+
| `key?(key)`, `keys`, `values`, `each`, `to_h` | Standard introspection. |
|
|
146
|
+
|
|
147
|
+
> **WARNING:** `Spree::UserIdentity` validates that `provider` is a registered strategy key. Registration must happen during boot — before the first login attempt.
|
|
148
|
+
|
|
149
|
+
For an admin-side equivalent, register under `Spree.admin_authentication_strategies` instead and instantiate your strategy against `Spree.admin_user_class`:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
Spree.admin_authentication_strategies.add(:okta, MyApp::Auth::OktaStrategy)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Step 3: Call the Exchange Endpoint
|
|
156
|
+
|
|
157
|
+
`POST /api/v3/store/auth/login` is the single dispatcher. The `provider` field in the body selects the strategy — omit it for built-in email/password, set it to your registered key for everything else. The remaining body fields are whatever your strategy reads from `params`.
|
|
158
|
+
|
|
159
|
+
```http
|
|
160
|
+
POST /api/v3/store/auth/login
|
|
161
|
+
X-Spree-API-Key: pk_your_publishable_key
|
|
162
|
+
Content-Type: application/json
|
|
163
|
+
|
|
164
|
+
{
|
|
165
|
+
"provider": "external_idp",
|
|
166
|
+
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The response is the standard Spree auth payload:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"token": "<Spree HS256 JWT, aud=store_api>",
|
|
175
|
+
"refresh_token": "rt_xxxxxxxxxxxx",
|
|
176
|
+
"user": { "id": "user_...", "email": "...", "first_name": "...", "...": "..." }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
From here, the client sends `Authorization: Bearer <Spree JWT>` on every subsequent Store API call. When the JWT expires (default: 1 hour), the client hits `POST /api/v3/store/auth/refresh` with the refresh token to rotate both.
|
|
181
|
+
|
|
182
|
+
From `@spree/sdk` the same call looks like:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { createClient } from '@spree/sdk'
|
|
186
|
+
|
|
187
|
+
const client = createClient({ baseUrl: 'https://your-store.com', publishableKey: '<pk>' })
|
|
188
|
+
|
|
189
|
+
const auth = await client.auth.login({
|
|
190
|
+
provider: 'external_idp',
|
|
191
|
+
token: thirdPartyJwt,
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`LoginCredentials` is a discriminated union — pass `{ email, password }` for the built-in strategy, or `{ provider, ...customFields }` for any strategy you registered.
|
|
196
|
+
|
|
197
|
+
## Account Linking
|
|
198
|
+
|
|
199
|
+
The naive flow above will create a brand new Spree user the first time a given `(provider, uid)` is seen — even if a user with the same email already exists from a password signup. If you want **same-email-means-same-customer**, look up by email first and attach an identity to the existing user:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
def authenticate
|
|
203
|
+
payload = verify_with_jwks(params[:token])
|
|
204
|
+
email = payload.fetch('email')
|
|
205
|
+
|
|
206
|
+
user = find_user_by_email(email)
|
|
207
|
+
|
|
208
|
+
if user
|
|
209
|
+
user.identities.find_or_create_by!(provider: PROVIDER, uid: payload['sub']) do |identity|
|
|
210
|
+
identity.info = { email: email, name: payload['name'] }
|
|
211
|
+
end
|
|
212
|
+
else
|
|
213
|
+
user = find_or_create_user_from_oauth(
|
|
214
|
+
provider: PROVIDER,
|
|
215
|
+
uid: payload['sub'],
|
|
216
|
+
info: { email: email, first_name: payload['given_name'], last_name: payload['family_name'] }
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
success(user)
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
> **WARNING:** **Only link by email if your IdP guarantees `email_verified`.** Silent linking against an unverified email is a known account-takeover vector: an attacker registers `victim@example.com` at the IdP without proving ownership, then logs into Spree as the real victim. Check the `email_verified` claim (or equivalent) before linking, and reject otherwise.
|
|
225
|
+
|
|
226
|
+
## Logout
|
|
227
|
+
|
|
228
|
+
```http
|
|
229
|
+
POST /api/v3/store/auth/logout
|
|
230
|
+
Content-Type: application/json
|
|
231
|
+
|
|
232
|
+
{ "refresh_token": "rt_xxxxxxxxxxxx" }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This revokes the Spree refresh token. The Spree JWT itself remains valid until it expires naturally (short-lived by design — default 1 hour). Spree does **not** call the IdP's revocation endpoint; if you need single sign-out, do that from the client.
|
|
236
|
+
|
|
237
|
+
## Security Notes
|
|
238
|
+
|
|
239
|
+
A few things worth getting right:
|
|
240
|
+
|
|
241
|
+
- **Don't try to pass the third-party JWT through to protected endpoints.** Spree's `JwtAuthentication` concern verifies `iss: 'spree'` and `aud: 'store_api'` with HS256 against the Spree secret — a foreign RS256 token will never validate, and you don't want it to. The exchange-at-login model is the right one.
|
|
242
|
+
- **JWKS caching and rotation.** Cache the JWKS (the example uses a 1-hour TTL) but make sure your loader honors the `invalidate: true` option so that an unrecognized `kid` triggers a refetch. Otherwise key rotation at the IdP locks users out for up to the TTL.
|
|
243
|
+
- **Validate `iss` and `aud` claims.** Always. The example passes `verify_iss: true, verify_aud: true` to `JWT.decode` — don't drop those.
|
|
244
|
+
- **Algorithm pinning.** Hard-code `algorithms: ['RS256']` (or whatever your IdP uses). Never let the token's own `alg` header decide — the classic `alg: none` and HS-as-RS confusion attacks both exploit lax algorithm selection.
|
|
245
|
+
- **Rate limiting.** `POST /auth/login` is rate-limited per IP via `Spree::Api::Config[:rate_limit_login]`. Tune it in your app config if needed — the same limit applies to email/password and provider-dispatched logins.
|
|
246
|
+
|
|
247
|
+
## Testing
|
|
248
|
+
|
|
249
|
+
A strategy is a plain Ruby class — test it in isolation without booting a controller:
|
|
250
|
+
|
|
251
|
+
```ruby spec/models/my_app/auth/external_jwt_strategy_spec.rb
|
|
252
|
+
require 'rails_helper'
|
|
253
|
+
|
|
254
|
+
RSpec.describe MyApp::Auth::ExternalJwtStrategy do
|
|
255
|
+
let(:rsa_private) { OpenSSL::PKey::RSA.generate(2048) }
|
|
256
|
+
let(:jwks) { { keys: [JWT::JWK.new(rsa_private).export] } }
|
|
257
|
+
|
|
258
|
+
let(:token) do
|
|
259
|
+
JWT.encode(
|
|
260
|
+
{ sub: 'idp-user-123', email: 'alice@example.com', iss: 'https://idp.example', aud: 'spree' },
|
|
261
|
+
rsa_private, 'RS256'
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
before do
|
|
266
|
+
stub_request(:get, ENV['EXTERNAL_IDP_JWKS_URL']).to_return(body: jwks.to_json)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
subject(:result) do
|
|
270
|
+
described_class.new(
|
|
271
|
+
params: { provider: 'external_idp', token: token },
|
|
272
|
+
request_env: {}
|
|
273
|
+
).authenticate
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'provisions a Spree user on first login' do
|
|
277
|
+
expect { result }.to change(Spree.user_class, :count).by(1)
|
|
278
|
+
expect(result).to be_success
|
|
279
|
+
expect(result.value.email).to eq('alice@example.com')
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'reuses the user on subsequent logins' do
|
|
283
|
+
described_class.new(params: { token: token }, request_env: {}).authenticate
|
|
284
|
+
expect { result }.not_to change(Spree.user_class, :count)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'fails on an expired token' do
|
|
288
|
+
expired = JWT.encode({ sub: 'x', exp: 1.hour.ago.to_i, iss: 'https://idp.example', aud: 'spree' }, rsa_private, 'RS256')
|
|
289
|
+
result = described_class.new(params: { token: expired }, request_env: {}).authenticate
|
|
290
|
+
expect(result).not_to be_success
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Reference
|
|
296
|
+
|
|
297
|
+
- `Spree::Authentication::Strategies::BaseStrategy` — `spree/core/app/models/spree/authentication/strategies/base_strategy.rb`
|
|
298
|
+
- `Spree::UserIdentity` — `spree/core/app/models/spree/user_identity.rb`
|
|
299
|
+
- `Spree::Api::V3::Store::AuthController` — `spree/api/app/controllers/spree/api/v3/store/auth_controller.rb`
|
|
300
|
+
- `Spree::Api::V3::JwtAuthentication` — `spree/api/app/controllers/concerns/spree/api/v3/jwt_authentication.rb`
|
|
301
|
+
- See also: [Authentication](../customization/authentication.md) for `Spree.user_class` integration.
|
|
@@ -39,7 +39,7 @@ If you used that gem in the past you need to remove it. Multi-Currency is now in
|
|
|
39
39
|
|
|
40
40
|
All international configuration is now kept on the `Store` model in the database rather than in initializer files.
|
|
41
41
|
|
|
42
|
-
If you used `spree_i18n` gem before please remove any `SpreeI18n::Config`references from your `config/initializers/spree.rb` file.
|
|
42
|
+
If you used `spree_i18n` gem before please remove any `SpreeI18n::Config` references from your `config/initializers/spree.rb` file.
|
|
43
43
|
|
|
44
44
|
## Optional Add `deface` gem
|
|
45
45
|
|
|
@@ -71,9 +71,9 @@ This rake task will enqueue background jobs to populate the product metrics for
|
|
|
71
71
|
|
|
72
72
|
### Replace `auto_strip_attributes` gem usage
|
|
73
73
|
|
|
74
|
-
`auto_strip_attributes` gem [was removed from Spree](https://github.com/spree/spree/pull/13462) due to bugs and conflicts with translations feature. Also it's not required anymore as built-in Rails `normalizes` provides the same feature without an additional
|
|
74
|
+
`auto_strip_attributes` gem [was removed from Spree](https://github.com/spree/spree/pull/13462) due to bugs and conflicts with translations feature. Also it's not required anymore as built-in Rails `normalizes` provides the same feature without an additional dependency.
|
|
75
75
|
|
|
76
|
-
If you used `
|
|
76
|
+
If you used `auto_strip_attributes` in your application, you will need to change it to `normalizes`, eg.
|
|
77
77
|
|
|
78
78
|
Replace
|
|
79
79
|
|