password_digest value is unique for user? - ruby-on-rails

I am using rails 4 with has_secure_password which has password_digest in users table.i would like to store some unique value to cookie, password_digest is unique for user in users table? how to use it as unique token? can i?

As #JonathonReinhart said, don't re-use the password_digest, and since the authenticity_token for CSRF changes in the session for every form that is submitted, you can't use that here either. If you just need to generate a unique token for your User model, I recommend doing something like this:
rails generate migration AddAccessTokenToUser access_token:string:uniq:index
Then you can generate the token on create with a callback like so:
class User < ActiveRecord::Base
# Call backs
# ----------
before_create :generate_access_token
private
def generate_access_token
begin
self.access_token = SecureRandom.hex
end while self.class.exists?(access_token: access_token)
end
end
The begin-end-while will check that the SecureRandom.hex value will always be unique in the table.
Once you have this token, you can use it in a cookie or wherever.

Related

Setting default username in Rails

I am using devise gem for user management. So, I have a User model.
What I want to be able to do is have a username column in the User model. By default, I want to be able to set the default username for users as 'user'+id where id is unique for every user and a column in User model.
I want to be able to do something like:
class AddColsToUser < ActiveRecord::Migration[6.1]
def change
add_column :users, :username, :string, default: 'user'+id
end
end
Is that possible? Thanks for reading :))
I would recommend using a before_validation callback to create a new username. You can expand this a bit to keep retrying in case of failure, but this should hopefully help you get started.
Make sure to add a unique constraint or validation as well!
before_validation :set_username
private
def set_username
return if user.blank?
username = "{user}_#{rand(100)}"
end
Another option if you just want a prettier parameter in your url's is to override the to_param method in your user model. This will give you a friendly url like "1-my-name", but when parsed to an integer will just be "1" so rails will still find it when doing lookups.
def to_param
[id, name.parameterize].join("-")
end

Rails 5 has_secure_token encryption

In the Ruby on Rails has_secure_token gem/feature, it creates a unique token upon record creation and stores it in the database as plain text. If I am using that token to grant users access to an API, is there a security risk in storing that token as plain text in the database?
I was hoping there would be a way to encrypt the token column when the has_secure_token method commits the token to the database, similar to how bcrypt encrypts passwords into a database.
I have tried using gems such as attr_encrypted to store a hashed value of the token, but it seems to be incompatible with has_secure_token. Here is how my model is currently set up:
class User < ApplicationRecord
has_secure_token :token
# attr_encrypted :token, key: :encrypt_token, attribute: 'token'
# def encrypt_token
# SecureRandom.random_bytes(32)
# end
end
The commented code is attr_encrypted code that has proven to be incompatible. If someone knew if there was a way to safely encrypt a column in the database, while also using has_secure_token, I would greatly appreciate it!
Let me know if any more information is needed or this is confusing. Thanks in advance!
Rails 6 ActiveModel::SecurePassword#has_secure_password accepts an attribute name argument and will use BCrypt to set the value of a corresponding #{attribute}_digest column. The default attribute name is password and the model must have an accessor for a #{attribute}_digest attribute.
A simplified example with both a password and an api_token:
rails generate model User password_digest:string api_token_digest:string
Rails 6
class User < ApplicationRecord
has_secure_password #create a `password` attribute
has_secure_password :api_token, validations: false
before_create do
self.reset_token = SecureRandom.urlsafe_base64
end
end
Prior to Rails 6 you can directly invoke BCrypt to encrypt the token.
require 'bcrypt'
class User < ApplicationRecord
has_secure_password #create a `password` attribute
before_create do
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.api_token = SecureRandom.urlsafe_base64
self.api_token_digest = BCrypt::Password.create(api_token, cost: cost)
end
end

Is it possible to limit which fields get persisted?

I've got a User model that is utilizing mongoid. The model has a password, password_confirmation and encrypted_password field. The password and password_confirmation fields are populated at runtime with the value the user would type on the screen when creating a new user. When I persist, I don't want to persist the unencrypted password values, I only want to persist the value contained in encrypted_password. Is this possible? Is there something I can use to denote certain fields as not being persistable?
Thanks in advance
Chris
Here's a way:
Model only needs the password field and use a before_filter:
def User
before_save :hash_password
attr_accessible :password, :password_confirmation
def hash_password
#todo: improve by adding a salt
self.password = Digest::SHA1.hexdigest(self.password)
end
end
Notes:
Passwords should be stored using a one-way hash, and so passwords should not be 'decryptable'
Use a salt (a random value) and add that to the password before passing it to the hexdigest(). Store the salt in the database as well - say a column called password_salt.
password_confirmation is a virtual attribute and does not need to be defined in the model (rails will manage the details internally)

Proper flow for processing non-displayed database fields in RoR forms?

I have the following database table:
account
-
id
first_name
last_name
email_address
password_md5_hash
last_logged_in
last_ip_address
date_time_created
As you can see, an account record has several fields with values the user will not enter themselves. For instance, the password_md5_hash will be the hashed value of the password they enter, and date_time_created will be set to the current date/time when the record is created.
So, my form will not necessarily contain entry fields for all values, or the values entered will be manipulated before being written to the database (the same would be true if I wanted to strip HTML from a text field before saving to the database).
What would be the proper flow for 1) hashing the password entered in the form and storing that and 2) setting and storing the date_time_created field's value in the database on form submission? These things would take place in the model, no? Can someone provide a code sample or link to one that explains this?
Thanks.
The best way would probably to use an ActiveRecord callback, I will use an accessor here so when you create User.new(:password => "something") in your controller, it'll be assigned to something that's available, but not stored in the database.
class User < ActiveRecord::Base
attr_accessor :password
before_save :hash_password
...
private
def hash_password
attribute(:password_md5_hash => Digest::MD5.hexdigest(password))
end
end
I'm not that an MD5 is the best way to store passwords (I think the general accepted practice is to use a hash and a salt, check out the way restful_authentication or authlogic does it for best practices)
For date_time_created, you should checkout the 'created_at' and 'updated_at' fields that are already given to you. If you need another column, you can use the same callback technique if you need to manipulate it before saving to the database.
When using the attr_accessor, my understanding is you're basically creating a reader and writer, so:
attr_accessor :password
is equal to
#Reader
def password
#password
end
#Writer
def password=(pwd)
write_attribute( :password, pwd )
end
In cases like this where you need to modify the field before saving the model's info to DB, you just need to manually create the writer and modify it how you wish... so in this case take the writer method and use it to create the salt and encrypt... something like:
def password=(pwd)
#password = pwd
return if pwd.blank?
create_new_salt
self.password_md5_hash = User.encrypted_password(self.password, self.salt)
end
Examples of the methods used above:
def create_new_salt
self.salt = "#{object_id}#{rand}"
end
def self.encrypted_password(password, salt)
hashme = password + "morerandomtexthere" + salt
Digest::SHA1.hexdigest(hashme)
end
Also, for the date_time_created, just use "t.timestamps" in your migration and if I'm not mistaken, Rails will handle those for you.

How can I cause a setter method to encrypt data before setting it in ActiveRecord?

I'd like to implement a Rails User model that has a DB column called password. I want to make it so that when I call...
user_instance.password = 'cleartext'
the method hashes the cleartext before setting it on the instance like so:
Digest::SHA1.hexdigest(cleartext)
I've tried using a callback, but the problem is that is hashes the pw every time the user is saved, even if the pw isn't updated. So it gets hashed and rehashed over and over.
I tried redefining the password= method...
alias password= old_password=
def password=(cleartext)
old_password=(Digest::SHA1.hexdigest(cleartext))
end
but got an error saying password= does not exist.
FYI, you may want to check out restful_authentication plugin as it will do this for you. Why roll your own?
The way acts_as_authenticated does it:
Database/model has a column called "encrypted_password"
Create a virtual attribute called password
Password isn't populated on a find(..) so if the password is blank, don't encrypt
If the password is nonblank, that means the user entered one, so go ahead and encrypt and stuff in encrypted_password
Code snip (random copy-paste from my user class, so don't blindly paste this in):
require 'digest/sha1'
class User < ActiveRecord::Base
# stuff
# callback
before_save :encrypt_password
attr_accessor :password
# methods
def encrypt_password
return if password.blank?
salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
crypted_password = Digest::SHA1.hexdigest("--#{salt}--#{self.password}--")
end
In response to Ethan's comment, what is a virtual attribute, a virtual attribute is one that is not in the database, like Matt's attr_accessor :password, this way you can accept that input from the user, but no necessarily store it in that form, in this instance we want to be able to accept a clear text password, but we want to store it encrypted. To do this we have a virtual attribute :password, and in the database we store it as encrypted_password.
Well, you could override the setter:
def password=(value)
self[:password] = Digest::SHA1.hexdigest(value)
end
And this would always encrypt the value. You don't need a before_save or an attr_accessor.
Note that it might be a good idea to choose a random n-bit value per user (upon creation of that user) and prepend that to the password when you hash it.
The reason being: if anyone gets a hold of your database, not only can't they immediately see the users' password, they also can't see if two users have identical passwords (important if one of those users grabs your database) and a certain class of hash-cracking attacks (rainbow tables) becomes harder.

Resources