spree_admin 5.5.0.rc1 → 5.5.0.rc3

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: 2f4bbbb1dd33ca70b641c0c4c687f4a8a36731ec816c3324930b44139d911bd6
4
- data.tar.gz: d402abe10df4c9fe520db4f9b97fb17e33172238ff7e58b14f71b2754a9c09a5
3
+ metadata.gz: aeccd346505aa57fc41defc8eeaf544748167ea25808680cf7b4071ca5404bf5
4
+ data.tar.gz: 74f389f5a27205ba33b7a3b796463a0649f1ce5925aac036353e2aabd00d2594
5
5
  SHA512:
6
- metadata.gz: 9931e85d7ade12bc71fd556f9014cb410ad03e205eedc6ba4c1bc03d3e5d38a32ddabd7a5b34114fe0cb1b58059d7b6ce36907c3515a866812609c9911989517
7
- data.tar.gz: 8f9090d2f5d25c761f4f3ce74701fabd179b9947c21ce23ab64362ac67363ce77b63c624136a35bb02b2897d60fe72e716ac4e8539fdc032f5bc7c7cdbcab12c
6
+ metadata.gz: 8eb54e03b82a3cbd05549fbd8cf4d1902610fe1e7179608374ce0544f2c4364f274bd23a70da5e6b78bcec4cb072731523a305cb49166199143fa74585cca98d
7
+ data.tar.gz: 9d60a5a4842dd23af84e986858497f8ab2b93bd1b7f29ac939b40fea952d3fa44e437c3a1ca709024003c72fda4d7e771e9bc316d20618e1fa558001bea730c8
@@ -0,0 +1,58 @@
1
+ module Spree
2
+ module Admin
3
+ module AuthRateLimiting
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ # @param limit_preference [Symbol] e.g. :rate_limit_login / :rate_limit_password_reset
8
+ # @param redirect_to [Proc] evaluated in controller context to the path to bounce
9
+ # back to when rate limited, e.g. `-> { new_session_path(resource_name) }`.
10
+ # @return [void]
11
+ def auth_rate_limit(limit_preference, redirect_to:)
12
+ limit = Spree::Admin::RuntimeConfig[limit_preference] || 5
13
+ window = (Spree::Admin::RuntimeConfig[:rate_limit_window] || 60).seconds
14
+ prefix = limit_preference.to_s # unique namespace per controller/action
15
+
16
+ # By IP — always present; backstops blank-email floods.
17
+ rate_limit(
18
+ to: limit,
19
+ within: window,
20
+ by: -> { "#{prefix}-ip:#{request.remote_ip}" },
21
+ with: -> { admin_auth_rate_limit_response(redirect_to) },
22
+ store: Rails.cache,
23
+ only: :create
24
+ )
25
+
26
+ # By submitted email (case-insensitive). Falls back to per-IP bucketing when
27
+ # the email is blank, so blank submissions don't all share one global bucket.
28
+ rate_limit(
29
+ to: limit,
30
+ within: window,
31
+ by: lambda {
32
+ email = admin_auth_rate_limit_email
33
+ email.present? ? "#{prefix}-email:#{email}" : "#{prefix}-email-ip:#{request.remote_ip}"
34
+ },
35
+ with: -> { admin_auth_rate_limit_response(redirect_to) },
36
+ store: Rails.cache,
37
+ only: :create
38
+ )
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Email is read via Devise's `resource_params` so it works regardless of the
45
+ # configured admin user class / resource param key.
46
+ def admin_auth_rate_limit_email
47
+ resource_params[:email].to_s.strip.downcase.presence
48
+ rescue StandardError
49
+ nil
50
+ end
51
+
52
+ def admin_auth_rate_limit_response(redirect_path)
53
+ flash[:alert] = I18n.t('devise.failure.too_many_attempts')
54
+ redirect_to instance_exec(&redirect_path)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -51,10 +51,11 @@ module Spree
51
51
  end
52
52
 
53
53
  def permitted_resource_params
54
- permitted = params.require(:api_key).permit(permitted_api_key_attributes)
55
- # key_type can only be set on create, not update
56
- permitted.delete(:key_type) unless action_name == 'create'
57
- permitted
54
+ # `key_type` and `scopes` are create-only (scope immutability lives on
55
+ # Spree::ApiKey); only the human-facing `name` is editable afterward.
56
+ return params.require(:api_key).permit(:name) unless action_name == 'create'
57
+
58
+ params.require(:api_key).permit(permitted_api_key_attributes)
58
59
  end
59
60
 
60
61
  def location_after_save
@@ -1,8 +1,12 @@
1
1
  module Spree
2
2
  module Admin
3
3
  class UserPasswordsController < defined?(Devise::PasswordsController) ? Devise::PasswordsController : Spree::Admin::BaseController
4
+ include Spree::Admin::AuthRateLimiting
5
+
4
6
  layout 'spree/minimal'
5
7
 
8
+ auth_rate_limit :rate_limit_password_reset, redirect_to: -> { new_password_path(resource_name) }
9
+
6
10
  def create
7
11
  self.resource = resource_class.send_reset_password_instructions(resource_params)
8
12
  yield resource if block_given?
@@ -1,8 +1,12 @@
1
1
  module Spree
2
2
  module Admin
3
3
  class UserSessionsController < defined?(Devise::SessionsController) ? Devise::SessionsController : Spree::Admin::BaseController
4
+ include Spree::Admin::AuthRateLimiting
5
+
4
6
  layout 'spree/minimal'
5
7
 
8
+ auth_rate_limit :rate_limit_login, redirect_to: -> { new_session_path(resource_name) }
9
+
6
10
  # We need to overwrite this action because `return_to` url may be in a different domain
7
11
  # So we need to pass `allow_other_host` option to `redirect_to` method
8
12
  def create
@@ -25,20 +25,36 @@
25
25
  </div>
26
26
  </div>
27
27
 
28
- <div class="card mb-6" id="api-key-scopes" data-secret-only data-show-when-key-type="secret">
29
- <div class="card-header">
30
- <h5 class="card-title"><%= Spree.t('admin.api_keys.scopes', default: 'Scopes') %></h5>
31
- <p class="text-sm text-gray-500 mt-1">
32
- <%= Spree.t('admin.api_keys.scopes_description', default: 'Required for secret keys. Pick the narrowest set of scopes your integration needs.') %>
33
- </p>
28
+ <% if f.object.new_record? %>
29
+ <div class="card mb-6" id="api-key-scopes" data-secret-only data-show-when-key-type="secret">
30
+ <div class="card-header">
31
+ <h5 class="card-title"><%= Spree.t('admin.api_keys.scopes', default: 'Scopes') %></h5>
32
+ <p class="text-sm text-gray-500 mt-1">
33
+ <%= Spree.t('admin.api_keys.scopes_description', default: 'Required for secret keys. Pick the narrowest set of scopes your integration needs.') %>
34
+ </p>
35
+ </div>
36
+ <div class="card-body">
37
+ <% Spree::ApiKey::SCOPES.each do |scope| %>
38
+ <div class="custom-control form-checkbox mb-1">
39
+ <%= f.check_box :scopes, { multiple: true, class: 'custom-control-input', id: "api_key_scopes_#{scope}" }, scope, nil %>
40
+ <%= f.label "scopes_#{scope}", scope, class: 'custom-control-label font-mono text-sm' %>
41
+ </div>
42
+ <% end %>
43
+ <%= f.hidden_field :scopes, value: '', multiple: true %>
44
+ </div>
34
45
  </div>
35
- <div class="card-body">
36
- <% Spree::ApiKey::SCOPES.each do |scope| %>
37
- <div class="custom-control form-checkbox mb-1">
38
- <%= f.check_box :scopes, { multiple: true, class: 'custom-control-input', id: "api_key_scopes_#{scope}" }, scope, nil %>
39
- <%= f.label "scopes_#{scope}", scope, class: 'custom-control-label font-mono text-sm' %>
40
- </div>
41
- <% end %>
42
- <%= f.hidden_field :scopes, value: '', multiple: true %>
46
+ <% elsif f.object.secret? %>
47
+ <div class="card mb-6">
48
+ <div class="card-header">
49
+ <h5 class="card-title"><%= Spree.t('admin.api_keys.scopes', default: 'Scopes') %></h5>
50
+ <p class="text-sm text-gray-500 mt-1">
51
+ <%= Spree.t('admin.api_keys.scopes_immutable', default: 'Scopes are fixed when the key is created. To change them, create a new key with the scopes you need and revoke this one.') %>
52
+ </p>
53
+ </div>
54
+ <div class="card-body">
55
+ <% f.object.scopes.each do |scope| %>
56
+ <span class="badge badge-light font-mono text-sm me-1 mb-1"><%= scope %></span>
57
+ <% end %>
58
+ </div>
43
59
  </div>
44
- </div>
60
+ <% end %>
@@ -101,7 +101,7 @@
101
101
  <%= render 'spree/admin/shared/index_table_options', collection: @products %>
102
102
  </div>
103
103
  <% else %>
104
- <%= render 'spree/admin/shared/no_resource_found', new_object_url: nil %>
104
+ <%= render 'spree/admin/shared/no_resource_found', new_object_url: nil, model_class: Spree::Product %>
105
105
  <% end %>
106
106
  </div>
107
107
  </div>
@@ -28,6 +28,7 @@ en:
28
28
  revoked_by: Revoked by
29
29
  scopes: Scopes
30
30
  scopes_description: Required for secret keys. Pick the narrowest set of scopes your integration needs.
31
+ scopes_immutable: Scopes are fixed when the key is created. To change them, create a new key with the scopes you need and revoke this one.
31
32
  secret_key_hidden: This secret key cannot be revealed. If you've lost it, revoke this key and create a new one.
32
33
  statuses:
33
34
  active: Active
@@ -17,6 +17,11 @@ module Spree
17
17
  preference :legacy_sidebar_navigation, :boolean, default: false
18
18
 
19
19
  preference :reports_line_items_limit, :integer, default: 1000
20
+
21
+ # Brute-force rate limiting for the admin dashboard auth endpoints (login / password reset).
22
+ preference :rate_limit_window, :integer, default: 60 # window in seconds
23
+ preference :rate_limit_login, :integer, default: 5 # per IP and per email
24
+ preference :rate_limit_password_reset, :integer, default: 3 # per IP and per email
20
25
  end
21
26
  end
22
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0.rc1
4
+ version: 5.5.0.rc3
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-06-10 00:00:00.000000000 Z
11
+ date: 2026-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.5.0.rc1
19
+ version: 5.5.0.rc3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.5.0.rc1
26
+ version: 5.5.0.rc3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active_link_to
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -284,6 +284,7 @@ files:
284
284
  - app/assets/tailwind/spree/admin/views/_dashboard.css
285
285
  - app/assets/tailwind/spree/admin/views/_page-builder.css
286
286
  - app/controllers/concerns/spree/admin/analytics_concern.rb
287
+ - app/controllers/concerns/spree/admin/auth_rate_limiting.rb
287
288
  - app/controllers/concerns/spree/admin/breadcrumb_concern.rb
288
289
  - app/controllers/concerns/spree/admin/bulk_operations_concern.rb
289
290
  - app/controllers/concerns/spree/admin/order_breadcrumb_concern.rb
@@ -1171,9 +1172,9 @@ licenses:
1171
1172
  - BSD-3-Clause
1172
1173
  metadata:
1173
1174
  bug_tracker_uri: https://github.com/spree/spree/issues
1174
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0.rc1
1175
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0.rc3
1175
1176
  documentation_uri: https://docs.spreecommerce.org/
1176
- source_code_uri: https://github.com/spree/spree/tree/v5.5.0.rc1
1177
+ source_code_uri: https://github.com/spree/spree/tree/v5.5.0.rc3
1177
1178
  post_install_message:
1178
1179
  rdoc_options: []
1179
1180
  require_paths: