Compare commits

...

9 Commits

Author SHA1 Message Date
Carlos Antonio da Silva
9932bc5364 Add changelog entry for rack error status 2025-12-31 10:30:44 -03:00
Carlos Antonio da Silva
3d3e75b49d Translate error status to code using Rails if available to avoid warning
Rack has deprecated `:unprocessable_entity` in favor of
`:unprocessable_content`, but the former has been used extensively
for years. Rails is now transparently converting that under the hood to
avoid the warnings, but our failure app wasn't going through the same
handling since it's more low level and responds to Rack directly. This
introduces the Rails translation handling if available on newer
versions, falling back to the Rack conversion which might emit the
warning if using `:unprocessable_entity` on Rack 3.1+

To fully fix it, people can configure their `error_status` to
`:unprocessable_content` on newer versions of Rack. The default for new
Devise apps will also change.

https://github.com/rack/rack/pull/2137
2025-12-31 10:26:02 -03:00
Carlos Antonio da Silva
f8b8092092 Add test for conditional error status 2025-12-31 10:25:57 -03:00
Carlos Antonio da Silva
9fa4d6389b Revert changes to controllers
We'll continue using `:unprocessable_entity` since Rails will
transparently convert this for us for the time being.
2025-12-31 10:05:53 -03:00
Carlos Antonio da Silva
9ac7f5395f Merge branch 'main' into update-rack-unprocessable_content 2025-12-31 09:41:29 -03:00
Carlos Antonio da Silva
d13ef89afb Replace [data-turbo-cache=false] with [data-turbo-temporary]
The default `_error_messages.html.erb` partial uses a deprecated Turbo
attribute `data-turbo-cache=false`, which was deprecated on Feb 15 2023
in [this pull request](https://github.com/hotwired/turbo/pull/871).

Use more up-to-date attribute name called
[data-turbo-temporary](https://github.com/hotwired/turbo/pull/871) to
avoid deprecation issues and reduce developer confusion as
`data-turbo-temporary` is the only attributed mentioned in
[Turbo Drive's documentation](https://turbo.hotwired.dev/handbook/building#preparing-the-page-to-be-cached)

Closes #5664
Closes #5662

Signed-off-by: Carlos Antonio da Silva <carlosantoniodasilva@gmail.com>
2025-12-31 09:26:09 -03:00
Carlos Antonio da Silva
051f94a498 Pass locale with activatable / timeoutable hooks (#5815)
We need to explicitly pass the `locale` around from the options (passed
to `warden.authenticate!` for instance) or the `I18n.locale` when
logging out and redirecting the user via `throw :warden`, otherwise in a
multi-locale app we'd lose the locale previously set / passed around and
fallback to the default for that flash message.

This is a follow-up of the fixes in #5567 where we implemented the
locale passing logic down to the failure app, but it missed these places
where we were using `throw :warden`.

Closes #5812
2025-12-31 09:12:25 -03:00
Taketo Takashima
e7f55961f2 Use :unprocessable_content in generated Devise config for Rack 3.1+
For rack 3.1 and higher, devise config uses `:unprocessable_content` instead of `:unprocessable_entity`.
For rack 3.0 and below, it continues to use `:unprocessable_entity`.
2025-09-12 19:27:55 +09:00
Taketo Takashima
ed625a804f Use Devise.responder.error_status instead of fixed :unprocessable_entity in confirmations and unlocks controllers 2025-09-12 14:54:00 +09:00
16 changed files with 58 additions and 18 deletions

View File

@@ -20,10 +20,16 @@
[#5645](https://github.com/heartcombo/devise/pull/5645)
* Change password instructions button label on devise view from `Send me reset password instructions` to `Send me password reset instructions` [#5515](https://github.com/heartcombo/devise/pull/5515)
* Change `<br>` tags separating form elements to wrapping them in `<p>` tags [#5494](https://github.com/heartcombo/devise/pull/5494)
* Replace `[data-turbo-cache=false]` with `[data-turbo-temporary]` on `devise/shared/error_messages` partial. This has been [deprecated by Turbo since v7.3.0 (released on Mar 1, 2023)](https://github.com/hotwired/turbo/releases/tag/v7.3.0).
If you are using an older version of Turbo and the default devise template, you'll need to copy it over to your app and change that back to `[data-turbo-cache=false]`.
* enhancements
* Add Rails 8 support.
- Routes are lazy-loaded by default in test and development environments now so Devise loads them before `Devise.mappings` call. [#5728](https://github.com/heartcombo/devise/pull/5728)
* New apps using Rack 3.1+ will be generated using `config.responder.error_status = :unprocessable_content`, since [`:unprocessable_entity` has been deprecated by Rack](https://github.com/rack/rack/pull/2137).
Latest versions of [Rails transparently convert `:unprocessable_entity` -> `:unprocessable_content`](https://github.com/rails/rails/pull/53383), and Devise will use that in the failure app to avoid Rack deprecation warnings for apps that are configured with `:unprocessable_entity`. They can also simply change their `error_status` to `:unprocessable_content` in latest Rack versions to avoid the warning.
* Add Ruby 3.4 and 4.0 support.
* Reenable Mongoid test suite across all Rails 7+ versions, to ensure we continue supporting it. Changes to dirty tracking to support Mongoid 8.0+. [#5568](https://github.com/heartcombo/devise/pull/5568)
* Password length validator is changed from

View File

@@ -493,7 +493,8 @@ Devise.setup do |config|
# apps is `200 OK` and `302 Found` respectively, but new apps are generated with
# these new defaults that match Hotwire/Turbo behavior.
# Note: These might become the new default in future versions of Devise.
config.responder.error_status = :unprocessable_entity
config.responder.error_status = :unprocessable_content # for Rack 3.1 or higher
# config.responder.error_status = :unprocessable_entity # for Rack 3.0 or lower
config.responder.redirect_status = :see_other
end
```

View File

@@ -27,7 +27,7 @@ class Devise::ConfirmationsController < DeviseController
set_flash_message!(:notice, :confirmed)
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
else
# TODO: use `error_status` when the default changes to `:unprocessable_entity`.
# TODO: use `error_status` when the default changes to `:unprocessable_entity` / `:unprocessable_content`.
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
end
end

View File

@@ -29,7 +29,7 @@ class Devise::UnlocksController < DeviseController
set_flash_message! :notice, :unlocked
respond_with_navigational(resource){ redirect_to after_unlock_path_for(resource) }
else
# TODO: use `error_status` when the default changes to `:unprocessable_entity`.
# TODO: use `error_status` when the default changes to `:unprocessable_entity` / `:unprocessable_content`.
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
end
end

View File

@@ -1,5 +1,5 @@
<% if resource.errors.any? %>
<div id="error_explanation" data-turbo-cache="false">
<div id="error_explanation" data-turbo-temporary>
<h2>
<%= I18n.t("errors.messages.not_saved",
count: resource.errors.count,

View File

@@ -77,9 +77,9 @@ module Devise
flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
self.response = recall_app(warden_options[:recall]).call(request.env).tap { |response|
response[0] = Rack::Utils.status_code(
response[0].in?(300..399) ? Devise.responder.redirect_status : Devise.responder.error_status
)
status = response[0].in?(300..399) ? Devise.responder.redirect_status : Devise.responder.error_status
# Avoid warnings translating status to code using Rails if available (e.g. `unprocessable_entity` => `unprocessable_content`)
response[0] = ActionDispatch::Response.try(:rack_status_code, status) || Rack::Utils.status_code(status)
}
end

View File

@@ -7,6 +7,6 @@ Warden::Manager.after_set_user do |record, warden, options|
if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication?
scope = options[:scope]
warden.logout(scope)
throw :warden, scope: scope, message: record.inactive_message
throw :warden, scope: scope, message: record.inactive_message, locale: options.fetch(:locale, I18n.locale)
end
end

View File

@@ -25,7 +25,7 @@ Warden::Manager.after_set_user do |record, warden, options|
record.timedout?(last_request_at) &&
!proxy.remember_me_is_active?(record)
Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
throw :warden, scope: scope, message: :timeout
throw :warden, scope: scope, message: :timeout, locale: options.fetch(:locale, I18n.locale)
end
unless env['devise.skip_trackable']

View File

@@ -305,7 +305,7 @@ Devise.setup do |config|
# apps is `200 OK` and `302 Found` respectively, but new apps are generated with
# these new defaults that match Hotwire/Turbo behavior.
# Note: These might become the new default in future versions of Devise.
config.responder.error_status = :unprocessable_entity
config.responder.error_status = <%= Rack::Utils::SYMBOL_TO_STATUS_CODE.key(422).inspect %>
config.responder.redirect_status = :see_other
# ==> Configuration for :registerable

View File

@@ -37,5 +37,4 @@ class DeviseGeneratorTest < Rails::Generators::TestCase
FileUtils.mkdir_p(destination)
FileUtils.cp routes, destination
end
end

View File

@@ -23,4 +23,12 @@ class InstallGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/initializers/devise.rb"
assert_no_file "config/locales/devise.en.yml"
end
test "responder error_status based on rack version" do
run_generator(["--orm=active_record"])
error_status = Rack::VERSION >= "3.1" ? :unprocessable_content : :unprocessable_entity
assert_file "config/initializers/devise.rb", /config\.responder\.error_status = #{error_status.inspect}/
end
end

View File

@@ -136,6 +136,15 @@ class ConfirmationTest < Devise::IntegrationTest
end
end
test 'not confirmed user redirect respects i18n locale set' do
swap Devise, allow_unconfirmed_access_for: 0.days do
sign_in_as_user(confirm: false, visit: new_user_session_path(locale: "pt-BR"))
assert_contain 'Você precisa confirmar seu email para continuar'
assert_not warden.authenticated?(:user)
end
end
test 'not confirmed user should not see confirmation message if invalid credentials are given' do
swap Devise, allow_unconfirmed_access_for: 0.days do
sign_in_as_user(confirm: false) do

View File

@@ -167,6 +167,17 @@ class SessionTimeoutTest < Devise::IntegrationTest
end
end
test 'error message redirect respects i18n locale set' do
user = sign_in_as_user
get expire_user_path(user)
get root_path(locale: "pt-BR")
follow_redirect!
assert_contain 'Sua sessão expirou. Por favor faça o login novamente para continuar.'
assert_not warden.authenticated?(:user)
end
test 'time out not triggered if remembered' do
user = sign_in_as_user remember_me: true
get expire_user_path(user)

View File

@@ -1,15 +1,8 @@
# frozen_string_literal: true
class AdminsController < ApplicationController
around_action :set_locale
before_action :authenticate_admin!
def index
end
private
def set_locale
I18n.with_locale(params[:locale] || I18n.default_locale) { yield }
end
end

View File

@@ -5,9 +5,20 @@
class ApplicationController < ActionController::Base
protect_from_forgery
around_action :set_locale
before_action :current_user, unless: :devise_controller?
before_action :authenticate_user!, if: :devise_controller?
respond_to(*Mime::SET.map(&:to_sym))
devise_group :commenter, contains: [:user, :admin]
private
def set_locale
I18n.with_locale(params[:locale] || I18n.default_locale) { yield }
end
def default_url_options
{locale: params[:locale]}.compact
end
end

View File

@@ -3,3 +3,5 @@ pt-BR:
failure:
invalid: "%{authentication_keys} ou senha inválidos."
unauthenticated: "Para continuar, faça login ou registre-se."
timeout: "Sua sessão expirou. Por favor faça o login novamente para continuar."
unconfirmed: "Você precisa confirmar seu email para continuar."