thoughtbot-clearance 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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