Skip to content

Commit

Permalink
Finish log in/log out
Browse files Browse the repository at this point in the history
  • Loading branch information
bodfarian committed Nov 2, 2014
1 parent 75dae5f commit 1babced
Show file tree
Hide file tree
Showing 22 changed files with 284 additions and 18 deletions.
3 changes: 2 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
//
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require turbolinks
//= require_tree .
//= require_tree .
3 changes: 3 additions & 0 deletions app/assets/javascripts/sessions.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
13 changes: 13 additions & 0 deletions app/assets/stylesheets/custom.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,19 @@ input {
}
}

.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}

#session_remember_me {
width: auto;
margin-left: 0;
}

/* miscellaneous */

Expand Down
3 changes: 3 additions & 0 deletions app/assets/stylesheets/sessions.css.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the Sessions controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
def hello
render text: "Howdy, folks!"
end
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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' # Not quite right!
render 'new'
end
end

def destroy
log_out if logged_in?
redirect_to root_url
end
end
2 changes: 1 addition & 1 deletion app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def new
def create
@user = User.new(user_params)
if @user.save
# Handle a successful save.
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
Expand Down
48 changes: 48 additions & 0 deletions app/helpers/sessions_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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
32 changes: 31 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
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 },
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
25 changes: 21 additions & 4 deletions app/views/layouts/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<li><%= link_to "Log in", '#' %></li>
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", '#' %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", '#' %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
</header>
24 changes: 24 additions & 0 deletions app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) do |f| %>

<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>

<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>

<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>

<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>

<p>New user? <%= link_to "Sign up now!", signup_path %></p>
</div>
</div>
15 changes: 8 additions & 7 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
Rails.application.routes.draw do
get 'users/new'

root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users
end

Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20141102194813_add_remember_digest_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddRememberDigestToUsers < ActiveRecord::Migration
def change
add_column :users, :remember_digest, :string
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20141029201149) do
ActiveRecord::Schema.define(version: 20141102194813) do

create_table "users", force: true do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
end

add_index "users", ["email"], name: "index_users_on_email", unique: true
Expand Down
1 change: 0 additions & 1 deletion spring/50c4ccb1d8f630979e84c03ba14676d1.pid

This file was deleted.

9 changes: 9 additions & 0 deletions test/controllers/sessions_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'test_helper'

class SessionsControllerTest < ActionController::TestCase
test "should get new" do
get :new
assert_response :success
end

end
5 changes: 4 additions & 1 deletion test/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# empty
michael:
name: Michael Example
email: [email protected]
password_digest: <%= User.digest('password') %>
19 changes: 19 additions & 0 deletions test/helpers/sessions_helper_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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
39 changes: 39 additions & 0 deletions test/integration/users_login_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

def setup
@user = users(:michael)
end

test "login with valid 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 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

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
1 change: 1 addition & 0 deletions test/integration/users_signup_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ class UsersSignupTest < ActionDispatch::IntegrationTest
password_confirmation: "password" }
end
assert_template 'users/show'
assert is_logged_in?
end
end
4 changes: 4 additions & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ def setup
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end

test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('')
end
end
25 changes: 24 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,28 @@ class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all

# Add more helper methods to be used by all tests here...
# 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

0 comments on commit 1babced

Please sign in to comment.