For the purpose of a new web app, I would need on my sign up page (which is administrator only) just only one email field.
The thing is that I'm totally new at rails and so even basics things like that are for me really difficult...
I created my authentification using Railscast #270 which uses has_secure_password method.
For now, everything works great except that I dont need all this bullcrap...
I also want to use Action Mailer to send the generated password to his email adress.
A hex(8) password would be perfect (I have seen SecureRandom but it seems to be depreciated)
Users_Controller:
class UsersController < ApplicationController
skip_before_filter :is_connected?, :only => [:new, :create]
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
# Tell the Mailer to send a welcome Email after save
Mailer.confirm_email(#user).deliver
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
end
User_model:
class User < ActiveRecord::Base
attr_accessible :email
has_secure_password
validates_presence_of :password, :email, :on => :create
end
For now, in my view, I have 2 fields. But as I said earlier, I only want one.
I would like to keep using has_secure_password which seems to offer a pretty good security regarding hash/salt.
Rails provides ActiveSupport::SecureRandom which either (depending on the Ruby version) is just a bridge to Ruby's SecureRandom or reimplemented it on older versions of Ruby (if my memory is correct SecureRandom was added in 1.8.7)
Now that all of the versions of Ruby that Rails supports have SecureRandom built-in ActiveSupport::SecureRandom is no longer needed and has been deprecated. SecureRandom itself is going nowhere -
require 'securerandom'
SecureRandom.hex(8)
should do fine (you might want to consider SecureRandom.urlsafe_base64 for a more compact representation of the same amount of actual randomness)
Here is one simple code for random password with lenth 8
rand_password=('0'..'z').to_a.shuffle.first(8).join
Hope it will help.
Sometimes things from Rails are deprecated because they duplicate functionality that has been added to Ruby core, and SecureRandom seems to be one of those things.
You can use any of those random generator methods to produce a one-time-use password.
To Create Random and unique token/password
class User < ActiveRecord::Base
before_create :generate_password
def generate_password
self.password = loop do
random_token = SecureRandom.urlsafe_base64
# If you are using FFaker gem then you can use it otherwise
# SecureRandom is great choice
# random_token = FFaker::Internet.password
break random_token unless User.exists?(password: random_token)
end
end
end
The main object here is to generate random token and do not repeat that token in the database. It can be really useful for some cases like generating unique token, unique invoice number, etc
Related
this is my first post on stack. I am just curious as I have read regarding password hashing and more often I was told to use BCrypt instead. However, are there any documentations that I can use in order for me to try and hash the passwords on my own? I know using BCrypt is optimal for safety reasons but I would like to play around and understand the codes or functions surrounding hashing a password when the user signs up without using BCrypt or any other gems.
For this, I have tried using 'SecureRandom' and 'Digest' but I still seem to be unable to get the desired output. Perhaps I am still new at this.
This is my codes. I am very new at this.
require 'digest'
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url
else
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, password_hash([:password]))
end
def password_hash(password)
hash = Digest::SHA256.hexdigest(password)
end
end
Once I convert it into strings, the password column in the database will be empty after the user sign up. I know my codes are a messy but I would like to try to encrypt the passwords without using gems.
My team always use Devise gem for work with users. It simple for using and have
great functional as: password encrypt, send email, authorization and other
I want to ask user a question, and let him sign up only if the user answers my question correctly. I searched devise how-to acticles but my case doesn't seem to be there.
Is there an idiomatic way to deal with this situation?
The first thought might be to use javascript, but answers are stored in LDAP, and I expect it will be easier to deal with this in rails.
I was also thinking about disabling /users/sign_up route, invoke the action (devise/registration#new) manually and render the view (devise/registration/new).
Another way I can think of, is to run a background daemon, which will collect session id, where user answered the questions correctly. On correct answer user will be redirected to the publicly available sign up page, which will get check user's session id with the daemon.
Assuming you have cookie data signed (as is the default in Rails 3), you could do as you say and use the session:
# app/controllers/preauth_controller.rb
def new
end
def create
if params[:answer] == 'correct answer'
session[:preauthorized] = true
redirect_to sign_up_path
end
flash[:error] = 'Incorrect answer'
render :new
end
# app/controllers/users_controller.rb
before_filter :verify_preauth, only: [:new, :create]
def verify_preauth
redirect_to new_preauth_path unless session[:preauthorized]
end
If cookie data is not signed, however, the preauthorized key can be tampered with by the client and thus should not be trusted.
Provided that your page is encrypted in transit with HTTPS via TLS and you don't have any XSS vulnerabilities present, this should be sufficiently secure for your needs. If you feel this is a particularly sensitive piece of code, you would want more than the passing thoughts of a StackOverflow user to guide and implement a comprehensive approach to securing your application.
To improve on security of previous suggestions, the best one seems to be by coreyward, but it's insecure (regardless if cookies are encrypted or not - see my comment on the OP)
# app/controllers/preauth_controller.rb
def new
end
def create
if params[:answer] == 'correct answer'
# Create a secret value, the `token`, we will share it to the client
# through the `session` and store it on the server in `Rails.cache`
# (or you can use the database if you like)
#
# The fact that this token expires (by default) in 15 minutes is
# a bonus, it will secure the system against later use of a stolen
# cookie. The token is also secure against brute force attack
#token = SecureRandom.base64
session[:preauthorization_token] = #token
Rails.cache.write("users/preauthorization_tokens/#{#token}", "OK")
redirect_to sign_up_path
else
flash[:error] = 'Incorrect answer'
render :new
end
end
# app/controllers/users_controller.rb
before_filter :verify_preauth, only: [:new, :create]
def verify_preauth
# When we approve preauthorization we confirm that the
# `token` is known to the client, if the client knows the token
# let him sign up, else make him go away
token = session[:preauthorization_token]
redirect_to new_preauth_path unless token and Rails.cache.read("users/preauthorization_tokens/#{token}") == "OK"
end
Optional things to do / play with....
delete the successfully used Rails.cache entry when user is created
play with :expires_in settings if you want, you generally want it as short as possible and as long as needed :) but the rails default of 15 minutes is pretty good
there are nicer ways of going around this and similar security issues with cookies - namely you can create a server_session object which does basically the same as session but stores the data in Rails.cache with a random expirable token stored in session used to access the cache entry in much the same way we do here
simply go to server-side sessions and don't worry about session security, but this means longer response times due to your Rails.cache round trip (redis, memcache, AR, ...)
instead of OK into the cache value you can store a hash of values, if you need more data, safely stored on the host, to work this out
...
I have a little different approach.
Show the question,receive the response and verify it.
Set the encrypted session.
Override the devise's registration controller with this so that even if they visit the url directly and tried signing up they won't be able to
#app/controllers/registration_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :check_answered_or_not,only:[:create,:new]
def check_answered_or_not
if not session[:answered]==true
redirect_to question_path
end
end
private
def sign_up_params
params.require(:user).permit(:name,:phone,:password,:password_confirmation,:email)
end
def account_update_params
params.require(:user).permit(:name,:phone,:password,:password_confirmation,:email,:current_password)
end
end
my 2cents
So... May be it should be in module Validatable?
Generate Validatables controler with this Tools
Customize this controller something like this:
(Code of this module You could see This)
...
base.class_eval do
validates_presence_of :email, if: :email_required?
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: password_length, allow_blank: true
validates_presence_of :question, if: :question_required?
validates_format_of :question, with: answered_regexp, if: :answered_changed?
end
end
...
def email_required?
true
end
def question_required?
true
end
This is not complied solution, but I hope it help You...
I think the easiest way to do it is to change default devise controller with custom one with before_action in it:
# routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
With following controller implementation:
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_action :check_answers, only: :new # :new action is responsible for :sign_up route
private
def check_answers
unless session[:gave_answers]
redirect_to ask_questions_path
false
end
end
end
And setting session like this:
# somewhere in questions controller:
if answers_correct?
session[:gave_answers] = true
redirect_to new_registration_path
end
As soon as this controller inherits from Devise::RegistrationsController all behavior stays default except checking answers functionality.
Regarding your question about idiomatic way - this approach was described in official documentation. Your app - your logic, it's OK.
UPDATE:
In comments #bbozo pointed on certain security issues with this and other answers. To make it more securely, you could add expiration time and set some random secret token (more information in comments).
I'm Using Devise and i'm trying to build a requirement that only emails that are included in my white list can actually Sign Up.
Over Time emails will be added to that list. Meaning that Today there are 10 emails, tomorrow another 20+.
But i don't know quite yet how to achieve this.
I know that i have to Create my own Registrations Controller, and for the Validation i think i need something similar to this:
before_validation :whitelisted?
def whitelisted?
unless WhiteList.exists?(:email => email)
errors.add :email, "is not on our beta list"
end
end
However, i am clueless on how to start or continue this. I don't even know if that's the best Practice.
How do i add emails to that whitelist and where is even that whitelist?
If someone could be noob-friendly enough to explain this to me.
Try the following i think this could help you.
create new registration controller
class RegistrationsController < Devise::RegistrationsController
def create
unless WhiteList.exists?(:email => params[:user][:email])
errors.add :email, "is not on our beta list"
else
super
end
end
end
and in routes file replace existing with following
devise_for :users, controllers: { registrations: "registrations" }
Create new model WhiteList using following
rails g model whitelist email:string
and run rake db:migrate command.
after this start Rails console add email's using following command.
Whitelist.create(email: "test#user.com")
I found #Amit Sharma's answer useful, but it doesn't work straight out of the box. Here's what I came up with:
class RegistrationsController < Devise::RegistrationsController
def create
if WhiteList.exists?(:email => params[:user][:email].downcase)
super
else
flash[:error] = "Your email is not on our beta list."
redirect_to new_user_registration_path
end
end
end
class WhiteList < ActiveRecord::Base
before_save :downcase_email
validates :email, presence: true
def downcase_email
self.email = email.downcase
end
end
This solves for case sensitivities when whitelisting an email and produces a flash error message when a Whitelisted email isn't matched.
With the recent upgrade to Rails 4, updating attributes using code resembling the below does not work, I get a ActiveModel::ForbiddenAttributes error:
#user.update_attributes(params[:user], :as => :admin)
Where User has the following attr_accessible line in the model:
attr_accessible :role_ids, :as =>admin
# or any attribute other than :role_ids contained within :user
How do you accomplish the same task in Rails 4?
Rails 4 now has features from the strong_parameters gem built in by default.
One no longer has to make calls :as => :admin, nor do you need the attr_accessible :user_attribute, :as => admin in your model. The reason for this is that, by default, rails apps now have 'security' for every attribute on models. You have to permit the attribute you want to access / modify.
All you need to do now is call permit during update_attributes:
#user.update_attributes(params[:user], permit[:user_attribute])
or, to be more precise:
#user.update_attributes(params[:user].permit(:role_ids))
This single line, however, allows any user to modify the permitted role. You have to remember to only allow access to this action by an administrator or any other desired role through another filter such as the following:
authorize! :update, #user, :message => 'Not authorized as an administrator.'
. . . which would work if you're using Devise and CanCan for authentication and authorization.
If you create a new Rails 4 site you'll notice that generated controllers now include a private method which you use to receive your sanitised params. This is a nice idiom, and looks something like this:
private
def user_params
params.require(:user).permit(:username, :email, :password)
end
The old way of allowing mass assignment was to use something like:
attr_accessible :username, :email, :password
on your model to mark certain parameters as accessible.
Upgrading
To upgrade you have several options. Your best solution would be to refactor your controllers with a params method. This might be more work than you have time for right now though.
Protected_attributes gem
The alternative would be to use the protected_attributes gem which reinstates the attr_accessible method. This makes for a slightly smoother upgrade path with one major caveat.
Major Caveat
In Rails 3 any model without an attr_accessible call allowed all attributes though.
In Rails 4 with the protected_attributes gem this behaviour is reversed. Any model without an attr_accessible call has all attributes restricted. You must now declare attr_accessible on all your models. This means, if you haven't been using attr_accessible, you'll need to add this to all your models, which may be as much work as just creating a params method.
https://github.com/rails/protected_attributes
This problem might also be caused by the Cancan gem
Just add to application_controller.rb
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
Works without any further modifications of code
got it from here: https://github.com/ryanb/cancan/issues/835#issuecomment-18663815
Don't forget to add your new user_params method to the controller action:
def create
#user = User.new(user_params)
#user.save
redirect_to 'wherever'
end
def create
#user = User.create(user_params)
....
end
def update
#user = User.find(params[:id])
if #user.update_attributes(blog_params)
redirect_to home_path, notice: "Your profile has been successfully updated."
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :age, :others)
end
I'm using Devise with login credentials: email/password - no usernames
I just noticed that the login process is case sensitive for emails. so if you register with bob#apPle.com, and then try to log in with Bob#apple.com you get an error. Very confusing.
How can I make devise log people in with their email/password, and the email being case insensitive?
You can easily fix the issue like below.
# config/initializers/devise.rb
Devise.setup do |config|
config.case_insensitive_keys = [:email, :username]
end
One option is to override the find method used by devise. Something like:
# User.rb
before_save do
self.email.downcase! if self.email
end
def self.find_for_authentication(conditions)
conditions[:email].downcase!
super(conditions)
end
I added this to my User model to store it case-sensitive but make it case-insensitive during sign in:
def self.find_for_database_authentication(conditions = {})
self.where("LOWER(email) = LOWER(?)", conditions[:email]).first || super
end
It works on Heroku.
By the way, this is just a temporary fix as the issue has been resolved and this will be the default behavior on Devise 1.2. See this pull request for details.
I also had some solution which making work with email is case-insensitive for all Devise controllers (functionality):
class ApplicationController < ActionController::Base
...
...
prepend_before_filter :email_to_downcase, :only => [:create, :update]
...
...
private
...
...
def email_to_downcase
if params[:user] && params[:user][:email]
params[:user][:email] = params[:user][:email].downcase
end
end
...
...
end
I know it is not the best solution: it involves another controllers of another models and executes code which is not necessary for them. But it was just makeshift and it works (at least for me ;) ).
Kevin and Andres, thanks for your answers. It is really good solutions and useful. I wanted to vote them up, but I haven't enough reputation yet. So, I just tell 'thanks' to you. ;)
Lets wait for Devise 1.2
Devise addresses the issue here:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address