I have an API built with Roda + Sequel stack. Here is how my User model looks like:
# frozen_string_literal: true
# {User} is model responsible for storing {User} authentication informations like email and password.
#
# #!attribute id
# #return [UUID] ID of the {User} in UUID format.
#
# #!attribute email
# #return [String] Email of the {User}, it's stored in the PostgreSQL citext column.
#
# #!attribute password_hash
# #return [String] {User} hashed password with bcrypt.
#
# #!attribute created_at
# #return [DateTime] Time when {User} was created.
#
# #!attribute updated_at
# #return [DateTime] Time when {User} was updated
class User < Sequel::Model
# It returns instance BCrypt::Password based on value in password_hash column.
#
# #return [BCrypt::Password ] based on value in password_hash column.
#
# #example Get {User} password hash:
# User.new(password: 'test').password #=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
def password
#password ||= BCrypt::Password.new(password_hash)
end
# It sets password_hash column with hashed user password.
#
# #return [String] user password hash.
#
# #example Set {User} password:
# User.new(password: 'test').password_hash #=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
def password=(new_password)
#password = BCrypt::Password.create(new_password)
self.password_hash = #password
end
end
and I have the following test:
describe 'update user password' do
let(:params) { { password: 'new-password' } }
before do
put '/api/v1/update_password', params
user.reload
end
it 'returns 200 HTTP status' do
expect(response.status).to eq 200
end
This one is failing.
it 'updates user password' do
expect(user.password == 'new-password').to eq true
end
# This one is passing.
it 'updates user password' do
expect(BCrypt::Password.new(user.password_hash).is_password?('new-password')).to eq true
end
end
This example is failing:
it 'updates user password' do
expect(user.password == 'new-password').to eq true
end
but this one is passing:
it 'updates user password' do
expect(BCrypt::Password.new(user.password_hash).is_password?('new-password')).to eq true
end
Could someone explain to me why my first example is failing?
Rewrite this one
it 'updates user password' do
expect(user.password == 'new-password').to eq true
end
to this one
it 'updates user password' do
expect(user.password).to eq(BCrypt::Password.create('new-password'))
end
Your ruby object User contains hashed version of that password
user.password
=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
'$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS' == 'new-password'
=> false
and in order to compare them you have to bcrypt your 'new-password' and compare two hashes. You can easily crypt any string with bcrypt, but decrypt it will take much time (years). It is common before verifying password, encrypt your input password with the same algorithm that your database contains
You're answering your question yourself: you're hashing the password before it's saved (in the password setter in User.rb:
# It sets password_hash column with hashed user password.
#
# #return [String] user password hash.
#
# #example Set {User} password:
# User.new(password: 'test').password_hash #=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
def password=(new_password)
#password = BCrypt::Password.create(new_password)
self.password_hash = #password
end
In your first test, you're trying to compare a hashed password to an unhashed string.
Related
I'm currently working on Ruby and I can't get, what is the difference between
def create_hash_digest(password)
BCrypt::Password.create(password)
end
def verify_hash_digest(password)
BCrypt::Password.new(password)
end
This sample is taken from Udemy course on complete Ruby on Rails
#create is used to Hash a secret, returning a BCrypt::Password instance i.e encryption
#new is used to decrypt BCrypt::Password instance with the data from a stored hash.
include BCrypt
# hash a user's password
#password = Password.create("my grand secret")
#password #=> "$2a$12$C5.FIvVDS9W4AYZ/Ib37YuWd/7ozp1UaMhU28UKrfSxp2oDchbi3K"
# store it safely #user.update_attribute(:password, #password)
# read it back
#user.reload!
#db_password = Password.new(#user.password)
# compare it after retrieval
#db_password == "my grand secret" #=> true
#db_password == "a paltry guess" #=> false
#create converts your password into a hash:
'my_password' ~> '$2a$12$C5.FIvVDS9W4AYZ/Ib37...'
while #new converts it back.
So a = first is
=> <Ng::EntityConfiguration id: 15903, entity_id: 1, entity_type: "Ng::Company", key: "wpa2.psk", value: "[{"ssid":"Main-Hall-Staff","password":"abc123","dhcp":"Enabled"},{"ssid":"Main-Hall-Guest","password":"def456","dhcp":"Disabled"}]", created_at: "2016-11-08 11:03:51", updated_at: "2016-11-08 11:03:51", name: "WIFI/Main Hall">
I have a.value which is will return:
"[
{\"ssid\":\"Main-Hall-Staff\",\"password\":\"abc123\"},
{\"ssid\":\"Main-Hall-Guest\",\"password\":\"def456\"}
]"
My question is, how to update both password value and save it?
new_pass1 = 'xyz123'
new_pass2 = 'xyz321'
I have tried code (below) but this will only update first password if i only have one hash_array.
Here is my full code
def encrypt_pass
# get the actual password
parse = JSON.parse(self.value)
get_pass = parse.last['password']
# encrypt the password
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
encrypted = crypt.encrypt_and_sign(get_pass)
# save the new encrypted password
parse.first['password'] = encrypted
encrypt_pass = parse.to_json
self.value = encrypt_pass
end
Just to be clear, you're trying to update both the Main-Hall-Staff password and the Main-Hall-Guest password (all passwords) from your record to be the encrypted version of themselves? I'm assuming this method is called in a before_save callback of some sort? If you show more code related to the model I can give you more details.
def encrypt_pass
# Changed the name to devises, always use easy to understand naming
# Also rescuing from a JSON parse error, this isnt always recommended
# as it hides other errors that might be unrelated to parsing
devices = JSON.parse(self.value) rescue []
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
devices.each do |device|
# get the actual password
password = device['password']
# encrypt the password
encrypted_pass = crypt.encrypt_and_sign(password)
# Save the encrypted password
device['password'] = encrypted_pass
end
self.value = devices.to_json
end
Hopefully you have some logic surrounding when this method is called as you dont want to encrypt an already encrypted password.
I have a YAML file containing usernames and passwords.
Overview of YAML:
users:
test:
password: test
test2:
password: test2
I want to encrypt the password value into an MD5 hash using Digest::MD5 for example:
user:
Lost Bam:
password: testtesttest #<=I want to overwrite this password with a MD5 hash
In Digest is there a way to encrypt a hash value? If so how do I implement this into a YAML file?
md5.rb Source:
require 'yaml'
require 'digest'
private
def load_file
File.exist?('info.yml') ? YAML.load_file('info.yml') : {users: {}}
end
def read_file
File.read('info.yml')
end
def save_file( hash )
File.open('info.yml', 'w') { |f| f.write(hash.to_yaml)}
end
def add_user
hash = load_file
hash["users"][prompt('Enter username:')] =
{ "password" => prompt('Enter password:') }
puts "Encrypt information?"
information = gets.chomp
case input
when /yes/i
# hash = Digest::MD5.digest(["password"]'value')<-Doesn't work
#
#This is where I want to be able to encrypt the
#value of the password key that was entered by the user
#
# save_file( hash )
else
puts "Add another?"#Not completed yet
end
save_file( hash )
end
main.rb Source:
require_relative 'md5.rb'
def main
puts <<-END.gsub(/^\s*>/, '')
>
>To load information type "L" to quit system type "Q"
>
END
input = gets.chomp.upcase
case input
when "L"
add_user
when "Q"
exit_system
else
exit_lock
end
end
def exit_system
puts "Exiting..."
exit
end
def exit_lock
puts "Locked out, please contact system administrator"
exit
end
def restart
puts "Encrypt more?"
input = gets.chomp
if input =~ /yes/i
return true
else
exit_system
end
end
def prompt( message )
puts message
gets.chomp
end
main
You can use Digest::MD5:
require 'digest'
Digest::MD5.digest('value')
http://ruby-doc.org/stdlib-2.1.0/libdoc/digest/rdoc/Digest.html
I'm trying to implement what seems like a very simple authentication approach using Sinatra and BCrypt but clearly I'm missing something...
Users are preassigned a temporary password which is stored in plaintext in the db.
I authenticate against the temp password and then create both a salt and password_hash and write them as strings to the db (mongo in this case).
To authenticate I fetch the salt from the db and user password to compare.
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret(params[:password], password_salt)
user.set(:password_hash => password_hash)
user.set(:password_salt => password_salt)
end
end
post "/auth" do
#user = User.first(:email => params[:email])
#user_hash = BCrypt::Password.new(#user.password_hash) #because the password_hash is stored in the db as a string, I cast it as a BCrypt::Password for comparison
if #user_hash == BCrypt::Engine.hash_secret(params[:password], #user.password_salt.to_s) then
auth = true
else
auth = false
end
end
The value returned by BCrypt::Engine.hash_secret(params[:password], password_salt) is different than what is stored in the db (both are of class BCrypt::Password, but they don't match).
What am I missing here? Many thanks in advance for any insight!
Marc
BCrypt::Password is a subclass of String, and it overrides the == method to make checking passwords easier. When you do
if #user_hash == BCrypt::Engine.hash_secret(params[:password], #user.password_salt.to_s)
you end up performing the hash twice, and so they don’t match. If you compared directly with #user.password_hash rather than using BCrypt::Password.new you should see that they match.
The more “correct” way to use bcrypt-ruby for passwords is to not use the Engine class at all, just the Password class. You don’t need to manage the salt yourself, bcrypt takes care of that and includes it in the password hash string:
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret("s3kr1t!", password_salt)
puts password_salt
puts password_hash
produces something like this:
$2a$10$4H0VpZjyQO9SoAGdfEB5j.
$2a$10$4H0VpZjyQO9SoAGdfEB5j.oanIOc4zp3jsdTra02SkdmhAVpGK8Z6
You’ll get something slightly different if you run it, since a different salt will be generated, but you can see that the password hash includes the salt.
In your case, you want something like this:
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_hash = BCrypt::Password.create(params[:password])
user.set(:password_hash => password_hash) # no need to store the salt separately in the database
end
end
post "/auth" do
#user = User.first(:email => params[:email])
#user_hash = BCrypt::Password.new(#user.password_hash)
if #user_hash == params[:password] then # overridden == method performs hashing for us
auth = true
else
auth = false
end
end
I'm trying write a Ruby script that checks if user credentials are valid using an active directory server. Here's what I've tried so far:
require 'rubygems'
require 'net-ldap'
host = '10.4.1.6'
port = 389
username = 'username'
password = 'password'
ldap = Net::LDAP.new
ldap.host = host
ldap.port = port
ldap.auth "CN=#{username},CN=Users,DC=companyname,DC=ad", password
if ldap.bind
puts 'YES!'
puts ldap.get_operation_result.message
else
puts 'NO :-('
puts ldap.get_operation_result.message
end
If I enter a non existing username and an empty string as a password, the bind operation succeeds. If I enter a valid username and a valid/invalid/empty password, the bind operation fails with error message 'Invalid Credentials'.
I've looked at other threads and read the net-ldap documentation but I can't figure out what I'm doing wrong.
Can someone give me some ideas on how to achieve this?
Thanks in advance for any replies :-)
Edit:
As #StuartEllis suggested, the problem was with the user identifier. To figure out the correct DN, I used the following script (taken from the net-ldap documentation):
ldap.auth "CN='adminUser',CN=Users,DC=companyname,DC=ad", 'adminUserPwd'
ldap.bind
treebase = "DC=companyname,DC=ad"
filter = Net::LDAP::Filter.eq( "mail", "username#companyname.com" )
attrs = ["mail", "cn", "sn","objectclass"]
ldap.search( :base => treebase, :filter => filter, :attributes => attrs, :return_result => false ) do |entry|
puts entry._dump 0
end
I then retried using my original script (above) with the obtained DN and voila!
I would guess that your LDAP account details aren't correct, but your LDAP server accepts anonymous binds, which is why it works when you don't specify a valid username and password. LDAP user identifiers are very fiddly, so I'd suggest double-checking the whole thing, including the case of the parts.
Here is sample code I use with the net-ldap gem to verify user logins from the ActiveDirectory server at my work:
def name_for_login( email, password )
email = email[/\A\w+/].downcase # Throw out the domain, if it was there
email << "#mycompany.com" # I only check people in my company
ldap = Net::LDAP.new(
host: 'ldap.mycompany.com', # Thankfully this is a standard name
auth: { method: :simple, email: email, password:password }
)
if ldap.bind
# Yay, the login credentials were valid!
# Get the user's full name and return it
ldap.search(
base: "OU=Users,OU=Accounts,DC=mycompany,DC=com",
filter: Net::LDAP::Filter.eq( "mail", email ),
attributes: %w[ displayName ],
return_result:true
).first.displayName.first
end
end