Ruby on Rails Michael Hartl chapter 8.5 Test Fails - ruby-on-rails

I've seen this question asked another time, in which the current_user variable was defined twice. I had the same error in my code, however removing the second definition of current_user still does not resolve the error message that I am receiving. This is the error message that I get when running my test:
1) Failure:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil [/home/ubuntu/workspace/sample_app/test/helpers/sessions_helper_test.rb:11]:
--- expected
+++ actual
## -1 +1 ##
-#<User id: 762146111, name: "Michael Example", email: "michael#example.com", created_at: "2016-06-06 19:15:21", updated_at: "2016-06-06 19:15:21", password_digest: "$2a$04$LkmHj4hHVcRoTuEH6icLs.E/GebaKaY2Y/TX5nqouIj...", remember_digest: nil>
+nil
This is my sessions_helper file:
module SessionsHelper
#logs in the given user
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
#returns current logged in user
#def current_user
##current_user ||= User.find_by(id: session[:user_id])
#end
#returns the user corresponding to the remember token
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
#forgets a persistent session
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
#logs out the current user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
This is my users_login_test file:
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "Login with invalid information followed by logout" do
get login_path
post login_path, 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_url
#simulate a user logging out in a different window
delete logout_path
follow_redirect!
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path, count: 0
assert_select "a[href=?]", user_path(#user), count: 0
end
test "login with remembering" do
log_in_as(#user, remember_me: '1')
assert_not_nil cookies['remember_token']
end
test "login without remembering" do
log_in_as(#user, remember_me: '0')
assert_nil cookies['remember_token']
end
end
This is my test_helper file:
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
fixtures :all
# Returns true if a test user is logged in.
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email: user.email,
password: password,
remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
This is my users.yml file:
michael:
name: Michael Example
email: michael#example.com
password_digest: <%= User.digest('password') %>
I'm really not sure whats going wrong, but I think I'm just missing something. Any help would be much appreciated! Thanks!
Edit: here is my user.rb file:
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = 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 },
niqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
#returns the digest of a given string
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#returns random token
def User.new_token
SecureRandom.urlsafe_base64
end
#remembers a user in the database for persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_token, User.digest(remember_token))
end
#returms true if the given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password? (remember_token)
end
#Forgets a user
def forget
update_attribute(:remember_digest, nil)
end
end
edit: sessions_helper_test.rb:
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:michael)
remember(#user)
end
test "current_user returns right user when session is nil" do
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
#user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end

Looking at your sessions_helper.rb, the # Logs out the current user method has been defined twice in the file. According to this tutorial in ch.8, in order to run the test suite green you need following content in your file.
I think you messed this up during listing 8.39 where you just need to add one line in the log_out method.
Please fill up the following code in the sessions_helper.rb and your tests would go green.
module SessionsHelper
# Logs in the given user.
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
# Returns the user corresponding to the remember token cookie.
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
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
Update:
Also in the app/models/user.rb file according to listing 8.45, you need to add a line return false if remember_digest.nil?. So the user.rb file should be as following.
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = 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 }
# Returns the hash digest of the given string.
def User.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 User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil? #Add this line
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Please let me know if you face any error further.

Related

undefined method forget for nil in test file - RailsTutorial 9.12

I am new to rails so please forgive the following noob question and thanks in advance...
I am following the rails tutorial however, once I got to section 9.12 where we add the forget logic to sessions_helper.rb, I get the below error which is confusing to me since I see the forget method defined properly. The error occurs when trying to run the users_login_test.rb file
Error:
UsersLoginTest#test_login_with_valid_information_followed_by_logout:
NoMethodError: undefined method `forget' for nil:NilClass
app/helpers/sessions_helper.rb:10:in `forget'
app/helpers/sessions_helper.rb:42:in `log_out'
app/controllers/sessions_controller.rb:18:in `destroy'
test/integration/users_login_test.rb:34:in `block in <class:UsersLoginTest>'
Here is my Sessions Helper:
module SessionsHelper
# Logs in the given user.
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
# Returns the user corresponding to the remember token cookie.
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
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
Here is my user.rb:
class User < ApplicationRecord
attr_accessor :remember_token
before_save { self.email = 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 }
# Returns the hash digest of the given string.
def User.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 User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.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
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Here is my failing testfile:
require test_helper
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "login with invalid information" do
get login_path
assert_template 'sessions/new'
post login_path, params: { session: { email: "", password: "" } }
assert_template 'sessions/new'
assert_not flash.empty?
get root_path
assert flash.empty?
end
test "login with valid information followed by logout" do
get login_path
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_url
# Simulate a user clicking logout in a second window.
delete logout_path
follow_redirect!
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
I noticed that I got ahead of myself in the tutorial and skipped a bit too far ahead by accident and added a second 'delete logout_path' to the test which was meant to demonstrate that the second call to delete logout_path in Listing 9.14 should raise an error due to the missing current_user. I just removed it and passed the test. I'll try to slow down as I continue through the tutorial :)

Hartl Rails Tutorial Chapter 8.46 NoMethodError

one my my tests receiving the following error in Chapter 8 of Hartl's Rails Tutorial.
> 1) Error:
> UsersLoginTest#test_login_with_valid_information_followed_by_logout:
> NoMethodError: undefined method `forget' for #<Class:0x000000079be3b0>
> app/helpers/sessions_helper.rb:30:in `forget'
> app/helpers/sessions_helper.rb:37:in `log_out'
> app/controllers/sessions_controller.rb:18:in `destroy'
> test/integration/users_login_test.rb:40:in `block in <class:UsersLoginTest>'
I have tried copying and pasting code exactly as it is in the tutorial, but it doesn't seem to be solving the issue.
Here is my Sessions Helper
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
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 forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
And my sessions controller is below
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 if logged_in?
redirect_to root_url
end
end
Any suggestions would be very much appreciated!
Here we can see the importance of really looking closely at the error message. It says there is a "No Method Error". OK. So we're doing something with the Users and there's no method. A user is a modeled thing, so let's look at app/models/user.rb:
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = 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 }
# Returns the hash digest of the given string.
def User.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 User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.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
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
we see that at the very end, there is a method being created called "forget"
def forget
#something
end
That opens up the method to calls from other controllers and helpers, which you've already created in the Sessions section.

Failure | SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil

This test failure is killing me. I'm following along with the railstutorial.org e-book. I've researched this failure as much as I can. I've read up on testing over and over, but I'm just missing something, and I have been stuck on this issue for nearly a week. I'm at wits end. Thank you for any insight.
rake test results in:
1) Failure:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil [/Users/test_user/app/test/helpers/sessions_helper_test.rb:11]:
--- expected
+++ actual
## -1 +1 ##
-#<User id: 762146111, first_name: "Michael", middle_name: "James", last_name: "Example", suffix: "Jr", created_at: "2015-08-01 14:13:26", updated_at: "2015-08-01 14:13:27", email: "michael#example.com", password_digest: "$2a$04$.AOr7J8q/Ft7Gu3HTNWjDet5kU2O8uFwV9l...", remember_digest: "$2a$04$./yszAudU5o/tovax413r.poDLg5N33a0XD...">
+nil
user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
validates :first_name, presence: true, length: { maximum: 20 }
validates :last_name, presence: true, length: { maximum: 30 }
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 }
validates :password, presence: true, length: { minimum: 6 }
has_secure_password
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# returns a url-safe random string of 20 characters, each character having 64 possibilities
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
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def forget
update_attribute(:remember_digest, nil)
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
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
sessions_helper.rb
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Returns the current logged-in user (if any).
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
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
sessions_helper_test.rb
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:michael)
remember(#user)
end
test "current_user returns right user when session is nil" do
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
#user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
users.yml
michael:
first_name: Michael
middle_name: James
last_name: Example
suffix: Jr
email: michael#example.com
password_digest: <%= User.digest('password') %>
The failing test makes no sense to me : the user is never logged in, and still the test expects current_user to returns a user.
Try with the 2 following tests :
test "current_user returns right user when session is set" do
log_in #user
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns no user when session is nil" do
assert_nil current_user
assert !is_logged_in?
end
Firstly, learn RSpec instead of test/unit.
Secondly, what's your version of Rails?
Generally the code you are presenting is honestly a really shitty implementation of login/logout functions. You don't need to store these functions in helper (controller can't access helper functions, at least not without magic, and you most definitely will need current_user function in your controllers), you don't need some rememberance 'tokens', and you don't need to mix 'cookies' and 'session' variables (by default 'session' IS special cookie, already signed/checked by Rails itself).
So just rewrite that example:
class User < ActiveRecord::Base
# validations, etc., what you have in your initial example
# just forget about 'remember token' and such stuff
end
class ApplicationController
# we don't want these methods to become actions
private
def log_in(user)
session[:user_id] = user.id
end
def current_user
#current_user ||= (session[:user_id] && User.find(session[:user_id]))
end
def logged_in?
current_user.present?
end
def log_out
# it's good idea to clear EVERYTHING from session on log-out, not just user_id
reset_session
#current_user = nil
end
end
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
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_url
end
end
Yeah, that looks much better. Unless you see a really really good reason to make your code look like mess with all that 'token' stuff.
Now to the specs!
Install rspec-rails (basically, add couple of lines to your Gemfile)
rails g rspec:controller sessions in your terminal
Something like that in your newly generated spec:
-
describe SesssionsController
# move your fixtures to appropriate folder beforehand (basically, from test/something to spec/something)
fixtures :users
before do
#michael = users(:michael)
end
it 'logs user in'
post :create, session: {email: 'michael#example.com', password: 'password'}
expect(controller.send(:current_user)).to_not be_nil
expect(controller.send(:current_user).id).to eq #michael.id
end
end
Didn't test that code, I must admit, so if you encounter some errors which are not obvious, feel free to ask for further help in comments to my reply.
The problem is in the remember method in the User model.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(:remember_token))
end
should be
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
I'm not sure on the specifics of why the original doesn't work, but putting the remember_token there as a symbol is what's breaking it.
In reference to the current best answer:
Adding a call to log_in in the test only circumvents the problem above, and actually makes the test pointless. What you're trying to test here, in this test:
test 'current_user returns right user when session is nil' do
assert_equal #user, current_user
assert is_logged_in?
end
is that the part of the current_user method that handles users that have a signed cookie, but don't have an active session, is working (logging the user in, and assigning them to #current_user).
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)
#### THIS BIT ####
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
##################
end
end
So by just logging in the user in the setup, you are passing the current_user method a user with an active session, which means it's not ever reaching the part of the method you're trying to test.

How do I troubleshoot this error?

I'm working my way through Michael Hartl's Rails Tutorial and I am currently on chapter 8, in section 8.55. I am trying to trouble shoot a failure that I'm receiving via that test, which I can't figure out where to solve.
FAIL["test_current_user_returns_right_user_when_session_is_nil", SessionsHelperTest, 0.05582] test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (0.06s)
--- expected
+++ actual
## -1 +1 ##
-#<User id: 584273342, name: "Kyle Example", email: "kyle#example.com", created_at: "2014-12-27 20:09:35", updated_at: "2014-12-27 20:09:35", password_digest: "$2a$04$yK7dSRppfGCqCbrXXZ34meDB2jEulxy9BDVNH32qLDz...", remember_digest: "$2a$04$GT65nJ.fE90LasXpaT1HruCChf.hl.4fvMrtx2iV48V...">
+nil
test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>'
My test code is using the following code.
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:kyle)
remember(#user)
end
test "current_user returns right user when session is nil" do
assert_equal #user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
#user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
SessionsHelper module
module SessionsHelper
# Logs in the given user.
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
#Returns the user corresponding to the remember token cookie.
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
#Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
#Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
#current_user = nil
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
User model
class User < ActiveRecord::Base
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\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 }
# Returns the hash digest of the given string.
def User.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 User.new_token
SecureRandom.urlsafe_base64
end
#Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
#Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember.digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Any ideas on what could have caused this?
You have two
def current_user
methods in module SessionsHelper
remove this one(which is old one and doesn't check for cookies presence):
#Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
it is being used in your test currently, because it is defined below updated one.

Chapter 8 - possible bypass of login?

I am working through Hartl's Ruby on Rails Tutorial (3rd Ed.) and just completed Chapter 8. One of the goals of the chapter is to have the application sign users out by deleting the session’s user id and remove the permanent cookie from the browser. However, I've logged out of the app and then typed http://localhost:3000/users/1 in the browser and have been taken back to the profile for user 1. Is this a security issue?
The log in screen:
Logged in:
Logged out and directed back to the home page:
Now, here is where I'm confused. If I type in users/1 in the browser's address bar, I seem to be taken back to user #1's page, although the upper right corner doesn't show me as logged in.
Does anybody think that this is a security issue?
Here is some of the code related to the app. Am I missing something?
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
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
params[:session][:remember_me] == '1' ? remember(#user) : forget(#user)
redirect_to #user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
sessions_helper.rb
module SessionsHelper
# Logs in the given user.
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
# Returns the user corresponding to the remember token cookie.
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
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = 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, length: { minimum: 6 }
# Returns the hash digest of the given string.
class << self
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
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Thanks.
No. At this point there's no security issue. You're issuing a GET request for /users/1 and Rails delivers that. There is nothing preventing that page from being shown.
As sevenseacat explained in a comment above, there are no access restrictions there.

Resources