thoughtbot-clearance 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. data/README.textile +17 -16
  2. data/Rakefile +8 -7
  3. data/TODO.textile +3 -3
  4. data/generators/clearance/clearance_generator.rb +1 -1
  5. data/generators/clearance/templates/app/views/clearance_mailer/change_password.html.erb +1 -1
  6. data/generators/clearance/templates/app/views/passwords/edit.html.erb +1 -1
  7. data/generators/clearance/templates/app/views/sessions/new.html.erb +2 -2
  8. data/generators/clearance/templates/db/migrate/create_users_with_clearance_columns.rb +3 -3
  9. data/generators/clearance/templates/db/migrate/update_users_with_clearance_columns.rb +14 -13
  10. data/generators/clearance/templates/test/factories/clearance.rb +16 -0
  11. data/lib/clearance/app/controllers/application_controller.rb +26 -14
  12. data/lib/clearance/app/controllers/confirmations_controller.rb +6 -3
  13. data/lib/clearance/app/controllers/passwords_controller.rb +15 -9
  14. data/lib/clearance/app/controllers/sessions_controller.rb +21 -17
  15. data/lib/clearance/app/controllers/users_controller.rb +4 -3
  16. data/lib/clearance/app/models/clearance_mailer.rb +1 -1
  17. data/lib/clearance/app/models/user.rb +15 -16
  18. data/lib/clearance/test/functional/confirmations_controller_test.rb +18 -30
  19. data/lib/clearance/test/functional/passwords_controller_test.rb +27 -45
  20. data/lib/clearance/test/functional/sessions_controller_test.rb +23 -27
  21. data/lib/clearance/test/functional/users_controller_test.rb +38 -28
  22. data/lib/clearance/test/test_helper.rb +7 -2
  23. data/lib/clearance/test/unit/clearance_mailer_test.rb +7 -5
  24. data/lib/clearance/test/unit/user_test.rb +107 -138
  25. data/shoulda_macros/clearance.rb +134 -6
  26. metadata +7 -3
  27. data/generators/clearance/templates/test/factories/clearance_user.rb +0 -9
@@ -23,7 +23,8 @@ In config/environment.rb:
23
23
  :version => '>= 1.1.5'
24
24
  config.gem "thoughtbot-clearance",
25
25
  :lib => 'clearance',
26
- :source => 'http://gems.github.com'
26
+ :source => 'http://gems.github.com',
27
+ :version => '>= 0.3.8'
27
28
 
28
29
  Then:
29
30
 
@@ -113,7 +114,7 @@ In config/environment.rb:
113
114
 
114
115
  h2. Tests
115
116
 
116
- The tests use "Shoulda":http://thoughtbot.com/projects/shoulda >= 2.0.4 and "Factory Girl":http://thoughtbot.com/projects/factory_girl. You need to add the Clearance module to your test/test_helper.rb:
117
+ The tests use "Shoulda":http://thoughtbot.com/projects/shoulda >= 2.0.6 and "Factory Girl":http://thoughtbot.com/projects/factory_girl >= 1.1.5. You need to add the Clearance module to your test/test_helper.rb:
117
118
 
118
119
  class Test::Unit::TestCase
119
120
  self.use_transactional_fixtures = true
@@ -121,7 +122,7 @@ The tests use "Shoulda":http://thoughtbot.com/projects/shoulda >= 2.0.4 and "Fac
121
122
  include Clearance::Test::TestHelper
122
123
  end
123
124
 
124
- The generator will create a user factory in test/factories/clearance_user.rb unless
125
+ The generator will create a user factory in test/factories/clearance.rb unless
125
126
  you have it defined somewhere else.
126
127
 
127
128
  h2. Usage: basic workflow
@@ -139,22 +140,22 @@ To protect your controllers with authentication add:
139
140
  class ProtectedController < ApplicationController
140
141
  before_filter :authenticate
141
142
 
142
- The filter will ensure that only authenticated users can access the controller. If someone who's not logged in tries to access a protected action:
143
+ The filter will ensure that only authenticated users can access the controller. If someone who's not signed in tries to access a protected action:
143
144
 
144
145
  * the URL is stored in the session,
145
146
  * the user is redirected to log in page, and
146
147
  * after successful authentication will be be redirected back to that URL.
147
148
 
148
- h2. Usage: logged_in?, current_user
149
+ h2. Usage: signed_in?, current_user
149
150
 
150
151
  Clearance provides two methods that can be used in controllers, helpers, and views to check if current user is authenticated and get the actual user:
151
152
 
152
- * logged_in?
153
+ * signed_in?
153
154
  * current_user
154
155
 
155
156
  This may be familiar to you if you've used "restful_authentication":http://github.com/technoweenie/restful-authentication.
156
157
 
157
- <% if logged_in? -%>
158
+ <% if signed_in? -%>
158
159
  Hello, <%= current_user.name %>!
159
160
  <% else -%>
160
161
  Please <%= link_to 'Log in', new_session_path %>
@@ -169,14 +170,14 @@ Please note that all User attributes except email, password and password_confirm
169
170
  attr_accessible :first_name, :last_name
170
171
  ...
171
172
 
172
- h2. Hooks and overwritables: return_to parameter
173
+ h2. Hooks: return_to parameter
173
174
 
174
- If you want to specify where to redirect a user (say you want to have a login box on every page and redirect the user to the same page) after he/she signs in, you can add a "return_to" parameter to the request (thanks to "Phillippe":http://www.sivarg.com/2009/01/clearance-coming-from-where-your-were.html for the tip):
175
+ If you want to specify where to redirect a user (say you want to have a sign in box on every page and redirect the user to the same page) after he/she signs in, you can add a "return_to" parameter to the request (thanks to "Phillippe":http://www.sivarg.com/2009/01/clearance-coming-from-where-your-were.html for the tip):
175
176
 
176
177
  <% form_for :session, :url => session_path(:return_to => request.request_uri) do |form| %>
177
178
  ...
178
179
 
179
- h2. Hooks and overwritables: url_after_create, url_after_destroy
180
+ h2. Hooks: url_after_create, url_after_destroy
180
181
 
181
182
  Actions that redirect (create and destroy) in Clearance controllers are customizable. If you want to redirect a user to a specific route after signing in, overwrite the "url_after_create" method in the SessionsController:
182
183
 
@@ -198,11 +199,11 @@ There are similar methods in other controllers as well:
198
199
  PasswordsController#url_after_create (password reset request)
199
200
  ConfirmationsController#url_after_create (confirmation)
200
201
 
201
- h2. Hooks and overrides: log_user_in
202
+ h2. Hooks: log_user_in
202
203
 
203
- Say you want to add a last_logged_in_at attribute to your User model. You would want to update it when the User logs in.
204
+ Say you want to add a last_signed_in_at attribute to your User model. You would want to update it when the User logs in.
204
205
 
205
- Clearance has a method named log_user_in that you can override with that logic. Be sure to call login(user) at the end (and write tests!).
206
+ Clearance has a method named log_user_in that you can overwrite with that logic. Be sure to call sign_in(user) at the end (and write tests!).
206
207
 
207
208
  class ApplicationController < ActionController::Base
208
209
  include Clearance::App::Controllers::ApplicationController
@@ -210,9 +211,9 @@ Clearance has a method named log_user_in that you can override with that logic.
210
211
  private
211
212
 
212
213
  def log_user_in(user)
213
- # store current time to display "last logged in at" message
214
- user.update_attribute(:last_logged_in_at, Time.now)
215
- login(user)
214
+ # store current time to display "last signed in at" message
215
+ user.update_attribute(:last_signed_in_at, Time.now)
216
+ sign_in(user)
216
217
  end
217
218
  end
218
219
 
data/Rakefile CHANGED
@@ -35,14 +35,15 @@ desc "Run the test suite"
35
35
  task :default => 'test:all'
36
36
 
37
37
  gem_spec = Gem::Specification.new do |gem_spec|
38
- gem_spec.name = "clearance"
39
- gem_spec.version = '0.3.8'
40
- gem_spec.summary = "Simple, complete Rails authentication."
41
- gem_spec.email = "support@thoughtbot.com"
42
- gem_spec.homepage = "http://github.com/thoughtbot/clearance"
38
+ gem_spec.name = "clearance"
39
+ gem_spec.version = "0.3.9"
40
+ gem_spec.summary = "Simple, complete Rails authentication."
41
+ gem_spec.email = "support@thoughtbot.com"
42
+ gem_spec.homepage = "http://github.com/thoughtbot/clearance"
43
43
  gem_spec.description = "Simple, complete Rails authentication scheme."
44
- gem_spec.authors = ["thoughtbot, inc.", "Josh Nichols", "Mike Breen"]
45
- gem_spec.files = FileList["[A-Z]*", "{generators,lib,shoulda_macros,rails}/**/*"]
44
+ gem_spec.authors = ["thoughtbot, inc.", "Dan Croak", "Mike Burns", "Jason Morrison",
45
+ "Eugene Bolshakov", "Josh Nichols", "Mike Breen"]
46
+ gem_spec.files = FileList["[A-Z]*", "{generators,lib,shoulda_macros,rails}/**/*"]
46
47
  end
47
48
 
48
49
  desc "Generate a gemspec file"
@@ -1,8 +1,8 @@
1
1
  (highest priority first)
2
2
 
3
+ # refactor password controller test
3
4
  # existing_user? methods ... if salt is wrong, user may not be found b/c of invalid credentials. is :not_found the correct code to return in that use case? if not, method probably needs to be split into another conditional.
4
- # email confirmation is a strategy
5
- # forgot password is a strategy
6
- # salted password is a strategy
5
+ # document shoulda macros
6
+ # will SHA512 hashes fit in all the places they are being used? (db columns, sessions) 128 characters
7
7
 
8
8
  http://adam.speaksoutofturn.com/post/57615195/entication-vs-orization
@@ -67,7 +67,7 @@ class ClearanceGenerator < Rails::Generator::Base
67
67
  end
68
68
 
69
69
  m.directory File.join("test", "factories")
70
- ["test/factories/clearance_user.rb"].each do |file|
70
+ ["test/factories/clearance.rb"].each do |file|
71
71
  m.file file, file
72
72
  end
73
73
 
@@ -3,6 +3,6 @@ Someone, hopefully you, has requested that we send you a link to change your pas
3
3
  Here's the link:
4
4
  <%= edit_user_password_url(@user,
5
5
  :email => @user.email,
6
- :password => @user.crypted_password) %>
6
+ :password => @user.encrypted_password) %>
7
7
 
8
8
  If you didn't request this, no need to freak out, your password hasn't been changed. You can just ignore this email.
@@ -7,7 +7,7 @@
7
7
  <% form_for(:user,
8
8
  :url => user_password_path(@user,
9
9
  :email => @user.email,
10
- :password => @user.crypted_password),
10
+ :password => @user.encrypted_password),
11
11
  :html => { :method => :put }) do |form| %>
12
12
  <div class="password_field">
13
13
  <%= form.label :password, 'Choose password' %>
@@ -12,13 +12,13 @@
12
12
  <%= form.label :remember_me %>
13
13
  </div>
14
14
  <div class="submit_field">
15
- <%= form.submit 'Login', :disable_with => 'Please wait...' %>
15
+ <%= form.submit 'Sign in', :disable_with => 'Please wait...' %>
16
16
  </div>
17
17
  <% end %>
18
18
 
19
19
  <ul>
20
20
  <li>
21
- <%= link_to "Sign up", new_user_path %>
21
+ <%= link_to "Register", new_user_path %>
22
22
  </li>
23
23
  <li>
24
24
  <%= link_to "Forgot My Password", new_password_path %>
@@ -2,14 +2,14 @@ class CreateOrUpdateUsersWithClearanceColumns < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table(:users) do |t|
4
4
  t.string :email
5
- t.string :crypted_password, :limit => 40
5
+ t.string :encrypted_password, :limit => 40
6
6
  t.string :salt, :limit => 40
7
7
  t.string :remember_token
8
8
  t.datetime :remember_token_expires_at
9
- t.boolean :confirmed, :default => false, :null => false
9
+ t.boolean :email_confirmed, :default => false, :null => false
10
10
  end
11
11
 
12
- add_index :users, [:email, :crypted_password]
12
+ add_index :users, [:email, :encrypted_password]
13
13
  add_index :users, [:id, :salt]
14
14
  add_index :users, :email
15
15
  add_index :users, :remember_token
@@ -1,14 +1,14 @@
1
1
  class CreateOrUpdateUsersWithClearanceColumns < ActiveRecord::Migration
2
2
  def self.up
3
3
  <%
4
- existing_columns = ActiveRecord::Base.connection.columns(:users).map { |column| column.name }
4
+ existing_columns = ActiveRecord::Base.connection.columns(:users).collect { |each| each.name }
5
5
  columns = [
6
6
  [:email, 't.string :email'],
7
- [:crypted_password, 't.string :crypted_password, :limit => 40'],
7
+ [:encrypted_password, 't.string :encrypted_password, :limit => 40'],
8
8
  [:salt, 't.string :salt, :limit => 40'],
9
9
  [:remember_token, 't.string :remember_token'],
10
10
  [:remember_token_expires_at, 't.datetime :remember_token_expires_at'],
11
- [:confirmed, 't.boolean :confirmed, :default => false, :null => false']
11
+ [:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
12
12
  ].delete_if {|c| existing_columns.include?(c.first.to_s)}
13
13
  -%>
14
14
  change_table(:users) do |t|
@@ -18,23 +18,24 @@ class CreateOrUpdateUsersWithClearanceColumns < ActiveRecord::Migration
18
18
  end
19
19
 
20
20
  <%
21
- existing_indexes = ActiveRecord::Base.connection.indexes(:users).map { |index| index.name }
22
- indexes = [
23
- [:index_users_on_email_and_crypted_password, 'add_index :users, [:email, :crypted_password]'],
24
- [:index_users_on_id_and_salt, 'add_index :users, [:id, :salt]'],
25
- [:index_users_on_email, 'add_index :users, :email'],
26
- [:index_users_on_remember_token, 'add_index :users, :remember_token']
27
- ].delete_if {|i| existing_indexes.include?(i.first.to_s)}
21
+ existing_indexes = ActiveRecord::Base.connection.indexes(:users)
22
+ index_names = existing_indexes.collect { |each| each.name }
23
+ new_indexes = [
24
+ [:index_users_on_email_and_encrypted_password, 'add_index :users, [:email, :encrypted_password]'],
25
+ [:index_users_on_id_and_salt, 'add_index :users, [:id, :salt]'],
26
+ [:index_users_on_email, 'add_index :users, :email'],
27
+ [:index_users_on_remember_token, 'add_index :users, :remember_token']
28
+ ].delete_if { |each| index_names.include?(each.first.to_s) }
28
29
  -%>
29
- <% indexes.each do |i| -%>
30
- <%= i.last %>
30
+ <% new_indexes.each do |each| -%>
31
+ <%= each.last %>
31
32
  <% end -%>
32
33
  end
33
34
 
34
35
  def self.down
35
36
  change_table(:users) do |t|
36
37
  <% unless columns.empty? -%>
37
- t.remove <%= columns.collect {|c| ":#{c.first}" }.join(',') %>
38
+ t.remove <%= columns.collect { |each| ":#{each.first}" }.join(',') %>
38
39
  <% end -%>
39
40
  end
40
41
  end
@@ -0,0 +1,16 @@
1
+ Factory.sequence :email do |n|
2
+ "user#{n}@example.com"
3
+ end
4
+
5
+ Factory.define :registered_user, :class => 'user' do |user|
6
+ user.email { Factory.next :email }
7
+ user.password { "password" }
8
+ user.password_confirmation { "password" }
9
+ end
10
+
11
+ Factory.define :email_confirmed_user, :class => 'user' do |user|
12
+ user.email { Factory.next :email }
13
+ user.password { "password" }
14
+ user.password_confirmation { "password" }
15
+ user.email_confirmed { true }
16
+ end
@@ -5,47 +5,57 @@ module Clearance
5
5
 
6
6
  def self.included(controller)
7
7
  controller.class_eval do
8
+
8
9
  helper_method :current_user
9
- helper_method :logged_in?
10
+ helper_method :signed_in?
10
11
 
11
12
  def current_user
12
13
  user_from_session || user_from_cookie
13
14
  end
14
15
 
15
- def logged_in?
16
+ def signed_in?
16
17
  ! current_user.nil?
17
18
  end
18
19
 
19
20
  protected
20
21
 
21
22
  def authenticate
22
- deny_access unless logged_in?
23
+ deny_access unless signed_in?
23
24
  end
24
25
 
25
26
  def user_from_session
26
- User.find_by_id session[:user_id]
27
+ if session[:user_id] && session[:salt]
28
+ user = User.find_by_id_and_salt(session[:user_id], session[:salt])
29
+ end
30
+ user && user.email_confirmed? ? user : nil
27
31
  end
28
32
 
29
33
  def user_from_cookie
30
- if cookies[:auth_token]
31
- user = User.find_by_remember_token(cookies[:auth_token])
34
+ if cookies[:remember_token]
35
+ user = User.find_by_remember_token(cookies[:remember_token])
32
36
  end
33
- user && user.remember_token? ? user : nil
37
+ user && user.remember? ? user : nil
34
38
  end
35
39
 
36
- # Level of indirection so you can easily overwrite this method
37
- # but also call #login .
40
+ # Hook
38
41
  def log_user_in(user)
39
- login(user)
42
+ sign_in(user)
40
43
  end
41
44
 
42
- def login(user)
43
- session[:user_id] = user.id if user
45
+ def sign_in(user)
46
+ if user
47
+ session[:user_id] = user.id
48
+ session[:salt] = user.salt
49
+ end
44
50
  end
45
51
 
46
52
  def redirect_back_or(default)
47
53
  session[:return_to] ||= params[:return_to]
48
- session[:return_to] ? redirect_to(session[:return_to]) : redirect_to(default)
54
+ if session[:return_to]
55
+ redirect_to(session[:return_to])
56
+ else
57
+ redirect_to(default)
58
+ end
49
59
  session[:return_to] = nil
50
60
  end
51
61
 
@@ -60,10 +70,12 @@ module Clearance
60
70
  def deny_access(flash_message = nil, opts = {})
61
71
  store_location
62
72
  flash[:failure] = flash_message if flash_message
63
- render :template => "/sessions/new",:status => :unauthorized
73
+ render :template => "/sessions/new", :status => :unauthorized
64
74
  end
75
+
65
76
  end
66
77
  end
78
+
67
79
  end
68
80
  end
69
81
  end
@@ -5,15 +5,17 @@ module Clearance
5
5
 
6
6
  def self.included(controller)
7
7
  controller.class_eval do
8
+
8
9
  before_filter :existing_user?, :only => :new
10
+ filter_parameter_logging :salt
9
11
 
10
12
  def new
11
13
  create
12
14
  end
13
15
 
14
16
  def create
15
- @user.confirm!
16
- session[:user_id] = @user.id
17
+ @user.confirm_email!
18
+ log_user_in(@user)
17
19
  redirect_to url_after_create
18
20
  end
19
21
 
@@ -31,7 +33,8 @@ module Clearance
31
33
  end
32
34
 
33
35
  end
34
- end
36
+ end
37
+
35
38
  end
36
39
  end
37
40
  end
@@ -5,6 +5,7 @@ module Clearance
5
5
 
6
6
  def self.included(controller)
7
7
  controller.class_eval do
8
+
8
9
  before_filter :existing_user?, :only => [:edit, :update]
9
10
  filter_parameter_logging :password, :password_confirmation
10
11
 
@@ -14,7 +15,7 @@ module Clearance
14
15
  def create
15
16
  user = User.find_by_email(params[:password][:email])
16
17
  if user.nil?
17
- flash.now[:warning] = 'Unknown email'
18
+ flash.now[:notice] = 'Unknown email'
18
19
  render :action => :new
19
20
  else
20
21
  ClearanceMailer.deliver_change_password user
@@ -25,16 +26,16 @@ module Clearance
25
26
  end
26
27
 
27
28
  def edit
28
- @user = User.find_by_email_and_crypted_password(params[:email],
29
- params[:password])
29
+ @user = User.find_by_email_and_encrypted_password(params[:email],
30
+ params[:password])
30
31
  end
31
32
 
32
33
  def update
33
- @user = User.find_by_email_and_crypted_password(params[:email],
34
- params[:password])
34
+ @user = User.find_by_email_and_encrypted_password(params[:email],
35
+ params[:password])
35
36
  if @user.update_attributes params[:user]
36
- session[:user_id] = @user.id
37
- redirect_to @user
37
+ log_user_in(@user)
38
+ redirect_to url_after_update
38
39
  else
39
40
  render :action => :edit
40
41
  end
@@ -43,8 +44,8 @@ module Clearance
43
44
  private
44
45
 
45
46
  def existing_user?
46
- user = User.find_by_email_and_crypted_password(params[:email],
47
- params[:password])
47
+ user = User.find_by_email_and_encrypted_password(params[:email],
48
+ params[:password])
48
49
  if user.nil?
49
50
  render :nothing => true, :status => :not_found
50
51
  end
@@ -54,8 +55,13 @@ module Clearance
54
55
  new_session_url
55
56
  end
56
57
 
58
+ def url_after_update
59
+ root_url
60
+ end
61
+
57
62
  end
58
63
  end
64
+
59
65
  end
60
66
  end
61
67
  end
@@ -5,23 +5,22 @@ module Clearance
5
5
 
6
6
  def self.included(controller)
7
7
  controller.class_eval do
8
- skip_before_filter :authenticate
8
+
9
9
  protect_from_forgery :except => :create
10
10
  filter_parameter_logging :password
11
11
 
12
12
  def create
13
- @user = User.authenticate(params[:session][:email], params[:session][:password])
13
+ @user = User.authenticate(params[:session][:email],
14
+ params[:session][:password])
14
15
  if @user.nil?
15
- login_failure
16
+ sign_in_failure
16
17
  else
17
- if @user.confirmed?
18
- remember_me = params[:session][:remember_me] if params[:session]
19
- remember(@user) if remember_me == '1'
18
+ if @user.email_confirmed?
19
+ remember(@user) if remember?
20
20
  log_user_in(@user)
21
- login_successful
21
+ sign_in_successful
22
22
  else
23
- ClearanceMailer.deliver_confirmation(@user)
24
- deny_access('Account not confirmed. Confirmation email sent.')
23
+ deny_access("User has not confirmed email.")
25
24
  end
26
25
  end
27
26
  end
@@ -29,31 +28,35 @@ module Clearance
29
28
  def destroy
30
29
  forget(current_user)
31
30
  reset_session
32
- flash[:notice] = 'You have been logged out.'
31
+ flash[:notice] = "You have been signed out."
33
32
  redirect_to url_after_destroy
34
33
  end
35
34
 
36
35
  private
37
36
 
38
- def login_successful
39
- flash[:notice] = 'Logged in successfully'
37
+ def sign_in_successful(message = "Signed in successfully")
38
+ flash[:notice] = message
40
39
  redirect_back_or url_after_create
41
40
  end
42
41
 
43
- def login_failure(message = "Bad email or password.")
42
+ def sign_in_failure(message = "Bad email or password.")
44
43
  flash.now[:notice] = message
45
44
  render :action => :new
46
45
  end
47
46
 
47
+ def remember?
48
+ params[:session] && params[:session][:remember_me] == "1"
49
+ end
50
+
48
51
  def remember(user)
49
52
  user.remember_me!
50
- cookies[:auth_token] = { :value => user.remember_token,
51
- :expires => user.remember_token_expires_at }
53
+ cookies[:remember_token] = { :value => user.remember_token,
54
+ :expires => user.remember_token_expires_at }
52
55
  end
53
56
 
54
57
  def forget(user)
55
58
  user.forget_me! if user
56
- cookies.delete :auth_token
59
+ cookies.delete :remember_token
57
60
  end
58
61
 
59
62
  def url_after_create
@@ -65,7 +68,8 @@ module Clearance
65
68
  end
66
69
 
67
70
  end
68
- end
71
+ end
72
+
69
73
  end
70
74
  end
71
75
  end