- rails pre/append 'salt' before running through a hash algorithm
Digest::SHA1.digest("abcd1234")
"|\xE05\x9F\x12\x85\x7F*\x90\xC7\xDEF_@\xA9_\x01\xCB]\xA9"
2.2.3 :007 >
- start by creating a user model
bin/rails g model user first_name last_name email password_digest
rake db:migrate
- User model
class User < ActiveRecord::Base
attr_accessor :password
attr_accessor :password_confirmation
end
- now we can do something like:
2.2.3 :001 > u = User.new
+----+----------+----------+-------+----------+---------+----------+
| id | first... | last_... | email | passw... | crea... | updat... |
+----+----------+----------+-------+----------+---------+----------+
| | | | | | | |
+----+----------+----------+-------+----------+---------+----------+
1 row in set
2.2.3 :002 > u.password = "super_secrete"
"super_secret"
- has_secure_password(options = {})
- Adds methods to set and authenticate against a BCrypt password.
- This mechanism requires you to have a password_digest attribute.
- there are a few validations: password present, length, ..
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
- Modify the user model
class User < ActiveRecord::Base
# attr_accessor :password
# attr_accessor :password_confirmation
# more info about has_secure_password can be found here:
# http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html
has_secure_password
validates :password, length: {minimum: 6}
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true,
uniqueness: true,
format: /\A([\w+\-]\.?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
end
- test out the has_secure_password model
2.2.3 :001 > u = User.new(first_name:"jennifer", last_name:"Li", email:"[email protected]", password:"supersecret", password_confirmation:"supersecret")
+----+-----------+-----------+-----------+------------+-----------+------------+
| id | first_... | last_name | email | passwor... | create... | updated_at |
+----+-----------+-----------+-----------+------------+-----------+------------+
| | jennifer | Li | ldnjen... | $2a$10$... | | |
+----+-----------+-----------+-----------+------------+-----------+------------+
1 row in set
2.2.3 :002 > u.save
(0.3ms) BEGIN
SQL (0.5ms) INSERT INTO "users" ("first_name", "last_name", "email", "password_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["first_name", "jennifer"], ["last_name", "Li"], ["email", "[email protected]"], ["password_digest", "$2a$10$nsf0CKMrmi0d1D7GKN5kmuZEZRF9fl/8WHPDRiY1fhBdjHxCA2OhO"], ["created_at", "2016-02-11 19:00:00.509358"], ["updated_at", "2016-02-11 19:00:00.509358"]]
(2.5ms) COMMIT
true
2.2.3 :003 > u = User.new(first_name:"jennifer", last_name:"Li", email:"[email protected]", password:"supersecret", password_confirmation:"supersecretdd")
+----+-----------+-----------+-----------+------------+-----------+------------+
| id | first_... | last_name | email | passwor... | create... | updated_at |
+----+-----------+-----------+-----------+------------+-----------+------------+
| | jennifer | Li | ldnjen... | $2a$10$... | | |
+----+-----------+-----------+-----------+------------+-----------+------------+
1 row in set
2.2.3 :004 > u.save
(0.2ms) BEGIN
(0.2ms) ROLLBACK
false
####cookies
- store session cookies in browser
def about
cookies.signed[:abc] = "xyz"
cookies.signed[:hello] = "good bye"
# will all be stored in the same session
session[:foo] = "bar"
session[:foo1] = "bar1"
session[:foo2] = "bar2"
end
- in the browser, the cookie will have this when about is visited:
- in application_controller
def user_signed_in?
session[:user_id].present?
end
helper_method :user_signed_in?
def current_user
@current_user ||= User.find(session[:user_id])
end
# adding 'helper_method :current_user' makes this method
# available in all view files as well as a helper method
helper_method :current_user
- in application.html.erb
<% if user_signed_in? %>
Hello <%= current_user.first_name %>
<% else %>
<%= link_to "Sign Up", new_user_path %>
<% end %>
- users_controller
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new user_params
if @user.save
session[:user_id] = @user.id
redirect_to root_path, notice: "User created successfully"
else
flash[:alert] = "Error creating user"
render :new
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
- user new view
<h1>Sign Up</h1>
<%= @user.errors.full_messages.join(",") %>
<div>
<%= form_for @user do |f| %>
<div>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
</div>
<div>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
</div>
<div>
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<div>
<%= f.label :password %>
<%= f.password_field :password, required:true%>
</div>
<div>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, required:true %>
</div>
<%= f.submit %>
<% end %>
</div>
- create a new controller called sessions
> rails g controller sessions
- routes:
resources :sessions, only: [:new, :create] do
delete :destroy, on: :collection
end
- view
<h1>Sign in</h1>
<%= form_tag sessions_path do %>
<div>
<%= label_tag :email %>
<%= email_field_tag :email %>
</div>
<div>
<%= label_tag :password %>
<%= password_field_tag :password %>
</div>
<%= submit_tag %>
<% end %>
- use the authenticate method came with rails
2.2.3 :002 > u = User.find_by_email "[email protected]"
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT 1 [["email", "[email protected]"]]
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
| id | first_name | last_name | email | password_digest | created_at | updated_at |
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
| 4 | Jennifer | hello | [email protected] | $2a$10$eN8M/myrih503r1UA41boe4Aw7rjNgV8FJVJtVBt5GxEKwlGjIFWW | 2016-02-11 19:28:58 UTC | 2016-02-11 19:28:58 UTC |
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
1 row in set
2.2.3 :003 > u.authenticate("123456")
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
| id | first_name | last_name | email | password_digest | created_at | updated_at |
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
| 4 | Jennifer | hello | [email protected] | $2a$10$eN8M/myrih503r1UA41boe4Aw7rjNgV8FJVJtVBt5GxEKwlGjIFWW | 2016-02-11 19:28:58 UTC | 2016-02-11 19:28:58 UTC |
+----+------------+-----------+--------------+--------------------------------------------------------------+-------------------------+-------------------------+
1 row in set
2.2.3 :004 > u.authenticate("12345633")
false
2.2.3 :005 >
- add a sign_in method in application controller
def sign_in(user)
session[:user_id] = user.id
end
- sessions_controller
class SessionsController < ApplicationController
def new
end
def create
# render json: params
user = User.find_by_email params[:email]
if user && user.authenticate(params[:password])
# the sign_in method is defined in application controller
sign_in(user)
redirect_to root_path, notice: "Welcome #{user.first_name}!"
else
flash[:alert] = "wrong credentials"
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: "Sign out!"
end
end
- controller
def destroy
session[:user_id] = nil
redirect_to root_path, notice: "Sign out!"
end
- application controller
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
def sign_in(user)
session[:user_id] = user.id
end
def user_signed_in?
session[:user_id].present?
end
helper_method :user_signed_in?
def current_user
@current_user ||= User.find(session[:user_id])
end
# adding 'helper_method :current_user' makes this method
# available in all view files as well as a helper method
helper_method :current_user
end
- application.html.erb view
<% if user_signed_in? %>
Hello, <%= current_user.first_name %>!
<%= link_to "Log out", sessions_path, method: :delete %>
<% else %>
<%= link_to "Sign in", new_session_path %>
<%= link_to "Sign Up", new_user_path %>
<% end %>
- start by adding user:references to answer and question
rails g migration add_user_to_answers user:references
- specify association in the user model
has_many :questions, dependent: :nullify
has_many :answers, dependent: :nullify
- and answer and question model:
belongs_to :user
- question/answer controller
before_action :authenticate_user, except: [:index, :show]
def create
@question = Question.new question_params
@question.user = current_user
if @question.save
...
- application controller:
def current_user
@current_user ||= User.find(session[:user_id])
end
def authenticate_user
# if session[:user_id] is nil we redirect to the sign in page
redirect_to new_session_path, notice: "Please sign in." unless user_signed_in?
end
- display user name by answer. show.html.erb:
<ul>
<% @question.answers.each do |ans| %>
<li><%= ans.body %> <em>created_by: <%= ans.user_full_name %></em>
<%= link_to "Delete", question_answer_path(@question, ans),
method: :delete,
data: {confirm: "Are you sure?"} %></li>
<% end %>
</ul>
- in answer controller
# delegate :full_name, to: :user, prefix: true
def user_full_name
user.full_name if user
end
- in user controller
def full_name
"#{first_name} #{last_name}".titleize
end
- application controller:
def current_user
@current_user ||= User.find(session[:user_id]) if user_signed_in?
end
end
- question controller
before_action :authorize_user, only: [:edit, :update, :destroy]
...
def authorize_user
if @question.user != current_user
redirect_to root_path, alert: "access denied!"
end
- view
<% if user_signed_in? && current_user == @question.user %>
<%= link_to "Edit", edit_question_path(@question) %>
<%# method: :delete asks Rails to send a DELETE request instead of GET which
is accomplished using Javascript/jQuery %>
<%= link_to "Delete",
question_path(@question),
method: :delete,
data: {confirm: "Are you sure?"} %>
<% end %>
- centralized location for defining user permission rules
gem 'cancancan'
- after installing the gem and run bundle, generate an ability.rb file:
bin/rails g cancan:ability
- the ability.rb is a part of the model
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
# if user.admin?
# can :manage, :all
# else
# can :read, :all
# end
#
# we defined an ability using the can method that comes from 'cancancan'
# :manage allows any action on the model (in this case Question)
# example: :edit, :destroy, :update..etc
# this doesn't enforce the rule, we just define the rule in here.
can :manage, Question do |question|
question.user == user
end
...
end
- application controller
def authorize_user
unless can? :manage
redirect_to root_path, alert: "access denied!"
end
- the view can now be changed to:
<% if can? :manage %>
<%= link_to "Edit", edit_question_path(@question) %>
<%# method: :delete asks Rails to send a DELETE request instead of GET which
is accomplished using Javascript/jQuery %>
<%= link_to "Delete",
question_path(@question),
method: :delete,
data: {confirm: "Are you sure?"} %>
<% end %>
- Add admin user column to users
bin/rails g migration add_admin_to_users admin:boolean
- the migration file looks like this, add default:false if you want to default admin to false
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
- run rake db:migrate, and add this to ability.rb (if the file has this commented out, uncomment it)
if user.admin?
can :manage, :all
else
can :read, :all
end