spree_api 6.0.1 → 6.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88513caab1df4ae8256a450d38b98601d724763697f75cd6bbe92f20986fac7c
4
- data.tar.gz: 87742481e6e84a33e11080d3c830ca960eb692a243fb6d1b6c926a43f7d951e0
3
+ metadata.gz: 3bc1eebac54270cff24aa58f2319414c1d2c3d3e2b62bc73ed1a99b3dcc8cef3
4
+ data.tar.gz: 63c9e47df2e22549f57a66be0af2bf781d35d44cfb42f70a7edacdc932f60dd2
5
5
  SHA512:
6
- metadata.gz: 10e0aa59755c08b5d83f12080be796d2a4a8cd3cbb70e77a47d369853385dc78f3583296534b0482dc76816396a285f6b476c12cc0c1863eac45defedc646f71
7
- data.tar.gz: fd76b27f733d1acf21c0deb06341cf2cde81de862dc1d1eaaefcb91f2ffb216bafd3c2f24d4f7a09f1a6dbe39d31f66b9e37584561c26e64563099c67a3592bb
6
+ metadata.gz: 4dc86cc7163081943b538408538ff9499ca25848f6bd3656933a8b79cc172d5a629e7cea4b0b22f5c55e22ba7a7b2fa342f8c360155b0759b879876f21d67972
7
+ data.tar.gz: ec1f8e51808bf0175f6c1ee135bc440cffcdff1e22eff9e920ac92e5d1610e22d8be00de38080d33acd629968b17c0f326b2c6a1651b3c7ee5643b7c5df52183
data/README.md CHANGED
@@ -133,5 +133,6 @@ bundle exec rspec
133
133
 
134
134
  ## Documentation
135
135
 
136
- - [API Reference](https://docs.spreecommerce.org/api)
137
- - [Authentication Guide](https://docs.spreecommerce.org/developer/api/authentication)
136
+ - [API Reference](https://spreecommerce.org/docs/api-reference)
137
+ - [Store API authentication](https://spreecommerce.org/docs/api-reference/store-api/authentication)
138
+ - [Admin API authentication](https://spreecommerce.org/docs/api-reference/admin-api/authentication)
@@ -29,7 +29,12 @@ module Spree
29
29
  'gift_card' => 'read_gift_cards',
30
30
  'gift_cards' => 'read_gift_cards',
31
31
  'store_credit' => 'read_store_credits',
32
- 'store_credits' => 'read_store_credits'
32
+ 'store_credits' => 'read_store_credits',
33
+ # The admin user serializer is nested in gift cards, task runs and
34
+ # tokens; its sessions carry IP addresses and its inviter is
35
+ # another staff member.
36
+ 'sessions' => 'read_staff',
37
+ 'inviter' => 'read_staff'
33
38
  }.freeze
34
39
 
35
40
  protected
@@ -72,6 +72,16 @@ module Spree
72
72
  [{ spree_roles: :resource, avatar_attachment: :blob }]
73
73
  end
74
74
 
75
+ # Sessions carry IP addresses and the inviter costs a query per row,
76
+ # so both are shown one person at a time.
77
+ ONE_AT_A_TIME_EXPANDS = %w[sessions inviter].freeze
78
+
79
+ def expand_list
80
+ return super unless action_name == 'index'
81
+
82
+ super.reject { |path| ONE_AT_A_TIME_EXPANDS.include?(path.split('.').first) }
83
+ end
84
+
75
85
  # Restrict to users with a role assignment on the current store.
76
86
  # `accessible_by` enforces CanCanCan on top. Deduplication happens via
77
87
  # an ID subquery rather than `SELECT DISTINCT *` — the users table is
@@ -10,11 +10,29 @@ module Spree
10
10
  selected_locale: [:string, nullable: true],
11
11
  avatar_url: [:string, nullable: true],
12
12
  roles: 'Array<{ id: string; name: string }>',
13
- stores: 'Array<{ id: string; name: string; code: string }>'
13
+ stores: 'Array<{ id: string; name: string; code: string }>',
14
+ status: [:string, enum: Spree::AdminUserMethods::STATUSES, enum_type_name: 'AdminUserStatus'],
15
+ last_active_at: [:string, nullable: true],
16
+ inviter: ['Actor', nullable: true]
14
17
 
15
- attributes :email, :first_name, :last_name, :full_name, :selected_locale,
18
+ attributes :email, :first_name, :last_name, :full_name, :selected_locale, :status,
16
19
  created_at: :iso8601, updated_at: :iso8601
17
20
 
21
+ attribute :last_active_at do |user|
22
+ user.try(:last_active_at)&.iso8601
23
+ end
24
+
25
+ # Carries IP addresses, so sent only to the caller who asks for it.
26
+ many :sessions,
27
+ resource: proc { Spree.api.admin_session_serializer },
28
+ source: ->(_params) { active_sessions },
29
+ if: proc { expand?('sessions') }
30
+
31
+ one :inviter,
32
+ resource: proc { Spree.api.admin_actor_serializer },
33
+ source: ->(params) { invited_by(params&.dig(:store)) },
34
+ if: proc { expand?('inviter') }
35
+
18
36
  attribute :avatar_url do |user|
19
37
  image_url_for(user.avatar)
20
38
  end
@@ -16,7 +16,9 @@ module Spree
16
16
  cvv_response_message: [:string, nullable: true],
17
17
  gateway_dashboard_payment_url: [:string, nullable: true],
18
18
  avs_risky: [:boolean, comment: 'Whether the card issuer reported that the billing address did not match.'],
19
- cvv_risky: [:boolean, comment: 'Whether the card issuer reported that the security code did not match.']
19
+ cvv_risky: [:boolean, comment: 'Whether the card issuer reported that the security code did not match.'],
20
+ can_void: [:boolean, comment: 'Whether the payment can be voided now, as decided by its payment method.'],
21
+ can_capture: [:boolean, comment: 'Whether the payment can be captured now, as decided by its payment method.']
20
22
 
21
23
  attributes :metadata, :avs_response, :cvv_response_code, :cvv_response_message,
22
24
  :gateway_dashboard_payment_url,
@@ -24,6 +26,8 @@ module Spree
24
26
 
25
27
  attribute(:avs_risky, &:is_avs_risky?)
26
28
  attribute(:cvv_risky, &:is_cvv_risky?)
29
+ attribute(:can_void, &:can_void?)
30
+ attribute(:can_capture, &:can_capture?)
27
31
 
28
32
  money_attributes :captured_amount
29
33
  typelize captured_amount: [:string, nullable: false]
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # A signed-in session of a staff account ({Spree::RefreshToken}),
6
+ # without its token.
7
+ class SessionSerializer < V3::BaseSerializer
8
+ typelize ip_address: [:string, nullable: true],
9
+ user_agent: [:string, nullable: true],
10
+ last_active_at: :string
11
+
12
+ attributes :ip_address, :user_agent, last_active_at: :iso8601
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -95,8 +95,8 @@ module Spree
95
95
  path.gsub(/:([a-z_]+)/) { "{#{Regexp.last_match(1)}}" }
96
96
  end
97
97
 
98
- # A packaged installation has no docs/ directory, and a body the
99
- # document does not describe is simply passed through unchecked.
98
+ # A body the document does not describe is simply passed through
99
+ # unchecked.
100
100
  #
101
101
  # `AgentWriteSchemas` already holds this file parsed, so it is read
102
102
  # through there rather than a second time: the document is nearly a
@@ -33,12 +33,14 @@ module Spree
33
33
  self.for(key).keys
34
34
  end
35
35
 
36
- # Where the generated document lives, relative to this engine. Resolved
37
- # lazily: the engine class does not exist yet while this file loads.
36
+ # Where the generated document lives: `docs/` in a source checkout,
37
+ # or the copy the release packs into the gem. Resolved lazily: the
38
+ # engine class does not exist yet while this file loads.
38
39
  #
39
40
  # @return [Pathname]
40
41
  def spec_path
41
- Spree::Api::Engine.root.join('..', '..', 'docs', 'api-reference', 'admin.yaml')
42
+ source = Spree::Api::Engine.root.join('..', '..', 'docs', 'api-reference', 'admin.yaml')
43
+ source.exist? ? source : Spree::Api::Engine.root.join('openapi', 'admin.yaml')
42
44
  end
43
45
 
44
46
  # The parsed document, for a reader that needs more of it than the
@@ -63,9 +65,7 @@ module Spree
63
65
  end
64
66
 
65
67
  # A missing or unparseable document is not a boot failure: the generic
66
- # write tools simply offer nothing, which is the safe direction. An
67
- # installation running from the packaged gems has no docs/ directory at
68
- # all.
68
+ # write tools simply offer nothing, which is the safe direction.
69
69
  def load_schemas
70
70
  path = spec_path
71
71
  return {} unless File.exist?(path)
@@ -32,7 +32,7 @@ module Spree
32
32
  exchange exchange_line_item claim claim_line_item package_type policy tax_category tax_rate
33
33
  tax_identifier company company_membership company_invitation catalog catalog_price catalog_price_tier
34
34
  catalog_product price_list_product catalog_assignment catalog_quantity_rule catalog_product_term
35
- catalog_order_minimum tax_exemption_certificate tax_exemption_reason admin_user actor seller_team_member product_submission
35
+ catalog_order_minimum tax_exemption_certificate tax_exemption_reason admin_user actor session seller_team_member product_submission
36
36
  seller seller_requirement seller_requirement_submission seller_requirement_status commission_rate
37
37
  commission_rule commission_line seller_transfer seller_payout seller_balance payment_split payment_capture_event address address_owner
38
38
  channel order_routing_rule product_publication market delivery_method delivery_method_service
@@ -15,23 +15,11 @@ module Spree
15
15
  # which is why this lives in `spree_api` rather than in any one gem that
16
16
  # happens to need it.
17
17
  module Oauth
18
- # Protected resources, each registered by the surface that owns it as a
19
- # path relative to this application's own origin.
20
- #
21
- # Spree::Api::Oauth.register_resource(:admin, '/api/v3/admin')
22
- #
23
- # A path rather than a full URL because the identifier must equal the
24
- # URL the client was actually pointed at: the origin comes from the
25
- # request where there is one, and from the application's configured
26
- # URL options otherwise. A store's own `url` column is deliberately not
27
- # consulted — it describes the storefront, which is frequently neither
28
- # the host nor the scheme the Admin API answers on.
29
- # Whether a redirect URI points at the machine the client runs on.
18
+ # Whether a redirect URI points at the machine the client runs on, by IP
19
+ # literal (`127.0.0.1`, `[::1]`).
30
20
  #
31
21
  # Decided by parsing the host as an address, the same way Doorkeeper
32
- # does when it ignores the port of a loopback redirect — so `localhost`
33
- # is deliberately not loopback here either, and a registration must use
34
- # the IP literal for an ephemeral port to match.
22
+ # does when it ignores the port of a loopback redirect.
35
23
  #
36
24
  # @param uri [URI, String]
37
25
  # @return [Boolean]
@@ -44,6 +32,37 @@ module Spree
44
32
  false
45
33
  end
46
34
 
35
+ # Whether a redirect URI may use plain http: an IP-literal loopback, or
36
+ # `localhost` with an explicit port.
37
+ #
38
+ # Claude Code only ever calls back on `http://localhost:<port>`. Doorkeeper
39
+ # compares a `localhost` callback exactly, so the host must be lowercase
40
+ # and the port explicit: anything else could never match an
41
+ # authorization.
42
+ #
43
+ # @param uri [URI, String]
44
+ # @return [Boolean]
45
+ def self.local_redirect_uri?(uri)
46
+ uri = URI.parse(uri.to_s) unless uri.respond_to?(:host)
47
+ return true if loopback_uri?(uri)
48
+
49
+ uri.host == 'localhost' &&
50
+ (1..65_535).cover?(uri.port) && uri.port != uri.default_port
51
+ rescue StandardError
52
+ false
53
+ end
54
+
55
+ # Protected resources, each registered by the surface that owns it as a
56
+ # path relative to this application's own origin.
57
+ #
58
+ # Spree::Api::Oauth.register_resource(:admin, '/api/v3/admin')
59
+ #
60
+ # A path rather than a full URL because the identifier must equal the
61
+ # URL the client was actually pointed at: the origin comes from the
62
+ # request where there is one, and from the application's configured
63
+ # URL options otherwise. A store's own `url` column is deliberately not
64
+ # consulted — it describes the storefront, which is frequently neither
65
+ # the host nor the scheme the Admin API answers on.
47
66
  mattr_accessor :resources, default: {}
48
67
 
49
68
  class << self
@@ -191,17 +210,18 @@ module Spree
191
210
 
192
211
  # A redirect URI is where authorization codes are delivered, so a
193
212
  # plain-http one hands them to anyone on the path — except a
194
- # loopback one, which never leaves the machine. A terminal client
195
- # listens on an ephemeral local port and has no certificate for
196
- # it, so RFC 8252 §7.3 makes that the recommended shape, and
197
- # refusing it would lock out Claude Code and Codex entirely.
213
+ # loopback or `localhost` one, which never leaves the machine. A
214
+ # terminal client listens on a local port and has no certificate
215
+ # for it. RFC 8252 §7.3 recommends the IP literal, but Claude Code
216
+ # only sends `localhost`, so refusing it would lock Claude Code
217
+ # out entirely.
198
218
  #
199
219
  # Passed as a callable so the exemption is per URI rather than
200
220
  # per environment: everything else still has to be https.
201
221
  force_ssl_in_redirect_uri do |uri|
202
222
  next false if Rails.env.development?
203
223
 
204
- !Spree::Api::Oauth.loopback_uri?(uri)
224
+ !Spree::Api::Oauth.local_redirect_uri?(uri)
205
225
  end
206
226
 
207
227
  # RFC 8707. Registering the validator is what binds an audience
@@ -22,7 +22,7 @@ module Typelizer
22
22
  CLOSED_TYPE_NAMES = %w[CountryCode CurrencyCode LocaleCode WeightUnit DimensionUnit].freeze
23
23
 
24
24
  CLOSED_LIST_NAMES = %w[
25
- ApiKeyType CaptureMethod CatalogAssignableType CatalogPriceSource ClaimResolution
25
+ AdminUserStatus ApiKeyType CaptureMethod CatalogAssignableType CatalogPriceSource ClaimResolution
26
26
  CollectionRuleMatchPolicy CollectionSortOrder CommissionKind CompanyInvitationStatus CompanyKind
27
27
  ConsentSource DataRequestKind DeliveryStatus DeliveryZoneMemberType DiscountKind DocumentNumberFormat
28
28
  EmailFont EmailTemplateKind FileUploadVisibility FulfillmentType MatchPolicy MediaType OptionTypeKind