spree_api 5.4.0.beta → 5.4.0.beta2
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/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +28 -3
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +6 -0
- data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +29 -6
- data/app/controllers/concerns/spree/api/v3/locale_and_currency.rb +123 -11
- data/app/controllers/concerns/spree/api/v3/security_headers.rb +22 -0
- data/app/controllers/spree/api/v3/base_controller.rb +5 -2
- data/app/controllers/spree/api/v3/store/auth_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/base_controller.rb +11 -0
- data/app/controllers/spree/api/v3/store/cart_controller.rb +14 -3
- data/app/controllers/spree/api/v3/store/orders/line_items_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/orders_controller.rb +3 -1
- data/app/controllers/spree/api/v3/store/products_controller.rb +2 -2
- data/app/jobs/spree/api_keys/mark_as_used.rb +15 -0
- data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +17 -0
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +8 -1
- data/app/serializers/spree/api/v3/order_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/payment_source_serializer.rb +1 -6
- data/app/services/spree/api/v3/orders/update.rb +2 -0
- data/config/routes.rb +1 -0
- data/lib/spree/api/configuration.rb +12 -0
- data/lib/spree/api/dependencies.rb +4 -9
- data/lib/spree/api/engine.rb +5 -0
- data/lib/spree/api/middleware/request_size_limit.rb +36 -0
- data/lib/spree/api/openapi/schema_helper.rb +0 -56
- data/lib/spree/api/testing_support/v3/base.rb +4 -1
- metadata +12 -10
- data/app/serializers/spree/api/v3/post_category_serializer.rb +0 -13
- data/app/serializers/spree/api/v3/post_serializer.rb +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e2afbf0958129824507229d5994a8cf37076727f77f7c8a9cebade0a59c237b
|
|
4
|
+
data.tar.gz: ca64a94e5e43b4bbc4e0bbc02123102c0e1c49f03381f41d9021596e4b2fe013
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56393e0ac09195448b830d59283804af1634734c656961fbe07ec21b1ae7f752cdc2a6c0cabd19f5a48a26a548d6ae616cfc5744b823734f3928369d4e201588
|
|
7
|
+
data.tar.gz: 21477917637463a37f0c3085c497974adc26aa3228b7b5665cc0f3da5051f459c5681b853e739fe44f6e4171d0be71a702baadf141dd450a437ffe1ce15cc6fd
|
|
@@ -5,9 +5,16 @@ module Spree
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
|
+
# @!attribute [r] current_api_key
|
|
9
|
+
# The authenticated API key for the current request.
|
|
10
|
+
# @return [Spree::ApiKey, nil]
|
|
8
11
|
attr_reader :current_api_key
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
# Authenticates a publishable API key (pk_*) for Store API requests.
|
|
15
|
+
# Looks up the key by plaintext token scoped to the current store.
|
|
16
|
+
#
|
|
17
|
+
# @return [Boolean] true if authentication succeeded, false otherwise
|
|
11
18
|
def authenticate_api_key!
|
|
12
19
|
@current_api_key = current_store.api_keys.active.publishable.find_by(token: extract_api_key)
|
|
13
20
|
|
|
@@ -20,12 +27,18 @@ module Spree
|
|
|
20
27
|
return false
|
|
21
28
|
end
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
touch_api_key_if_needed(@current_api_key)
|
|
24
31
|
true
|
|
25
32
|
end
|
|
26
33
|
|
|
34
|
+
# Authenticates a secret API key (sk_*) for Admin API requests.
|
|
35
|
+
# Computes the HMAC-SHA256 digest of the provided token and looks up
|
|
36
|
+
# by +token_digest+, then verifies it belongs to the current store.
|
|
37
|
+
#
|
|
38
|
+
# @return [Boolean] true if authentication succeeded, false otherwise
|
|
27
39
|
def authenticate_secret_key!
|
|
28
|
-
@current_api_key =
|
|
40
|
+
@current_api_key = Spree::ApiKey.find_by_secret_token(extract_api_key)
|
|
41
|
+
@current_api_key = nil if @current_api_key && @current_api_key.store_id != current_store.id
|
|
29
42
|
|
|
30
43
|
unless @current_api_key
|
|
31
44
|
render_error(
|
|
@@ -36,12 +49,24 @@ module Spree
|
|
|
36
49
|
return false
|
|
37
50
|
end
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
touch_api_key_if_needed(@current_api_key)
|
|
40
53
|
true
|
|
41
54
|
end
|
|
42
55
|
|
|
43
56
|
private
|
|
44
57
|
|
|
58
|
+
# Marks the API key as used at most once per hour
|
|
59
|
+
# to avoid unnecessary DB writes and job queue pressure on every request.
|
|
60
|
+
# This follows the same approach as GitHub's personal access tokens.
|
|
61
|
+
def touch_api_key_if_needed(api_key)
|
|
62
|
+
return if api_key.last_used_at.present? && api_key.last_used_at > 1.hour.ago
|
|
63
|
+
|
|
64
|
+
Spree::ApiKeys::MarkAsUsed.perform_later(api_key.id, Time.current)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Extracts the API key from the request headers or params.
|
|
68
|
+
#
|
|
69
|
+
# @return [String, nil] the API key token
|
|
45
70
|
def extract_api_key
|
|
46
71
|
request.headers['X-Spree-Api-Key'].presence || params[:api_key]
|
|
47
72
|
end
|
|
@@ -46,6 +46,12 @@ module Spree
|
|
|
46
46
|
digital_link_expired: 'digital_link_expired',
|
|
47
47
|
download_limit_exceeded: 'download_limit_exceeded',
|
|
48
48
|
|
|
49
|
+
# Rate limiting errors
|
|
50
|
+
rate_limit_exceeded: 'rate_limit_exceeded',
|
|
51
|
+
|
|
52
|
+
# Request errors
|
|
53
|
+
request_too_large: 'request_too_large',
|
|
54
|
+
|
|
49
55
|
# General errors
|
|
50
56
|
processing_error: 'processing_error',
|
|
51
57
|
invalid_request: 'invalid_request'
|
|
@@ -9,6 +9,10 @@ module Spree
|
|
|
9
9
|
USER_TYPE_CUSTOMER = 'customer'.freeze
|
|
10
10
|
USER_TYPE_ADMIN = 'admin'.freeze
|
|
11
11
|
|
|
12
|
+
JWT_AUDIENCE_STORE = 'store_api'.freeze
|
|
13
|
+
JWT_AUDIENCE_ADMIN = 'admin_api'.freeze
|
|
14
|
+
JWT_ISSUER = 'spree'.freeze
|
|
15
|
+
|
|
12
16
|
included do
|
|
13
17
|
attr_reader :current_user
|
|
14
18
|
end
|
|
@@ -20,7 +24,8 @@ module Spree
|
|
|
20
24
|
|
|
21
25
|
payload = decode_jwt(token)
|
|
22
26
|
@current_user = find_user_from_payload(payload)
|
|
23
|
-
rescue JWT::DecodeError, JWT::ExpiredSignature,
|
|
27
|
+
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::InvalidIssuerError,
|
|
28
|
+
JWT::InvalidAudError, ActiveRecord::RecordNotFound => e
|
|
24
29
|
Rails.logger.debug { "JWT authentication failed: #{e.message}" }
|
|
25
30
|
@current_user = nil
|
|
26
31
|
end
|
|
@@ -40,12 +45,16 @@ module Spree
|
|
|
40
45
|
|
|
41
46
|
# Generate a JWT token for a user
|
|
42
47
|
# @param user [Object] The user to generate a token for
|
|
43
|
-
# @param expiration [Integer] Time in seconds until expiration (default
|
|
48
|
+
# @param expiration [Integer] Time in seconds until expiration (default from config, 1 hour)
|
|
49
|
+
# @param audience [String] The audience claim (default: store_api)
|
|
44
50
|
# @return [String] The JWT token
|
|
45
|
-
def generate_jwt(user, expiration:
|
|
51
|
+
def generate_jwt(user, expiration: jwt_expiration, audience: JWT_AUDIENCE_STORE)
|
|
46
52
|
payload = {
|
|
47
53
|
user_id: user.id,
|
|
48
54
|
user_type: determine_user_type(user),
|
|
55
|
+
jti: SecureRandom.uuid,
|
|
56
|
+
iss: JWT_ISSUER,
|
|
57
|
+
aud: audience,
|
|
49
58
|
exp: Time.current.to_i + expiration
|
|
50
59
|
}
|
|
51
60
|
JWT.encode(payload, jwt_secret, 'HS256')
|
|
@@ -58,18 +67,32 @@ module Spree
|
|
|
58
67
|
header = request.headers['Authorization']
|
|
59
68
|
return header.split(' ').last if header.present? && header.start_with?('Bearer ')
|
|
60
69
|
|
|
61
|
-
#
|
|
62
|
-
params[:token]
|
|
70
|
+
# Restricted fallback: only for digital download endpoints
|
|
71
|
+
params[:token] if controller_name == 'digitals'
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
def decode_jwt(token)
|
|
66
|
-
JWT.decode(token, jwt_secret, true,
|
|
75
|
+
JWT.decode(token, jwt_secret, true,
|
|
76
|
+
algorithm: 'HS256',
|
|
77
|
+
iss: JWT_ISSUER,
|
|
78
|
+
aud: expected_audience,
|
|
79
|
+
verify_iss: true,
|
|
80
|
+
verify_aud: true
|
|
81
|
+
).first
|
|
67
82
|
end
|
|
68
83
|
|
|
69
84
|
def jwt_secret
|
|
70
85
|
Rails.application.credentials.jwt_secret_key || ENV['JWT_SECRET_KEY'] || Rails.application.secret_key_base
|
|
71
86
|
end
|
|
72
87
|
|
|
88
|
+
def jwt_expiration
|
|
89
|
+
Spree::Api::Config[:jwt_expiration]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def expected_audience
|
|
93
|
+
JWT_AUDIENCE_STORE
|
|
94
|
+
end
|
|
95
|
+
|
|
73
96
|
def find_user_from_payload(payload)
|
|
74
97
|
user_id = payload['user_id']
|
|
75
98
|
user_type = payload['user_type'] || USER_TYPE_CUSTOMER
|
|
@@ -1,60 +1,172 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
|
+
# Handles locale, currency, and market resolution for API v3 controllers.
|
|
5
|
+
#
|
|
6
|
+
# This concern is fully self-contained and does not depend on
|
|
7
|
+
# +Spree::Core::ControllerHelpers::Locale+ or +Spree::Core::ControllerHelpers::Currency+.
|
|
8
|
+
#
|
|
9
|
+
# Resolution order:
|
|
10
|
+
# 1. Market is resolved from +x-spree-country+ header (sets +Spree::Current.market+)
|
|
11
|
+
# 2. Locale is resolved: +x-spree-locale+ header > +params[:locale]+ > +Spree::Current.locale+ (market -> store fallback)
|
|
12
|
+
# 3. Currency is resolved: +x-spree-currency+ header > +params[:currency]+ > +Spree::Current.currency+ (market -> store fallback)
|
|
13
|
+
# 4. Mobility fallback locale is configured for the current store
|
|
4
14
|
module LocaleAndCurrency
|
|
5
15
|
extend ActiveSupport::Concern
|
|
6
16
|
|
|
7
17
|
included do
|
|
8
18
|
before_action :set_market_from_country
|
|
9
|
-
before_action :
|
|
10
|
-
before_action :
|
|
19
|
+
before_action :set_locale
|
|
20
|
+
before_action :set_currency
|
|
21
|
+
before_action :set_fallback_locale
|
|
11
22
|
end
|
|
12
23
|
|
|
13
24
|
protected
|
|
14
25
|
|
|
15
|
-
#
|
|
26
|
+
# Returns the current locale for this request.
|
|
27
|
+
#
|
|
28
|
+
# Priority: x-spree-locale header > params[:locale] > Spree::Current.locale (market -> store fallback)
|
|
29
|
+
#
|
|
30
|
+
# @return [String] the locale code, e.g. +"en"+, +"fr"+
|
|
16
31
|
def current_locale
|
|
17
32
|
@current_locale ||= begin
|
|
18
|
-
locale = locale_from_header || locale_from_params
|
|
19
|
-
locale.to_s if supported_locale?(locale)
|
|
20
|
-
end ||
|
|
33
|
+
locale = locale_from_header || locale_from_params
|
|
34
|
+
locale.to_s if locale.present? && supported_locale?(locale)
|
|
35
|
+
end || Spree::Current.locale
|
|
21
36
|
end
|
|
22
37
|
|
|
23
|
-
#
|
|
38
|
+
# Returns the current currency for this request.
|
|
39
|
+
#
|
|
40
|
+
# Priority: x-spree-currency header > params[:currency] > Spree::Current.currency (market -> store fallback)
|
|
41
|
+
#
|
|
42
|
+
# @return [String] the currency ISO code, e.g. +"USD"+, +"EUR"+
|
|
24
43
|
def current_currency
|
|
25
44
|
@current_currency ||= begin
|
|
26
|
-
currency = currency_from_header || currency_from_params
|
|
45
|
+
currency = currency_from_header || currency_from_params
|
|
27
46
|
currency = currency&.upcase
|
|
28
|
-
|
|
47
|
+
currency if currency.present? && supported_currency?(currency)
|
|
48
|
+
end || Spree::Current.currency
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the default locale, delegating to +Spree::Current.locale+
|
|
52
|
+
# which falls back through market -> store.
|
|
53
|
+
#
|
|
54
|
+
# @return [String] the default locale code
|
|
55
|
+
def default_locale
|
|
56
|
+
Spree::Current.locale
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the list of supported locale codes for the current store.
|
|
60
|
+
#
|
|
61
|
+
# When markets are configured, this aggregates locales from all markets.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<String>] supported locale codes
|
|
64
|
+
def supported_locales
|
|
65
|
+
@supported_locales ||= current_store&.supported_locales_list
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Checks if the given locale is supported by the current store.
|
|
69
|
+
#
|
|
70
|
+
# @param locale_code [String, nil] the locale code to check
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def supported_locale?(locale_code)
|
|
73
|
+
return false if supported_locales.nil?
|
|
74
|
+
|
|
75
|
+
supported_locales.include?(locale_code&.to_s)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the list of supported currencies for the current store.
|
|
79
|
+
#
|
|
80
|
+
# When markets are configured, this aggregates currencies from all markets.
|
|
81
|
+
#
|
|
82
|
+
# @return [Array<Money::Currency>] supported currencies
|
|
83
|
+
def supported_currencies
|
|
84
|
+
@supported_currencies ||= current_store&.supported_currencies_list
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Checks if the given currency ISO code is supported by the current store.
|
|
88
|
+
#
|
|
89
|
+
# @param currency_iso_code [String, nil] the currency ISO code to check, e.g. +"USD"+
|
|
90
|
+
# @return [Boolean]
|
|
91
|
+
def supported_currency?(currency_iso_code)
|
|
92
|
+
return false if supported_currencies.nil?
|
|
93
|
+
|
|
94
|
+
supported_currencies.map(&:iso_code).include?(currency_iso_code&.upcase)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Finds a record using the given block, falling back to the store's default locale
|
|
98
|
+
# if the record is not found in the current locale.
|
|
99
|
+
#
|
|
100
|
+
# Used for slug/permalink lookups where translated slugs may not exist in all locales.
|
|
101
|
+
#
|
|
102
|
+
# @yield the block that performs the lookup
|
|
103
|
+
# @return [ActiveRecord::Base] the found record
|
|
104
|
+
# @raise [ActiveRecord::RecordNotFound] if not found in any locale
|
|
105
|
+
def find_with_fallback_default_locale(&block)
|
|
106
|
+
result = begin
|
|
107
|
+
block.call
|
|
108
|
+
rescue ActiveRecord::RecordNotFound => _e
|
|
109
|
+
nil
|
|
29
110
|
end
|
|
111
|
+
|
|
112
|
+
result || Mobility.with_locale(current_store.default_locale) { block.call }
|
|
30
113
|
end
|
|
31
114
|
|
|
32
115
|
private
|
|
33
116
|
|
|
34
|
-
|
|
117
|
+
# Sets +I18n.locale+ and +Spree::Current.locale+ from the resolved locale.
|
|
118
|
+
def set_locale
|
|
119
|
+
Spree::Current.locale = current_locale
|
|
35
120
|
I18n.locale = current_locale
|
|
36
121
|
end
|
|
37
122
|
|
|
38
|
-
|
|
123
|
+
# Sets +Spree::Current.currency+ from the resolved currency.
|
|
124
|
+
def set_currency
|
|
39
125
|
Spree::Current.currency = current_currency
|
|
40
126
|
end
|
|
41
127
|
|
|
128
|
+
# Configures Mobility fallback locales for the current store.
|
|
129
|
+
#
|
|
130
|
+
# This runs after market resolution so fallbacks are aware of the store's
|
|
131
|
+
# full locale configuration.
|
|
132
|
+
def set_fallback_locale
|
|
133
|
+
return unless current_store.present?
|
|
134
|
+
|
|
135
|
+
Spree::Locales::SetFallbackLocaleForStore.new.call(store: current_store)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Reads the locale from the +x-spree-locale+ request header.
|
|
139
|
+
#
|
|
140
|
+
# @return [String, nil]
|
|
42
141
|
def locale_from_header
|
|
43
142
|
request.headers['x-spree-locale'].presence
|
|
44
143
|
end
|
|
45
144
|
|
|
145
|
+
# Reads the currency from the +x-spree-currency+ request header.
|
|
146
|
+
#
|
|
147
|
+
# @return [String, nil]
|
|
46
148
|
def currency_from_header
|
|
47
149
|
request.headers['x-spree-currency'].presence
|
|
48
150
|
end
|
|
49
151
|
|
|
152
|
+
# Reads the locale from request params.
|
|
153
|
+
#
|
|
154
|
+
# @return [String, nil]
|
|
50
155
|
def locale_from_params
|
|
51
156
|
params[:locale].presence
|
|
52
157
|
end
|
|
53
158
|
|
|
159
|
+
# Reads the currency from request params.
|
|
160
|
+
#
|
|
161
|
+
# @return [String, nil]
|
|
54
162
|
def currency_from_params
|
|
55
163
|
params[:currency].presence
|
|
56
164
|
end
|
|
57
165
|
|
|
166
|
+
# Resolves the market from the +x-spree-country+ header or +params[:country]+.
|
|
167
|
+
#
|
|
168
|
+
# When a matching market is found, it is set on +Spree::Current.market+,
|
|
169
|
+
# which influences the default locale and currency fallbacks.
|
|
58
170
|
def set_market_from_country
|
|
59
171
|
country_iso = request.headers['x-spree-country'].presence || params[:country].presence
|
|
60
172
|
return unless country_iso
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module SecurityHeaders
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_action :set_security_headers
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def set_security_headers
|
|
14
|
+
response.headers['X-Content-Type-Options'] = 'nosniff'
|
|
15
|
+
response.headers['X-Frame-Options'] = 'DENY'
|
|
16
|
+
response.headers.delete('X-Powered-By')
|
|
17
|
+
response.headers.delete('Server')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -6,13 +6,12 @@ module Spree
|
|
|
6
6
|
include CanCan::ControllerAdditions
|
|
7
7
|
include Spree::Core::ControllerHelpers::StrongParameters
|
|
8
8
|
include Spree::Core::ControllerHelpers::Store
|
|
9
|
-
include Spree::Core::ControllerHelpers::Locale
|
|
10
|
-
include Spree::Core::ControllerHelpers::Currency
|
|
11
9
|
include Spree::Api::V3::LocaleAndCurrency
|
|
12
10
|
include Spree::Api::V3::JwtAuthentication
|
|
13
11
|
include Spree::Api::V3::ApiKeyAuthentication
|
|
14
12
|
include Spree::Api::V3::ErrorHandler
|
|
15
13
|
include Spree::Api::V3::HttpCaching
|
|
14
|
+
include Spree::Api::V3::SecurityHeaders
|
|
16
15
|
include Spree::Api::V3::ResourceSerializer
|
|
17
16
|
include Pagy::Method
|
|
18
17
|
|
|
@@ -22,6 +21,7 @@ module Spree
|
|
|
22
21
|
protected
|
|
23
22
|
|
|
24
23
|
# Override to use current_user from JWT authentication
|
|
24
|
+
# @return [Spree.user_class]
|
|
25
25
|
def spree_current_user
|
|
26
26
|
current_user
|
|
27
27
|
end
|
|
@@ -29,10 +29,13 @@ module Spree
|
|
|
29
29
|
alias try_spree_current_user spree_current_user
|
|
30
30
|
|
|
31
31
|
# CanCanCan ability
|
|
32
|
+
# @return [Spree::Ability]
|
|
32
33
|
def current_ability
|
|
33
34
|
@current_ability ||= Spree::Ability.new(current_user, ability_options)
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# Options passed to the CanCanCan ability
|
|
38
|
+
# @return [Hash]
|
|
36
39
|
def ability_options
|
|
37
40
|
{ store: current_store }
|
|
38
41
|
end
|
|
@@ -3,6 +3,12 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
5
|
class AuthController < Store::BaseController
|
|
6
|
+
# Tighter rate limits for auth endpoints (per IP to prevent brute force)
|
|
7
|
+
rate_limit to: Spree::Api::Config[:rate_limit_login], within: 1.minute, store: Rails.cache, only: :create, with: RATE_LIMIT_RESPONSE
|
|
8
|
+
rate_limit to: Spree::Api::Config[:rate_limit_register], within: 1.minute, store: Rails.cache, only: :register, with: RATE_LIMIT_RESPONSE
|
|
9
|
+
rate_limit to: Spree::Api::Config[:rate_limit_refresh], within: 1.minute, store: Rails.cache, only: :refresh, with: RATE_LIMIT_RESPONSE
|
|
10
|
+
rate_limit to: Spree::Api::Config[:rate_limit_oauth], within: 1.minute, store: Rails.cache, only: :oauth_callback, with: RATE_LIMIT_RESPONSE
|
|
11
|
+
|
|
6
12
|
skip_before_action :authenticate_user, only: [:create, :register, :oauth_callback]
|
|
7
13
|
prepend_before_action :require_authentication!, only: [:refresh]
|
|
8
14
|
|
|
@@ -3,6 +3,17 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
5
|
class BaseController < Spree::Api::V3::BaseController
|
|
6
|
+
RATE_LIMIT_RESPONSE = -> {
|
|
7
|
+
body = { error: { code: 'rate_limit_exceeded', message: 'Too many requests. Please retry later.' } }
|
|
8
|
+
[429, { 'Content-Type' => 'application/json', 'Retry-After' => '60' }, [body.to_json]]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Global rate limit per publishable API key
|
|
12
|
+
rate_limit to: Spree::Api::Config[:rate_limit_per_key], within: 1.minute,
|
|
13
|
+
store: Rails.cache,
|
|
14
|
+
by: -> { request.headers['X-Spree-Api-Key'] || request.remote_ip },
|
|
15
|
+
with: RATE_LIMIT_RESPONSE
|
|
16
|
+
|
|
6
17
|
# Require publishable API key for all Store API requests
|
|
7
18
|
before_action :authenticate_api_key!
|
|
8
19
|
end
|
|
@@ -11,13 +11,20 @@ module Spree
|
|
|
11
11
|
# Creates a new shopping cart (order)
|
|
12
12
|
# Can be created by guests or authenticated customers
|
|
13
13
|
def create
|
|
14
|
-
|
|
14
|
+
result = Spree.cart_create_service.call(
|
|
15
|
+
user: current_user,
|
|
15
16
|
store: current_store,
|
|
16
17
|
currency: current_currency,
|
|
17
|
-
|
|
18
|
+
locale: current_locale,
|
|
19
|
+
metadata: cart_params[:metadata] || {}
|
|
18
20
|
)
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
if result.success?
|
|
23
|
+
@cart = result.value
|
|
24
|
+
render json: serialize_resource(@cart), status: :created
|
|
25
|
+
else
|
|
26
|
+
render_service_error(result.error.to_s)
|
|
27
|
+
end
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
# GET /api/v3/store/cart
|
|
@@ -53,6 +60,10 @@ module Spree
|
|
|
53
60
|
|
|
54
61
|
private
|
|
55
62
|
|
|
63
|
+
def cart_params
|
|
64
|
+
params.permit(metadata: {})
|
|
65
|
+
end
|
|
66
|
+
|
|
56
67
|
# Find incomplete cart by order token for associate action
|
|
57
68
|
# Only finds guest carts (no user) or carts already owned by current user (idempotent)
|
|
58
69
|
def find_cart_by_token
|
|
@@ -15,6 +15,7 @@ module Spree
|
|
|
15
15
|
order: @parent,
|
|
16
16
|
variant: variant,
|
|
17
17
|
quantity: permitted_params[:quantity] || 1,
|
|
18
|
+
metadata: permitted_params[:metadata] || {},
|
|
18
19
|
options: permitted_params[:options] || {}
|
|
19
20
|
)
|
|
20
21
|
|
|
@@ -29,6 +30,8 @@ module Spree
|
|
|
29
30
|
def update
|
|
30
31
|
@line_item = scope.find_by_prefix_id!(params[:id])
|
|
31
32
|
|
|
33
|
+
@line_item.metadata = @line_item.metadata.merge(permitted_params[:metadata].to_h) if permitted_params[:metadata].present?
|
|
34
|
+
|
|
32
35
|
if permitted_params[:quantity].present?
|
|
33
36
|
result = Spree.cart_set_item_quantity_service.call(
|
|
34
37
|
order: @parent,
|
|
@@ -41,6 +44,9 @@ module Spree
|
|
|
41
44
|
else
|
|
42
45
|
render_service_error(result.error, code: ERROR_CODES[:invalid_quantity])
|
|
43
46
|
end
|
|
47
|
+
elsif @line_item.changed?
|
|
48
|
+
@line_item.save!
|
|
49
|
+
render_order
|
|
44
50
|
else
|
|
45
51
|
render_order
|
|
46
52
|
end
|
|
@@ -103,11 +103,13 @@ module Spree
|
|
|
103
103
|
params.permit(
|
|
104
104
|
:email,
|
|
105
105
|
:currency,
|
|
106
|
+
:locale,
|
|
106
107
|
:special_instructions,
|
|
107
108
|
:ship_address_id,
|
|
108
109
|
:bill_address_id,
|
|
109
110
|
ship_address: address_params,
|
|
110
|
-
bill_address: address_params
|
|
111
|
+
bill_address: address_params,
|
|
112
|
+
metadata: {}
|
|
111
113
|
)
|
|
112
114
|
end
|
|
113
115
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module ApiKeys
|
|
3
|
+
class MarkAsUsed < Spree::BaseJob
|
|
4
|
+
queue_as Spree.queues.api_keys
|
|
5
|
+
|
|
6
|
+
def perform(api_key_id, used_at)
|
|
7
|
+
api_key = Spree::ApiKey.find_by(id: api_key_id)
|
|
8
|
+
return if api_key.nil?
|
|
9
|
+
return if api_key.last_used_at.present? && api_key.last_used_at >= used_at
|
|
10
|
+
|
|
11
|
+
api_key.update_column(:last_used_at, used_at)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API Line Item Serializer
|
|
6
|
+
# Extends the store serializer with metadata visibility
|
|
7
|
+
class LineItemSerializer < V3::LineItemSerializer
|
|
8
|
+
typelize metadata: 'Record<string, unknown> | null'
|
|
9
|
+
|
|
10
|
+
attribute :metadata do |line_item|
|
|
11
|
+
line_item.metadata.presence
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -11,7 +11,8 @@ module Spree
|
|
|
11
11
|
store_owner_notification_delivered: :boolean,
|
|
12
12
|
internal_note: [:string, nullable: true], approver_id: [:string, nullable: true],
|
|
13
13
|
canceler_id: [:string, nullable: true], created_by_id: [:string, nullable: true],
|
|
14
|
-
canceled_at: [:string, nullable: true], approved_at: [:string, nullable: true]
|
|
14
|
+
canceled_at: [:string, nullable: true], approved_at: [:string, nullable: true],
|
|
15
|
+
metadata: 'Record<string, unknown> | null'
|
|
15
16
|
|
|
16
17
|
# Admin-only attributes
|
|
17
18
|
attributes :channel, :last_ip_address, :considered_risky,
|
|
@@ -19,6 +20,10 @@ module Spree
|
|
|
19
20
|
:internal_note, :approver_id,
|
|
20
21
|
canceled_at: :iso8601, approved_at: :iso8601
|
|
21
22
|
|
|
23
|
+
attribute :metadata do |order|
|
|
24
|
+
order.metadata.presence
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
attribute :canceler_id do |order|
|
|
23
28
|
order.canceler_id
|
|
24
29
|
end
|
|
@@ -27,6 +32,8 @@ module Spree
|
|
|
27
32
|
order.created_by_id
|
|
28
33
|
end
|
|
29
34
|
|
|
35
|
+
many :line_items, resource: Spree.api.admin_line_item_serializer
|
|
36
|
+
|
|
30
37
|
one :user,
|
|
31
38
|
resource: Spree.api.admin_customer_serializer,
|
|
32
39
|
if: proc { params[:includes]&.include?('user') }
|
|
@@ -5,7 +5,7 @@ module Spree
|
|
|
5
5
|
# Customer-facing order data
|
|
6
6
|
class OrderSerializer < BaseSerializer
|
|
7
7
|
typelize number: :string, state: :string, token: :string, email: [:string, nullable: true],
|
|
8
|
-
special_instructions: [:string, nullable: true], currency: :string, item_count: :number,
|
|
8
|
+
special_instructions: [:string, nullable: true], currency: :string, locale: [:string, nullable: true], item_count: :number,
|
|
9
9
|
shipment_state: [:string, nullable: true], payment_state: [:string, nullable: true],
|
|
10
10
|
item_total: :string, display_item_total: :string,
|
|
11
11
|
ship_total: :string, display_ship_total: :string,
|
|
@@ -18,7 +18,7 @@ module Spree
|
|
|
18
18
|
bill_address: { nullable: true }, ship_address: { nullable: true }
|
|
19
19
|
|
|
20
20
|
attributes :number, :state, :token, :email, :special_instructions,
|
|
21
|
-
:currency, :item_count, :shipment_state, :payment_state,
|
|
21
|
+
:currency, :locale, :item_count, :shipment_state, :payment_state,
|
|
22
22
|
:item_total, :display_item_total, :ship_total, :display_ship_total,
|
|
23
23
|
:adjustment_total, :display_adjustment_total, :promo_total, :display_promo_total,
|
|
24
24
|
:tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
|
|
@@ -2,16 +2,11 @@ module Spree
|
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
4
|
class PaymentSourceSerializer < BaseSerializer
|
|
5
|
-
typelize gateway_payment_profile_id: [:string, nullable: true]
|
|
6
|
-
public_metadata: ['Record<string, unknown>', nullable: true]
|
|
5
|
+
typelize gateway_payment_profile_id: [:string, nullable: true]
|
|
7
6
|
|
|
8
7
|
attribute :gateway_payment_profile_id do |source|
|
|
9
8
|
source.try(:gateway_payment_profile_id)
|
|
10
9
|
end
|
|
11
|
-
|
|
12
|
-
attribute :public_metadata do |source|
|
|
13
|
-
source.try(:public_metadata)
|
|
14
|
-
end
|
|
15
10
|
end
|
|
16
11
|
end
|
|
17
12
|
end
|
|
@@ -54,6 +54,8 @@ module Spree
|
|
|
54
54
|
order.email = params[:email] if params[:email].present?
|
|
55
55
|
order.special_instructions = params[:special_instructions] if params.key?(:special_instructions)
|
|
56
56
|
order.currency = params[:currency].upcase if params[:currency].present?
|
|
57
|
+
order.locale = params[:locale] if params[:locale].present?
|
|
58
|
+
order.metadata = order.metadata.merge(params[:metadata].to_h) if params[:metadata].present?
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def assign_address(address_type)
|
data/config/routes.rb
CHANGED
|
@@ -9,6 +9,18 @@ module Spree
|
|
|
9
9
|
preference :api_v2_content_type, :string, default: 'application/vnd.api+json'
|
|
10
10
|
preference :api_v2_per_page_limit, :integer, default: 500
|
|
11
11
|
|
|
12
|
+
preference :jwt_expiration, :integer, default: 3600 # 1 hour in seconds
|
|
13
|
+
|
|
14
|
+
# Rate limiting (requests per minute)
|
|
15
|
+
preference :rate_limit_per_key, :integer, default: 300 # per publishable API key
|
|
16
|
+
preference :rate_limit_login, :integer, default: 5 # per IP
|
|
17
|
+
preference :rate_limit_register, :integer, default: 3 # per IP
|
|
18
|
+
preference :rate_limit_refresh, :integer, default: 10 # per IP
|
|
19
|
+
preference :rate_limit_oauth, :integer, default: 5 # per IP
|
|
20
|
+
|
|
21
|
+
# Request body size limit in bytes
|
|
22
|
+
preference :max_request_body_size, :integer, default: 102_400 # 100KB
|
|
23
|
+
|
|
12
24
|
preference :webhooks_enabled, :boolean, default: true
|
|
13
25
|
preference :webhooks_verify_ssl, :boolean, default: !Rails.env.development?
|
|
14
26
|
end
|
|
@@ -72,8 +72,6 @@ module Spree
|
|
|
72
72
|
storefront_line_item_serializer: 'Spree::V2::Storefront::LineItemSerializer',
|
|
73
73
|
storefront_option_type_serializer: 'Spree::V2::Storefront::OptionTypeSerializer',
|
|
74
74
|
storefront_option_value_serializer: 'Spree::V2::Storefront::OptionValueSerializer',
|
|
75
|
-
storefront_post_category_serializer: 'Spree::V2::Storefront::PostCategorySerializer',
|
|
76
|
-
storefront_post_serializer: 'Spree::V2::Storefront::PostSerializer',
|
|
77
75
|
storefront_product_property_serializer: 'Spree::V2::Storefront::ProductPropertySerializer',
|
|
78
76
|
storefront_order_promotion_serializer: 'Spree::V2::Storefront::OrderPromotionSerializer',
|
|
79
77
|
storefront_shipping_method_serializer: 'Spree::V2::Storefront::ShippingMethodSerializer',
|
|
@@ -136,8 +134,6 @@ module Spree
|
|
|
136
134
|
import_row_serializer: 'Spree::Api::V3::ImportRowSerializer',
|
|
137
135
|
invitation_serializer: 'Spree::Api::V3::InvitationSerializer',
|
|
138
136
|
newsletter_subscriber_serializer: 'Spree::Api::V3::NewsletterSubscriberSerializer',
|
|
139
|
-
post_serializer: 'Spree::Api::V3::PostSerializer',
|
|
140
|
-
post_category_serializer: 'Spree::Api::V3::PostCategorySerializer',
|
|
141
137
|
promotion_serializer: 'Spree::Api::V3::PromotionSerializer',
|
|
142
138
|
refund_serializer: 'Spree::Api::V3::RefundSerializer',
|
|
143
139
|
reimbursement_serializer: 'Spree::Api::V3::ReimbursementSerializer',
|
|
@@ -156,6 +152,7 @@ module Spree
|
|
|
156
152
|
admin_price_serializer: 'Spree::Api::V3::Admin::PriceSerializer',
|
|
157
153
|
admin_metafield_serializer: 'Spree::Api::V3::Admin::MetafieldSerializer',
|
|
158
154
|
admin_taxon_serializer: 'Spree::Api::V3::Admin::TaxonSerializer',
|
|
155
|
+
admin_line_item_serializer: 'Spree::Api::V3::Admin::LineItemSerializer',
|
|
159
156
|
admin_taxonomy_serializer: 'Spree::Api::V3::Admin::TaxonomySerializer',
|
|
160
157
|
|
|
161
158
|
# platform serializers
|
|
@@ -164,10 +161,9 @@ module Spree
|
|
|
164
161
|
# sorters
|
|
165
162
|
storefront_collection_sorter: -> { Spree::Dependencies.collection_sorter },
|
|
166
163
|
storefront_order_sorter: -> { Spree::Dependencies.collection_sorter },
|
|
164
|
+
storefront_posts_sorter: nil,
|
|
167
165
|
storefront_products_sorter: -> { Spree::Dependencies.products_sorter },
|
|
168
166
|
platform_products_sorter: -> { Spree::Dependencies.products_sorter },
|
|
169
|
-
storefront_posts_sorter: -> { Spree::Dependencies.posts_sorter },
|
|
170
|
-
|
|
171
167
|
# paginators
|
|
172
168
|
storefront_collection_paginator: 'Spree::Api::Paginate',
|
|
173
169
|
|
|
@@ -180,8 +176,8 @@ module Spree
|
|
|
180
176
|
storefront_completed_order_finder: -> { Spree::Dependencies.completed_order_finder },
|
|
181
177
|
storefront_credit_card_finder: -> { Spree::Dependencies.credit_card_finder },
|
|
182
178
|
storefront_find_by_variant_finder: -> { Spree::Dependencies.line_item_by_variant_finder },
|
|
179
|
+
storefront_posts_finder: nil,
|
|
183
180
|
storefront_products_finder: -> { Spree::Dependencies.products_finder },
|
|
184
|
-
storefront_posts_finder: -> { Spree::Dependencies.posts_finder },
|
|
185
181
|
storefront_taxon_finder: -> { Spree::Dependencies.taxon_finder },
|
|
186
182
|
storefront_variant_finder: -> { Spree::Dependencies.variant_finder },
|
|
187
183
|
|
|
@@ -195,7 +191,6 @@ module Spree
|
|
|
195
191
|
platform_country_serializer: 'Spree::Api::V2::Platform::CountrySerializer',
|
|
196
192
|
platform_credit_card_serializer: 'Spree::Api::V2::Platform::CreditCardSerializer',
|
|
197
193
|
platform_customer_return_serializer: 'Spree::Api::V2::Platform::CustomerReturnSerializer',
|
|
198
|
-
platform_data_feed_serializer: 'Spree::Api::V2::Platform::DataFeedSerializer',
|
|
199
194
|
platform_digital_link_serializer: 'Spree::Api::V2::Platform::DigitalLinkSerializer',
|
|
200
195
|
platform_digital_serializer: 'Spree::Api::V2::Platform::DigitalSerializer',
|
|
201
196
|
platform_gift_card_serializer: 'Spree::Api::V2::Platform::GiftCardSerializer',
|
|
@@ -218,8 +213,8 @@ module Spree
|
|
|
218
213
|
platform_promotion_action_serializer: 'Spree::Api::V2::Platform::PromotionActionSerializer',
|
|
219
214
|
platform_promotion_category_serializer: 'Spree::Api::V2::Platform::PromotionCategorySerializer',
|
|
220
215
|
platform_promotion_rule_serializer: 'Spree::Api::V2::Platform::PromotionRuleSerializer',
|
|
221
|
-
platform_promotion_serializer: 'Spree::Api::V2::Platform::PromotionSerializer',
|
|
222
216
|
platform_property_serializer: 'Spree::Api::V2::Platform::PropertySerializer',
|
|
217
|
+
platform_promotion_serializer: 'Spree::Api::V2::Platform::PromotionSerializer',
|
|
223
218
|
platform_prototype_serializer: 'Spree::Api::V2::Platform::PrototypeSerializer',
|
|
224
219
|
platform_refund_reason_serializer: 'Spree::Api::V2::Platform::RefundReasonSerializer',
|
|
225
220
|
platform_refund_serializer: 'Spree::Api::V2::Platform::RefundSerializer',
|
data/lib/spree/api/engine.rb
CHANGED
|
@@ -14,6 +14,11 @@ module Spree
|
|
|
14
14
|
Spree::Api::Dependencies = Spree::Api::ApiDependencies.new
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
initializer 'spree.api.request_size_limit' do |app|
|
|
18
|
+
require_relative 'middleware/request_size_limit'
|
|
19
|
+
app.middleware.insert_before Rack::Runtime, Spree::Api::Middleware::RequestSizeLimit
|
|
20
|
+
end
|
|
21
|
+
|
|
17
22
|
# Add API event subscribers
|
|
18
23
|
config.after_initialize do
|
|
19
24
|
Spree.subscribers << Spree::WebhookEventSubscriber
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module Middleware
|
|
4
|
+
class RequestSizeLimit
|
|
5
|
+
def initialize(app, limit: nil)
|
|
6
|
+
@app = app
|
|
7
|
+
@limit = limit
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(env)
|
|
11
|
+
if api_request?(env) && content_length_exceeded?(env)
|
|
12
|
+
body = { error: { code: 'request_too_large', message: 'Request body too large' } }
|
|
13
|
+
[413, { 'Content-Type' => 'application/json' }, [body.to_json]]
|
|
14
|
+
else
|
|
15
|
+
@app.call(env)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def api_request?(env)
|
|
22
|
+
env['PATH_INFO']&.start_with?('/api/v3/')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def content_length_exceeded?(env)
|
|
26
|
+
content_length = env['CONTENT_LENGTH'].to_i
|
|
27
|
+
content_length > max_body_size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def max_body_size
|
|
31
|
+
@limit || Spree::Api::Config[:max_request_body_size]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -102,66 +102,10 @@ module Spree
|
|
|
102
102
|
with_typelizer_enabled do
|
|
103
103
|
schemas = Typelizer.openapi_schemas
|
|
104
104
|
schemas.each_value { |s| s[:'x-typelizer'] = true }
|
|
105
|
-
fix_refs(schemas)
|
|
106
105
|
schemas
|
|
107
106
|
end
|
|
108
107
|
end
|
|
109
108
|
|
|
110
|
-
# Typelizer's native OpenAPI generator treats unrecognized type strings
|
|
111
|
-
# as $ref to other schemas. This fixes three cases:
|
|
112
|
-
# 1. any → {type: :object}
|
|
113
|
-
# 2. Record<string, unknown> → {type: :object}
|
|
114
|
-
# 3. Union types (e.g., 'TypeA | TypeB | null') → proper anyOf schemas
|
|
115
|
-
def fix_refs(schemas)
|
|
116
|
-
schemas.each_value do |schema|
|
|
117
|
-
next unless schema[:properties]
|
|
118
|
-
|
|
119
|
-
schema[:properties].each do |key, prop|
|
|
120
|
-
schema[:properties][key] = fix_ref(prop)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def fix_ref(prop)
|
|
126
|
-
# Direct $ref
|
|
127
|
-
if (ref_value = prop['$ref'])
|
|
128
|
-
return resolve_ref(ref_value, prop)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# allOf wrapper (used for nullable refs)
|
|
132
|
-
if prop[:allOf]&.length == 1 && (ref_value = prop[:allOf][0]['$ref'])
|
|
133
|
-
resolved = resolve_ref(ref_value, {})
|
|
134
|
-
resolved[:nullable] = true if prop[:nullable]
|
|
135
|
-
return resolved
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
prop
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def resolve_ref(ref_value, base)
|
|
142
|
-
ref_name = ref_value.sub('#/components/schemas/', '')
|
|
143
|
-
|
|
144
|
-
# any → {type: :object} (closest OpenAPI equivalent)
|
|
145
|
-
return base.except('$ref').merge(type: :object) if ref_name == 'any'
|
|
146
|
-
|
|
147
|
-
# Record<string, unknown> → {type: :object}
|
|
148
|
-
if ref_name.start_with?('Record<')
|
|
149
|
-
return base.except('$ref').merge(type: :object)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Union types (e.g., 'TypeA | TypeB | null') → anyOf
|
|
153
|
-
if ref_name.include?(' | ')
|
|
154
|
-
types = ref_name.split(' | ').map(&:strip)
|
|
155
|
-
nullable = types.delete('null')
|
|
156
|
-
refs = types.map { |t| { '$ref' => "#/components/schemas/#{t}" } }
|
|
157
|
-
result = { anyOf: refs }
|
|
158
|
-
result[:nullable] = true if nullable
|
|
159
|
-
return result
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
base
|
|
163
|
-
end
|
|
164
|
-
|
|
165
109
|
# Typelizer is normally disabled in test/production, but we need it
|
|
166
110
|
# enabled to generate OpenAPI schemas from serializer type hints
|
|
167
111
|
def with_typelizer_enabled
|
|
@@ -3,11 +3,14 @@ module Spree
|
|
|
3
3
|
module Api
|
|
4
4
|
module V3
|
|
5
5
|
module TestingSupport
|
|
6
|
-
def self.generate_jwt(user, expiration:
|
|
6
|
+
def self.generate_jwt(user, expiration: 1.hour.to_i, audience: Spree::Api::V3::JwtAuthentication::JWT_AUDIENCE_STORE)
|
|
7
7
|
user_type = user.is_a?(Spree.admin_user_class) ? 'admin' : 'customer'
|
|
8
8
|
payload = {
|
|
9
9
|
user_id: user.id,
|
|
10
10
|
user_type: user_type,
|
|
11
|
+
jti: SecureRandom.uuid,
|
|
12
|
+
iss: Spree::Api::V3::JwtAuthentication::JWT_ISSUER,
|
|
13
|
+
aud: audience,
|
|
11
14
|
exp: Time.current.to_i + expiration
|
|
12
15
|
}
|
|
13
16
|
secret = Rails.application.credentials.jwt_secret_key || ENV['JWT_SECRET_KEY'] || Rails.application.secret_key_base
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.4.0.
|
|
4
|
+
version: 5.4.0.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rswag-specs
|
|
@@ -72,28 +72,28 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0.
|
|
75
|
+
version: '0.9'
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0.
|
|
82
|
+
version: '0.9'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: spree_core
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - '='
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 5.4.0.
|
|
89
|
+
version: 5.4.0.beta2
|
|
90
90
|
type: :runtime
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - '='
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 5.4.0.
|
|
96
|
+
version: 5.4.0.beta2
|
|
97
97
|
description: Spree's API
|
|
98
98
|
email:
|
|
99
99
|
- hello@spreecommerce.org
|
|
@@ -110,6 +110,7 @@ files:
|
|
|
110
110
|
- app/controllers/concerns/spree/api/v3/locale_and_currency.rb
|
|
111
111
|
- app/controllers/concerns/spree/api/v3/order_concern.rb
|
|
112
112
|
- app/controllers/concerns/spree/api/v3/resource_serializer.rb
|
|
113
|
+
- app/controllers/concerns/spree/api/v3/security_headers.rb
|
|
113
114
|
- app/controllers/spree/api/v3/base_controller.rb
|
|
114
115
|
- app/controllers/spree/api/v3/resource_controller.rb
|
|
115
116
|
- app/controllers/spree/api/v3/store/auth_controller.rb
|
|
@@ -142,9 +143,11 @@ files:
|
|
|
142
143
|
- app/controllers/spree/api/v3/store/taxons_controller.rb
|
|
143
144
|
- app/controllers/spree/api/v3/store/wishlist_items_controller.rb
|
|
144
145
|
- app/controllers/spree/api/v3/store/wishlists_controller.rb
|
|
146
|
+
- app/jobs/spree/api_keys/mark_as_used.rb
|
|
145
147
|
- app/jobs/spree/webhook_delivery_job.rb
|
|
146
148
|
- app/serializers/spree/api/v3/address_serializer.rb
|
|
147
149
|
- app/serializers/spree/api/v3/admin/customer_serializer.rb
|
|
150
|
+
- app/serializers/spree/api/v3/admin/line_item_serializer.rb
|
|
148
151
|
- app/serializers/spree/api/v3/admin/metafield_serializer.rb
|
|
149
152
|
- app/serializers/spree/api/v3/admin/order_serializer.rb
|
|
150
153
|
- app/serializers/spree/api/v3/admin/price_serializer.rb
|
|
@@ -181,8 +184,6 @@ files:
|
|
|
181
184
|
- app/serializers/spree/api/v3/payment_session_serializer.rb
|
|
182
185
|
- app/serializers/spree/api/v3/payment_setup_session_serializer.rb
|
|
183
186
|
- app/serializers/spree/api/v3/payment_source_serializer.rb
|
|
184
|
-
- app/serializers/spree/api/v3/post_category_serializer.rb
|
|
185
|
-
- app/serializers/spree/api/v3/post_serializer.rb
|
|
186
187
|
- app/serializers/spree/api/v3/price_serializer.rb
|
|
187
188
|
- app/serializers/spree/api/v3/product_serializer.rb
|
|
188
189
|
- app/serializers/spree/api/v3/promotion_serializer.rb
|
|
@@ -222,6 +223,7 @@ files:
|
|
|
222
223
|
- lib/spree/api/configuration.rb
|
|
223
224
|
- lib/spree/api/dependencies.rb
|
|
224
225
|
- lib/spree/api/engine.rb
|
|
226
|
+
- lib/spree/api/middleware/request_size_limit.rb
|
|
225
227
|
- lib/spree/api/openapi/schema_helper.rb
|
|
226
228
|
- lib/spree/api/testing_support/factories.rb
|
|
227
229
|
- lib/spree/api/testing_support/matchers/webhooks.rb
|
|
@@ -233,9 +235,9 @@ licenses:
|
|
|
233
235
|
- BSD-3-Clause
|
|
234
236
|
metadata:
|
|
235
237
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
236
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.
|
|
238
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.beta2
|
|
237
239
|
documentation_uri: https://docs.spreecommerce.org/
|
|
238
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.4.0.
|
|
240
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.4.0.beta2
|
|
239
241
|
post_install_message:
|
|
240
242
|
rdoc_options: []
|
|
241
243
|
require_paths:
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spree
|
|
4
|
-
module Api
|
|
5
|
-
module V3
|
|
6
|
-
class PostCategorySerializer < BaseSerializer
|
|
7
|
-
typelize title: :string, slug: :string
|
|
8
|
-
|
|
9
|
-
attributes :title, :slug, created_at: :iso8601, updated_at: :iso8601
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spree
|
|
4
|
-
module Api
|
|
5
|
-
module V3
|
|
6
|
-
class PostSerializer < BaseSerializer
|
|
7
|
-
typelize title: :string, slug: :string,
|
|
8
|
-
meta_title: [:string, nullable: true], meta_description: [:string, nullable: true],
|
|
9
|
-
published_at: [:string, nullable: true],
|
|
10
|
-
author_id: [:string, nullable: true], post_category_id: [:string, nullable: true]
|
|
11
|
-
|
|
12
|
-
attributes :title, :slug, :meta_title, :meta_description,
|
|
13
|
-
published_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
|
|
14
|
-
|
|
15
|
-
attribute :author_id do |post|
|
|
16
|
-
post.author&.prefixed_id
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
attribute :post_category_id do |post|
|
|
20
|
-
post.post_category&.prefixed_id
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|