I'm quite new with DataMapper and Sinatra and especially attr_encrypted. What I want is to store my passwords encrypted and then be able to search for a user by username and password. I read the documentation of attr_encrypted, but I still don't get what to do :(
Can you please give my some example of a project using these two technologies or tell how to change my code to work :(
My User Class:
class User
include DataMapper::Resource
attr_encryptor :password, :key => 'secret key'
property :id, Serial
property :encrypted_password, Text
end
When I save a User, I do it this way:
username = params[:username]
password = params[:password]
user = User.new(:username => username, :encrypted_password => password)
user.save
which is saving the original password, not the encrypted one.
And I have no idea how to search for users when the password is encrypted :(
Now it is something like this:
#user = User.all(:username => username, :password => password)
Please excuse me for the newbie quiestion, but I really can't quite understand it :(
Thank you very much in advance!
You need to add the attr_encryptor line after you have specified the Data Mapper properties. This prevents DataMapper simply replacing the encrypted_password accessors with its own:
class User
include DataMapper::Resource
property :id, Serial
property :encrypted_password, Text
# this line moved down from above
attr_encryptor :password, :key => 'secret key'
end
and then create the user with:
user = User.new(:username => username, :password => password)
Are you sure you want to search for a User based on an encrypted password? Normally you would find the user based on e.g. username and then just check the password matches.
If you do want to do this, you will have to either recreate the encrypted password in your code and search with that (you’ll need to check the docs to see how encryption is done):
User.all(:username => username, :encrypted_password => encrypt(password))
Alternatively fetch all matching users and filter them in your code:
User.all(:username => name).select {|u| u.password == password}
Your encrypted password is :password, so you have to do
User.new(:username => username, :password => password)
To find an user by username and password, you should just do
User.first(:username => username, :password => password)
Anyway you could avoid that gem using instead bcrypt doing a thing like this.
Related
I'm trying to build a very simple login page with username and password authentication using BCrypt.
There are two features, the first is to create a username and password, and the second is authenticate using login page. (create a new user, login with that user)
Below is the controller and User model code for creating a username and password. View model is not included, but it includes a simple form asking the user to create a username, password and confirm password
Controller:
get '/new_user' do
erb :new_user
end
post '/new_user' do
if #password == #password_confirm
new_user = User.new(username: params[:username])
new_user.password = params[:password]
new_user.insert_user
redirect '/index'
else
redirect '/new_user'
end
end
Model:
def initialize(params = {})
#username = params.fetch(:username, "test")
#password = params.fetch(:password, "test")
end
def password=(new_password)
#password = BCrypt::Password.create(new_password)
#db_password = BCrypt::Password.new(#password)
end
def insert_user
db = SQLite3::Database.open("helper_database")
db_results_as_hash = true
db.execute("INSERT INTO users (username, password) VALUES (?,?)", [#username, #db_password])
end
In the above, a new instance is created to pass username and password. If password and confirm password match, the password method will create an encrypted password, and the insert method will insert username and the encrypted password into the database using simple SQL command.
After the username and encrypted password have been inserted into the database. I want to use the login page to validate. I create a new User instance, passing the username and password params to the authentication method shown below. The method will look up the password corresponding the params username and evaluate the params password against the encrypted password.
Below is the controller and User model for Login
Controller:
get '/login' do
erb :login
end
post '/login' do
#user = User.new(username: params[:username], password: params[:password])
if #user.authenticate()
redirect '/index'
else
erb :login
end
end
Model:
def authenticate
db = SQLite3::Database.open("helper_database")
db.results_as_hash = true
password = db.execute("SELECT password FROM users WHERE username = '#{#username}'")
password = password[0]["password"]
password = BCrypt::Password.new(password)
#password == password
end
Above, I'm attempting to retrieve the encrypted password (string format) from SQL, convert to a Bcrypt object and validate the password against it. Theoretically, the method should return true assuming the entered password is correct, but it returns false.
What might the issue be?
You don’t appear to be hashing the incoming password when you authenticate. This is because you’re doing #password == password where #password is the plain-text String password (from User#initialize from post '/login').
You should flip the comparison so it uses BCrypt::Password#==: password == #password.
I have 2 models
User
Profile (belongs_to User)
I have 'username' in Profile model. I want to take value of 'username' in
User new form and save it to username of Profile. How can I do that. Please suggest code.
Try this
Add these in your Gemfile
gem 'simple_form'
gem 'nested_form'
In your user model add this
accepts_nested_attributes_for :profile
In your form under simple_nested_form_for
= f.simple_fields_for :profile do |p|
= p.input :username
In you user controller
add this to permit profile parameters
def user_params
params.require(:user).permit(
#User Attribute Names,
profile_attributes: [#Profile atrribute names]
)
I have something like:
class User
include DataMapper::Resource
property :id, Serial
property :username, String, :unique => true
end
post '/signup' do
user = User.create(username: params['username'])
if user.save
puts "New user was created"
else
puts user.errors
end
end
Parameter :unique => true is case-sensitive. It does not prevent to create users with usernames 'admin' and 'Admin'. How can I validate case-insensitive username unique, with out downcased username property, so users can make usernames as they choose.
You can provide your own custom validation:
class User
include DataMapper::Resource
property :id, Serial
property :username, String
validates_with_method :username,
:method => :case_insensitive_unique_username
def case_insensitive_unique_username
User.first(conditions: ["username ILIKE ?", self.username]).nil?
end
end
Note that ILIKE will only work with PostgreSQL, you will have to find out how to find records case insensitively with your specific adapter for yourself.
I'm currently developing a quick little sinatra app, and I've managed to conquer authentication quite easily. However I cannot for the life of me get password changing to work. I'm using the code below with Datamapper, and although it reaches the redirect, the password does not change.
user = User.first(:token => session[:user])
if params[:newpassword] == params[:newpasswordconfirm]
if BCrypt::Engine.hash_secret(params[:oldpassword], user.salt) == user.password_hash
user.password_hash = BCrypt::Engine.hash_secret(params[:newpassword], user.salt)
user.save
redirect '/'
I've also tried
user = User.first(:token => session[:user])
if params[:newpassword] == params[:newpasswordconfirm]
if BCrypt::Engine.hash_secret(params[:oldpassword], user.salt) == user.password_hash
user.update(:password_hash = BCrypt::Engine.hash_secret(params[:newpassword], user.salt)
redirect '/'
however this also fails to update the value. Unsure what I've done wrong.
class User
include DataMapper::Resource
attr_accessor :password, :password_confirmation
property :id, Serial
property :username, String, :required => true, :unique => true
property :password_hash, Text
property :salt, Text
property :token, String
validates_presence_of :password
validates_confirmation_of :password
validates_length_of :password, :min => 6
end
Assuming I have a view in my CouchDB named "user/all" and a CouchRest ExtendedDocument as follows:
class User < CouchRest::ExtendedDocument
property :username
property :password
property :realname
property :role
property :rights
end
How would I go about retrieving a document for the key 'admin' from this view using this ExtendedDocument?
(If I need to make changes to the ExtendedDocument subclass, what should be changed?)
Many thanks.
Try this:
class User < CouchRest::ExtendedDocument
property :username
property :password
property :realname
property :role
property :rights
view_by :role
end
Here, I am assuming 'admin' is a role property. This will make a view in your design document keyed by role. Then, to get all 'admin' documents, you just do the following:
#admins = User.by_role(:key => 'admin')
If in fact the actual id of the document is 'admin', then all you have to do is this:
#admin = User.get('admin')
Or, alternatively:
#admin = User.all(:key => 'admin')
I would also suggest taking a look at CouchRest Model, which is basically an Active Model complaint extension to CouchRest if you are using this with Rails. Good Luck!