BCrypt::Errors::InvalidHash in SessionsController#create - ruby-on-rails

I've tried looking at previous questions and answers regarding this error, but the solutions didn't seem to work for me. The error is thrown on the line:
if customer && customer.authenticate(params[:session][:password_digest])
I am able to Sign Up a user (under a company) and sign up a company, but I get this BCrypt error whenever I try to log a user in. I only started using the has_secure_password stuff etc once my user model and company model etc. were in place, I just realised later that it was silly to not protect passwords.
Even if I create a new user now, I still get this error logging on.
I think this might be due to my relationships of my Users? (maybe completely not, I am very new to Ruby!) My users belong to a company (many to one), and when they sign up they choose a company from a list in order to be listed under it in the database. Here is my code relating to this error:
class SessionsController < ApplicationController
def new
end
def create
customer = Customer.find_by_email(params[:session][:email])
if customer && customer.authenticate(params[:session][:password_digest])
log_in customer #see sessions helper
remember customer #see sessions helper
redirect_to '/main'
else
redirect_to '/login'
end
end
def destroy
#session[:user_id] = nil
forget(current_customer)
session.delete(:customer_id)
#current_customer = nil
redirect_to '/'
end
end
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.timestamps
t.references :business, foreign_key: true
t.timestamps
t.string :first_name
t.string :last_name
t.string :email
t.string :password_digest
t.string :remember_digest
t.string :role
end
end
end
class CustomersController < ApplicationController
def new
#customer = Customer.new
#businesses = Business.all
end
def create
#customer = Customer.create(customer_params)
#customer.save!
session[:customer_id] = #customer.id
redirect_to '/'
rescue ActiveRecord::RecordInvalid => ex
render action: 'new', alert: ex.message
end
private
def customer_params
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password_digest, :business_id) #replace company with company ID
end
end
Please help me, I'm tearing my hair out :(

The error you're getting probably means that the password_digest stored for that user is invalid for some reason (empty perhaps).
Enter a rails console: rails console
and execute the following:
u = User.find_by(email: "your_user_email")
puts u.password_digest
and see what the output is.
(also as mentioned in a comment on your question, you should use the plain text password when you use the authenticate method)
Update
You shouldn't use the password_digest attribute directly, instead there are two attributes you should be using: password and password_confirmation (these attributes become available to you automatically when you use has_secure_password, so you don't need to define them).
So in your controller, instead of this:
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password_digest, :business_id) #replace company with company ID
You should have:
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password, :password_confirmation :business_id) #replace company with company ID
and edit your form accordingly to provide inputs for password and password_confirmation.
Now when you create your object using these params, it will automatically assign the password digest into password_digest by encrypting the clear text contained in password and password_confirmation.

Related

Why does my regular user have the same permissions as the admin user?

I am using this command in my views/welcome/index.html.erb:
<% if current_user.admin? %>
<%= link_to 'Post New Load Data!', new_article_path %>
<% else %>
<% end %>
Earlier today, this would allow only my admin user to see this path.
I installed Devise a few hours ago but ended up not liking it. So I have gone through and removed what I thought was every file that it created.
Earlier, if I wanted a regular user to see the path, I would use...
<% if current_user %>
instead of
<% if current_user && current_user.admin? %>
I don't if that is what is creating my problem. My migrations did get messed up so I had to reset everything and I created new migration under db/migrate/20161209013349_create_users.rb:
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_digest
t.string :admin, :boolean, null: false, default: false
t.timestamps
end
end
end
I checked my MySQL users table and the non admin user has a 0 under the admin column. My admin user has as 1 under the admin column. There is a 0 under the boolean column for both regular user and admin.
my application_controller.rb:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def authorize
redirect_to '/login' unless current_user
end
end
users_controller.rb:
class UsersController < ApplicationController
def new
end
def create
if User.exists?(email: params[:user][:email])
redirect_to '/articles?user_or_pass_already_exists'
else
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
Any ideas on what caused this?
I think it's the way I set up this model.
EDIT
I also created this method for my admin in /app/models/user.rb:
class User < ApplicationRecord
has_secure_password
# convienience method to access the vaulue of admin:
def admin?
admin
end
# this makes sure the same email and user can't be registered twice
# this only works well if you are only wanting to validate one field such as name
#validates :email, uniqueness: true
end
This is why I am using admin? in my current_user.admin?
First, You must understand that even If you hide links from user You need server side check (authorization) before every action. cancancan gem will help you.
Now, for your question,
def admin?
admin
end
This method will return value of admin column.
According to this line in your migration
t.string :admin, :boolean, null: false, default: false
My guess is column type of admin column in database is string. Where It should be of type boolean. All string values are true in ruby.
2.2.0 :004 > if "0"
2.2.0 :005?> puts "I am true"
2.2.0 :006?> end
(irb):6: warning: string literal in condition
I am true
So either change your column type to boolean or change your method in user.rb like this
def admin?
admin == "1"
end

Can you check previously used passwords (password history) using Authlogic?

I am using Authlogic in a rails app for password validation. I would like to ensure that the user doesn't use any of the past 10 used passwords. Does Authlogic allow you to do that, or do you have to hand roll something?
To make sure that your users dont repeat passwords you will need a password history
$ rails g migration CreatePasswordHistory
class CreatePasswordHistories < ActiveRecord::Migration
def self.change
create_table(:password_histories) do |t|
t.integer :user_id
t.string :encrypted_password
t.timestamps
end
end
end
Now you can update the users model to save the password to the password history model something like:
class AdminUser < ActiveRecord::Base
include ActiveModel::Validations
has_many :password_histories
after_save :store_digest
validates :password, :unique_password => true
...
private
def save_password_history
if encrypted_password_changed?
PasswordHistory.create(:user => self, :encrypted_password => encrypted_password)
end
end
end
Finally create a model called unique_password_validator
require 'bcrypt'
class UniquePasswordValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.password_histories.each do |password_history|
bcrypt = ::BCrypt::Password.new(password_history.encrypted_password)
hashed_value = ::BCrypt::Engine.hash_secret(value, bcrypt.salt)
record.errors[attribute] << "has been used previously." and return if hashed_value == password_history.encrypted_password
end
end
end
Hope this helps
Happy Hacking

How to make an admin user using sign up with google gem in rails 3

I am making an app that I want to only use google to log in (because I am using other google api and i want to sync account.) I also want admin to be able to make other user admin too.
I want to make some user admin, but if they are log in with google, how can i make this exception in controller?
Is it best to save specific user in database?
I'm so confuse! please help.
right now this is what i have for admin
omniauth for checking google login
class OmniauthCallbacksController < ApplicationController
def google_oauth2
if auth_details.info['email'].split("#")[1] == "company_name.net"
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in Through Google!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
flash.notice = "Please provide a password"
redirect_to new_user_registration_url
end
else
render :text => "Sorry this site is for company_name employees only"
end
end
end
migration for admin to user
class AddAdminToUser < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, :default => true
end
end
table for user roles
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps
end
end
end
HaveAndBelongToMany migration
class UsersHaveAndBelongToManyRoles < ActiveRecord::Migration
def self.up
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
end
def self.down
drop_table :roles_users
end
end
First, I think you probably want to make new users default to NOT being admins, and only set that admin boolean to true if a current admin later makes them an admin.
To authenticate people, I suggest using the omniauth gem with one of the Google strategies. It will help validate their credentials against the user's Google account. Then you can store any information about that user relevant to your app, including whether they're an admin, in your own database, and use that information for authorization within your app. Hope that helps.

Rails: Undefined method error on authentication

I am trying to create an authentication system on RAILS with find_by_user_name_and_password()
I have the following problem:
undefined method `find_by_user_name_and_password' for #<Class:0x007fae373d5698>
Rails.root: /ror/blog/Blog`
Application Trace | Framework Trace | Full Trace
app/controllers/sessions_controller.rb:7:in `create'
Here is my code from sessions_controller
class SessionsController < ApplicationController
skip_before_filter :authorizes
def new
end
def create
user = User.find_by_user_name_and_password(params[:name], params[:password])
if user
session[:user_id] = user.id
redirect_to admin_url
else
redirect_to login_url, alert: "Bad datas"
end
end
def destroy
session[:user_id] = nil
rederect_to blog_url, notice: "End seans"
end
end
model defenition:
class User < ActiveRecord::Base
attr_accessible :name, :password_digest, :password_confirmation
validates :name, presence: true, uniqueness: true
#validates_confirmation_of :password
has_secure_password
end
Migrate
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :password_digest
t.timestamps
end
end
end
Looking at your model that you just posted, I see you don't have a password field... so no find_by_whatever_and_password.
You need to use
user = User.find_by_name(params[:user][:name])
if user && user.authenticate(params[:user][:password])
# etc.

Rails Scaffold destroy not working as expected

I was adding uniqueness validation to the model in charge of user registration. The username should be uniqueness.
I created a user when testing some days ago, lets call it "example-guy". Then I deleted him from the scaffolding user interface and now when trying to register a new user as "example-guy", it returns that the name has already been taken.
So how can I fix this from the DB without reverting to its "birth-state" and modify the controller to actually destroy the table entry?
Or maybe Rails is keeping track of what was writed to DB, even after destruction?
Im using omniauth-identity, the user model:
class User < ActiveRecord::Base
attr_accessible :name, :provider, :uid, :email
# This is a class method, callable from SessionsController
# hence the "User."
def User.create_with_omniauth(auth)
user = User.new()
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
# ADD EMAIL
user.email = auth["info"]["email"]
user.save
return user
end
end
User controller's destroy function:
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
Validations are made trought "Identity" model inherited from omniauth's active record:
class Identity < OmniAuth::Identity::Models::ActiveRecord
attr_accessible :email, :name, :password_digest, :password, :password_confirmation
#attr_accessible :email, :name, :password_digest :password, :password confirmation
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :in => 6..24
validates_format_of :name, :with => /^[a-z]+$/
validate :name_blacklist
validates_uniqueness_of :email, :case_sensitive => false
validates_format_of :email,
:with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
:message => "doesn't look like a proper email address"
#validates_presence_of :password, on: :create
#validates_presence_of :password_confirmation
validates_length_of :password, :in => 6..24
def name_blacklist
unless self.name.blank? then
case self.name
when "user", "photo", "photos",
"application", "sessions",
"identities", "home"
self.errors.add(:name, "prohibited")
end
end
end
end
Identities controllar manage registration form sent to omniauth and calling new.html.erb:
class IdentitiesController < ApplicationController
def new
#identity = env['omniauth.identity']
end
end
Thanks in advance.
I'm not entirely sure what caused the problem, but I came across a similar problem but with email addresses with a Devise based user.
I was wanting to re-use the same email on a different user. I changed the original user to a different email, but when I then tried to update the second user with the "now unique" email, it failed the unique validation.
A query for users with that email returned nothing.
It seemed to be cache-related to the uniqueness constraint, as restarting the server, after deleting the email address, fixed the problem.

Resources