First I'd like to let you know I can successfully create a new user and start a session for that new user. What I cannot do is then log in as that new user.
Not to distract anyone from answering but I think the error is within Authenticating the User.
Currently if in the sessions controller. If I have the below.
user = User.find_by(params[:email])
if user && user.authenticate(params[:session][:password])
I will get redirected to the login page because the password doesn't match.
I can login if I have the below. But I will not start the session as the correct user.
user = User.find_by(params[:email])
if user
Again I can create new users, my user count does increase, but I cannot log in with the user once I end the session.
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(params[:email])
if user && user.authenticate(params[:session])
session[:user_id] = user.id
case user.admin_level
when 1
session[:site_admin] = true
session[:can_create_post] = true
when 2
session[:creator] = true
session[:can_create_post] = true
when 3
session[:friend] = true
session[:can_comment_on_post] = true
end
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
log_in user
redirect_to user, :notice => "Logged in!"
else
flash.now[:danger] = 'Invalid email/password combination' # Not quite right!
redirect_to root_url
end
end
def loggedin?
#user = session[:user_id]
redirect_to page_path(current_user)
end
def destroy
log_out if logged_in?
cookies.delete(:auth_token)
session.clear
redirect_to root_url, :notice => "Logged out!"
end
def session_params
params.require(:session).permit(:user_id)
end
end
My User Model Looks like below
class User < ActiveRecord::Base
attr_accessor :remember_token, :image
before_create { generate_token(:auth_token) }
mount_uploader :image, ImageUploader
mount_uploader :avatar, AvatarUploader
validates :name, presence: true, length: { maximum: 50 }
before_save { email.downcase! }
before_save { name.capitalize! }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true , length: { maximum: 255 },
format: {with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def User.new_token
SecureRandom.urlsafe_base64
end
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
def forget
update_attribute(:remember_digest, nil)
end
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
Related
I warmly welcome. I recently started learning RoR. Currently processed course Michael Hartl. I got to the lesson 9 - cookies. Unfortunately, I received an error that I can not fix. Please help.
Error:
ERROR["test_login_with_valid_information_followed_by_logout", UserLoginTest, 1.443278428982012]
test_login_with_valid_information_followed_by_logout#UserLoginTest (1.44s)
NoMethodError: NoMethodError: undefined method `remember' for #<User:0x0000000a0c5698>
Did you mean? remember_token
app/helpers/sessions_helper.rb:8:in `remember'
app/controllers/sessions_controller.rb:10:in `create'
test/integration/user_login_test.rb:23:in `block in <class:UserLoginTest>'
My files:
User.rb
class User < ApplicationRecord
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: {maximum: 50}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: {maximum: 255},
format: { with: VALID_EMAIL_REGEX},
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: {minimum: 6}
class << self
# Returns the hash digest of the given string.
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def new_token
SecureRandom.urlsafe_base64
end
def remember
remember_token = new_token
update_attribute(:remember_digest, digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
end
Sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = "Invalid email/password combination"
render 'new'
end
end
def destroy
log_out
redirect_to root_path
end
end
Sessions_helper.rb
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
# Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
def current_user
if (user_id = session[:user_id])
current_user ||= User.find_by(id: user_id)
elsif ( user_id = cookies.signed[:user_id] )
user = User.find_by(id: user_id )
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
end
end
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
#current_user = nil
end
end
**user_login_test.rb**
require 'test_helper'
class UserLoginTest < ActionDispatch::IntegrationTest
def setup
#user = users(:marcin)
end
test "user invalid login" do
get login_path
assert_template 'sessions/new'
post login_path, params: { session: { email: "", password: "" } }
assert_template 'sessions/new'
assert_not flash.empty?
assert flash.any?
get root_path
assert_not flash.any?
end
test "login with valid information followed by logout" do
get login_path
assert_template 'sessions/new'
post login_path, params: { session: { email: #user.email,
password: 'password' } }
assert is_logged_in?
assert_redirected_to #user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(#user)
delete logout_path
assert_not is_logged_in?
assert_redirected_to root_path
follow_redirect!
skip
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path, count: 0
assert_select "a[href=?]", user_path(#user), count: 0
end
end
The problem is that you have wrapped the remember method within class << self, making it a class method of User instead of an instance method.
Remove the class << self and just put self in front of the methods you want to be class methods. Or you can move remember so it's outside the class << self.
....
has_secure_password
validates :password, presence: true, length: {minimum: 6}
def remember
remember_token = new_token
update_attribute(:remember_digest, digest(remember_token))
end
...
This should solve your problem, Just make the method remember an instance method by moving it out of the class << self declaration.
# frozen_string_literal: true
class User < ApplicationRecord
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
def remember
remember_token = new_token
update_attribute(:remember_digest, digest(remember_token))
end
class << self
# Returns the hash digest of the given string.
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def new_token
SecureRandom.urlsafe_base64
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
end
I need to add the skip validation methods to my sessions controller but i am unsure what to define omniauth as and basic login as. Ie they are both from class User but in sessions controller do I put
def create
user = User.from_omniauth(env["omniauth.auth"])
**user.from_omniauth.skip_name_validation = true**
etc etc
Then also want to add validate for the methods in the model as put in bold and add that to my sessions controller so both instances of each login are able to be bypassed.
My sessions controller
def create
user = User.from_omniauth(env["omniauth.auth"])
unless user.present?
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
# Log the user in and redirect to the user's show page.
else
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
else
log_in user
redirect_to user
end
end
My model.rb
class User < ApplicationRecord
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 },
**unless: :skip_name_validation**
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false },
**unless: :skip_email_validation**
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil:true,
**unless: :skip_password_validation**
**attr_accessor :skip_name_validation, :skip_email_validation, :skip_password_validation**
**attr_accessor :skip_User, :skip_self**
**validate :User, unless: :skip_User**
**validate :self, unless: :skip_self**
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
class User < ApplicationRecord
attr_accessor :password_confirmation, :skip_password_validation
validates :password, :presence => true ,unless: :skip_password_validation
validates_confirmation_of :password, :if => ->{ password.present? },unless: :skip_password_validation
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
def create
user = User.from_omniauth(env["omniauth.auth"])
#######try this #####
user.skip_password_validation = true
####or try this ####
user.from_omniauth.skip_name_validation = true
unless user.present?
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
# Log the user in and redirect to the user's show page.
else
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
else
log_in user
redirect_to user
end
end
I'm getting the following error when trying access the log in method of my sessions controller:
JWT::DecodeError (Nil JSON web token):
lib/json_web_token.rb:11:in `decode'
app/helpers/sessions_helper.rb:15:in `current_user'
app/controllers/api/sessions_controller.rb:11:in `create'
If I comment out my render json: user in my controller response, all is good, except I need to respond with the user...Why on earth is the current_user method called on through line 11 of the sessions_controller.rb. Here's the relevant code:
lib/json_web_token.rb
require 'jwt'
class JsonWebToken
def self.encode(payload, expiration = 24.hours.from_now)
payload = payload.dup
payload['exp'] = expiration.to_i
JWT.encode(payload, Rails.application.secrets.json_web_token_secret)
end
def self.decode(token)
JWT.decode(token, Rails.application.secrets.json_web_token_secret).first
end
end
sessions_helper.rb
require 'json_web_token'
module SessionsHelper
def create_session(user)
session[:user_id] = user.id
end
def current_user
auth_token = request.headers["Authorization"]
if auth_token
auth_token = auth_token.split(" ").last
begin
decoded_token = JsonWebToken.decode auth_token
rescue JWT::ExpiredSignature
return
end
#current_user ||= User.find_by(auth_token: auth_token)
end
end
def log_out(user)
logged_in? ? user.generate_authentication_token! : user.destroy_token!
auth_token = user.auth_token
user.update_attribute(:auth_token, auth_token)
end
def logged_in?
current_user.present?
end
def authenticate_with_token!
render json: { errors: "Not authenticated" }, status: :unauthorized unless logged_in?
end
def log_in(user)
create_session(user)
user.generate_authentication_token!
user.update_attribute(:auth_token, user.auth_token)
end
def authenticate_as_self_or_admin!
render json: { errors: "Not authorized" }, status: :unauthorized unless is_self? || is_admin?
end
def is_self?
user = User.find(params[:id])
auth_token = request.headers["Authorization"]
auth_token = auth_token.split(" ").last if auth_token
user.auth_token != auth_token
end
def is_admin?
if logged_in? && current_user.authenticate(params[:password])
current_user.admin
end
end
end
sessions_controller.rb
class Api::SessionsController < ApplicationController
before_action :authenticate_with_token!, only: [:destroy]
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
render json: user, status: :created
else
render json: user, status: :unprocessable_entity
end
end
def destroy
log_out current_user
render status: 204
end
end
user.rb
require 'json_web_token'
class User < ApplicationRecord
attr_reader :current_password
before_save { email.downcase! }
before_create :generate_authentication_token!
before_update :reset_confirmed!, :if => :email_changed?
has_secure_password
has_many :posts
has_many :comments
has_many :votes
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
validates :username, presence: true, length: { maximum: 24 }, uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 8 }
validates :auth_token, uniqueness: true
def generate_authentication_token!
begin
self.auth_token = JsonWebToken.encode('id' => self.id, 'username' => self.username, 'email' => self.email, 'bio' => self.bio, 'confirmed' => self.confirmed, 'admin' => self.admin, 'points' => self.points)
end while self.class.exists?(auth_token: auth_token)
end
def destroy_token!
self.auth_token = nil
end
def reset_confirmed!
self.confirmed = false
end
def upvotes
self.votes.where(polarity: 1)
end
def downvotes
self.votes.where(polarity: -1)
end
def update_with_password(user_params)
current_password = user_params.delete(:current_password)
user_params[:password] = current_password if user_params[:password].nil?
if self.authenticate(current_password)
self.update(user_params)
else
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
end
end
No, I am not using devise.
I'm really hoping my eyes are just tired here...
It turns out that current_user was in fact being called since it is the default scope_name for Active Model Serializers. I changed the name of my current_user method to avoid this conflict. Here are the relevant docs.
I am working on a project, i need to have different type of users, some will be admins and others will be just users, i have a model for all users, and i have a model "login" where i would store an ecrypted password. The problem is, i create the user, and it saves on the database, but the model where the password should be stored is empty...
I tried to make assossiation on both user and login controller, and both models as well.
Ask me for the code you need to see and ill post it here.
this is my user.rb code
class User < ActiveRecord::Base
has_and_belongs_to_many :requisitions
has_many :historics
has_one :login
belongs_to :rank
belongs_to :sub_unit
belongs_to :user_type
#attr_accessor :password, :password_confirmation, :salt, :encrypted_password
validates :nim, :posto_id, :apelido, :nome, :telefone, :sub_un_id, :tipo_util_id, presence: true
validates :email, format: { with: /#/ }
validates :nim, uniqueness: true
validates :password, :confirmation => true
validates_length_of :password, :in => 6..20, :on => :create
# Encrypting calls for new user's password
before_save :encrypt_password
after_save :clear_password
# Authentication process for user's login
def match_password(login_password = '')
encrypted_password == BCrypt::Engine.hash_secret(login_password, salt)
end
def self.authenticate(nim_as_login = '', login_password = '')
user = User.find_by_nim(nim_as_login)
if user && user.match_password(login_password)
return user
else
return false
end
end
# Encrypting process for new user's password
private
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password = BCrypt::Engine.hash_secret(password, salt)
end
end
private
def clear_password
self.password = nil
end
end
This is my login.rb code
class Login < ActiveRecord::Base
belongs_to :user
attr_accessor :password, :password_confirmation, :salt, :encrypted_password
#validating fields
validates :user_id, :password, presence: true
validates :password, confirmation: true
validates :password, length: { in: 6..30 }
# Encrypting calls for new user's password
before_save :encrypt_password
after_save :clear_password
# Authentication process for user's login
def match_password(login_password = '')
encrypted_password == BCrypt::Engine.hash_secret(login_password, salt)
end
def self.authenticate(nim_as_login = '', login_password = '')
user = User.find_by_nim(nim_as_login)
login = Login.find_by_user_id(user.id)
if login && login.match_password(login_password)
return login
else
return false
end
end
# Encrypting process for new user's password
private
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password = BCrypt::Engine.hash_secret(password, salt)
end
end
private
def clear_password
self.password = nil
end
end
this is my users controller code
class UsersController < ApplicationController
before_filter :save_login_state, only: [:new, :create]
def new
#user = User.new
#ranks = Rank.all
#types = UserType.all
#unit = SubUnit.all
#logins = Login.all
#page_title = "LoginUser | Novo"
end
def create
#user = User.new(user_params, login_params)
#LoginsController(:password, :password_confirmation)
#ranks = Rank.all
#types = UserType.all
#unit = SubUnit.all
#logins = Login.all
if #user.save
flash[:notice] = "Bem vindo #{#user.apelido}, sua conta foi criada com sucesso!"
redirect_to sessions_path
else
#user.errors.full_messages.each do |e|
if e == "Nim has already been taken"
flash.now[:error] = "Este Utilizador já está registado!"
else
flash.now[:error] = "Corrija os campos do formulário!"
end
end
render 'new'
end
end
def show
#user = User.find(params[:id])
#ranks = Rank.all
#types = UserType.all
#unit = SubUnit.all
#logins = Login.all
end
private
def user_params
params.require(:user).permit(:id, :nim, :posto_id, :apelido, :nome, :telefone, :telemovel, :email, :sub_un_id, :tipo_util_id)
end
def login_params
params.require(Login).permit(:password, :password_confirmation, :user_id )
end
end
this is my login controller
class LoginsController < ApplicationController
def create
#login = Login.all
end
end
this is my sessions controller
class SessionsController < ApplicationController
before_filter :authenticate_user, only: [:home, :profile, :setting]
before_filter :save_login_state, only: [:login, :login_attempt]
def login
#page_title = "LoginUser | Entrar"
end
def login_attempt
authorized_user = User.authenticate(params[:nim_as_login], params[:login_password])
if authorized_user
session[:user_id] = authorized_user.id
flash[:notice] = "Benvindo de volta #{authorized_user.apelido}"
redirect_to user_path
else
flash.now[:error] = "Email ou palavra passe inválida!"
render 'sessions/login'
end
end
def home
end
def profile
end
def setting
end
def logout
session[:user_id] = nil
redirect_to sessions_path
end
end
My question is, can i store the encrypted password and the user on different tables?
I'm at the end of 9th chapter and i'm getting an error on the root page itself..
sessions helper is
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
def sign_out
cookies.delete(:remember_token)
current_user = nil
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
here is my user.rb
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => {:within => 6..40 }
before_save :encrypt_password
def signed_in?
!current_user.nil?
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 if new_record?
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
end
and my session_controller is
class SessionsController < ApplicationController
def new
#title = "Sign in"
end
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to root_path
end
end
and users_controler is
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#user = User.new
#title = "Sign up"
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
end
thing is i'm a beginer in Rails and i was going through this tutorials n got stuck right here.
Pls help guyz
here is the screenshot of the error
error says: undefined method 'authentication_with_salt'
I think it is similar issue that was asked and the link is here
You miss current_user scope. Check the above link and hope you can fix your issue asap.