Rail 4 Devise 3.2 forgot password token is invalid - ruby-on-rails

Whenever I set a new password I got an invalid token error message. I've debug this method in Devise, reset_password_token = Devise.token_generator.digest(self, :reset_password_token, params[:reset_token]) and the token is indeed different from the one saved in the database. does any one here or know why the token are different?
EDIT: here's the controller code that I use to override Devise::PasswordController
class PasswordsController < Devise::PasswordsController
def edit
original_token = params[:reset_password_token]
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
self.resource = resource_class.find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
if !resource.errors.empty?
flash[:alert] = "Password token is invalid"
redirect_to new_session_path(resource_name)
end
end
end

The problem is with the following line
Devise.token_generator.digest(self, :reset_password_token, original_token)
The first parameter should be the model class which acts as your user model. At the moment, you pass the PasswordsController class. If you also name your user model User then change that line to
Devise.token_generator.digest(User, :reset_password_token, original_token)

You need to check reset_password_period_valid?:
if resource.reset_password_period_valid?
set_minimum_password_length
resource.reset_password_token = params[:reset_password_token]
else
flash[:alert] = 'Your password reset link has expired, please enter your email to send a new one.'
redirect_to new_password_path(resource_name)
end
An expired token error won't added to the resource unless you attempt to update by token.

Related

Devise - how to check if reset password is token is valid

I'm trying to figure out how I can check if a user reset token is valid BEFORE loading the reset password form. The issue is, currently users don't find out until after they submit.
Here is what I have
class PasswordsController < Devise::PasswordsController
before_action :check_valid_token
private
def check_valid_token
resetCode = (params['resetCode'])
reset_password_token = Devise.token_generator.digest(self, :reset_password_by_token, resetCode)
user = User.find_by(reset_password_token: #reset_password_token)
if user == nil
redirect_to root_path
end
end
end
This doesn't work and I can't find much documentation.
Devise reset password token will be stored as hashed value. You need to decode it.
def check_valid_token
token = Devise.token_generator.digest(User, :reset_password_token, params['reset_password_token'])
user = User.find_by(reset_password_token: token)
user.present?
end
This method will return, true or false
I would do something basic, like this:
def check_valid_token
#user = User.find_by!(reset_password_token: params[:token])
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
so you will have #user instance if token fits and if not it will redirect user to the root_path. You can also add some message before redirecting, like
flash.now[:error] = "Some message here"

How to generate reset password token

I am using devise gem for authentication.
In my application admin will create the users, so I want the user's reset password link when admin creates users.
This is my action:-
def create
#user = User.new(user_params)
#user.password = '123123123'
#user.password_confirmation = '123123123'
if #user.save
#user.update_attributes(:confirmation_token => nil,:confirmed_at => Time.now,:reset_password_token => (0...16).map{(65+rand(26)).chr}.join,:reset_password_sent_at => Time.now)
UserMailer.user_link(#user).deliver
redirect_to users_path
else
render :action => "new"
end
end
This is my link to reset a user's password
But I am getting reset password token is invalid when I open the link and update the password.
If you are using devise why are you creating your own password reset token?
Devise has a feature for that.
http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/Recoverable
In case you wonder this is what devise does when the user wants to reset his password:
raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.now.utc
self.save(validate: false)
self is a User object here
In your URL you then have to pass raw as reset_password_token parameter
You can generate a token with:
Devise.token_generator.generate(User, :reset_password_token)
Though this is just a useless string by itself. You need to attach it to the user if you actually want to use it in a link to reset passwords:
user.reset_password_token = hashed_token
user.reset_password_sent_at = Time.now.utc
Then send them an email with the link:
edit_password_url(#user, reset_password_token: #token)
You can use user.send_reset_password_instructions for that.
If you don't want it to send the instructions, just set and store the token you can call the private method in devise recoverable concern set_reset_password_token.
You can do this by doing something like user.send(:set_reset_password_token).
To get the url to reset the password using Devise I use this snippet of code:
user = User.find(id)
raw, enc = Devise.token_generator.generate(User, :reset_password_token)
user.update_columns(reset_password_token: enc, reset_password_sent_at: Time.current)
puts Rails.application.routes.url_helpers.edit_user_password_url(reset_password_token: raw, host: 'localhost:3000')
Expanding upon #Rails Fan's answer. The specific method that handles the password reset in Recoverable module is a protected method set_reset_password_token .
You can access it by the following code and it will return the token directly.
## your model.send(:set_reset_password_token)
user.send(:set_reset_password_token)

Ruby on Rails no such column: authentication.provider: Omniauth

I was following this tutorial on Omniauth: http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
I keep getting this error:
no such column: authentication.provider:
Now the main thing I want to know is why "provider" isn't being accepted. It exists in the class... the authentications database exists... so why is it saying it isn't there?
Here's my authentications controller:
class AuthenticationsController < InheritedResources::Base
def index
#authentications = current_user.authentications if current_user
end
def create
#user = User.where(authentication: auth).first_or_create(:provider => auth['provider'], :uid => auth['uid'])
self.current_user = #user
# auth = request.env["omniauth.auth"] current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
def auth
request.env['omniauth.auth']
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
I can assure you I have a model called authentication and that this model has a provider and uid field. I've also tried where(authentications: auth) and where(auth: auth)
each with no luck.
Any ideas would be appreciated.
UPDATE
authentication.rb (model)
class Authentication < ActiveRecord::Base
attr_accessible :create, :destroy, :index, :provider, :uid, :user_id
belongs_to :user
end
UPDATE 2
I'm basically attempting to adapt this tutorial to rails 3.2.
The original line from the tutorial is commented out above.
UPDATE 3
Here is the entire first line of error:
SQLite3::SQLException: no such column: authentication.provider: SELECT "users".* FROM "users" WHERE "authentication"."provider" = 'facebook' AND "authentication"."uid" = '2222222' AND "authentication"."info" = '--- !ruby/hash:OmniAuth::AuthHash::InfoHash
Hate to be a burden... but the clock's really ticking, my ass is on the line, and I'm about to go completely insane trying to figure this out. If you can tell me just why provider isn't being accepted I'm sure I can figure out the rest.
your create action has not sense
User.where(authentication: auth) converts to SELECT * FROM users WHERE authentication = a_hash
You shoul do something like
auth1 = Authentication.where(provider: auth['provider'], uid: auth['uid']).first
if !auth1.nil?
current_user = auth.user
else
user = User.new
user.authentications.build(provider: auth['provider'], uid: auth['uid'])
user.save!
current_user = user
end
Since you are just adding a record in the authentications table, I am unable to understand why you are reassigning this.current_user. Also is current_user a helper method or a member, if it's a member where is it declared?
Don't you just want to create an authentication for the current user as such?:
def create
current_user.authentications.first_or_create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
This finds the first authentication record by provider and uid, if not found then creates that authentication record.
Also by that error, I hope you have figured out the answer to this question:
Now the main thing I want to know is why "provider" isn't being
accepted. It exists in the class... the authentications database
exists... so why is it saying it isn't there?
It is because you are calling first_or_create() on User object, not Authentication.
I also faced this issue recently. At first I thought I had forgotten to add a provider column to users table, but that wasn't it.
This is how I eventually solved it:
def self.from_omniauth(auth)
where(provider: auth["provider"], uid: auth["uid"]).first_or_create do |user|
user.email = auth["info"]["email"]
user.password = Devise.friendly_token[0, 20]
user.logo = auth["info"]["image"]
# if you use confirmable, since facebook validates emails
# skip confirmation emails
user.skip_confirmation!
end
end
auth is a hash like the one below, so instead of auth.provider, I used auth["provider"] etc:
omniauth.auth: {"provider"=>"facebook", "uid"=>"11111111111111", "info"=>{"email"=>"some#email.com", "image"=>"http://graph.facebook.com/v2.6/11111111111111/picture"}, "credentials"=>{"token"=>"sometoken", "expires_at"=>1506680013, "expires"=>true}, "extra"=>{"raw_info"=>{"email"=>"some#email.com", "id"=>"11111111111111"}}}

ActiveSupport::TimeWithZone failed error in ruby on rails

I'm using code identical to my password_reset code for my email change code.
User wants change their email address so they type in their email address click a button and they're logged out.
An email is sent to them containing a link they click containing a code as id which is then matched up with the one stored in the db to confirm they are in fact the accounts owner. Any way when I click the I get the error shown below.
Problem is I'm getting this error:
ArgumentError in EmailsController#edit
comparison of String with ActiveSupport::TimeWithZone failed
Rails.root: /Users/greg/site
Application Trace | Framework Trace | Full Trace
app/controllers/emails_controller.rb:19:in `<'
app/controllers/emails_controller.rb:19:in `edit'
Request
Parameters:
{"id"=>"KdFTTeWuOGqpDm6F_iY7aw"}
Show session dump
Show env dump
Response
Headers:
None
Emails controller create:
def create
#user = User.find_by_email(params[:email_change][:email])
logout if logged_in?
#user.generate_and_store_email_change_token && UserMailer.email_change(#user).deliver if #user
flash[:success] = "Email sent with email reset instructions."
redirect_to root_url
end
Emails controller edit:
def edit
#user = User.find_by_email_change_token(params[:id])
if #user.nil?
flash[:error] = "The email change link you clicked has been used."
redirect_to root_url
elsif #user.email_change_sent_at < 2.hours.ago
flash[:error] = "Email change token has expired."
redirect_to email_change_url
end
end
User model:
def generate_and_store_email_change_token
self.email_change_token = SecureRandom.urlsafe_base64
self.email_change_sent_at = Time.zone.now
save!(:validate => false)
end
def remove_used_email_change_token
self.email_change_token = nil
save!(:validate => false)
end
This is strange because the exact same code works for my password reset. I tested again and I don't get the error the email version giving me.
Kind regards
Seems like email_change_sent_at type is a string. You should change it to datetime

Rails 3 update_attribute not firing

I have a member model with a reset_token method (which assigns a user a new token in order to send them an email to reset their password). But update_attribute never saves anything in the database. I have :new_password_token assigned to attr_accessible and attr_accessor. The log picks up the salt and token but it always returns nil
def self.reset_token(email)
member = find_by_email(email)
if member
#Reset token, and then send email
salt = BCrypt::Engine.generate_salt
logger.error "Salt is #{salt}"
token = BCrypt::Engine.hash_secret(email, salt)
logger.error "token is #{token}"
if member.update_attribute(:new_password_token, token)
member
end
end
nil
end
Controller method in which it is called:
def reset_password
#member = Member.reset_token(params[:email])
if #member
redirect_to(root_url, :notice => "Please check your email for instructions")
else
redirect_to(root_url, :notice => "Sorry we have no record of your account")
end
end
Try removing attr_accessor from your model. attr_accessor is creating reader and writer methods for new_password_token. The writer method is equivalent to:
def new_password_token=(new_password_token)
#new_password_token = new_password_token
end
So when you update_attribute it is just setting an instance variable on your object and bypassing the database altogether.

Resources