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
Related
I have a route with my JWT token as a path variable but it doesn't work with the JWT token eyJhbGciOiJub25lIn0.eyJjb25maXJtYXRpb25fc2VudF9hdCI6IjIwMTgtMDEtMjQgMDY6MDQ6MzEgKzEzMDAiLCJleHBpcmVzX2F0IjoiMjAxOC0wMS0yNyAwNjowNDozMSArMTMwMCIsInVzZXJfaWQiOjM5fQ.
When I switch the JWT token to something less complicated e.g. 5 then the route works. I'm guessing that the format of the JWT need some special treatment in the rails router?
get '/verify/:jwt', to: 'users#verify_email'
No route matches [GET] "/verify/eyJhbGciOiJub25lIn0.eyJjb25maXJtYXRpb25fc2VudF9hdCI6IjIwMTgtMDEtMjQgMDY6MDQ6MzEgKzEzMDAiLCJleHBpcmVzX2F0IjoiMjAxOC0wMS0yNyAwNjowNDozMSArMTMwMCIsInVzZXJfaWQiOjM5fQ
routes.rb
Rails.application.routes.draw do
extend DeviseRoutes
extend PageRoutes
# Root route
root to: "pages#home"
end
devise_routes.rb
module DeviseRoutes
def self.extended(router)
router.instance_exec do
devise_for :users, path: '', path_names: {
sign_in: 'login',
sign_out: 'logout',
sign_up: 'register',
edit: '/user/edit'
}, controllers: { registrations: 'users' }
# User profile management
devise_scope :user do
get '/profile/:id', to: 'users#profile_home', as: 'profile'
# Verify email
get '/verify', to: 'users#verify_email'
end
end
end
end
users_controller
class UsersController < Devise::RegistrationsController
include AuthenticationConcern
require 'utilities/custom_mailer'
require 'jwt'
def profile_home
#user_id = params[:id]
check_user_route_access current_user, #user_id
#user = User.includes(:skills).find_by(id: #user_id)
#skills = #user.skills.eager_load(:profession)
end
def create
super
if current_user
CustomMailer.send_initial_user_signup(user_id: current_user.id,
to_email: current_user.email,
user_full_name: current_user.full_name)
end
end
def verify_email
jwt_token = params[:token]
#jwt_token_decoded = true
# make sure the jwt_token can be decoded, if not crash softly via
# error on the page rather than hard crash
begin
decoded = (JWT.decode jwt_token, nil, false)[0]
rescue
#jwt_token_decoded = false
end
if #jwt_token_decoded
decoded_expires_at = decoded["expired_at"]
user_id = decoded["user_id"]
#user = User.find_by(id: user_id)
# 1. if user is verified, redirect to login page
if #user != nil and #user.confirmed_at != nil
# flash[:success] = t('successfully_created')
redirect_to new_user_session_path
end
# 2. once verified, provide option in view to go to login page
end
# render verification page
end
end
Does anyone have any suggestions?
In your route the JWT consists of various special characters like '.' because of which the rails router is unable to route it properly.
Solution :-
1:- Use route globbing using wildcard segments
Example :-
Change you route to this
get '/verify/*jwt', to: 'users#verify_email', constraints: { jwt: /.*/ }
This will give params[:jwt] = "passed jwt in url"
You can customize the regex used here to make it more meaningful for jwt token which consists of two '.' and other special characters.
Read this for more information :- Rails route globbing and wildcard segments
The most probable cause of this issue is that you have another route before than get '/verify/:jwt', to: 'users#verify_email'. Rails Router gives priority to the first instance it finds.
So if for example, your routes.rb looks like this:
resources :verify # this may not be your case, just an example
get '/verify/:jwt', to: 'users#verify_email'
In this case, rails will ignore get line, and whenever you GET /verify/<anything> will be routed to verify#show
On the other hand, if you swap those lines like this,
get '/verify/:jwt', to: 'users#verify_email'
resources :verify # this may not be your case, just an example
Then every GET /verify/<anything> will be routed to verify_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) %>
I want to redirect by the role_ids of the user:
If it's equal to 2, redirect to workers_path.
if it's equal to 1, redirect to tasksadmins_path.
I defined the next things:
class ApplicationController < ActionController::Base
include ApplicationHelper
protect_from_forgery
before_filter :authenticate_user!
def stored_location_for(user)
nil
end
def after_sign_in_path_for(user)
if current_user.role_ids == [2]
return redirect_to(workers_path)
else
return redirect_to (tasksadmins_path)
end
end
end
but when I sign in, I got errors:
AbstractController::DoubleRenderError in UserSessionsController#create
Render and/or redirect were called multiple times in this action. Please note
that you may only call render OR redirect, and at most once per action. Also
note that neither redirect nor render terminate execution of the action, so
if you want to exit an action after redirecting, you need to do something
like "redirect_to(...) and return".
Rails.root: /home/alon/alon/todolist
Application Trace | Framework Trace | Full Trace
app/controllers/user_sessions_controller.rb:5:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"jRNZkIXvToEhl9YVxaQoa2hLJiSaHI6JAdfpUNAQMJI=",
"user"=>{"email"=>"worker216#gmail.com",
"password"=>"[FILTERED]",
"remember_me"=>"0"},
"commit"=>"Sign in"}
This is my user_session_controller.rb:
class UserSessionsController < Devise::SessionsController
include ApplicationHelper
def create
response = super
require "uri"
require "net/http"
## get the user id from the database
user_id = session['warden.user.user.key'][1][0];
## get the row by id
user = User.find(user_id)
# ============================
# Ensure that a user exists
# ============================
code, body = http_request(Net::HTTP::Put.new("/api/v1/users/external/#{user_id}"), email: user.email);
if code != 200
Rails.logger.error("Unable to register user #{current_user.email} at Licensario");
end
response
end
end
and this is my routes.rb:
TODOLIST::Application.routes.draw do
devise_for :users, :controllers => { :sessions => 'user_sessions'} do
get '/users/sign_out' => 'devise/sessions#destroy'
end
resources :tasksadmins
resources :workers
root to: "workers#index"
end
You shouldn't return redirect_to in the after_sign_in_path_for. You should return url:
def after_sign_in_path_for(user)
if current_user.role_ids == [2]
return workers_url
else
return tasksadmins_url
end
end
I added to 'create' the next lines:
in the beginning, I wrote:
resource = warden.authenticate!(:scope => resource_name)
and then I wrote in the end of the 'create' function:
sign_in(resource_name, resource)
if current_user.role_ids == [2]
respond_with resource, :location => workers_path
else
respond_with resource, :location => tasksadmins_path
end
so my create looks so:
class UserSessionsController < Devise::SessionsController
include ApplicationHelper
def create
resource = warden.authenticate!(:scope => resource_name)
require "uri"
require "net/http"
## get the user id from the database
user_id = session['warden.user.user.key'][1][0];
## get the row by id
user = User.find(user_id)
# ============================
# Ensure that a user exists
# ============================
code, body = http_request(Net::HTTP::Put.new("/api/v1/users/external/#{user_id}"), email: user.email);
if code != 200
Rails.logger.error("Unable to register user #{current_user.email} at Licensario");
end
sign_in(resource_name, resource)
if current_user.role_ids == [2]
respond_with resource, :location => workers_path
else
respond_with resource, :location => tasksadmins_path
end
end
end
I faced this issue while using devise gem for my admin account management, but mine was on a production server.
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".):
The issue was that I wanted to sign_in into the admin dashboard, rather than entering the admin_dashboard URL into the browser, I put in the admin_sign_in URL in the browser, meanwhile, an admin account was already logged in. So when I tried to sign in it didn't work, because I can't sign-in 2 accounts at the same time to the admin dashboard.
Here's how I solved it:
Enter the URL for the admin dashboard into the browser, which signed me into the previous account
Log out from the account on the admin dashboard
And then sign-in to the admin dashboard using the account that I want.
That's all.
I hope this helps
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
What is the rails way, to create seperate actions for get or post?
Is it possible to have the same name but decorate the action to only run if its a get or post?
e.g.
'get only'
def forgot_password
end
'post only'
def forgot_passord
end
Rails way is to have a resource -- Each method should have a responsibility.
resources :password_resets
And then you'll let a user reset their password by visiting the form:
link_to 'Lost your Password?', new_password_reset_path
And then the form will post to create a new password_reset... That will send an email with a link to show the password_reset.
form_tag(password_resets_path, :method=>:post) do
When the use enters their updated password, it will update the password_reset.
# in routes.rb
resources :password_resets
# in app/controllers/password_resets.rb
class PasswordResets < ApplicationController
def new
#user = current_user
# render new reset form
end
def create
#user = current_user
#new_password = User.generate_random_password
if #user.update_attributes(:password => #new_password)
UserMailer.password_reset(#new_password).deliver
flash[:notice] = 'Successfully reset your password, check your email!'
else
flash[:error] = 'Could not reset password'
end
redirect_to login_path
end
end
Name them differently and create two routes in config/routes.rb. If you really really want one action doing different things, which is not a great idea, check request.method or request.get?, request.post?, etc.
you can rename the actions in your controller
#get only
def get_forgot_password
end
#post only
def post_forgot_passord
end
and then on your routes.rb
match '/forgot_password' => 'pass_reset#get_forgot_password', :via => 'get'
match '/forgot_password' => 'pass_reset#post_forgot_password', :via => 'post'
the :via option do the trick.