Skip to content

Commit 163163b

Browse files
committed
Implement advanced login
1 parent be6ca5a commit 163163b

File tree

11 files changed

+125
-9
lines changed

11 files changed

+125
-9
lines changed

app/assets/stylesheets/custom.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ input {
194194
color: $state-danger-text;
195195
}
196196
}
197+
.checkbox {
198+
margin-top: -10px;
199+
margin-bottom: 10px;
200+
span {
201+
margin-left: 20px;
202+
font-weight: normal;
203+
}
204+
}
205+
206+
#session_remember_me {
207+
width: auto;
208+
margin-left: 0;
209+
}
197210

198211
/* Dropdown menu */
199212

app/controllers/sessions_controller.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def create
77
user = User.find_by(email: params[:session][:email].downcase)
88
if user && user.authenticate(params[:session][:password])
99
reset_session
10+
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
1011
log_in user
1112
redirect_to user
1213
else
@@ -16,7 +17,7 @@ def create
1617
end
1718

1819
def destroy
19-
log_out
20+
log_out if logged_in?
2021
redirect_to root_url, status: :see_other
2122
end
2223
end

app/helpers/sessions_helper.rb

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@ def log_in(user)
55
session[:user_id] = user.id
66
end
77

8-
# Returns the current logged-in user (if any).
8+
# Remembers a user in a persistent session.
9+
def remember(user)
10+
user.remember
11+
cookies.permanent.encrypted[:user_id] = user.id
12+
cookies.permanent[:remember_token] = user.remember_token
13+
end
14+
15+
# Returns the user corresponding to the remember token cookie.
916
def current_user
10-
if session[:user_id]
11-
@current_user ||= User.find_by(id: session[:user_id])
17+
if (user_id = session[:user_id])
18+
@current_user ||= User.find_by(id: user_id)
19+
elsif (user_id = cookies.encrypted[:user_id])
20+
user = User.find_by(id: user_id)
21+
if user && user.authenticated?(cookies[:remember_token])
22+
log_in user
23+
@current_user = user
24+
end
1225
end
1326
end
1427

@@ -17,8 +30,16 @@ def logged_in?
1730
!current_user.nil?
1831
end
1932

33+
# Forgets a persistent session.
34+
def forget(user)
35+
user.forget
36+
cookies.delete(:user_id)
37+
cookies.delete(:remember_token)
38+
end
39+
2040
# Logs out the current user.
2141
def log_out
42+
forget(current_user)
2243
reset_session
2344
@current_user = nil
2445
end

app/models/user.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
class User < ApplicationRecord
2+
attr_accessor :remember_token
23
before_save { self.email = email.downcase }
34
validates :name, presence: true, length: { maximum: 50 }
45
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
@@ -14,4 +15,26 @@ def User.digest(string)
1415
BCrypt::Engine.cost
1516
BCrypt::Password.create(string, cost: cost)
1617
end
18+
19+
# Returns a random token.
20+
def User.new_token
21+
SecureRandom.urlsafe_base64
22+
end
23+
24+
# Remembers a user in the database for use in persistent sessions.
25+
def remember
26+
self.remember_token = User.new_token
27+
update_attribute(:remember_digest, User.digest(remember_token))
28+
end
29+
30+
# Returns true if the given token matches the digest.
31+
def authenticated?(remember_token)
32+
return false if remember_digest.nil?
33+
BCrypt::Password.new(remember_digest).is_password?(remember_token)
34+
end
35+
36+
# Forgets a user.
37+
def forget
38+
update_attribute(:remember_digest, nil)
39+
end
1740
end

app/views/sessions/new.html.erb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
<%= f.label :password %>
1212
<%= f.password_field :password, class: 'form-control' %>
1313

14+
<%= f.label :remember_me, class: "checkbox inline" do %>
15+
<%= f.check_box :remember_me %>
16+
<span>Remember me on this computer</span>
17+
<% end %>
18+
1419
<%= f.submit "Log in", class: "btn btn-primary" %>
1520
<% end %>
1621

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddRememberDigestToUsers < ActiveRecord::Migration[7.0]
2+
def change
3+
add_column :users, :remember_digest, :string
4+
end
5+
end

db/schema.rb

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/helpers/sessions_helper_test.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require "test_helper"
2+
3+
class SessionsHelperTest < ActionView::TestCase
4+
5+
def setup
6+
@user = users(:michael)
7+
remember(@user)
8+
end
9+
10+
test "current_user returns right user when session is nil" do
11+
assert_equal @user, current_user
12+
assert is_logged_in?
13+
end
14+
15+
test "current_user returns nil when remember digest is wrong" do
16+
@user.update_attribute(:remember_digest, User.digest(User.new_token))
17+
assert_nil current_user
18+
end
19+
end

test/integration/users_login_test.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,24 @@ def setup
3333
assert_not is_logged_in?
3434
assert_response :see_other
3535
assert_redirected_to root_url
36+
# Simulate a user clicking logout in a second window.
37+
delete logout_path
3638
follow_redirect!
3739
assert_select "a[href=?]", login_path
3840
assert_select "a[href=?]", logout_path, count: 0
3941
assert_select "a[href=?]", user_path(@user), count: 0
4042
end
43+
44+
test "login with remembering" do
45+
log_in_as(@user, remember_me: '1')
46+
assert_not cookies[:remember_token].blank?
47+
end
48+
49+
test "login without remembering" do
50+
# Log in to set the cookie.
51+
log_in_as(@user, remember_me: '1')
52+
# Log in again and verify that the cookie is deleted.
53+
log_in_as(@user, remember_me: '0')
54+
assert cookies[:remember_token].blank?
55+
end
4156
end

test/models/user_test.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def setup
66
@user = User.new(name: "Example User", email: "[email protected]",
77
password: "foobar", password_confirmation: "foobar")
88
end
9-
9+
1010
test "should be valid" do
1111
assert @user.valid?
1212
end
@@ -64,4 +64,8 @@ def setup
6464
@user.password = @user.password_confirmation = "a" * 5
6565
assert_not @user.valid?
6666
end
67+
68+
test "authenticated? should return false for a user with nil digest" do
69+
assert_not @user.authenticated?('')
70+
end
6771
end

0 commit comments

Comments
 (0)