Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows multiple messages #5764

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions Earthfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
VERSION 0.7

# This allows one to change the running Ruby version with:
#
# `earthly --allow-privileged +test --EARTHLY_RUBY_VERSION=2.7`
ARG --global EARTHLY_RUBY_VERSION=3.3
ARG --global BUNDLER_VERSION=2.4.5

FROM ruby:$EARTHLY_RUBY_VERSION
WORKDIR /gem

deps:
# No need to keep a single `RUN` here since this target uses `SAVE ARTIFACT`
# which means there's no Docker image created here.
RUN apt update \
&& apt install --yes \
--no-install-recommends \
build-essential \
git \
&& mkdir /gems \
&& git clone https://github.com/Pharmony/warden.git /gems/warden \
&& cd /gems/warden \
&& git checkout features/support-multiple-messages \
&& gem install bundler:${BUNDLER_VERSION}

COPY Gemfile /gem/Gemfile
COPY Gemfile.lock /gem/Gemfile.lock
COPY *.gemspec /gem
COPY lib/devise/version.rb /gem/lib/devise/version.rb

RUN bundle install --jobs $(nproc)

SAVE ARTIFACT /gems git-gems
SAVE ARTIFACT /usr/local/bundle bundler
SAVE ARTIFACT /gem/Gemfile Gemfile
SAVE ARTIFACT /gem/Gemfile.lock Gemfile.lock

dev:
RUN apt update \
&& apt install --yes \
--no-install-recommends \
git \
&& gem install bundler:${BUNDLER_VERSION}

# Import cached gems
COPY +deps/git-gems /gems
COPY +deps/bundler /usr/local/bundle
COPY +deps/Gemfile /gem/Gemfile
COPY +deps/Gemfile.lock /gem/Gemfile.lock

# Import gem files
FOR gem_folder IN app config lib test *.gemspec Rakefile
COPY $gem_folder /gem/$gem_folder
END

ENTRYPOINT ["bundle", "exec"]
CMD ["rake"]

# Run `earthly +dev` in order to get the Docker image exported to your
# Docker images.
SAVE IMAGE heartcombo/devise:latest

#
# This target runs the test suite.
#
# On you local machine you would likely use `docker compose run --rm gem`
# instead, avoiding to refresh the Docker image which takes some seconds.
#
# Use the following command in order to run the tests suite:
# earthly --allow-privileged +test [--TEST_COMMAND="rake test TEST=test/test_foobar.rb"]
#
# See the above `EARTHLY_RUBY_VERSION` variable.
test:
FROM earthly/dind:alpine

COPY docker-compose-earthly.yml ./docker-compose.yml

# Optionnal argument in the case you'd like to run something else than
# `rake test`
ARG TEST_COMMAND

# Creates a temporary Docker image using the output from the +dev target,
# that will be used within the `WITH DOCKER ... END` block only.
WITH DOCKER --load heartcombo/devise:latest=+dev
RUN docker-compose run --rm gem $TEST_COMMAND
END
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ end
# group :mongoid do
# gem "mongoid", "~> 4.0.0"
# end

gem "warden", "~> 1.2.9", github: 'Pharmony/warden',
branch: 'features/support-multiple-messages'
11 changes: 9 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
GIT
remote: https://github.com/Pharmony/warden.git
revision: baf7b149b5322ac2b2f81cdc94d4e5331ff7ef1c
branch: features/support-multiple-messages
specs:
warden (1.2.9)
rack (>= 2.2.3)

GIT
remote: https://github.com/rails/rails-controller-testing.git
revision: c203673f8011a7cdc2a8edf995ae6b3eec3417ca
Expand Down Expand Up @@ -236,8 +244,6 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
version_gem (1.1.3)
warden (1.2.9)
rack (>= 2.0.9)
webrat (0.7.3)
nokogiri (>= 1.2.0)
rack (>= 1.0)
Expand Down Expand Up @@ -265,6 +271,7 @@ DEPENDENCIES
rexml
sqlite3 (~> 1.4)
timecop
warden (~> 1.2.9)!
webrat (= 0.7.3)

BUNDLED WITH
Expand Down
7 changes: 7 additions & 0 deletions docker-compose-earthly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file is only used by Earthly

name: devise

services:
gem:
image: heartcombo/devise:latest
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file allows you to use the `docker compose` command in order to run Ruby
# commands or tests.

name: devise

services:
# docker compose run --rm gem [rspec [path to spec file]]
gem:
image: heartcombo/devise:latest
volumes:
- $PWD:/gem/
26 changes: 15 additions & 11 deletions lib/devise/failure_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def recall
end
end

flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
flash.now[:alert] = i18n_messages(: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
Expand All @@ -90,7 +90,7 @@ def redirect
flash.keep(:timedout)
flash.keep(:alert)
else
flash[:alert] = i18n_message
flash[:alert] = i18n_messages
end
end
redirect_to redirect_url
Expand All @@ -102,9 +102,13 @@ def i18n_options(options)
options
end

def i18n_message(default = nil)
message = warden_message || default || :unauthenticated
def i18n_messages(default = nil)
Array(warden_messages || default || :unauthenticated).map do |message|
i18n_message(message)
end
end

def i18n_message(message)
if message.is_a?(Symbol)
options = {}
options[:resource_name] = scope
Expand All @@ -126,7 +130,7 @@ def i18n_locale
end

def redirect_url
if warden_message == :timeout
if Array(warden_messages).include?(:timeout)
flash[:timedout] = true if is_flashing_format?

path = if request.get?
Expand Down Expand Up @@ -199,14 +203,14 @@ def http_auth_header?
end

def http_auth_body
return i18n_message unless request_format
return i18n_messages unless request_format
method = "to_#{request_format}"
if method == "to_xml"
{ error: i18n_message }.to_xml(root: "errors")
i18n_messages.to_xml(root: 'errors', skip_types: true)
elsif {}.respond_to?(method)
{ error: i18n_message }.send(method)
{ errors: i18n_messages }.send(method)
else
i18n_message
i18n_messages
end
end

Expand All @@ -225,8 +229,8 @@ def warden_options
request.respond_to?(:get_header) ? request.get_header("warden.options") : request.env["warden.options"]
end

def warden_message
@message ||= warden.message || warden_options[:message]
def warden_messages
@messages ||= warden.messages.presence || warden_options[:messages].presence
end

def scope
Expand Down
2 changes: 1 addition & 1 deletion lib/devise/hooks/activatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
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, messages: record.inactive_message
end
end
2 changes: 1 addition & 1 deletion lib/devise/hooks/timeoutable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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, messages: [:timeout]
end

unless env['devise.skip_trackable']
Expand Down
11 changes: 7 additions & 4 deletions lib/devise/models/authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ module Authenticatable
class_attribute :devise_modules, instance_writer: false
self.devise_modules ||= []

class_attribute :devise_messages
self.devise_messages = []

before_validation :downcase_keys
before_validation :strip_whitespace
end
Expand All @@ -82,10 +85,6 @@ def valid_for_authentication?
block_given? ? yield : true
end

def unauthenticated_message
:invalid
end

def active_for_authentication?
true
end
Expand Down Expand Up @@ -124,6 +123,10 @@ def inspect
"#<#{self.class} #{inspection.join(", ")}>"
end

def reset_devise_messages!
self.devise_messages = []
end

protected

def devise_mailer
Expand Down
9 changes: 7 additions & 2 deletions lib/devise/models/confirmable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,15 @@ def resend_confirmation_instructions
# is already confirmed, it should never be blocked. Otherwise we need to
# calculate if the confirm time has not expired for this user.
def active_for_authentication?
super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
valid = super && (!confirmation_required? || confirmed? || confirmation_period_valid?)

devise_messages << :unconfirmed unless valid

valid
end

# The message to be shown if the account is inactive.
# Devise::RegistrationsController uses this method to determine the flash
# message to be shown to the user with `signed_up_but_#{inactive_message}`
def inactive_message
!confirmed? ? :unconfirmed : super
end
Expand Down
21 changes: 13 additions & 8 deletions lib/devise/models/lockable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def self.required_fields(klass)
# `{ send_instructions: false } as option`.
def lock_access!(opts = { })
self.locked_at = Time.now.utc
devise_messages << :locked

if unlock_strategy_enabled?(:email) && opts.fetch(:send_instructions, true)
send_unlock_instructions
Expand Down Expand Up @@ -87,13 +88,11 @@ def resend_unlock_instructions
# Overwrites active_for_authentication? from Devise::Models::Activatable for locking purposes
# by verifying whether a user is active to sign in or not based on locked?
def active_for_authentication?
super && !access_locked?
end
valid = super && !access_locked?

devise_messages << :locked unless valid

# Overwrites invalid_message from Devise::Models::Authenticatable to define
# the correct reason for blocking the sign in.
def inactive_message
access_locked? ? :locked : super
valid
end

# Overwrites valid_for_authentication? from Devise::Models::Authenticatable
Expand All @@ -115,6 +114,8 @@ def valid_for_authentication?
else
save(validate: false)
end

add_unauthenticated_message
false
end
end
Expand All @@ -124,17 +125,21 @@ def increment_failed_attempts
reload
end

def add_unauthenticated_message
devise_messages << unauthenticated_message
end

def unauthenticated_message
# If set to paranoid mode, do not show the locked message because it
# leaks the existence of an account.
if Devise.paranoid
super
:invalid
elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
:locked
elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
:last_attempt
else
super
:invalid
end
end

Expand Down
19 changes: 9 additions & 10 deletions lib/devise/strategies/authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ def clean_up_csrf?
# given as parameter. Check Devise::Models::Authenticatable.valid_for_authentication?
# for more information.
#
# In case the resource can't be validated, it will fail with the given
# unauthenticated_message.
# In case the resource can't be validated, it will fail with collected
# devise_messages.
def validate(resource, &block)
result = resource && resource.valid_for_authentication?(&block)
result = resource && resource.reset_devise_messages! && resource.valid_for_authentication?(&block)

if result
true
else
if resource
fail!(resource.unauthenticated_message)
end
false
return true if result

if resource
fail!(resource.devise_messages)
end

false
end

# Get values from params and set in the resource.
Expand Down
2 changes: 1 addition & 1 deletion lib/devise/strategies/database_authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class DatabaseAuthenticatable < Authenticatable
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
hashed = false

if validate(resource){ hashed = true; resource.valid_password?(password) }
Expand Down
Loading
Loading