I was wondering why my rails is not saving my encrypted_password field.
Here is my UserController
class UserController < ApplicationController
require 'bcrypt'
before_filter :save_login_state, :only => [:new, :create]
def new
new_user = User.new(user_params)
new_user.numRatings = 0
if new_user.save
flash[:notice] = "You signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
end
redirect_to(:controller => 'sessions', :action => 'login')
end
def create
end
def update
end
def view
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
Here is my User model
class User < ActiveRecord::Base
require 'BCrypt'
attr_accessor :password, :encrypted_password
EMAIL_REGEX = /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
validates :name, :presence => true
validates :password, :confirmation => true, :presence => true, :on => :create
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password = BCrypt::Engine.hash_secret(password, salt)
end
end
def clear_password
self.password = nil
end
def self.authenticate(username_or_email="", login_password="")
if EMAIL_REGEX.match(username_or_email)
user = User.find_by_email(username_or_email)
end
if user && user.match_password(login_password)
return user
else
return false
end
end
def match_password(login_password="")
encrypted_password == BCrypt::Engine.hash_secret(login_password, salt)
end
end
In addition, I use this function to save it
def login_attempt
authorized_user = User.authenticate(params["user"]["email"],params["user"]["password"])
if authorized_user
session[:user_id] = authorized_user.id
flash[:notice] = "Wow Welcome again, you logged in as #{authorized_user.username}"
redirect_to(:action => 'home')
else
flash[:notice] = "Invalid Username or Password"
flash[:color]= "invalid"
render "login"
end
end
One thing that I suspect that I created my first migration
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :password
t.string :name
t.float :rating
t.integer :numRatings
t.timestamps
end
end
end
However, I changed the :password field to :encrypted_password and it's reflected in a the table. I've been stuck on this for 2 hours. I was wondering if anything sticks out. Thanks.
The logs say that the data is being injected minus the encrypted password
INSERT INTO "users" ("created_at", "email", "name", "numRatings", "salt", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["created_at", "2014-08-05 06:22:41.335373"], ["email", "w#w.com"], ["name", "w"], ["numRatings", 0], ["salt", "$2a$10$pagmnNzT4FWEBmaPmiLX1u"], ["updated_at", "2014-08-05 06:22:41.335373"]]
Your attr_accessor :encrypted_password sticks out - this is overwriting the Rails-generated attribute getter/setter with one that will simply set an instance variable called #encrypted_password in your model instance.
You mentioned you changed the migration :password field to :encrypted_password field, but are you sure you reinitialized the models? I.e. correctly migrated the changes? Finally, if you did change the migration, there is no need for an attr_accessor of encrypted_password. Having both of them may cause shadowing and be the problem.
Related
I followed RailsCast 274 to add Remember Me & Reset Password functionality to my app.
I have no problem locally, the app seems to run and authenticate users fine. The problem is when I deploy the production version to Heroku I get the error:
undefined method `find_by_auth_token!' for #<Class:0x007f35fbe37a78>
current_user is defined in my ApplicationController as:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
before_action :require_user
def current_user
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
def require_user
if current_user.nil?
redirect_to new_session_path
end
end
end
This is my SessionsController:
class SessionsController < ApplicationController
layout false
skip_before_action :require_user
def create
user = User.find_by(email: params["email"])
if user && user.authenticate(params["password"])
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
redirect_to root_path, notice: "Login successful!"
else
redirect_to new_session_path, alert: "Email or password incorrect"
end
end
def destroy
cookies.delete(:auth_token)
redirect_to new_session_path, notice: "Logout successful!"
end
end
And this is the User model:
class User < ActiveRecord::Base
has_secure_password
has_one :patient, :dependent => :destroy
has_one :clinician, :dependent => :destroy
accepts_nested_attributes_for :patient, :allow_destroy => true
accepts_nested_attributes_for :clinician, :allow_destroy => true
validates :password,
:length => { :minimum => 6 }, :if => :password_digest_changed?
validates_presence_of :password, on: :create
before_validation(on: :update) do
# only want confirmation validation to run if user enters password
self.password_confirmation = nil unless self.password.present?
end
# validates_uniqueness_of :email
before_create { generate_token(:auth_token) }
def send_password_reset
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
And in my schema.rb:
create_table "users", force: true do |t|
t.string "timezone"
t.boolean "terms_agreement", default: false
t.string "email"
t.string "password_digest"
t.string "auth_token"
t.string "password_reset_token"
t.datetime "password_reset_sent_at"
end
Why is this working in development but not production?
Ruby 2.2.1 & Rails 4.1.8
development:
PostgresSQL 9.4.1
It's an old tutorial, rails 4 has different dynamic matchers
Rails 3
User.find_by_auth_token!(cookies[:auth_token])
Rails 4
User.find_by!(auth_token: cookies[:auth_token])
I'm new to rails (and ruby in general), so my problem is probably easy to solve. I'm trying to create a simple app where you can create a user and log in. I'm encrypting the password with BCrypt and when i try to log in i get this error: BCrypt::Errors::InvalidSalt in SessionsController#login_attempt
Not sure what files i need to share to solve the problem, so i'll start by sharing the files where it says the error occours.
user.rb
class User < ActiveRecord::Base
before_save :encrypt_password
after_save :clear_password
attr_accessor :password
attr_accessible :username, :email, :password, :password_confirmation
EMAIL_REGEX = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+.[A-Z]{2,4}$/i
validates :username, :presence => true, :uniqueness => true, :length => { :in => 3..20 }
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
validates :password, :confirmation => true #password_confirmation attr
validates_length_of :password, :in => 6..20, :on => :create
def encrypt_password
if :password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password= BCrypt::Engine.hash_secret(:password, :salt)
end
end
def clear_password
self.password = nil
end
def self.authenticate(username_or_email="", login_password="")
if EMAIL_REGEX.match(username_or_email)
user = User.find_by_email(username_or_email)
else
user = User.find_by_username(username_or_email)
end
if user && user.match_password(login_password)
return user
else
return false
end
end
def match_password(login_password="")
encrypted_password == BCrypt::Engine.hash_secret(login_password, salt)
end
end
session_controller.rb
class SessionsController < ApplicationController
before_filter :authenticate_user, :only => [:home, :profile, :setting]
before_filter :save_login_state, :only => [:login, :login_attempt]
def login
#Login Form
end
def login_attempt
authorized_user = User.authenticate(params[:username_or_email],params[:login_password])
if authorized_user
flash[:notice] = "Wow Welcome again, you logged in as #{authorized_user.username}"
redirect_to(:action => 'home')
else
flash[:notice] = "Invalid Username or Password"
flash[:color]= "invalid"
render "login"
end
end
def home
end
def profile
end
def setting
end
def logout
session[:user_id] = nil
redirect_to :action => 'login'
end
end
I followed a tutorial to get this far, so if you can please explain the error too.
Thanks!
Is not necessary to have a salt field in the db, with the encrypted password should be enough. If you use BCrypt::Password instead of BCrypt::Engine you could save both the salt and enc_pasword in the same field. Try to change these methods in user.rb
def encrypt_password
self.encrypted_password = BCrypt::Password.create(password) if password.present?
end
def match_password(login_password="")
BCrypt::Password.new(password) == login_password
end
I have a rails app, for some reason my login action does not work. I put in a correct username/password in, however it does not redirect to the desired 'menu' action. It just redirects me to the login action everytime (I have set that to happen when the login is unsucessful). I state unless session[:user_id] . When I input the wrong password on purpose, the flash message is correct, it says "Invalid Username/Password", when the correct one is input, it doesn't which means it recognises it, somehow the session is not being created. Below is my code
Application Controller
protected
def confirm_logged_in
unless session[:user_id]
flash[:notice] = "Please Log In"
redirect_to(:controller => 'access', :action => 'login')
return false
else
return true
end
end
Access Controller (Where the magic should be happening)
Class AccessController < ApplicationController
layout 'admin'
before_filter :confirm_logged_in, :except => [:login, :attempt_login, :logout]
def index
menu
render('menu')
end
def menu
#display text & links
end
def login
#login form
end
def attempt_login
authorised_user = AdminUser.authenticate(params[:username], params[:password])
if authorised_user
flash[:notice] = "You are now logged in"
redirect_to(:action => 'menu')
else
flash[:notice] = "Invalid username/password"
redirect_to(:action => 'login')
end
end
def logout
session[:user_id] = nil
session[:username] = nil
flash[:notice] = "You have been logged out"
redirect_to(:action => 'login')
end
end
AdminUser Model
require 'digest/sha1'
class AdminUser < ActiveRecord::Base
# because we created a migration to change the name of the users tabe to admin_users we have to specify
# set_table_name("admin_users")
# or we can change the class name and file name like we did
attr_accessible :first_name, :last_name, :username, :email
attr_accessor :password
attr_protected :hashed_password, :salt
scope :named, lambda {|first,last| where(:first_name => first, :last_name => last)}
has_and_belongs_to_many :pages
has_many :section_edits
has_many :sections, :through => :section_edits
EMAIL_REGEX = /^[A-Z0-9._%+-]+#[A-Z)0-9.-]+\.[A-Z]{2,4}$/i
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :username
validates_length_of :first_name, :maximum => 25
validates_length_of :last_name, :maximum => 50
validates_length_of :username, :within => 3..25
validates_length_of :password, :within => 8..25, :on => :create
validates_uniqueness_of :username
validates :email, :presence => true, :length => {:maximum => 100}, :format => EMAIL_REGEX, :confirmation => true
before_save :create_hashed_password
after_save :clear_password
def self.authenticate(username="", password="")
user = AdminUser.find_by_username(username)
if user && user.password_match?(password)
return user
else
return false
end
end
def password_match?(password="")
hashed_password == AdminUser.hash_with_salt(password,salt)
end
def self.make_salt(username="")
Digest::SHA1.hexdigest("User #{username} with #{Time.now} to make salt")
end
def self.hash_with_salt(password="", salt="")
Digest::SHA1.hexdigest("Put #{salt} on the #{password}")
end
private
def create_hashed_password
unless password.blank?
self.salt = AdminUser.make_salt(username) if salt.blank?
self.hashed_password = AdminUser.hash_with_salt(password,salt)
end
end
def clear_password
self.password = nil
end
end
I found the solution. It was pretty simple. The problem was that I did not create the sessions when the login was made, this is why the login did not recognise the sessions because they were not initialised.
In the Access Controller I simply changed it to this :
def attempt_login
authorised_user = AdminUser.authenticate(params[:username], params[:password])
if authorised_user
session[:user_id] = authorised_user.id
session[:username] = authorised_user.username
flash[:notice] = "You are now logged in"
redirect_to(:action => 'menu')
else
flash[:notice] = "Invalid username/password"
redirect_to(:action => 'login')
end
end
The amendments are the two session lines added to the code
I have a problem and I need an idea how to fix my update method. I have an admin panel where I can create users. This form include name, mail, password, repeated password fields and it works fine. Then I want to have a list of all users and to edit these who I want. The problem is that I want to edit part of the information which is not included in the form of the registration and default is empty. In edit mode my form has two new fields - notes and absences. When I change these fields and call update method I see message that password and repeated password don't match which is validation in the registration but I do not have these files in edit mode. How could I fix this problem. This is part of my code:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#title = "Edit user"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
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
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
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
The validation for password presence is true when you are creating a user, but once a user has an encrypted password, you don't want to force it to be present in all form submissions in the future.
Active record supports adding conditions to validations, so I would suggest putting a condition on the password validation to make it only execute if the user object does not already have an encrypted password. The relevant snippet would be:
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :needs_password?
def needs_password?
encrypted_password.nil?
end
I'm very new to rails and I'm trying to accomplish the following authentication issue:
User makes a comment or grants "absolution" (similar to comment) and he gets some coins for it. Coins is the virtual currency in my app and is also a column in the users table.
Because of your kind help, I was already capable to update the coins value after writing a comment or grant absolution. However, when I write a comment and log out after that, my login name or password gets changed(?)...I can't login anymore with this account.
This is how my User model looks like:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation, :twitter_url, :homepage_url, :coins
has_many :comments, :dependent => :destroy
has_many :absolutions, :dependent => :destroy
has_many :ratings
has_many :rated_sins, :through => :ratings, :source => :sins
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
homepage_regex = /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :twitter_url, :format => { :with => homepage_regex }
validates :homepage_url, :format => { :with => homepage_regex }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
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
class << self
def authenticate(email, submitted_password)
user = find_by_email(email)
(user && user.has_password?(submitted_password)) ? user : nil
end
def authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
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
And this is my comments controller:
class CommentsController < ApplicationController
before_filter :authenticate, :only => [:create, :destroy]
def new
#comment = Comment.new
end
def create
#sin = Sin.find(params[:sin_id])
#comment = current_user.comments.build(params[:comment])
#comment.sin_id = #sin.id
if #comment.save
flash[:success] = "Comment created! Earned 20 coins."
coins_new = current_user.coins.to_i + 20
current_user.update_attribute(:coins, coins_new)
redirect_to sin_path(#sin)
else
flash[:error] = "Comment should have 1 - 1000 chars."
redirect_to sin_path(#sin)
end
end
def destroy
end
private
def authenticate
deny_access unless signed_in?
end
end
I assume, that it has something to do with the before_save encrypt_password method, but its only a guess. I really appreciate your help and suggestions!
Edit:
It gets warmer...It has something to do with the following line in the Comments Controller:
current_user.update_attribute(:coins, coins_new)
When he updates the :coins column, something seems to go wrong. If you need further info, just drop a comment. Thanks for your help!
Your problem is that you're encrypting the already encrypted password in your "encrypt_password" method.
So, when the user is a new_record?, you're taking the password (say it's "cat"), hashing it, and storing it in the database.
So, what gets stored is a cryptographic hash of "cat", which we'll say is "dog"
Then, the next time you're saving the user record, you're taking the hashed password ("dog") in line #2 of your "encrypt_password" method, and hashing it again, which we'll say generates "kangaroo".
Next time you log in, your app is hashing the password you enter into the login form "cat", hashing it to "dog", and comparing it to the hashed version in the database, "kangaroo". Oh, but "dog" doesn't match "kangaroo", so the login fails.
So change:
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
To either:
def encrypt_password
self.salt = make_salt if new_record?
self.password = decrypt(password) # decrypt it first to the plain text
self.encrypted_password = encrypt(password) # then re-encrypt the plain text with the salt
end
Or:
def encrypt_password
self.salt = make_salt if new_record?
if (password_has_changed?) # somehow you'll have to figure this out
self.encrypted_password = encrypt(password)
end