From bbca9e830edf49f2ae897686a0962dca84546021 Mon Sep 17 00:00:00 2001 From: "Carlos A. da Silva" Date: Wed, 21 Oct 2009 11:20:10 -0200 Subject: [PATCH] Verify confirmation time frame to let the user sign in or block it if the user is not confirmed. --- app/controllers/sessions_controller.rb | 8 +++++-- config/locales/en.yml | 1 + lib/devise/active_record.rb | 3 +++ lib/devise/controllers/helpers.rb | 10 +++++++-- lib/devise/hooks/confirmable.rb | 20 ++++++++++++++++++ lib/devise/hooks/rememberable.rb | 2 +- lib/devise/models/confirmable.rb | 2 +- test/integration/confirmable_test.rb | 29 ++++++++++++++++++++++++++ 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 lib/devise/hooks/confirmable.rb diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 69ec1d24..6a62f77f 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -5,6 +5,7 @@ class SessionsController < ApplicationController # GET /resource/sign_in def new unauthenticated! if params[:unauthenticated] + unconfirmed! if params[:unconfirmed] end # POST /resource/sign_in @@ -28,8 +29,11 @@ class SessionsController < ApplicationController protected def unauthenticated! - flash.now[:failure] = I18n.t(:"#{resource_name}.unauthenticated", - :scope => [:devise, :sessions], :default => :unauthenticated) + set_now_flash_message :failure, :unauthenticated + end + + def unconfirmed! + set_now_flash_message :failure, :unconfirmed end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 490e2785..982de5a0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,6 +4,7 @@ en: signed_in: 'Signed in successfully.' signed_out: 'Signed out successfully.' unauthenticated: 'Invalid email or password.' + unconfirmed: 'Your account was not confirmed and your confirmation period has expired.' passwords: send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' updated: 'Your password was changed successfully. You are now signed in.' diff --git a/lib/devise/active_record.rb b/lib/devise/active_record.rb index 88c24210..d2d41b20 100644 --- a/lib/devise/active_record.rb +++ b/lib/devise/active_record.rb @@ -58,6 +58,9 @@ module Devise include Devise::Models.const_get(m.to_s.classify) end + # Assert valid keys after including modules to ensure MODEL_CONFIG is fully loaded + options.assert_valid_keys(:except, *Devise::MODEL_CONFIG) + # Convert new keys to methods which overwrites Devise defaults options.each { |key, value| send(:"#{key}=", value) } end diff --git a/lib/devise/controllers/helpers.rb b/lib/devise/controllers/helpers.rb index 65dc1e3d..b718a16a 100644 --- a/lib/devise/controllers/helpers.rb +++ b/lib/devise/controllers/helpers.rb @@ -81,11 +81,17 @@ module Devise # # Please refer to README or en.yml locale file to check what messages are # available. - def set_flash_message(key, kind) - flash[key] = I18n.t(:"#{resource_name}.#{kind}", + def set_flash_message(key, kind, now=false) + flash_hash = now ? flash.now : flash + flash_hash[key] = I18n.t(:"#{resource_name}.#{kind}", :scope => [:devise, controller_name.to_sym], :default => kind) end + # Shortcut to set flash.now message. Same rules applied from set_flash_message + def set_now_flash_message(key, kind) + set_flash_message(key, kind, true) + end + end end end diff --git a/lib/devise/hooks/confirmable.rb b/lib/devise/hooks/confirmable.rb new file mode 100644 index 00000000..a1bacca2 --- /dev/null +++ b/lib/devise/hooks/confirmable.rb @@ -0,0 +1,20 @@ +# Each time the user is set we verify if it is still able to really sign in. +# This is done by checking the time frame the user is able to sign in without +# confirming it's account. If the user has not confirmed it's account during +# this time frame, he/she will not able to sign in anymore. +Warden::Manager.after_set_user do |record, auth, options| + if record.present? && record.respond_to?(:active?) && !record.active? + scope = options[:scope] + mapping = Devise.mappings[scope] + auth.logout(scope) + # TODO: Change the way we handle redirect here after updating warden + throw :warden, [ + 302, + { + 'Location' => "/#{mapping.as}/#{mapping.path_names[:sign_in]}?unconfirmed=true", + 'Content-Type' => 'text/plain' + }, + ['You are being redirected'] + ] + end +end diff --git a/lib/devise/hooks/rememberable.rb b/lib/devise/hooks/rememberable.rb index 7335344f..a9101e3e 100644 --- a/lib/devise/hooks/rememberable.rb +++ b/lib/devise/hooks/rememberable.rb @@ -19,6 +19,6 @@ end Warden::Manager.before_logout do |record, auth, scope| if record.respond_to?(:forget_me!) record.forget_me! - auth.cookies['remember_token'] = nil + auth.cookies.delete('remember_token') end end diff --git a/lib/devise/models/confirmable.rb b/lib/devise/models/confirmable.rb index ef107725..ed584012 100644 --- a/lib/devise/models/confirmable.rb +++ b/lib/devise/models/confirmable.rb @@ -82,7 +82,7 @@ module Devise # confirmation_period_valid? # will always return false def confirmation_period_valid? confirmation_sent_at? && - (Date.today - confirmation_sent_at.to_date).days.to_i < confirm_in + (Date.today - confirmation_sent_at.to_date).days.to_i < confirm_in.to_i end # Checks whether the record is confirmed or not, yielding to the block diff --git a/test/integration/confirmable_test.rb b/test/integration/confirmable_test.rb index dd9cf242..e4c02196 100644 --- a/test/integration/confirmable_test.rb +++ b/test/integration/confirmable_test.rb @@ -57,4 +57,33 @@ class ConfirmationTest < ActionController::IntegrationTest assert warden.authenticated?(:user) end + + test 'not confirmed user and setup to block without confirmation should not be able to sign in' do + Devise.confirm_in = 0 + user = sign_in_as_user(:confirm => false) + + assert_redirected_to new_user_session_path(:unconfirmed => true) + assert_not warden.authenticated?(:user) + end + + test 'not confirmed user but configured with some days to confirm should be able to sign in' do + Devise.confirm_in = 1 + user = sign_in_as_user(:confirm => false) + + assert_response :success + assert warden.authenticated?(:user) + end + + test 'error message is configurable by resource name' do + begin + I18n.backend.store_translations(:en, :devise => { :sessions => + { :admin => { :unconfirmed => "Not confirmed user" } } }) + + get new_admin_session_path(:unconfirmed => true) + + assert_contain 'Not confirmed user' + ensure + I18n.reload! + end + end end