I am implementing a validation scheme and am using the bcrypt-ruby gem.
require 'bcrypt'
class User < ActiveRecord::Base
include BCrypt
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :password, :presence => true, :on => :create,
:confirmation => true,
:length => {:within => 6..12}
before_save :encrypt_password
def has_password?(submitted_password)
self.encrypted_password == submitted_password # this calls a method in bcrypt
# File lib/bcrypt.rb, line 171
# def ==(secret)
# super(BCrypt::Engine.hash_secret(secret, #salt))
# end
end
private
def encrypt_password
self.encrypted_password = Password.create(password, :cost => 5)
end
end
Now in the console I create a new user
>> user = User.create!(:name => "test", :email => "test#test.com", :password => "foobar", :password_confirmation => "foobar")
=> #<User id: 1, name: "test", email: "test#test.com", created_at: "2011-06-23 05:00:00", updated_at: "2011-06-23 05:00:00", encrypted_password: "$2a$10$I7Wy8NDMeVcNgOsE3J/ZyubiNAESyxA7Z49H4p1x5xxH...">
And if I check if the password is valid I do the following:
>> user.has_password?("foobar")
=> true
but if I get the user from the database it fails:
user = User.find(1)
user.has_password?("foobar")
=> false
Why does that happen and how can I implement bcrypt to make this work?
Thank you in advance.
My guess would be that since encrypted_password is stored in the database as a string and not a BCrypt::Password, you're not calling into BCrypt's ==, but rather String's ==. You have to instantiate an instance of the Password around the string hash value. That would be where I'd look.
As described over here you have to use the password class of Bcrypt to utilize the ==
def has_password?(submitted_password)
Bcrypt::Password.new(self.encrypted_password) == submitted_password
end
Related
I am trying to implement a user authentication system inside rails, this is my model:
class User < ActiveRecord::Base
attr_accessible :id, :email, :name, :password, :created_at, :updated_at
has_secure_password
before_save { email.downcase! }
validates :email, presence: true, :uniqueness => { :case_senstive => false }
validates :name, presence: true
validates :password, presence: true, length: { minimum: 6 }
end
Running in the console i can read the User table successfully, then when i try to create a record:
User.new(:name => "A", :email => "a#a.a", :password => "password")
running valid on it retrurns true, but when saving the record, i get error:
users.password may not be NULL
Extracting the password out of the hash works fine.
What is the problem?
Thanks
Where are you setting :password_confirmation? The example in the docs suggests you need it.
http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html
Main problem is in has_secure_password. If you are using that you should have string field :password_digest (it's where your encrypted password will be saved). And you need delete field :password and your validator of presence for that too. After add gem 'bcrypt-ruby', '~> 3.0.0' .
And now that should work
user = User.new(:name => "A", :email => "a#a.a",
:password => "password", :password_confirmation => "password")
But better use great gem https://github.com/plataformatec/devise . It has everything you need.
NEW ADDED
you should have attr_accessible for :password_confirmation too and that field should be used in your form.
more info about has_secure_password
I'm just following Ruby on Rails 3 Tutorials (Mhartl) chapter-7 at the stage of 7.3.2 name and Gravatar.
Here I am facing a problem when I open on my browser it's says:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with id=1
Rails.root: C:/RubyOnRails/MyWorkPlace/sample_app_1
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:5:in `show'
Request
Parameters:
{"id"=>"1"}
Show session dump
Show env dump
Response
Headers:
None
Also I pasted below User_controller.rb and user.rb
user.rb:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :pasword
attr_accessible :login,
:username,
:email,
:password,
:password_confirmation,
:remember_me
email_regex = /\A[\w+\-.]+#[a-z\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :pasword, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
users_controller.rb:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#title = "Sign up"
end
end
Are you sure you created any user with id=1 ?
To check, go to rails console and get the user with id 1. If there is no user, then create one.
At firest, I see you have attr_accessor :pasword
I think it should be :password
Ontopic:
There are some actions missing in the restful controller, so it wont be possible to create a user.
See http://guides.rubyonrails.org/getting_started.html#rest for more details on RESTful controllers.
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#user = User.new #this creates a empty user object to be filled with signup data
#title = "Sign up"
end
def create
#user = User.new(params[:user]) #this creates a new user object with the data you entered before.
if #user.save #if the data is valid, save it
redirect_to user_path(#user) #and go to the #user show action
else
render :action => :new #edit the invalid user data
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to user_url(#user)
else
render edit_user_url(#user)
end
end
def index
#users = User.all
end
def destroy
#user = User.find(params[:id]
#user.destroy
redirect_to :action => :index
end
end
edit: complete restful actions
I had the same problema. In my case, my 'redirect_to' on my detroy action was missin a 's' in 'posts_path'. It was post_path Noob, but worth i had checked up.
The reason you could not find the "user/1" is when you Added microposts to the sample data(db/seeds.rb) by typing
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
You forgot the "END" of the previous code, so the full picture of db/seeds.rb is
User.create!(name: "Example User",
email: "example#railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}#railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
In attempting to seed my database I ran into a validation error on my User model's email attribute. The error:
Validation failed: Email is too short (minimum is 5 characters), Email is invalid
The thing is, my email is xxxxxxxx#gmail.com. I have five characters. Sorry for the beginner question but I don't know what is going on. I recently followed Railscasts to reset a User's password, and enable CanCan. I'm not sure if CanCan would affect anything, but prior to exploring that new functionality I've been able to fully seed my database without problems. I've pasted in some of my code below. I'm running Rails 3.0.5 and Ruby 1.9.2.
An example of how I create a User in my seed file:
me = User.create(:email => 'me#gmail.com', :password => 'test', :profile => my_profile)
User.rb model:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :password, :password_confirmation
before_save :encrypt_new_password
before_create { generate_token(:auth_token) }
before_validation :downcase_email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within => 4..20 },
:presence => true,
:if => :password_required?
Add :email to attr_accessible to allow mass assignment on it. Without that the email field will not even be set so validation will fail.
I'm using a nested model form for sign-up and am working through the kinks as a beginner. One issue that popped up in particular though that I don't really get is user.email is returning as nil.
Before I started playing around with the nested model form, I could create records in the console wihtout a problem. Now, however I can't create records and some of the latest records created have nil as their email. (I'm not sure if it has anything to do with the nested model at all, but that's my reference point for when it started going haywire.)
If I go into rails console to create a new User/Profile, I follow this process:
user = User.new
user.email = ""
user.password = ""
user.profile = Profile.new
user.profile.first_name = ""
...
user.profile.save
user.save
Everything goes well until user.save, which gives me the NameError: undefined local variable or method 'params' for #<User:>. In rails console it pinpoints to user.rb:25 in create_profile
So here is my User model:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within 4..20 },
:presence => true,
:if => :password_required?
before_save :encrypt_new_password
after_save :create_profile
def self.authenticate(email, password)
user = find_by_email(email)
return user if user && user.authenticated?(password)
end
def authenticated?(password)
self.hashed_password == encrypt(password
end
protected
def encrypt_new_password
return if password.blank?
self.hashed_password = encrypt(password)
end
def password_required?
hashed_password.blank? || password.present?
end
def encrypt(string)
Digest::SHA1.hexdigest(string)
end
end
Can anyone help me figure out what's going on?
UPDATE: I tried changing my regex but I'm still seeing nil for email. Though a prior SO post said not to blindly copy regex without testing, so maybe I just didn't test it correctly. Good news though: I no longer get the error.
attr_accessor simply defines a "property" on the object and has no relation to the attributes of a ActiveRecord model (attributes is a Hash of the fields and values obtained from a table).
ActiveRecord does not save such "properties" as defined by the attr_accessor. (Essentially, attr_accessor defines a attr_reader and attr_writer (i.e. "getter" and "setter") at the same time)
Ok.
1) I need to validate :link in my model and do that only if it is not blank (or nil).
2) If :link is not blank and standard validation passes — I need to run my custom validation method to check URL availability.
By "standard" validation I mean something like this:
validates :link, :presence => true, :uniqueness => true,
:format => { :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix }
What is the correct way to implement this?
It checks for validation in your model only if link isn't blank:
validates_presence_of :link, :uniqueness => true,
:format => { :with => /^(http|https)://[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(/.)?$/ix }, :if => :link_present?
def link
self.link
end
def link_present?
link.present?
end
Ok. With friends help I finally solved this.
class Post < ActiveRecord::Base
# skipped some things not necessary
validates_format_of :link, :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix , :allow_blank => true
validates_length_of :link, :maximum => 2000
validates_uniqueness_of :link, :allow_blank => true
validate :ensure_link_is_available, :if => proc{|post| post.link.present? && post.errors.empty?}
def ensure_link_is_available
begin
require "net/http"
url = URI.parse(self.link)
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)
rescue
# error occured, add error
self.errors.add(:link, 'The requested URL could not be retrieved')
else
# valid site
if (res.code.to_i > 308)
error_message = 'Server responded with ' + res.code
self.errors.add(:link, error_message)
end
end
end
end
validates_format_of :url_field, :with => URI::regexp(%w(http https))