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)
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?
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"
I currently have a Rails application that is connected to an existing SQL database. I am using Devise for my user management, however the pre-existing User table in the database uses a very customized password encryption method.
There is a web service I can connect to that passes a JSON object with the login information to authenticate whether it is valid or not, and I have to manage my own session and everything after that.
I attempted to follow "Railscast #250", and combine it with Devise and some Stack Overflow searches, but things are not going very well.
This is what I have now, but it isn't doing anything, and I just don't feel like I am on the right track with this.
class SessionsController < Devise::SessionsController
def new
super
end
def create
post_params = {
"RuntimeEnvironment" => 1,
"Email" => params[:session][:email],
"Password" => params[:session][:password]
}.to_json
user_params = RestClient.post 'http://some.ip/WebServices', post_params, :content_type => "json"
user = User.authenticate(user_params)
if user
session[:user_id] = user.user_id
redirect_to root_path
else
flash.now.alert = "Invalid Username or Password"
render "new"
end
end
end
This is the JSON Object returned if there is a successful login:
{"Success":true,"ErrorMessage":"","ResponseString":"","LoginResultData":{"FailMessage":"","ResultCode":0,"User":{"AccountCompleteFlag":1,"CreationDtime":"\/Date(1430848539000-0400)\/","DeleteFlag":0,"Email":"john#doe.com","FailedPasswordCount":1,"HistoricalFlag":0,"IsDirty":false,"IsAdminFlag":0,"IsSiteAdminFlag":0,"LastLoginDtime":"\/Date(1447789258000-0500)\/","NameFirst":"Ttest","NameLast":"test","Password":"TRQt3d2Z7caDsSKL0ARVRd8nInks+pIyTSqp3BLxUgg=","PasswordLockDtime":"\/Date(-62135578800000-0500)\/","PasswordLockFlag":0,"PasswordResetCode":"","PasswordResetStatus":0,"Phone":"1-X-5555555555-","RegistrationSource":"Registration","UserId":100029,"UserType":1,"PhoneInfo":{"AreaCode":"555","CountryCode":"X","Extension":"","FirstThree":"555","InternationalPhoneNumber":"","IsDirty":false,"IsInternational":false,"LastFour":"5555"}}}}
And what is returned for a failed one:
{"Success":true,"ErrorMessage":"","ResponseString":"","LoginResultData":{"FailMessage":"Invalid email address","ResultCode":1,"User":null}}
Is there a way where I can use Devise's session management while connecting to the API?
You can still authenticate through Devise using the email and password that the user provided. The RestClient would just be like a double check: just make sure that there are no routes that the user can authenticate through besides going through the RestClient. You can check this by doing rake routes.
For checking whether the result code was valid, you can do some JSON parsing as follows:
authentication_response = RestClient.post 'http://some.ip/WebServices', post_params, :content_type => "json"
json_authentication_response = JSON.parse(authentication_response)
result_code = json_authentication_response["LoginResultData"]["ResultCode"]
if result_code == 0
# Authenticate
else
# Don't authenticate
end
I am using omniauth to let people sign up/sign in with Facebook and its working well ! But I wanted to add the omniauth-twitter gem to let them connect with Twitter.
I followed the same steps than when I set up the Facebook connect: https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
But when I signing up/in I get the following error:
ActionDispatch::Cookies::CookieOverflow in OmniauthCallbacksController#twitter
at the following URL:
http://localhost:3000/users/auth/twitter/callback?oauth_token=HRjON8J4bj9EcbjiELHcpHmSXo0cPd0wCHyuWG8ATZU&oauth_verifier=ZiZb1FAKZmNML1gVu5RKBLEGzbeAPPzC80QCpPDGU
I tried different things suggested on similar posts but none of these worked :(
Here is my configuration:
omniauth_callbacks_controller.rb => app/controllers/omniauth_callbacks_controller.rb
def twitter
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_twitter_oauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "twitter") if is_navigational_format?
else
session["devise.twitter_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
user.rb => app/models/user.rb
def self.find_for_twitter_oauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name # assuming the user model has a name
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.twitter_data"] && session["devise.twitter_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
devise.rb => app/config/initializers/devise.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, "KEY, "KEYPASSWORD
end
Any ideas what's wrong?
As Michael says in the comments, you're storing a large hash in the session and it's too big (you're using the default CookieStore and cookies can only contain 4KB of data). That hash provided by omniauth has all the data returned by twitter, which can be quite a lot. For example, see the README: https://github.com/arunagw/omniauth-twitter#authentication-hash
If the code in your question is all the code relating to twitter login, then it looks like you only need to keep the email in the session as that is all that is used by your new_with_session code. So your line in the else in twitter which is:
session["devise.twitter_data"] = request.env["omniauth.auth"]
could be something like:
session["devise.twitter_data"] = request.env["omniauth.auth"].select { |k, v| k == "email" }
However the major flaw with this is that twitter doesn't return an email address for a user, so data["email"] will always be nil in new_with_session anyway! So it's pointless keeping anything in the session if you are only later interested in the email which is never returned by twitter. Perhaps you instead want to retrieve a name to help prefill the registration form instead of the email address. In this case, you could just keep that in the hash from omniauth. If you want to keep a few things in the hash, then instead of selecting them all to put in the session, you could do something like:
session["devise.twitter_data"] = request.env["omniauth.auth"].delete_if("extra")
which will remove the "extra" nested hash which could help everything else to fit in the session.
For a complete solution you'll have to consider messy situations like dealing with people who have signed in with Facebook and then come and sign in with Twitter and want to use the same email address and merge with their existing account on your system.
In any case, note that if you are using Rails 3 then the session cookie is not encrypted so the user or anyone with access to their computer could read the contents of the cookie with whatever data from twitter you end up keeping in there. If you're using Rails 4, then the cookie should be encrypted to protect against that.
In my Rails application, I have a form for admin to create normal users. In create action, I am generating a reset password token, and sending a welcome mail to the user, with a link in it to reset his password. This is my code.
#user = User.new params[:user]
#user.reset_password_token = User.reset_password_token
#user.reset_password_sent_at = Time.now.utc
if #user.save
UserMailer.welcome_email(#user).deliver
..
This works fine, but I have another app with the same code, but uses devise 3.2.2 in which I get the this error.
NoMethodError - undefined method `reset_password_token' for User:Class:
I see that the method has been removed. How can I generate a reset password token and send it to a user?
Note: I don't want to send the default reset password email
After a lot of digging into devise's source code, I got it to work by doing this.
raw, enc = Devise.token_generator.generate(User, :reset_password_token)
#user.reset_password_token = enc
#user.reset_password_sent_at = Time.now.utc
if #user.save
UserMailer.welcome_email(#user, raw).deliver
..
Use raw as the reset_password_token