Bcrypt and Rails finding an admin user just by entering password - ruby-on-rails

I'm building an app that has 3 models (Customer, Points, Admin). Customer has Points, Points belong to Customer. Then Admin has user_name and password_hash as attributes, storing the passwords via Bcrypt. Once a customer searches themselves via phone number, then their points show up. But to add points, an admin has to log in with just a password (code of 4 digits) then get access to adding points.
I'm having trouble how to find the admin via only a password, not user_name and password.
class AdminsController < ApplicationController
def new
#admin = Admin.new
end
def create
#Admin = Admin.new(admin_params)
if #admin.save
redirect_to root_path
else
flash[:error] = "incorrect data, please check form"
render new_admin_path
end
end
def login
#customer = Customer.find(params[:id])
# Need to get the input password
params[:password]
# Change the inputed password into a password hash
# inputed_password_hash (NEED HELP HERE)
# Compare the password hash with password hashes in the Admin model/database
# to see if it exists.
# if true, send to add points page
# if false, send back to customer page
if Admin.find_by(password_hash: inputed_password_hash)
redirect_to new_points_path
else
render customer_path
end
end
private
def admin_params
params.require(:admin).permit(:user_name, :password, :password_confirmation)
end
end

Related

Rails devise signup with JWE token payload

I need to create functionality where other microservice creates a link to my app with JWE token as a params in which is encrypted json user params e.g.:
json_payload = {
email: 'test#test.com',
external_id: '1234'
}.to_json
The flow should be:
user gets the url generated by different app with JWE token as params (e.g. http://localhost:3000/users/sign_up/?jwe_token=some_gigantic_string_123)
enter that url
under the hood Rails app creates new user based on encrypted params
after successful user creation redirect that user to the edit page
So as you see, the user shouldn't notice that there was an account creation but the first page it will see is the password edit.
Am I not doing some sort of antipaternity here with below code? Please take a look:
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
return redirect_to(new_user_session_path) unless params[:jwe_token]
json_payload = JWE.encrypt(payload, rsa_key)
payload = JSON.parse json_payload
user = User.new(user_params)
if user.save
redirect_to generate_password_url(request.base_url, user)
else
redirect_to new_user_session_path, alert: 'Something went wrong'
end
end
private
def generate_password_url(base_url, user)
path = edit_password_path(user, reset_password_token: fetch_token(user))
"#{base_url}#{path}"
end
def fetch_token(user)
user.send(:set_reset_password_token)
end
end
I assume that if user creation is to be handled by a link I have to use new method. Am I not creating an antipattern here? Is there any other way to do so?

function logic for password verification using bcrypt that is getting a double render error

working on a Passwords_Controller that allows the password to be changed, also validates if the user is using same password, or if they are trying to use different password, and another password to confirm. I have it set to "redirect_to" to my dashboard controller that lands them back to be the page to enter the password again before user is updated with new password.
currently I am getting a DoubleRenderError. I have some "puts" with extrapolation see if the logic is passing through the controller and rendering I'm new to rails, so i'm not exactly sure if its my redirect statements that are stopping the validation of old pw, new password - confirm password logic in my controller, or I have too many if statements that is forcing the controller to do double render.
(output of on the terminal with statements for error)
Redirected to http://127.0.0.1:3000/dashboard
true
is this working asdf <~~ test username
is this also working $2a$12$d5WadQunMyww2r4lnmqgveoXaq6WO6hNXvsG/h3RxqUxGFCp6tnWm
Redirected to
Completed 500 Internal Server Error in 738ms (ActiveRecord: 9.1ms | Allocations: 6474)
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".):
this is my password controller
require 'bcrypt'
class PasswordsController < ApplicationController
include BCrypt
def passwordchanged
end
def update
user = current_user;
#user entered correct password
if user.authenticate(params[:password])
helpers.flash_message :danger, "current password doesnt match"
redirect_to '/dashboard'
end
puts "#{user.authenticate(params[:password])}"
puts "is this working #{params[:new_password]}"
puts "is this also working #{Password.new(user.password)}"
#if new password is the same as new password, doesnt not save
if Password.new(user.password) == params[:new_password]
helpers.flash_message :danger, "new password can't match current password, please try again"
redirect_to '/login'
end
puts "#{redirect_to '/login'}"
#if new password doesnt match confirm password
if params[:new_password] != params[:new_password_confirm]
helpers.flash_message :danger, "password, password confirm needs to match"
redirect_to '/dashboard'
end
puts "#{params[:new_password] != params[:new_password_confirm]}"
#create a new password and save the user ID
user.password = params[:new_password]
user.save
helpers.flash_message :success, "change successful, logout for new session"
redirect_to '/passwordchangesuccess'
end
end
this is my Application Controller
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
User.find_by(id: session[:user_id])
end
def authenticate_user!
redirect_to '/login' unless current_user
end
end

Rails: Default admin for first registration user on devise and milia

I'm working for a multi-tenant application using Ruby on Rails, Devise, Stripe and milia.
Basic Concept: At first a user create an account by procedural way like using devise sign_up page and create an organization this user has the ability to add multiple members for this organization at this point first user is organization admin and others member/members are the just members like there has no writing permission just reading permission.
At second the first user an send an invitation to a member who will have an organization member, now everything is working like registration, payment and invite to member email and join this member.
I have tried like this
on the user.rb
before_save {self.is_admin = true}
But this saving for all including members.
This is my registration code
class RegistrationsController < Milia::RegistrationsController
skip_before_action :authenticate_tenant!, :only => [:new, :create, :cancel]
def create
# have a working copy of the params in case Tenant callbacks
# make any changes
tenant_params = sign_up_params_tenant
user_params = sign_up_params_user
coupon_params = sign_up_params_coupon
sign_out_session!
# next two lines prep signup view parameters
prep_signup_view( tenant_params, user_params, coupon_params )
# validate recaptcha first unless not enabled
if !::Milia.use_recaptcha || verify_recaptcha
Tenant.transaction do
#tenant = Tenant.create_new_tenant( tenant_params, user_params, coupon_params)
if #tenant.errors.empty? # tenant created
if #tenant.plan == 'premium'
#payment = Payment.new({email: user_params["email"],
token: params[:payment]["token"],
tenant: #tenant
})
flash[:error] = "Please check registration errors" unless #payment.valid?
begin
#payment.process_payment
#payment.save
rescue Exception => e
flash[:error] = e.message
#tenant.destroy
log_action('Payment Failed')
render :new and return
end
end
else
resource.valid?
log_action( "tenant create failed", #tenant )
render :new
end # if .. then .. else no tenant errors
if flash[:error].blank? || flash[:error].empty?
initiate_tenant( #tenant ) # first time stuff for new tenant
devise_create( user_params ) # devise resource(user) creation; sets resource
if resource.errors.empty? # SUCCESS!
log_action( "signup user/tenant success", resource )
# do any needed tenant initial setup
Tenant.tenant_signup(resource, #tenant, coupon_params)
else # user creation failed; force tenant rollback
log_action( "signup user create failed", resource )
raise ActiveRecord::Rollback # force the tenant transaction to be rolled back
end # if..then..else for valid user creation
else
resource.valid?
log_action('Payment proccesing fails', #tenant)
render :new and return
end # if. . then .. else no tenant errors
end # wrap tenant/user creation in a transaction
else
flash[:error] = "Recaptcha codes didn't match; please try again"
# all validation errors are passed when the sign_up form is re-rendered
resource.valid?
#tenant.valid?
log_action( "recaptcha failed", resource )
render :new
end
end # def create
end
My question is: How to create is_admin: true for who create organization?
Thanks!
If I understood your concept like you need to assign is_admin: true for who can register using this registration controller, Right? If yes then it's very easy update this user_params = sign_up_params_user line of code
Try the following
user_params = sign_up_params_user.merge({ is_admin: true })
Now assign is_admin: true for only who can create an account with an organization.
Now if you block to special permission for normal members then create a method to user.rb file like
def is_admin?
is_admin
end
Then for permission
if current_user.is_admin?
#=> Permission for admin
else
#=> Permission denied for normal members
end
Hope it helps
If using something like rolify gem, you can add the line
#user.add_role(:admin)
in the following context in registrations_controller.rb`:
Tenant.transaction do
#tenant = Tenant.create_new_tenant( tenant_params, user_params, coupon_params)
if #tenant.errors.empty? # tenant created
initiate_tenant( #tenant ) # first time stuff for new tenant
devise_create( user_params ) # devise resource(user) creation; sets resource
#user.add_role(:admin)

Access current_user in model (or some other workaround)

I've read several SO links on this topic. Even if you can hack it to get current_user in model, you shouldn't do it. So, what are my options in my case?
I'm using the devise_invitable gem, and one of the commands is User.invite!({:email => email}, current_user), which stores who the user is invited by (current_user). I'd like to have this information.
Currently, users are invited to join a private group, and this process is handled in my group.rb model:
# group.rb
def user_emails
end
def user_emails=(emails_string)
emails_string = emails_string.split(%r{,\s*})
emails_string.each do |email|
user = User.find_for_authentication(email: email)
if user
self.add user
GroupMailer.welcome_email(user)
else
User.invite!(email: email) # But I want this: User.invite!({:email => email}, current_user)
user = User.order('created_at ASC').last
self.add user
end
end
end
If relevant, it's just a text_area that receives these emails to process:
# groups/_form.html.erb
<%= f.text_area :user_emails, rows: 4, placeholder: 'Enter email addresses here, separated by comma', class: 'form-control' %>
Without having to re-arrange too much, how can I run User.invite!({:email => email}, current_user) in this process, so that this useful information (who is invited by whom) is stored in my database? Much thanks!
Update:
With #Mohamad's help below, I got it working.
# group.rb
def emails
end
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.group_invite user
else
add User.invite!({:email => email}, inviter)
end
end
end
# groups_controller.rb
def update
#group = Group.friendly.find(params[:id])
if #group.update_attributes(group_params)
emails = params[:group][:emails]
#group.invite_many(emails, current_user) # also put this in #create
redirect_to #group
else
flash[:error] = "Error saving group. Please try again."
render :edit
end
end
And then nothing in my User model because User.invite is defined already by devise_invitable and I didn't need to do anything else. This process is working great now!
There are some subtle issues with your code. There's a potential race condition on the else branch of your code where you try to add the last created user. I'm also unsure that you need a setter method here unless you are access emails from elsewhere in the instance of Group.
As suggested by others, pass the current user as an argument form the controller. I'm not sure how invite! is implemented, but assuming it returns a user, you can refactor your code considerably.
I would do somethng like this:
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.welcome_email user
else
add User.invite!(email, inviter)
end
end
end
# controller
#group.invite_many(emails, current_user)
# User.invite
def invite(email, inviter)
# create and return the user here, and what else is necessary
end
If you are calling user_emails() from the controller (and I'm guessing you are as that must be where you are receiving the form to pass in emails_string), you can pass in the current_user:
user_emails(emails_string, current_user)
and change user_emails to receive it:
def user_emails=(emails_string, current_user)
You can store the current_user with global scope ,like #current_user,which can be assigned in sessions controller,so in model you will just #current_user as the current user of the app.

What has happened to the salt with bcrypt?

This is from the github page:
require 'bcrypt'
class User < ActiveRecord::Base
# users.password_hash in the database is a :string
include BCrypt
def password
#password ||= Password.new(password_hash)
end
def password=(new_password)
#password = Password.create(new_password)
self.password_hash = #password
end
end
It appears that to access the password method, you need to call it as an attribute from a create method:
#user.password = user_params[:password]
#user.save
Okay...fine? But where's the salt now stored? I just don't get it at all, how is this even remotely secure anymore?
To retrieve a hashed password, you need this method:
def password
#password ||= Password.new(password_hash)
end
And call it as an attribute:
if #user.password == params[:password]
give_token
else
false
end
So it appears everything's working without a salt...how does it do this?
This means I only need one column in my database to do with passowords now, right?password or password_hash instead of password_salt | password_hash?
Well then why does the github page say this:
But even this has weaknesses -- attackers can just run lists of
possible passwords through the same algorithm, store the results in a
big database, and then look up the passwords by their hash:
PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1"
Salts
And then this is what really gets me:
The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed:
Why are they explaining all of this if bcrypt is handling everything automatically?
hash(salt + p) #=> <really unique gibberish>
The salt is then stored along with the hash in the database, and used to check potentially valid passwords:
<really unique gibberish> =? hash(salt + just_entered_password)
bcrypt-ruby automatically handles the storage and generation of these salts for you.
Could someone explain how bcrypt stores and generates these salts? Why does it say it handles it all for me and then goes on to tell me how to generate a salt? Do I need to run something like this in my model: self.password_hash = hash(salt + p)
Argh so confused I used to get salts and hashes utterly and now they've changed it all. Terrible, unclear docs...they appear to show you how to use bcrypt without a salt with loads of examples, and then briefly mention how to do it properly with a salt down at the bottom.
Could someone please give me an example how to use the new version of bcrypt to generate a salt and hash, and how to authenticate?
Okay, the has_secure_password is really cool. You don't need to worry about salts and hashes anymore, the salt and hash are stored as one attribute ( password_digest) in the database.
It's saved in such a way that bcrypt knows which part of the password_digest string is the salt, and what is the hash.
If you're setting up authentication from scratch, you literally need to do the following:
1) Add the bcrypt rails gem:
gem bcrypt-rails
2) Add the has_secure_password method to the model tasked with handling your user records:
class User < ActiveRecord::Base
has_secure_password
end
3) Make sure your users table has a password_digest column:
class CreateUser < ActiveRecord::Migration
create_table :users do |t|
t.username
t.password_digest
end
end
4) Create a new method to create a a new empty user instance for the form to use:
class UsersController < ApplicationController
def new
#user = User.new
end
end
5) In the new view, make a form that creates populates the params hash' :password and :username entries:
<% form_for( #user ) do |f| %>
<%= f.text_field :username %>
<%= f.password_field :password %>
<% end %>
6) Back in our controller, permit the username and the password using strong params. The whole reason behind strong params is to prevent some cheeky chappy from using dev tools to create their own html form field (such as one pertaining to id) and populating the database with malicious data:
class UsersController < ApplicationController
def new
#user = User.new
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
7) Let's create the create method that will use these permitted entries to create a new user, populated by the form:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
#user.save
redirect_to root_path
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
Set up your routes as you see fit, and that's it! The user record's password_digest column will be automatically populated with one string comprised of a salt appended with the hash of the password. Very elegant.
All you need to remember: password -> password_digest.
In order to authorise the user and signout the user, create a sessions controller with a create method and a destroy method:
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to admin_root_path, :notice => "Welcome back, #{user.username}"
else
flash.now.alert = "Invalid email or password"
redirect_to root_path
end
end
def destroy
reset_session
flash[:info] = "Signed out successfully!"
redirect_to root_path
end
Hope this helps someone!
bcrypt got everything covered for you. Your password digest consists of a few types of information, bcrypt algorithm type, cost, salt and the checksum.
for example:
my_password = BCrypt::Password.create("my password")
#=> "$2a$10$.kyRS8M3OICtvjBpdDd1seUtlvPKO5CmYz1VM49JL7cJWZDaoYWT."
The first part: $2a$ is the variant of the algorithm see: Where 2x prefix are used in BCrypt?
The second part 10 is the cost parameter, you can increase it to slow down the process (logarithmic value) by providing a hash {cost: 12} as the second argument to create.
Now if you call my_password.salt you get "$2a$10$.kyRS8M3OICtvjBpdDd1se" which identifies the part that is being used as the key to creating your checksum.
And finally, your checksum is "UtlvPKO5CmYz1VM49JL7cJWZDaoYWT.". That's the reason if you call create the second time the string is going to be different as another salt will be used.
But as I mentioned earlier you don't need to do anything extra as all these are being taken care of for you.

Resources