How to add extra checks during login (Rails + Devise) - ruby-on-rails

I have a Ruby On Rails 3.x application using device.
My goal is to add a Yubikey input field to the login screen.
I have generated the views, adjusted the screen (i.e. the extra field shows up) and updated the routes as follows:
devise_for :users, :controllers => { :sessions=>"sessions", :registrations => "registrations" }, :path => "users", :path_names => { :sign_in => "login", :sign_out => "logout", :sign_up => "register" }
Not to forget, I created a session controller:
class SessionsController < Devise::SessionsController
def create
begin
if do_some_other_checks
super
else
build_resource
clean_up_passwords(resource)
flash[:alert] = "Login error"
render :new
end
rescue => e
build_resource
clean_up_passwords(resource)
flash[:alert] = "Login error"
render :new
end
end
end
Unfortunately the code doesn't quite work, it gets called after Devise has the user logged on, i.e. even if the additional check fails, the user still gets logged in.

There is a simple way to do this in your user model by adding in the following, no need to create any devise controllers or change default routes ...
class User < ActiveRecord::Base
# check to see if a user is active or not and deny login if not
def active_for_authentication?
super && do_some_other_checks
end
end

Related

How to create new edit forms in Devise?

Right now Devise generated edit.html.erb, which is great for editing essential info. However, I want to generate a new edit page, similar to edit.html.erb, but there, users will be able to edit other information about themselves.. I don't want to put everything on edit.html.erb because of UI.
So far I only added new columns to users table for a new edit page that I'm going to create.
Right now these are my routes:
devise_for :users, class_name: 'FormUser', :controllers => { omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations' }
root 'setup#index'
get '/setup' => 'setup#index'
get '/users/sign_out' => 'setup#index'
And this is my registration_controller.rb:
class RegistrationsController < Devise::RegistrationsController
def update_resource(resource, params)
if resource.encrypted_password.blank? # || params[:password].blank?
resource.email = params[:email] if params[:email]
if !params[:password].blank? && params[:password] == params[:password_confirmation]
logger.info "Updating password"
resource.password = params[:password]
resource.save
end
if resource.valid?
resource.update_without_password(params)
end
else
resource.update_with_password(params)
end
end
end
So I created a new controller with views but I have no idea what to put now in the routes.
Name of the new controller is styles_controller.rb and and views are fits.html.erb..
This script will generate controllers for you to override the behavior of Devise (but I highly recommend you do not do that):
https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers
This shows you how to generate the view templates for you to override the default screens to login/etc.
https://github.com/plataformatec/devise#configuring-views
And this tutorial shows how to allow users to edit their profile without a password:
https://github.com/plataformatec/devise/wiki/How-To%3a-Allow-users-to-edit-their-account-without-providing-a-password

Trying to override controller in Thoughtbot's Clearance gem for Rails

I am using Clearance 1.1.0 gem with Ruby on Rails 4.0.1. I am trying to override the sessions controller to provide my own custom method. I have not been able to successfully get rails to use my controller.
/app/controllers/sessions_controller.rb
class SessionsController < Clearance::SessionsController
private
def flash_failure_after_create
flash.now[:notice] = translate(:bad_email_or_password,
:scope => [:clearance, :controllers, :sessions],
:default => t('flashes.failure_after_create', :new_password_path => new_password_path).html_safe)
end
end
I have tried a few different things inside my routes.rb file, and have been unsuccessful. I want to change the route sign_in.
get '/sign_in' => 'sessions#new', :as => 'sign_in'
Yields the following error.
You may have defined two routes with the same name using the :as
option, or you may be overriding a route already defined by a resource
with the same naming.
Any ideas? Thank you!
Edit: I made a mistake. I actually need sessions#create to use my controller. I'm trying to pass a different variable to the yaml file for the flash when the session login fails.
Edit 2: I the appropriate session#create line to to my routes. In my session controller, I copied and edited for testing the flash_failure_after_create method. It is not being called. So I then copy the create method over. Now, my create method is being called, but not my flash_failure_after_create method. To get it to be called, I had to have the create method copied from gem, and changed status.failure_message to directly call the flash_failure_after_create method. Is this some sort of bug with clearance?
routes.rb
post 'session' => 'sessions#create', :as => nil
sessions_controller.rb
class SessionsController < Clearance::SessionsController
def create
#user = authenticate(params)
sign_in(#user) do |status|
if status.success?
redirect_back_or url_after_create
else
#flash.now.notice = status.failure_message
flash.now.notice = flash_failure_after_create
render :template => 'sessions/new', :status => :unauthorized
end
end
end
private
def flash_failure_after_create
# Changed flash for easy testing
flash.now[:notice] = 'Ballz.'
#flash.now[:notice] = translate(:bad_email_or_password,
# :scope => [:clearance, :controllers, :sessions],
# :default => t('flashes.failure_after_create', :sign_up_path => sign_up_path).html_safe)
end
end
I believe this will work:
get '/sign_in' => 'sessions#new', :as => nil
Rails 4 no longer supports overriding route names, so don't name your override. The mapping is still the same so sign_in_path should still work.

How can I customize Devise's "resend confirmation email"

I have a custom mailer (UserMailer.rb) and a few methods to override the default Devise methods for the welcome email and forgot password emails. The mailer uses a custom template to style the emails--and it works great.
In config/initializers, I have a file with
module Devise::Models::Confirmable
# Override Devise's own method. This one is called only on user creation, not on subsequent address modifications.
def send_on_create_confirmation_instructions
UserMailer.welcome_email(self).deliver
end
...
end
(Again, UserMailer is setup and works great for the welcome email and reset password email.)
But what's not working is the option to "Resend confirmation instructions." It sends with the default Devise styling and I want it to use the styling of my mailer layout. I know I can manually add the layout to the default Devise layout, but I'd like to keep DRY in effect and not have to do that.
I've tried overriding the send_confirmation_instructions method found here, but I'm getting a wrong number of arguments (1 for 0) error in create(gem) devise-2.2.3/app/controllers/devise/confirmations_controller.rb at
7 # POST /resource/confirmation
8 def create
9 self.resource = resource_class.send_confirmation_instructions(resource_params)
In my initializer file, I'm able to get to this error by adding a new override for Devise, but I'm probably not doing this correctly:
module Devise::Models::Confirmable::ClassMethods
def send_confirmation_instructions
UserMailer.send_confirmation_instructions(self).deliver
end
end
Any ideas?
You don't have to go through that initializer to do that. I've done this by overriding the confirmations controller. My routes for devise look like:
devise_for :user, :path => '', :path_names => { :sign_in => 'login', :sign_out => 'logout', :sign_up => 'signup'},
:controllers => {
:sessions => "sessions",
:registrations => "registrations",
:confirmations => "confirmations"
}
Then, create the confirmations_controller and extend the Devise::ConfirmationsController to override:
class ConfirmationsController < Devise::ConfirmationsController
In that controller, I have a create method to override the default:
def create
#user = User.where(:email => params[:user][:email]).first
if #user && #user.confirmed_at.nil?
UserMailer.confirmation_instructions(#user).deliver
flash[:notice] = "Set a notice if you want"
redirect_to root_url
else
# ... error messaging or actions here
end
end
Obviously, in UserMailer you can specify the html/text templates that will be used to display the confirmation message. confirmation_token should be a part of the #user model, you can use that to create the URL with the correct token:
<%= link_to 'Confirm your account', confirmation_url(#user, :confirmation_token => #user.confirmation_token) %>

wrong redirect after signing in

I have this controller.
class SessionsController < Devise::SessionsController
# GET /resource/sign_in
def new
self.resource = build_resource(nil, :unsafe => true)
clean_up_passwords(resource)
respond_with(resource, serialize_options(resource))
end
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in, :username => resource.username) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
and this step-defenitions for cucumber:
Given /^a user "(.*?)" exists$/ do |user_name|
#user = User.create!(:username => user_name , :password => "tamara")
end
When /^he logs in$/ do
visit("/users/sign_in")
fill_in('Username', :with => "roelof")
fill_in('Password', :with => "tamara")
click_button('Sign in')
end
Then /^he should see "(.*?)"$/ do |message|
page.should have_content(message)
end
Everything works fine only after a successfull login I get redirect to the homepage and not to the login succceed page. So I don't see the flash message.
Roelof
Edit 1 : I checked the controller and resource_name and resource seems to have the right values.
the standard DeviseHelper for after_sign_in_path_for is the signed_in_root_path
# The scope root url to be used when he's signed in. By default, it first
# tries to find a resource_root_path, otherwise it uses the root_path.
You can check your routes and scope , and even debug by duplicating the Devise signed_in_root_path into your ApplicationController with a debugger line
By default, Devise's internal after_sign_in_path_for(resource) method first tries to find a valid {resource}_return_to key in the session, then it fallbacks to {resource}_root_path, otherwise it uses your root path route.
For instance, setting user_root_path would set direct path in the users scope after successful sign in (probably what you want):
# config/routes.rb
...
devise_for :users do
get 'users', :to => 'users#show', :as => :user_root
end
Or even:
# config/routes.rb
...
match 'user_root' => 'users#show'
Another way to override the redirect on successful login by overriding after_sign_in_path_for(resource):
# app/controllers/application_controller.rb
...
def after_sign_in_path_for(resource)
# Put some path, like:
current_user_path
end
Links for you to read:
How to: redirect to a specific page on successful sign in out
Method: Devise::Controllers::Helpers#after_sign_in_path_for
How To: Customize the redirect after a user edits their profile
How To: Change the redirect path after destroying a session i.e. signing out

Render same page with routes match

I'm trying to learn RoR by developing my own custom CMS (yeah, yeah...) and I'm wanting to do a simple thing, but am unable to come up with a clean solution.
My routes file has the following match:
match '/login' => "sessions#new"
What it does is, upon hitting "http://localhost:3000/login", is let me login to create a new session. It works perfectly...
However, this is what my Sessions controller looks like:
class SessionsController < ApplicationController
def create
#username = params[:session][:username]
#password = params[:session][:password]
#rUser = User.find_by_username(params[:session][:username])
#if user && user.authenticate(params[:session][:password])
if #rUser && Digest::SHA2.hexdigest(#password) == #rUser.password
# Sign the user in and redirect to the user's show page.
redirect_to users_path, :flash => { :notice => "Logged in as #{#username}." }
else
# Create an error message and re-render the signin form.
redirect_to '/login', :flash => { :error => "Login failed. Either the username or password was incorrect." }
end
end
end
If you take a look at the "else" statement in my if-else conditional, you'll see that I'm using redirect_to '/login' to display the same form on login fail. Is there any better way to create a permanent link to '/login' rather than having to use a string, because if I decide to change the URL in the future, I'd prefer not to have to make the change in multiple places.
Sorry if it's not clear! Feel free to ask for further code (not showing in fear of pathetic-ness of it).
match '/login' => "sessions#new", :as => :login
Then you can use login_path in your controllers.
You can do like this
match '/login' => "sessions#new", :as => :login
#if want to call like localhost:3000/login
match '/sign_in' => "sessions#new", :as => :sign_in
#if want to call like localhost:3000/sign_in

Resources