Commit 6a081d30 by Mai Hoang Thai Ha

Add account activation

parent 62a1e377
Pipeline #1274 failed with stages
in 0 seconds
...@@ -5,10 +5,17 @@ class SessionsController < ApplicationController ...@@ -5,10 +5,17 @@ class SessionsController < ApplicationController
def create def create
user = User.find_by(email: params[:session][:email].downcase) user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password]) if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user log_in user
params[:session][:remember_me] == '1'? remember(user) : forget(user) params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user redirect_back_or user
else else
messages = "Account not activated"
messages += "Check you email for the activation link"
flash[:warning] = messages
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination' flash.now[:danger] = 'Invalid email/password combination'
render 'new' render 'new'
end end
......
...@@ -18,10 +18,9 @@ class UsersController < ApplicationController ...@@ -18,10 +18,9 @@ class UsersController < ApplicationController
def create def create
@user = User.new(user_params) @user = User.new(user_params)
if @user.save if @user.save
log_in @user @user.send_activation_email
remember @user flash[:info] = "Please check your email to activate your account"
flash[:success] = "Welcome to the Sample App!" redirect_to root_url
redirect_to @user
else else
render 'new' render 'new'
end end
......
...@@ -17,7 +17,7 @@ module SessionsHelper ...@@ -17,7 +17,7 @@ module SessionsHelper
@current_user ||= User.find_by(id: user_id) @current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.encrypted[:user_id]) elsif (user_id = cookies.encrypted[:user_id])
user = User.find_by(id: user_id) user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token]) if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user log_in user
@current_user = user @current_user = user
end end
......
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com' default from: 'noreply@example.com'
layout 'mailer' layout 'mailer'
end end
class User < ApplicationRecord class User < ApplicationRecord
attr_accessor :remember_token attr_accessor :remember_token, :activation_token
before_save { self.email = email.downcase } before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum:50 } validates :name, presence: true, length: { maximum:50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum:255 }, validates :email, presence: true, length: { maximum:255 },
...@@ -28,13 +29,38 @@ class User < ApplicationRecord ...@@ -28,13 +29,38 @@ class User < ApplicationRecord
end end
# Return true if the given token matches the digest # Return true if the given token matches the digest
def authenticated?(remember_token) def authenticated?(attribute, token)
return false if remember_digest.nil? digest = send("#{attribute}_digest")
BCrypt::Password.new(remember_digest).is_password?(remember_token) return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end end
# Forgets a user # Forgets a user
def forget def forget
update_attribute(:remember_digest, nil) update_attribute(:remember_digest, nil)
end end
# Activates an account
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
private
# Convaerts email to all lowr_case
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token digest
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end end
\ No newline at end of file
...@@ -35,6 +35,9 @@ Rails.application.configure do ...@@ -35,6 +35,9 @@ Rails.application.configure do
# Don't care if the mailer can't send. # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
host = 'localhost:3000' # Don't use this literally; use your local dev host instead
# Use this if developing on localhost.
config.action_mailer.default_url_options = { host: host, protocol: 'http' }
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
......
...@@ -66,7 +66,19 @@ Rails.application.configure do ...@@ -66,7 +66,19 @@ Rails.application.configure do
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
host = 'https://enigmatic-retreat-09976.herokuapp.com'
config.action_mailer.default_url_options = { host: host }
ActionMailer::Base.smtp_settings = {
:address => 'smtp.sendgrid.net',
:port => '587',
:authentication => :plain,
:user_name => ENV['SENDGRID_USERNAME'],
:password => ENV['SENDGRID_PASSWORD'],
:domain => 'heroku.com',
:enable_starttls_auto => true
}
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found). # the I18n.default_locale when a translation cannot be found).
......
...@@ -42,6 +42,7 @@ Rails.application.configure do ...@@ -42,6 +42,7 @@ Rails.application.configure do
# The :test delivery method accumulates sent emails in the # The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array. # ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { host: 'example.com' }
# Print deprecation notices to the stderr. # Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr config.active_support.deprecation = :stderr
......
...@@ -10,4 +10,5 @@ Rails.application.routes.draw do ...@@ -10,4 +10,5 @@ Rails.application.routes.draw do
post '/login', to: 'sessions#create' post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy' delete '/logout', to: 'sessions#destroy'
resources :users resources :users
resources :account_activations, only: [:edit]
end end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_06_15_072636) do ActiveRecord::Schema.define(version: 2021_06_16_070505) do
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "name" t.string "name"
...@@ -20,6 +20,9 @@ ActiveRecord::Schema.define(version: 2021_06_15_072636) do ...@@ -20,6 +20,9 @@ ActiveRecord::Schema.define(version: 2021_06_15_072636) do
t.string "password_digest" t.string "password_digest"
t.string "remember_digest" t.string "remember_digest"
t.boolean "admin", default: false t.boolean "admin", default: false
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
end end
......
...@@ -2,11 +2,18 @@ User.create!( name: "Example User", ...@@ -2,11 +2,18 @@ User.create!( name: "Example User",
email: "example@railstutorial.org", email: "example@railstutorial.org",
password: "foobar", password: "foobar",
password_confirmation: "foobar", password_confirmation: "foobar",
admin: true) admin: true,
activated: true,
activated_at: Time.zone.now)
99.times do |num| 99.times do |num|
name = Faker::Name.name name = Faker::Name.name
email = "example-#{num+1}@railstutorials.org" email = "example-#{num+1}@railstutorials.org"
password = "password" password = "password"
User.create!(name: name, email: email, password: password, password_confirmation: password) User.create!(name: name,
email: email,
password: password,
password_confirmation:
password,activated: true,
activated_at: Time.zone.now)
end end
\ No newline at end of file
...@@ -2,25 +2,36 @@ michael: ...@@ -2,25 +2,36 @@ michael:
name: Michael Example name: Michael Example
email: michael@example.com email: michael@example.com
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
admin: true
activated: true
activated_at: <%= Time.zone.now %>
archer: archer:
name: Sterling Archer name: Sterling Archer
email: duchess@example.gov email: duchess@example.gov
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
lana: lana:
name: Lana Kane name: Lana Kane
email: hands@example.gov email: hands@example.gov
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
malory: malory:
name: Malory Archer name: Malory Archer
email: boss@example.gov email: boss@example.gov
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
<% 30.times do |n| %> <% 30.times do |n| %>
user_<%= n %>: user_<%= n %>:
name: <%= "User #{n}" %> name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %> email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
<% end %> <% end %>
\ No newline at end of file
...@@ -18,9 +18,9 @@ class UsersIndexTest < ActionDispatch::IntegrationTest ...@@ -18,9 +18,9 @@ class UsersIndexTest < ActionDispatch::IntegrationTest
assert_select 'a[href=?]', user_path(user),text: 'delete' assert_select 'a[href=?]', user_path(user),text: 'delete'
end end
end end
# assert_difference 'User.count', -1 do assert_difference 'User.count', -1 do
# delete user_path(@non_admin) delete user_path(@non_admin)
# end end
end end
test "index as non-admin" do test "index as non-admin" do
......
require "test_helper" require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest class UsersSignupTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
end
test "invalid signup information" do test "invalid signup information" do
get signup_path get signup_path
assert_no_difference 'User.count' do assert_no_difference 'User.count' do
...@@ -10,9 +15,11 @@ class UsersSignupTest < ActionDispatch::IntegrationTest ...@@ -10,9 +15,11 @@ class UsersSignupTest < ActionDispatch::IntegrationTest
password_confirmation: "bar" } } password_confirmation: "bar" } }
end end
assert_template 'users/new' assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end end
test "valid signup information" do test "valid signup information with account activation" do
get signup_path get signup_path
assert_difference 'User.count', 1 do assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User", post users_path, params: { user: { name: "Example User",
...@@ -20,6 +27,21 @@ class UsersSignupTest < ActionDispatch::IntegrationTest ...@@ -20,6 +27,21 @@ class UsersSignupTest < ActionDispatch::IntegrationTest
password: "password", password: "password",
password_confirmation: "password" } } password_confirmation: "password" } }
end end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
# Try to log in before activation.
log_in_as(user)
assert_not is_logged_in?
# Invalid activation token
get edit_account_activation_path("invalid token", email: user.email)
assert_not is_logged_in?
# Valid token, wrong email
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
# Valid activation token
get edit_account_activation_path(user.activation_token, email: user.email)
assert user.reload.activated?
follow_redirect! follow_redirect!
assert_template 'users/show' assert_template 'users/show'
assert is_logged_in? assert is_logged_in?
......
...@@ -56,6 +56,6 @@ class UserTest < ActiveSupport::TestCase ...@@ -56,6 +56,6 @@ class UserTest < ActiveSupport::TestCase
end end
test "authenticated? should return false for a user with nil digest" do test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('') assert_not @user.authenticated?(:remember, '')
end end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment