I'm attempting to get has_secure_password to play nice with mongoid. I'm following Railscasts #270, however when I to signin with a username/password, I get the error:
undefined method `find_by_email' for User:Class
I see a similar post (http://stackoverflow.com/questions/6920875/mongoid-and-has-secure-password) however, having done as it suggested I still get the same error.
Here is my model:
class User
include Mongoid::Document
include ActiveModel::SecurePassword
validates_presence_of :password, :on => :create
attr_accessible :email, :password, :password_confirmation
field :email, :type => String
field :password_digest, :type => String
has_secure_password
end
Controller:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Logged out!"
end
end
Thanks.
Option 1: In your controller, you should use
user = User.where(:email => params[:email]).first
Option 2: In your model, you should define
def self.find_by_email(email)
where(:email => email).first
end
Mongoid doesn't support the find_by_attribute methods.
You should better use where instead:
User.where(:email => email).all
Related
I am new to rails and I am trying to build a simple web site where it requires creating users that are able to log in and out
but when creating the user I get
ArgumentError in UsersController#create
wrong number of arguments (2 for 1)
and this happens also when I try to log in where I get
ArgumentError in SessionsController#create
wrong number of arguments (2 for 1)
any thoughts on how to fix this ?
the model for the user is as follows
Class User < ActiveRecord::Base
attr_accessor :password
#validation for password on creation.
validates :password, :presence => true,
:confirmation => true,
:length => {:within => 6..40},
:on => :create
#validation for password on update, forget password option
validates :password, :confirmation => true,
:length => {:within => 6..40},
:on => :update
validates :user_name, presence: true,
uniqueness: true
validates :status, presence: true
before_save :encrypt_password
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def self.authenticate(user_name, password)
user = find_by_user_name(user_name)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
#authenticate
user
else
# la2 :(
nil
end
end
end
and the user controller :
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
private
def user_params
params.require(:user).permit(:user_name, :status, :password, :password_confirmation)
end
end
and regarding the sessions it has no model
where the session controller is as follows :
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:user_name], params[:password])
if user
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Logged out!"
end
end
It's generally a bad idea to roll your own security.
I would highly recommend checking out Devise
I am a newB in rails and I am working on a project and wanted to remove trailing and leading white spaces from username and email. So I created a method in user model
class User < ActiveRecord::Base
include CarrierWave::MiniMagick
#removes the white spaces before validating the data
before_validation :strip_whitespace, :only => [:name,:email]
#data validations
validates :email, :presence =>true, :uniqueness => {case_sensitive: false}, :format => { :with=> /([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)/, :message => "please enter a valid e-mail" }
validates :name, :presence=>true
validates :password ,:presence =>true, :confirmation=> true #, :length =>{ :minimum=>6, :maximum=>30}, :format=>{:with=>/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,30}/}
#for the image
mount_uploader :image, ImageUploader
#for the password
has_secure_password
#associations
has_many :issues
has_many :comments
end
def strip_whitespace
self.email = self.email.squish
self.name = self.name.squish
end
when I enter user information in Users.create action, the leading and trailing spaces are removed, But when I login from my sessions controller, the leading and trailing spaces are not removed and hence it shows me an error.
Please help
Code for the users controller
class UsersController < ApplicationController
before_action :not_logged_in?, :only => [:new]
def new
#user=User.new
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
RegistrationsMailer.welcome_email(#user).deliver
flash[:success] = 'you are successfully registered'
redirect_to :controller => 'users' , :action => 'show', :id => #user.id
else
render 'new'
end
end
def show
#user=User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user
else
render 'edit'
end
end
protected
def user_params
params.require(:user).permit(:name,:email,:password,:image)
end
end
code for sessions controller
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_email(params[:sessions][:email])
if #user && #user.authenticate(params[:sessions][:password])
sign_in #user
redirect_to #user
else
flash.now[:error] = 'Invalid email or password'
render 'new'
end
end
def destroy
end
end
Please help
According to When does Validation happen?
The following methods trigger validations, and will save the object to the database only if the object is valid:
create
create!
save
save!
update
update!
When you are creating or destroying a new session, you don't actually call any of those methods on the User model so validations won't be called.
But since your database has the squished values, you need to modify your create action in the SessionsController to squish the passed in params.
def create
#user = User.find_by_email(params[:sessions][:email].squish)
if #user && #user.authenticate(params[:sessions][:password].squish)
sign_in #user
redirect_to #user
else
flash.now[:error] = 'Invalid email or password'
render 'new'
end
end
I want an email to be sent when a user registers. This email should contain a link that changes the account to a full user. I want this email link to be a token for security.
email_token is a random generated token per user
email_activation_token is a boolean saying if the user completed registration or not
Currently: I get the email to send but when I click the link I get this error.
ActiveRecord::RecordNotFound in UsersController#accept_invitation
Couldn't find User without an ID
Link Sent http://localhost:3000/users/accept_invitation.P3Iu5-21nlISmdu2TlQ08w
user_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
UserMailer.registration_confirmation(#user).deliver
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
def accept_invitation
#user = User.find(params[:email_token])
#user.email_activation_token = true
redirect_to root_url, :notice => "Email has been verified."
end
end
end
registration_confirmation.html.haml
Confirm your email address please!
= accept_invitation_users_url(#user.email_token)
user.rb model
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
before_save { |user| user.email = email.downcase }
before_create { generate_token(:auth_token) }
before_create { generate_token(:email_token) }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
VALID_PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9]).{6,}$/
validates_confirmation_of :password
validates :password, :on => :create, presence: true, format: { with: VALID_PASSWORD_REGEX }
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
You get that error because in your accept_invitation method calls find on the User model expecting an id and you are passing the email_token parameter.
Try this..
def accept_invitation
#user = User.find_by_email_token(params[:email_token])
#user.email_activation_token = true
#user.save
redirect_to root_url, :notice => "Email has been verified."
end
In your controller you are doing:
User.find(params[:email_token])
This will attempt to find a user with an id that is equal to the email token passed in by the params. I think you are really trying to do something more like:
User.find_by_email_token(params[:email_token])
The find method will raise an exception if no record with the given id can be found. You need either need to be able to find by the token or to get the id of the record from the token.
I want to skip validation when I am trying to edit user as admin.
Model
class User
...
attr_accessible :company_id, :first_name, :disabled, as: :admin
Controller
class Admin::UsersController
...
def update
#user = User.find(params[:id])
#user.update_attributes(params[:user], as: :admin)
redirect_to edit_admin_user_path(#user), :notice => "User Account Updated"
end
So I tried to change update action to
def update
#user = User.find(params[:id])
#user.attributes = params[:user]
#user.save(validate: false)
redirect_to edit_admin_user_path(#user), :notice => "User Account Updated"
end
But then I dont have access to set :disabled and :company_id attributes because i dont know where to set as: :admin
Try this:
#user = User.find(params[:id])
#user.assign_attributes(params[:user], as: :admin)
#user.save(validate: false)
Strong Parameters
This has been an issue with rails for a long time, in Rails 4 they are introducing "Strong Parameters"
https://github.com/rails/strong_parameters
http://railscasts.com/episodes/371-strong-parameters
You can use strong parameters gem in rails 3 applications as well
Alternative: Context Attribute
Another way to do it, introduce a context variable on the user model - *Note I am not familiar with the 'as' option for attr_accessible*
class User < ActiveRecord::Base
attr_accessor :is_admin_applying_update
validate :company_id, :presence => :true, :unless => is_admin_applying_update
validate :disabled, :presence => :true, :unless => is_admin_applying_update
validate :first_name, :presence => :true, :unless => is_admin_applying_update
# etc...
In your admin controller set the is_admin_applying_update attribute to true
class Admin::UsersController
# ...
def update
#user = User.find(params[:id])
#user.is_admin_applying_update = true
#user.update_attributes(params[:user])
NOTE: you can also group the validations and use a single conditional
http://guides.rubyonrails.org/active_record_validations.html#grouping-conditional-validations
Hack method:
def update
#user = User.find(params[:id])
#user.define_singleton_method :run_validations! do true; end
#user.update_attributes(params[:user], :as => :admin)
redirect_to edit_admin_user_path(#user), :notice => "User Account Updated"
end
I have a rails app, for some reason my login action does not work. I put in a correct username/password in, however it does not redirect to the desired 'menu' action. It just redirects me to the login action everytime (I have set that to happen when the login is unsucessful). I state unless session[:user_id] . When I input the wrong password on purpose, the flash message is correct, it says "Invalid Username/Password", when the correct one is input, it doesn't which means it recognises it, somehow the session is not being created. Below is my code
Application Controller
protected
def confirm_logged_in
unless session[:user_id]
flash[:notice] = "Please Log In"
redirect_to(:controller => 'access', :action => 'login')
return false
else
return true
end
end
Access Controller (Where the magic should be happening)
Class AccessController < ApplicationController
layout 'admin'
before_filter :confirm_logged_in, :except => [:login, :attempt_login, :logout]
def index
menu
render('menu')
end
def menu
#display text & links
end
def login
#login form
end
def attempt_login
authorised_user = AdminUser.authenticate(params[:username], params[:password])
if authorised_user
flash[:notice] = "You are now logged in"
redirect_to(:action => 'menu')
else
flash[:notice] = "Invalid username/password"
redirect_to(:action => 'login')
end
end
def logout
session[:user_id] = nil
session[:username] = nil
flash[:notice] = "You have been logged out"
redirect_to(:action => 'login')
end
end
AdminUser Model
require 'digest/sha1'
class AdminUser < ActiveRecord::Base
# because we created a migration to change the name of the users tabe to admin_users we have to specify
# set_table_name("admin_users")
# or we can change the class name and file name like we did
attr_accessible :first_name, :last_name, :username, :email
attr_accessor :password
attr_protected :hashed_password, :salt
scope :named, lambda {|first,last| where(:first_name => first, :last_name => last)}
has_and_belongs_to_many :pages
has_many :section_edits
has_many :sections, :through => :section_edits
EMAIL_REGEX = /^[A-Z0-9._%+-]+#[A-Z)0-9.-]+\.[A-Z]{2,4}$/i
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :username
validates_length_of :first_name, :maximum => 25
validates_length_of :last_name, :maximum => 50
validates_length_of :username, :within => 3..25
validates_length_of :password, :within => 8..25, :on => :create
validates_uniqueness_of :username
validates :email, :presence => true, :length => {:maximum => 100}, :format => EMAIL_REGEX, :confirmation => true
before_save :create_hashed_password
after_save :clear_password
def self.authenticate(username="", password="")
user = AdminUser.find_by_username(username)
if user && user.password_match?(password)
return user
else
return false
end
end
def password_match?(password="")
hashed_password == AdminUser.hash_with_salt(password,salt)
end
def self.make_salt(username="")
Digest::SHA1.hexdigest("User #{username} with #{Time.now} to make salt")
end
def self.hash_with_salt(password="", salt="")
Digest::SHA1.hexdigest("Put #{salt} on the #{password}")
end
private
def create_hashed_password
unless password.blank?
self.salt = AdminUser.make_salt(username) if salt.blank?
self.hashed_password = AdminUser.hash_with_salt(password,salt)
end
end
def clear_password
self.password = nil
end
end
I found the solution. It was pretty simple. The problem was that I did not create the sessions when the login was made, this is why the login did not recognise the sessions because they were not initialised.
In the Access Controller I simply changed it to this :
def attempt_login
authorised_user = AdminUser.authenticate(params[:username], params[:password])
if authorised_user
session[:user_id] = authorised_user.id
session[:username] = authorised_user.username
flash[:notice] = "You are now logged in"
redirect_to(:action => 'menu')
else
flash[:notice] = "Invalid username/password"
redirect_to(:action => 'login')
end
end
The amendments are the two session lines added to the code