I have a Sessions controller that requires authentication for creating a session using .authenticate method of has_secure_password defined in the User model, as per below:
Edit:
class User < ApplicationRecord
before_save { self.email = email.downcase }
# Relationships
has_secure_password
has_many :pedidos
# Validations
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :nome, presence: true
validates :empresa, presence: true
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
validates :cpf, presence: true, length: { minimum: 11, maximum: 14 }
validates :password, presence: true, length: { minimum: 6 }
def admin?
self.admin
end
end
SessionsController
def create
user = User.find_by(email: params[:sessions][:email])
if user && user.authenticate(params[:sessions][:password])
flash[:success] = "Seja bem vindo(a), #{user.nome}!"
session[:user_id] = user.id
redirect_to user
else
flash.now[:danger] = 'Não foi possível realizar o login, '
flash.now[:danger] += 'combinação usuário/senha inválido'
render 'new'
end
end
Now I created an admin/Users controller that I would like to perform database operations without having to enter password and password_confirmation fields. In the current scenario I'm getting error messages saying "password and password_confirmation can't be blank"
How should I proceed? Thank you.
If presumably your problem is that you'd like to be able to save new Users to your db without needing a password, one way might be to just add a dummy password for admins as explained here
Another might be to skip validations when saving an admin to the db. (I haven't tried this with has_secure_password so I'm not positive that it'd work but worth a shot)
Related
I have a edit form for a client. It is also possible to change the password, but of course you don't want to change(and/or reenter) your password every time you change on of the other settings. To avoid updating the password I tried this:
def client_update_params
if admin? == true
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,:billing_informations)
else
if params[:client][:password].blank?
params[:client].delete("password")
params[:client].delete("password_confirmation")
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country)
else
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,
:password,:password_confirmation)
end
end
end
So the idea is to check if the password field is set or not. If it is set, update with new password, else do not update the password. But every time I hit submit(and leave the password field empty), the form validation says the password is to short....
Is there maybe a working/more elegant solution for this problem ?
EDIT:VALIDATIONS ON MODEL:
attr_accessor :is_admin_applying_update
attr_accessor :plain_password
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :company_name,presence:true
validates :address_street,presence:true
validates :address_number,presence:true
validates :address_city,presence:true
validates :address_zip,presence:true
validates :address_country,presence:true
validates :billing_informations,presence:true
has_secure_password
validates :password, length: { minimum: 6 }, :unless => :is_admin_applying_update
def Client.new_remember_token
SecureRandom.urlsafe_base64
end
def Client.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = Client.encrypt(Client.new_remember_token)
end
Remember that you don't really have a password attribute in your model. The password is stored encrypted in a field named password_digest.
You should only be validating the password attribute when a password and a password_confirmation is given. You can do this in your model validation rather than deleting things from the params hash.
Also, you should validate the existence of a password_confirmation if password is present.
Add a virtual attribute skip_password like the devise uses
In model
attr_accessible :skip_password
validates :password, length: { minimum: 6 }, :unless => :skip_password
def skip_password=(value)
#skip = value
end
def skip_password
#skip
end
In controller
#client.skip_password = true
I'm trying to do a custom authentication to better understand what's going on under the hood. It took a while to get it to accept password and password_confirmation, but now it just wont work and I'm all out of ideas
class UsersController < ApplicationController
def new
#user = User.new
end
def create
par = params[:user]
#user = User.new(params[:user])
if #user.verify_password_confirmation(par[:password], par[:password_confirmation]) && #user.save
sign_in #user
redirect_to user_url(#user)
else
render 'new'
end
end
end
Is there any danger to having password and password_confirmation in attr_accessor?
require 'bcrypt'
class User < ActiveRecord::Base
attr_accessor :password_confirmation, :password
attr_accessible :name, :email, :password, :password_confirmation
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d]\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
validates :password_confirmation, presence: true
def password=(password)
self.password_digest = BCrypt::Password.create(password)
end
def verify_password(password) #for the sessions, which obviously I can't check yet
BCrypt::Password.new(self.password_digest) == password
end
def verify_password_confirmation(pass, pass_con) #couldn't see how else to confirm it
pass_con == pass
end
def reset_session_token!
self.session_token = SecureRandom::base64(32)
self.save!
self.session_token
end
end
EDIT: Specifically the problem is that it's failing at either the #user.save or the verify_password_confirmation and re-rendering
Look at secure_password.rb on github. has_secure_password is much more simple, clear and easy to understand than other third party authentication gems or plugins.
Also note that has_secure_password uses bcrypt-ruby gem for generating password_digest. So if you want to roll your own authenticating system then you will want to think about its security as well.
I am trying to validate an email address.. whether it is present or not.. and whether it meets a regex criteria (/xyz/). I really need to do this on the controller or view level in rails as I am going to be dumping ActiveRecord for my app as I am planning on using a nosql database later.
Any hints? Suggestions?
In the model, it would be the following code.. but I'm trying to do this in controller or view:
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
class UsersController < ApplicationController
def create
user = User.new(params[:user])
if valid_email?(user.email)
user.save
redirect_to root_url, :notice => 'Good email'
else
flash[:error] = 'Bad email!'
render :new
end
end
private
def valid_email?(email)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
email.present? &&
(email =~ VALID_EMAIL_REGEX) &&
User.find_by(email: email).empty?
end
end
However, I would say that validation still belongs in the model, even if it isn't an ActiveRecord-based one. Please take a look at how to use ActiveModel::Validations:
http://api.rubyonrails.org/classes/ActiveModel/Validations.html
http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
http://asciicasts.com/episodes/219-active-model
You can leave it in the model even if you use nosql later on. Just use ActiveModel instead of ActiveRecord. For example, do not inherit from ActiveRecord.
class ModelName
include ActiveModel::Validations
attr_accessor :email
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
I just came across omniauth-identity which enables users to sign in and register without using a Facebook, Twitter, etc.
There is a step were you have to create a Identity model (I'm following this Railscast):
class Identity < OmniAuth::Identity::Models::ActiveRecord
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
end
Now, I already have an User model and a login and registration system (created by following the Ruby on Rails Tutorial):
user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
So I'm a bit confused. Should remove the lines that have to do with authentication in the User model (e.g. validation, attr_accesible, create_remember_token etc. along with the name and email fields in the users table)?
And remove sessions_helper.rb too?
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def current_user?(user)
user == current_user
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url
end
end
Because, correct me if I'm wrong, but I I think omniauth-identity handles that too (except for the current_user part.
Creating the Identity model is useful mostly to authenticate with multiple providers. Here's a good description of how to go about doing that:
https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
It answers your question what should be in the User model and what in the Identity model.
I have some password validation for my User model, and a create_with_omniauth method to get the information from the user's Facebook account:
user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
user.email = auth["info"]["email"]
end
end
end
Now when I click link_to "Sign in with Facebook, "auth/facebook" I get this error:
Validation failed: Password can't be blank, Password can't be blank,
Password is too short (minimum is 6 characters), Password confirmation
can't be blank
Because of this two lines in the User model:
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
How to bypass that validation when the user is trying to log in with the OmniAuth login method?
This can be done in 2 ways .
1) Just save a random secure generated number in the password field (better because it's easy and also to maintain the consistency) personally I have applied this method as the users who have signed through a social site will not login through site login.
2) Or, use an attr_accesor
:login_social (will be treated as boolean
validates :password, presence: true, length: { minimum: 6 }, :if => !login_social?
validates :password_confirmation, presence: true, :if => :login_social?
Whenever logging through any social site just make this field true. I followed the second method then turned to the first solution as it was better.
Personally I suggest you go for the first method
As #Aayush suggested to create a field to detect social login, then instead of creating your own validation you can
override this devise method:
def password_required?
self.social_login? ? false : super
end
in your model e.g User
My solution to this is to add a simple password to omniauth users, and make it impossible to sign in with those passwords if Rails detects that they are omniauth users. This simplifies my User model as I can still use has_secure_password, and makes the password irrelevant (and impossible to use) if they use a social login.
My User database table has the following:
name
email
password
password_confirmation
uid
provider
sessions_controller.rb
def create
auth = request.env["omniauth.auth"]
if auth
sign_in_with_auth(auth)
else
sign_in_with_password
end
end
sessions_helper.rb
def sign_in_with_auth(auth)
user = User.find_by(uid: auth['uid'])
if user
#sign in the user
else
#create a user with any password.
user = User.create! do |user|
...
user.password = 'bojanglesicedtea'
user.password_confirmation = 'bojanglesicedtea'
user.provider = auth['provider']
end
#then sign in the user
end
end
def sign_in_with_password
user = User.find_by(email: params[:sessions][:email].downcase)
if user == user.authenticate(params[:sessions][:password]) && user.provider.nil?
#sign in the user
#user.provider.nil? will be true only if it is not a social login user
else
#direct to error notification
end
end