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.
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 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
I was adding uniqueness validation to the model in charge of user registration. The username should be uniqueness.
I created a user when testing some days ago, lets call it "example-guy". Then I deleted him from the scaffolding user interface and now when trying to register a new user as "example-guy", it returns that the name has already been taken.
So how can I fix this from the DB without reverting to its "birth-state" and modify the controller to actually destroy the table entry?
Or maybe Rails is keeping track of what was writed to DB, even after destruction?
Im using omniauth-identity, the user model:
class User < ActiveRecord::Base
attr_accessible :name, :provider, :uid, :email
# This is a class method, callable from SessionsController
# hence the "User."
def User.create_with_omniauth(auth)
user = User.new()
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
# ADD EMAIL
user.email = auth["info"]["email"]
user.save
return user
end
end
User controller's destroy function:
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
Validations are made trought "Identity" model inherited from omniauth's active record:
class Identity < OmniAuth::Identity::Models::ActiveRecord
attr_accessible :email, :name, :password_digest, :password, :password_confirmation
#attr_accessible :email, :name, :password_digest :password, :password confirmation
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :in => 6..24
validates_format_of :name, :with => /^[a-z]+$/
validate :name_blacklist
validates_uniqueness_of :email, :case_sensitive => false
validates_format_of :email,
:with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
:message => "doesn't look like a proper email address"
#validates_presence_of :password, on: :create
#validates_presence_of :password_confirmation
validates_length_of :password, :in => 6..24
def name_blacklist
unless self.name.blank? then
case self.name
when "user", "photo", "photos",
"application", "sessions",
"identities", "home"
self.errors.add(:name, "prohibited")
end
end
end
end
Identities controllar manage registration form sent to omniauth and calling new.html.erb:
class IdentitiesController < ApplicationController
def new
#identity = env['omniauth.identity']
end
end
Thanks in advance.
I'm not entirely sure what caused the problem, but I came across a similar problem but with email addresses with a Devise based user.
I was wanting to re-use the same email on a different user. I changed the original user to a different email, but when I then tried to update the second user with the "now unique" email, it failed the unique validation.
A query for users with that email returned nothing.
It seemed to be cache-related to the uniqueness constraint, as restarting the server, after deleting the email address, fixed the problem.
I have a problem and I need an idea how to fix my update method. I have an admin panel where I can create users. This form include name, mail, password, repeated password fields and it works fine. Then I want to have a list of all users and to edit these who I want. The problem is that I want to edit part of the information which is not included in the form of the registration and default is empty. In edit mode my form has two new fields - notes and absences. When I change these fields and call update method I see message that password and repeated password don't match which is validation in the registration but I do not have these files in edit mode. How could I fix this problem. This is part of my code:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#title = "Edit user"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
The validation for password presence is true when you are creating a user, but once a user has an encrypted password, you don't want to force it to be present in all form submissions in the future.
Active record supports adding conditions to validations, so I would suggest putting a condition on the password validation to make it only execute if the user object does not already have an encrypted password. The relevant snippet would be:
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :needs_password?
def needs_password?
encrypted_password.nil?
end
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