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"
Related
I need to create functionality where other microservice creates a link to my app with JWE token as a params in which is encrypted json user params e.g.:
json_payload = {
email: 'test#test.com',
external_id: '1234'
}.to_json
The flow should be:
user gets the url generated by different app with JWE token as params (e.g. http://localhost:3000/users/sign_up/?jwe_token=some_gigantic_string_123)
enter that url
under the hood Rails app creates new user based on encrypted params
after successful user creation redirect that user to the edit page
So as you see, the user shouldn't notice that there was an account creation but the first page it will see is the password edit.
Am I not doing some sort of antipaternity here with below code? Please take a look:
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
return redirect_to(new_user_session_path) unless params[:jwe_token]
json_payload = JWE.encrypt(payload, rsa_key)
payload = JSON.parse json_payload
user = User.new(user_params)
if user.save
redirect_to generate_password_url(request.base_url, user)
else
redirect_to new_user_session_path, alert: 'Something went wrong'
end
end
private
def generate_password_url(base_url, user)
path = edit_password_path(user, reset_password_token: fetch_token(user))
"#{base_url}#{path}"
end
def fetch_token(user)
user.send(:set_reset_password_token)
end
end
I assume that if user creation is to be handled by a link I have to use new method. Am I not creating an antipattern here? Is there any other way to do so?
working on a Passwords_Controller that allows the password to be changed, also validates if the user is using same password, or if they are trying to use different password, and another password to confirm. I have it set to "redirect_to" to my dashboard controller that lands them back to be the page to enter the password again before user is updated with new password.
currently I am getting a DoubleRenderError. I have some "puts" with extrapolation see if the logic is passing through the controller and rendering I'm new to rails, so i'm not exactly sure if its my redirect statements that are stopping the validation of old pw, new password - confirm password logic in my controller, or I have too many if statements that is forcing the controller to do double render.
(output of on the terminal with statements for error)
Redirected to http://127.0.0.1:3000/dashboard
true
is this working asdf <~~ test username
is this also working $2a$12$d5WadQunMyww2r4lnmqgveoXaq6WO6hNXvsG/h3RxqUxGFCp6tnWm
Redirected to
Completed 500 Internal Server Error in 738ms (ActiveRecord: 9.1ms | Allocations: 6474)
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".):
this is my password controller
require 'bcrypt'
class PasswordsController < ApplicationController
include BCrypt
def passwordchanged
end
def update
user = current_user;
#user entered correct password
if user.authenticate(params[:password])
helpers.flash_message :danger, "current password doesnt match"
redirect_to '/dashboard'
end
puts "#{user.authenticate(params[:password])}"
puts "is this working #{params[:new_password]}"
puts "is this also working #{Password.new(user.password)}"
#if new password is the same as new password, doesnt not save
if Password.new(user.password) == params[:new_password]
helpers.flash_message :danger, "new password can't match current password, please try again"
redirect_to '/login'
end
puts "#{redirect_to '/login'}"
#if new password doesnt match confirm password
if params[:new_password] != params[:new_password_confirm]
helpers.flash_message :danger, "password, password confirm needs to match"
redirect_to '/dashboard'
end
puts "#{params[:new_password] != params[:new_password_confirm]}"
#create a new password and save the user ID
user.password = params[:new_password]
user.save
helpers.flash_message :success, "change successful, logout for new session"
redirect_to '/passwordchangesuccess'
end
end
this is my Application Controller
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
User.find_by(id: session[:user_id])
end
def authenticate_user!
redirect_to '/login' unless current_user
end
end
In my application I am trying to manually make devise sessions unusable by setting a session_validity_token.
How I do it:
The devise User model has a column named session_validity_token
I also have a SeesionHistory model which has the same column
In my devise initialization ...
Warden::Manager.after_set_user except: :fetch do |user, warden, opts|
user.update_attribute(:session_validity_token, Devise.friendly_token) if user.session_validity_token.nil?
warden.raw_session["validity_token"] = user.session_validity_token
end
Warden::Manager.after_fetch do |user, warden, opts|
unless user.session_histories.unblocked.where(session_validity_token: warden.raw_session["validity_token"]).exists?
warden.logout
end
end
... when a user signs in or up I set the validity_token of the stored Cookie to the Users session_validity_token. If the User doesn't have one yet (signup), I create a token.... when a URL gets fetched I check before authorizing the User if a unblocked session to that token exists. If not, the User gets logged out.
In the ApplicationController ...
def after_sign_in_path_for(resource_or_scope)
session = SessionHistory.create(user_id: current_user.id, session_validity_token: current_user.session_validity_token)
current_user.update_attribute(:session_validity_token, Devise.friendly_token)
request.env['omniauth.origin'] || stored_location_for(resource) || root_path
end
... after a User gets signed in, I create a SessionHistory Record and simply set it's session_validity_token to the Users token and then recreate the Users token.
Unfortunately I get the following error:
NoMethodError in Users::SessionsController#create
undefined method `session_validity_token' for nil:NilClass
Here is the SessionController#Create Action:
def create
if User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").exists?
#user = User.find(User.where("email = '#{params[:user][:login]}' or username = '#{params[:user][:login]}'").first.id)
if #user.confirmed? || ((Time.now - #user.created_at).to_i / (24 * 60 * 60)) < 1
super
else
redirect_to new_user_confirmation_path(q: "unconfirmed")
end
else
flash[:alert] = "The email or username does not match any accounts"
redirect_to new_user_session_path
end
end
So I guess I did something wrong when handling the tokens with Warden ...
Please ask if you need additional Information.
You may have a namespace collision between two customizations named session_validity_token. This is not naturally in the Devise model (and is not in the source for devise--I checked that).
If that is the case, and you have power over the source, consider changing the name of one, or both of the session_validity_token symbols to clarify the specific usage and relieve the conflict.
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.
I decided to customize(override) Devise's session controller to implement my own sign in logic. I've left the sessions#new controller alone and simply call super in it.
def new
super
end
However, I have removed the call to super in my custom sessions#create method so that I can send an HTTP request to a server for authentication. Subsequently, I either sign in the user or return the user to the sessions#new path to try their login again.
def create
#member = Member.find_by_username(params[:username])
response = authenticate_with_api
if response[:authenticated]
if #member
#member.update(response.[:member])
sign_in #member
else
#member.create(response.[:member])
sign_in #member
end
else
flash[:alert] = "Incorrect username or password"
flash[:error] = "Incorrect username or password"
flash[:notice] = "Incorrect username or password" #setting all the messages out of frustration!
redirect_to new_member_session_path
end
The logic flow is working correctly, however the flash messages are not displaying. I have set a flash message just before calling super in the new method to ensure that the view was setup properly:
def new
flash[:notice] = "Test flash message"
super
end
This works. I can see the flash message appear in the template. The only explanation I can come up with is that somehow, there are multiple redirects occurring. However, when I look in the logs, I can only see the redirect I specified. I have also tried using keep to persist the flash message through multiple requests, but even that does not work:
flash.keep[:notice] = "Test flash message"
redirect_to new_member_session_path #does not work
I also tried using now as a last ditch effort:
flash.now[:notice] = "Test flash message"
No dice. Lastly, I have tried placing the flash message inline with the redirect:
redirect_to new_member_session_path, flash: { :notice => "Test flash message" } #also does not work
I am at a loss. Not sure why the flash messages are being lost. I am running Rails 4.1.2 and Devise 3.2.4
EDIT:
I have decided this is not the best way to customize authentication. Instead, I've created a custom Warden strategy and told devise about it:
#config/initializers/devise.rb
config.warden do |manager|
manager.intercept_401 = false
manager.default_strategies(:scope => :member).unshift :member_login
end
I put all my logic in that file:
Warden::Strategies.add(:member_login) do
def member_params
ActionController::Parameters.new(#response).permit(:first_name, :last_name, :role, :username, :parkplace_user_id)
end
def valid?
params[:member] && ( params[:member][:username] || params[:member][:password] )
end
def authenticate!
member = Member.find_by_username(params[:member][:username])
if authenticate_with_ppd_api
if member
member.update(member_params)
success!(member)
else
member = Member.create(member_params)
success!(member)
end
else
fail!("Incorrect username or password")
end
end
def authenticate_with_api
#response = HTTParty.post('https://path.to.server/admin/login', body: { username: params[:member][:username], password: params[:member][:password] } )
#response = JSON.parse(#response.body).first
#response["authenticated"]
end
end
Flash messages are appearing now, but I have other problems with sign in. I created a new question for that. Thanks!