For those of you who actually have, or actually are watching RailsCasts, and for those of you who have actually watched these two videos:
http://railscasts.com/episodes/250-authentication-from-scratch?view=comments
http://railscasts.com/episodes/250-authentication-from-scratch-revised
you will notice that in both of them, on every login blank test he does, Ryan Bates doesn't seem to bother about the fact that upon login validation_presence error, he gets redirected from /sessions/new to /sessions. This has bothered me, and I am still in big trouble finding the solution to this? I have a feeling that it's fairly simple, but I guess it's only hidden someplace visible, but not to me.
*THIS IS BASICALLY RYAN BATES CODE:*He's got the error - I've got the error.: IDEAS???
USER MODEL:
has_secure_password
attr_accessible :email, :password, :password_confirmation
validates_uniqueness_of :email
SESSIONS CONTROLLER:
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
SESSIONS LOGIN PAGE:
<h1>Log In</h1>
<%= form_tag sessions_path do %>
<div class="field">
<%= label_tag :email %><br />
<%= text_field_tag :email, params[:email] %>
</div>
<div class="field">
<%= label_tag :password %><br />
<%= password_field_tag :password %>
</div>
<div class="actions"><%= submit_tag "Log In" %></div>
<% end %>
I don't have pro so can't watch the episode... why do you say you're being redirected to /sessions? Does the log file actually say it's doing that?
Remember... when you submit that form, it's going to POST to /sessions. So you'll see "/sessions" in the address bar, but you aren't being redirected there. And when that form submission fails, it's going to render :action => 'new' -- at the current url of "/sessions". But you still haven't redirected....
Related
How can I make an error notice when somebody leaves spaces empty, etc...
I tried enumerous codes and they either didn't work or failed... I even installed a gem but nothing worked until now... please help with ideas/solutions
<div class="jumbotron"
<div class="container">
<h2>Signup</h2>
<%= form_for :user, url: '/users' do |f| %>
Número de Empregado: <br>
<%= f.number_field :NumeroEmpregado %><br>
Primeiro e Último Nome: <br>
<%= f.text_field :nome %><br>
Password: <br>
<%= f.password_field :password %><br>
Confirmação Password: <br>
<%= f.password_field :password_confirmation %><br>
<%= f.submit "Submit" %>
</div>
</div>
<% end %>
Controller:
class UsersController < ApplicationController
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/'
else
flash[:error] = 'invalid value'
end
end
private
def user_params
params.require(:user).permit(:NumeroEmpregado, :nome, :password, :password_confirmation)
end
end
Rails only can validate when you submitted your form. In this case, you can check out this guide.
In other case, if you want immediately show validate message when user enter into input, you must validate it by using Javascript. You also can use Jquery validation plugin to do that.
Hope its help :)
you should use jquery validations if you want to check if the field is empty type validations before submit https://jqueryvalidation.org/documentation/ follow this link.
Or if you are ok with validating fields on submit then in create or update action of your controller you can check from the params if that field is empty. then you can show flash messages like this
flash[:notice] = 'valid value'
flash[:error] = 'invalid value'
Or you can add model level validations on that fields http://guides.rubyonrails.org/active_record_validations.html here
You can use the Active Record Validations.
If you have a model called User, then you can specify which attributes to validate for presence using the validates method and setting the presence to true, this will check if the field on the form has been filled, also if has only whitespaces.
For instance:
class User < ApplicationRecord
validates :nome, presence: true
...
For your controller:
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/'
else
flash[:error] = 'invalid value'
redirect_to new_user_path
end
end
Redirect to the new user path with flash[:error] as "invalid value".
For your view:
<% if flash[:error] %>
<div class="error">
<%= flash[:error] %>
</div>
<% end %>
I have got an requirement to auto populate email and password to be auto populated in the login form, if the user is checked the remember me check box in the user login form.
As i have checked through some documents and googling i got some of the understanding that this is the browser behavior. And is to be the user acceptance of the browsers automatic popup to be used to get the user acceptance to save the user info.
I came to know that the devise is making the browser to store the values in cache and managing the session to be continued even the browser is closed.
As I have to work on that can any some one of you please show me the right path or suggest me to achieve this if it is possible.
Usually with username and password field we have remember me:
<%= label_tag :remember_me %>
<%= check_box_tag :remember_me, 1, params[:remember_me] %>
<%= submit_tag "Log in" %>
When user hits submit button if we call login action then usually it looks like this:
def login
user = User.find_by_email(params[:username])
if user && user.authenticate(params[:password])
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
Now as your user wants to auto populate the username and password rather than auto login. Then I think you should not permanently save the token to cookies like this: cookies.permanent[:auth_token] = user.auth_token rather than put it like this cookies[:auth_token] = user.auth_token. And let the browser auto fill the login form.
def login
user = User.find_by_email(params[:username])
if user && user.authenticate(params[:password])
if params[:remember_me]
cookies[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
If you don't want to put it on the hand of browser then you can do the following, though I DON'T THINK YOU SHOULD DO THAT:
<%= form_tag sessions_path do %>
<div class="field">
<%= label_tag :email %>
<%= text_field_tag :email, #user.email %>
</div>
<div class="field">
<%= label_tag :password %>
<%= password_field_tag #user.password %>
</div>
<div class="field">
<%= label_tag :remember_me %>
<%= check_box_tag :remember_me, 1,remember_me %>
</div>
<div class="actions"><%= submit_tag "Log in" %></div>
I must be missing something... my views are being rendered, but it appears like code in the controller isn't being executed. I can't find what's wrong.
When I click the login button in login.html.erb, the login_process method of the PublicController should be called to authenticate the user. That doesn't seem to happen as the view login_process.html.erb just displays, which shouldn't be possible.
I've even tried putting a flash[:notice] = "heyhey" on the login_screen action so it appears on the login form, but that doesn't happen either.
What have I done wrong?
Is it something in the routes.rb?
routes.rb
root :to => "public#index"
get "login",
:to => "public#login",
:as => "login_screen"
post "login_process",
:to => "public#login_process",
:as => "login_process"
get "logout",
:to => "public#logout",
:as => "logout"
public_controller.rb
class PublicController < ApplicationController
def login_screen
flash[:notice] = "heyhey"
end
def login_process
authenticated_user = User.authenticate params[:email], params[:password]
if authenticated_user
session[:user_id] = authenticated_user.id
session.countdown_start(1.minute)
redirect_to :public => :index
else
flash[:notice] = 'Email/Password incorrect. Receive a new password.'
flash[:color] = "invalid"
end
render :login_screen
end
def logout
session.countdown_abort
flash[:notice] = "You are now logged out"
redirect_to :login_screen
end
def count
render :text => session.countdown_count.to_i
end
end
views/public/login.html.erb
<h1>Login</h1>
<p id="notice"><%= notice %></p>
<%= form_tag :login_process do %>
<div class="field">
<%= label_tag :email %><br>
<%= text_field_tag :email %>
</div>
<div class="field">
<%= label_tag :password %><br>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag "Login" %>
</div>
<% end %>
<h1>Login</h1>
<p id="notice"><%= notice %></p>
<%= form_tag :login_process do %>
<div class="field">
<%= label_tag :email %><br>
<%= text_field_tag :email %>
</div>
<div class="field">
<%= label_tag :password %><br>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag "Login" %>
</div>
<% end %>
login_process.html.erb
I never expect to see this page as the user should be directed elsewhere
<p>login_process.html.erb</p>
<p id="notice"><%= notice %></p>
Update 1:
I've modified the authenticate action in the User model to be:
def authenticate(:email, :password)
#x = new User
#x.id = 123
return false #x
end
and login_process on PublicController to be:
def login_process
if true
flash[:notice] = 'boom'
else
flash[:notice] = 'Email/Password incorrect. Receive a new password.'
flash[:color] = "invalid"
end
redirect_to :login_screen
end
Unfortunately though... same issue. The login_process.html.erb is
displayed with no flash notices either.
Versions:
Ruby 2.0.0p247
Rails 4.0.0
The code in your controller is probably executing just fine.
I think the authenticate class method on User is not giving you the correct result. That's why it doesn't go through the if authenticated_user and just renders the login_process view. You don't have to call this render command though, it's done for you if you do nothing.
There's a problem with the login_process method though. You can't have a render call and a redirect_to be called together, now it can happen in your case, if the user is authenticated. Put the render :login_screen in the else case, otherwise you get the AbstractController::DoubleRenderError error saying: Render and/or redirect were called multiple times in this action. [etc.]
This is what you would get as an error if the user was authenticated.
Edit: Running this locally (stubbing User.authenticate) on a new Rails project it works. Flash notices appear and everything.
Also: render :login_screen is probably render :login, no? in login_process?
If nothing works still, what's your: Ruby version, Rails version, put authenticated_user = false # or true in login_process to see if it behaves differently.
redirect_to :public => :index
It seems like you are redirecting from login_process to /login_process?public=index, which I don't think is your intention.
If you are meaning to redirect to public#index, try
redirect_to root_path
(But I can't see that action in your public controller.)
I believe the problem is that you call two renders in one action. You can only render one page per action.
def login_process
authenticated_user = User.authenticate params[:email], params[:password]
if authenticated_user
session[:user_id] = authenticated_user.id
session.countdown_start(1.minute)
redirect_to :public => :index
else
flash[:notice] = 'Email/Password incorrect. Receive a new password.'
flash[:color] = "invalid"
end
render :login_screen
end
The user is authenticated and the redirect_to should bring you to the public/index, however, you call render :login_screen after the if statement, so it will execute that portion of the code no matter what and bring you back to the login.
My question refers to setting up the view and the controller for updating a user's "profile" by confirming the password from the user before updating the attributes. As you've all probably seen a million times before, the user would go to /users/:id/edit, enter a new email in the text field, enter the current password in the password field and click on the submit button to ultimately update the user's email. If the password entered is incorrect then the edit template is rendered again, otherwise the user record is updated with the new email and redirected to :show (or whatever is appropriate for the app). While in the update action I think it makes sense to stick with using the update_attributes method. However the current password value would end up throwing us off.
What I'm really asking though is if there's anything wrong with my approach. I ended up with including a call to password_field_tag for the :current_password field inside the form_for block in order to call update_attributes with params[:user] without making attr_accessible angry. But then I looked up a couple forms in websites that already do this (hulu and destroyallsoftware for example) and they seem to accept the :current_password value in the user hash (assuming they're built with rails). Looking up twitter's settings page it looks like they retrieve this in a separate hash in param (so params[:current_password] instead of params[:user][:current_password]).
Is it wrong to use password_field_tag within form_for? How are these other sites really doing this? The only thing I can think of is that they're either deleting :current_password from the params hash or assigning each attribute individually.
Here is what I basically ended up with:
# /app/models/user.rb
class User < Activerecord::Base
attr_accessible :email, # ...
# ...
end
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:current_password]
<%= label_tag :current_password, 'Re-enter your password to update your email' %>
<%= password_field_tag :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:current_password])
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Otherwise, this is the one other way I thought of:
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:user][:current_password]
<%= f.label :current_password, 'Re-enter your password to update your email' %>
<%= f.password_field :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
params[:user].delete(:current_password) # <-- this makes me feel a bit uneasy
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Or, should I just do this in the controller?:
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
#user.email = params[:user][:email]
if #user.save
# ...
Any advice is appreciated.
P.S. - Additionally how would you go about refactoring that update action? I tried out a before_filter to authenticate with :current_password and keep only the #update_attributes part in #update, but it got a bit messy. This post is getting long enough though so maybe I'll post this as a separate question if I can not figure it out by next week.
I've recently done something similar to this, except I used a virtual attribute to handle the current_password. You can then add the :current_password attribute to attr_accessible and keep it happy.
I'm wondering if there is a manner of calling the 'forgot password' procedure without forcing my user to log out
The case I'm running into is:
a user logs in with Facebook, a fake password is generated for them
the user then wants to change their email/name/password, or just use non-facebook login
since devise requires a password to change these fields, as it should, the user is unable to modify them
I had thought about just not forcing the password to be set but that doesn't make sense to security wise so instead I just display the fields as text and notify the user to follow the 'forgot password' procedure in order to set a password and then they can change the fields
The issue then is that I cannot simply link to this from the user profile since devise will tell the user that they can't do this while already logged in.
So is there a manner of overriding the forgot password or /users/password/edit method so that a logged-in user can perform this action as well?
The reason that you cannot reset password is because the devise tries to authenticate the user with the current session and when succeeded you are automatically redirected to whatever path it is supposed to go to. What you need is to override the edit and update action of passwords controller to make it skip this step.
Here's the code. In your passwords controller add the following codes (you can ask devise to generate the controllers for you, or you can just create the following controller). The override for update is necessary because otherwise a logged in user will be automatically signout after your reset password. (Or if you want it to be like that you can get rid of the #update override)
class PasswordsController < Devise::PasswordsController
# here we need to skip the automatic authentication based on current session for the following two actions
# edit: shows the reset password form. need to skip, otherwise it will go directly to root
# update: updates the password, need to skip otherwise it won't even reset if already logged in
skip_before_filter :require_no_authentication, :only => [:edit, :update]
# we need to override the update, too.
# After a password is reset, all outstanding sessions are gone.
# When already logged in, sign_in is a no op, so the session will expire, too.
# The solution is to logout and then re-login which will make the session right.
def update
super
if resource.errors.empty?
sign_out(resource_name)
sign_in(resource_name, resource)
end
end
end
The routes are like the following
# config/routes.rb
devise_for :users, :controllers => {:passwords => 'passwords'}
You can use the #user.send_reset_password_instructions to generate the password reset token and send the email. If you just call the mailer directly, a password reset token won't be generated to authenticate the reset.
My complete solution here, because I then also learned that the user would have to log out after clicking the link in the email, was to add an some additional UserController actions for actually editing the password as well as saving it. This is not an ideal solution and cold probably be done in a better manner but it works for me.
users controller; added methods to do the reset
before_filter :authenticate_user!, :except => [:do_reset_password, :reset_password_edit]
def reset_password
id = params[:id]
if id.nil?
id = current_user.id
end
if (!user_signed_in? || current_user.id.to_s != id.to_s)
flash[:alert] = "You don't have that right."
redirect_to '/home'
return
end
#user = User.find(id)
#user.send_reset_password_instructions
respond_to do |format|
format.html { redirect_to '/users/edit', notice: 'You will receive an email with instructions about how to reset your password in a few minutes.' }
end
end
def do_reset_password
id = params[:id]
if id.nil? && !current_user.nil?
id = current_user.id
end
if id.nil?
#user = User.where(:reset_password_token => params[:user][:reset_password_token]).first
else
#user = User.find(id)
end
if #user.nil? || #user.reset_password_token.to_s != params[:user][:reset_password_token]
flash[:alert] = "Url to reset was incorrect, please resend reset email."
redirect_to '/home'
return
end
# there may be a better way of doing this, devise should be able to give us these messages
if params[:user][:password] != params[:user][:password_confirmation]
flash[:alert] = "Passwords must match."
redirect_to :back
return
end
if #user.reset_password!(params[:user][:password],params[:user][:password_confirmation])
#user.hasSetPassword = true
#user.save
respond_to do |format|
format.html { redirect_to '/home', notice: 'Your password has been changed.' }
end
else
flash[:alert] = "Invalid password, must be at least 6 charactors."
redirect_to :back
end
end
def reset_password_edit
#user = User.where(:reset_password_token => params[:reset_password_token]).first
if #user.nil? || !#user.reset_password_period_valid?
flash[:alert] = "Password reset period expired, please resend reset email"
redirect_to "/home"
return
end
end
views/devise/registrations/edit; changed the view to not let the user edit fields that require a password
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<% if !resource.hasSetPassword %>
<%= f.label :name %><br />
<p style="line-height:24px;"><b><%= #user.name %></b></p>
<div><%= f.label :email %><br />
<p style="line-height:24px;"><b><%= #user.email %> </b></p>
<p style="position:relative; left:150px; width:420px;">
<i>you cannot change any settings because you have not set a password <br />yet, you can do so by following the </i>
<%= link_to "Forgot your password", "/users/reset_password" %> <i> procedure</i>
</p>
</div>
<% else %>
<p><%= f.label :name %><br />
<%= f.text_field :name %></p>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %> <br />
<%= f.password_field :password %><i>(leave blank if you don't want to change it)</i></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :current_password %> <br />
<%= f.password_field :current_password %>
<i>(we need your current password to confirm your changes)</i>
</div>
<div><%= f.submit "Update" %></div>
<% end %>
<% end %>
views/devise/mailer/reset_password_instructions; had to change it to point to the right URL in our new case
<p>Hello <%= #resource.email %>!</p>
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
<% if !#resource.hasSetPassword %>
<p><%= link_to 'Change my password', 'http://streetsbehind.me/users/reset_password_edit?reset_password_token='+#resource.reset_password_token %></p>
<!-- todo: there's probably a better way of doing this than just hardcoding streetsbehind.me -->
<% else %>
<p><%= link_to 'Change my password', edit_password_url(#resource, :reset_password_token => #resource.reset_password_token) %></p>
<% end %>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
views/users/reset_password_edit.erb
<%= form_for(#user, :url => url_for(:action => :do_reset_password) , :html => { :method => :post }) do |f| %>
<%= f.hidden_field :reset_password_token %>
<div><%= f.label :password, "New password" %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Change my password" %></div>
<% end %>
config/routes.rb
get "users/reset_password"
get "users/reset_password_edit"
resource :users do
post 'do_reset_password'
end
I adapted #user3294438's answer to make it work perfectly for me.
class PasswordsController < Devise::PasswordsController
# here we need to skip the automatic authentication based on current session for the following four actions
# new : shows the "enter email to reset". need to skip, otherwise it will go directly to root
# create : launches the reset email
# edit: shows the reset password form. need to skip, otherwise it will go directly to root
# update: updates the password, need to skip otherwise it won't even reset if already logged in
skip_before_action :require_no_authentication, :only => [:new, :create, :edit, :update]
# we need to override the update, too.
# After a password is reset, all outstanding sessions are gone.
# When already logged in, sign_in is a no op, so the session will expire, too.
# The solution is to logout and then re-login which will make the session right.
def update
super
if resource.errors.empty?
sign_out(resource_name)
sign_in(resource_name, resource)
end
end
private
# Overriding this method allows to show a nice flash message to the signed-in user that just
# asked for a password reset by email. Otherwise he gets a "you are already signed in" falsh error
def after_sending_reset_password_instructions_path_for(resource_name)
if current_user
flash[:info] = I18n.t "devise.passwords.send_instructions"
return root_path
end
super
end
end