thoughtbot-clearance 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ h2. 0.8.0 (08/31/2009)
2
+
3
+ * Always remember me. Replaced session-and-remember-me authentication with
4
+ always using a cookie with a long timeout. (Dan Croak)
5
+ * Documented Clearance::Authentication with YARD. (Dan Croak)
6
+ * Documented Clearance::User with YARD. (Dan Croak)
7
+
1
8
  h2. 0.7.0 (08/04/2009)
2
9
 
3
10
  * Redirect signed in user who clicks confirmation link again. (Dan Croak)
@@ -57,6 +64,7 @@ h2. 0.6.2 (04/22/2009)
57
64
  * World(NavigationHelpers) Cucumber 3.0 style. (Shay Arnett & Mark Cornick)
58
65
 
59
66
  h2. 0.6.1 (04/21/2009)
67
+
60
68
  * Scope operators are necessary to keep Rails happy. Reverting the original
61
69
  revert so they're back in the library now for constants referenced inside of
62
70
  the gem. (Nick Quaranto)
@@ -4,9 +4,9 @@ Rails authentication with email & password.
4
4
 
5
5
  "We have clearance, Clarence.":http://www.youtube.com/v/mNRXJEE3Nz8
6
6
 
7
- h2. Wiki
7
+ h2. Suspenders
8
8
 
9
- Most information regarding Clearance is on the "Github Wiki":http://wiki.github.com/thoughtbot/clearance.
9
+ Clearance is included in "Suspenders":http://github.com/thoughtbot/suspenders, which thoughtbot uses on all of our apps. We highly recommend you try it. Suspenders is the "King Gem" in our ecosystem, representating what we think the current state-of-the-art is in Rails development.
10
10
 
11
11
  h2. Installation
12
12
 
@@ -15,10 +15,10 @@ Clearance is a Rails engine. It works with versions of Rails greater than 2.3.
15
15
  In config/environment.rb:
16
16
 
17
17
  <pre>
18
- config.gem "thoughtbot-clearance",
19
- :lib => 'clearance',
20
- :source => 'http://gems.github.com',
21
- :version => '0.6.9'
18
+ config.gem "thoughtbot-clearance",
19
+ :lib => 'clearance',
20
+ :source => 'http://gems.github.com',
21
+ :version => '0.7.0'
22
22
  </pre>
23
23
 
24
24
  Vendor the gem:
@@ -30,7 +30,9 @@ rake gems:unpack
30
30
 
31
31
  Make sure the development database exists and run the generator:
32
32
 
33
- @script/generate clearance@
33
+ <pre>
34
+ script/generate clearance
35
+ </pre>
34
36
 
35
37
  A number of files will be created and instructions will be printed.
36
38
 
@@ -38,51 +40,37 @@ You may already have some of these files. Don't worry. You'll be asked if you wa
38
40
 
39
41
  Run the migration:
40
42
 
41
- @rake db:migrate@
43
+ <pre>
44
+ rake db:migrate
45
+ </pre>
46
+
47
+ h2. If you aren't on Suspenders, you aren't done
42
48
 
43
49
  Define a HOST constant in your environment files.
44
50
  In config/environments/test.rb and config/environments/development.rb it can be:
45
51
 
46
- @HOST = "localhost"@
52
+ <pre>
53
+ HOST = "localhost"
54
+ </pre>
47
55
 
48
56
  In production.rb it must be the actual host your application is deployed to.
49
57
  The constant is used by mailers to generate URLs in emails.
50
58
 
51
59
  In config/environment.rb:
52
60
 
53
- @DO_NOT_REPLY = "donotreply@example.com"@
54
-
55
- Define root_url to *something* in your config/routes.rb:
56
-
57
- @map.root :controller => 'home'@
58
-
59
- h2. Cucumber Features
60
-
61
- As your app evolves, you want to know that authentication still works. Clearance's opinion is that you should test its integration with your app using "Cucumber":http://cukes.info/.
62
-
63
- In config/environments/test.rb:
64
-
65
61
  <pre>
66
- config.gem 'webrat',
67
- :version => '= 0.4.4'
68
- config.gem 'cucumber',
69
- :version => '= 0.3.0'
70
- config.gem 'thoughtbot-factory_girl',
71
- :lib => 'factory_girl',
72
- :source => "http://gems.github.com",
73
- :version => '1.2.1'
62
+ DO_NOT_REPLY = "donotreply@example.com"
74
63
  </pre>
75
64
 
76
- Vendor the gems:
65
+ Define root_url to *something* in your config/routes.rb:
77
66
 
78
67
  <pre>
79
- rake gems:install RAILS_ENV=test
80
- rake gems:unpack RAILS_ENV=test
68
+ map.root :controller => 'home'
81
69
  </pre>
82
70
 
83
- We don't vendor nokogiri due to its native extensions, so install it normally on your machine:
71
+ h2. Cucumber Features
84
72
 
85
- @sudo gem install nokogiri@
73
+ As your app evolves, you want to know that authentication still works. thoughtbot's opinion is that you should test its integration with your app using "Cucumber":http://cukes.info/.
86
74
 
87
75
  Run the Cucumber generator (if you haven't already) and Clearance's feature generator:
88
76
 
@@ -107,6 +95,18 @@ def path_to(page_name)
107
95
  end
108
96
  </pre>
109
97
 
98
+ h2. Formtastic views
99
+
100
+ We have begun standardizing our forms using "Formtastic":http://github.com/justinfrench/formtastic. We highly recommend trying it. It will make your Rails view life more interesting.
101
+
102
+ Clearance has another generator to generate Formastic views:
103
+
104
+ <pre>
105
+ script/generate clearance_views
106
+ </pre>
107
+
108
+ Its implementation is designed so that other view styles can be generated if the community wants it. However, we haven't needed them so you'll have to write the patch and send it back if you want other styles (such as Haml).
109
+
110
110
  h2. Authors
111
111
 
112
112
  Clearance was extracted out of "Hoptoad":http://hoptoadapp.com. We merged the authentication code from two of thoughtbot's clients' Rails apps and have since used it each time we need authentication. The following people have improved the library. Thank you!
data/Rakefile CHANGED
@@ -80,7 +80,7 @@ task :default => ['test:basic', 'test:features',
80
80
 
81
81
  gem_spec = Gem::Specification.new do |gem_spec|
82
82
  gem_spec.name = "clearance"
83
- gem_spec.version = "0.7.0"
83
+ gem_spec.version = "0.8.0"
84
84
  gem_spec.summary = "Rails authentication with email & password."
85
85
  gem_spec.email = "support@thoughtbot.com"
86
86
  gem_spec.homepage = "http://github.com/thoughtbot/clearance"
@@ -13,7 +13,8 @@ class Clearance::ConfirmationsController < ApplicationController
13
13
  end
14
14
 
15
15
  def create
16
- @user = ::User.find_by_id_and_token(params[:user_id], params[:token])
16
+ @user = ::User.find_by_id_and_confirmation_token(
17
+ params[:user_id], params[:token])
17
18
  @user.confirm_email!
18
19
 
19
20
  sign_in(@user)
@@ -46,7 +47,8 @@ class Clearance::ConfirmationsController < ApplicationController
46
47
  end
47
48
 
48
49
  def forbid_non_existent_user
49
- unless ::User.find_by_id_and_token(params[:user_id], params[:token])
50
+ unless ::User.find_by_id_and_confirmation_token(
51
+ params[:user_id], params[:token])
50
52
  raise ActionController::Forbidden, "non-existent user"
51
53
  end
52
54
  end
@@ -22,12 +22,14 @@ class Clearance::PasswordsController < ApplicationController
22
22
  end
23
23
 
24
24
  def edit
25
- @user = ::User.find_by_id_and_token(params[:user_id], params[:token])
25
+ @user = ::User.find_by_id_and_confirmation_token(
26
+ params[:user_id], params[:token])
26
27
  render :template => 'passwords/edit'
27
28
  end
28
29
 
29
30
  def update
30
- @user = ::User.find_by_id_and_token(params[:user_id], params[:token])
31
+ @user = ::User.find_by_id_and_confirmation_token(
32
+ params[:user_id], params[:token])
31
33
 
32
34
  if @user.update_password(params[:user][:password],
33
35
  params[:user][:password_confirmation])
@@ -49,7 +51,8 @@ class Clearance::PasswordsController < ApplicationController
49
51
  end
50
52
 
51
53
  def forbid_non_existent_user
52
- unless ::User.find_by_id_and_token(params[:user_id], params[:token])
54
+ unless ::User.find_by_id_and_confirmation_token(
55
+ params[:user_id], params[:token])
53
56
  raise ActionController::Forbidden, "non-existent user"
54
57
  end
55
58
  end
@@ -17,7 +17,6 @@ class Clearance::SessionsController < ApplicationController
17
17
  else
18
18
  if @user.email_confirmed?
19
19
  sign_in(@user)
20
- remember(@user) if remember?
21
20
  flash_success_after_create
22
21
  redirect_back_or(url_after_create)
23
22
  else
@@ -29,7 +28,7 @@ class Clearance::SessionsController < ApplicationController
29
28
  end
30
29
 
31
30
  def destroy
32
- forget(current_user)
31
+ sign_out(current_user)
33
32
  flash_success_after_destroy
34
33
  redirect_to(url_after_destroy)
35
34
  end
@@ -2,6 +2,8 @@ Someone, hopefully you, has requested that we send you a link to change your pas
2
2
 
3
3
  Here's the link:
4
4
 
5
- <%= edit_user_password_url(@user, :token => @user.token, :escape => false) %>
5
+ <%= edit_user_password_url(@user,
6
+ :token => @user.confirmation_token,
7
+ :escape => false) %>
6
8
 
7
9
  If you didn't request this, ignore this email. Don't worry. Your password hasn't been changed.
@@ -1,2 +1,5 @@
1
1
 
2
- <%= new_user_confirmation_url :user_id => @user, :token => @user.token, :encode => false %>
2
+ <%= new_user_confirmation_url(
3
+ :user_id => @user,
4
+ :token => @user.confirmation_token,
5
+ :encode => false) %>
@@ -7,7 +7,7 @@
7
7
  <%= error_messages_for :user %>
8
8
 
9
9
  <% form_for(:user,
10
- :url => user_password_path(@user, :token => @user.token),
10
+ :url => user_password_path(@user, :token => @user.confirmation_token),
11
11
  :html => { :method => :put }) do |form| %>
12
12
  <div class="password_field">
13
13
  <%= form.label :password, "Choose password" %>
@@ -3,16 +3,12 @@
3
3
  <% form_for :session, :url => session_path do |form| %>
4
4
  <div class="text_field">
5
5
  <%= form.label :email %>
6
- <%= form.text_field :email %>
6
+ <%= form.text_field :email %>
7
7
  </div>
8
8
  <div class="text_field">
9
9
  <%= form.label :password %>
10
10
  <%= form.password_field :password %>
11
11
  </div>
12
- <div class="text_field">
13
- <%= form.check_box :remember_me %>
14
- <%= form.label :remember_me %>
15
- </div>
16
12
  <div class="submit_field">
17
13
  <%= form.submit "Sign in", :disable_with => "Please wait..." %>
18
14
  </div>
@@ -25,4 +21,4 @@
25
21
  <li>
26
22
  <%= link_to "Forgot password?", new_password_path %>
27
23
  </li>
28
- </ul>
24
+ </ul>
@@ -4,15 +4,16 @@ class ClearanceCreateUsers < ActiveRecord::Migration
4
4
  t.string :email
5
5
  t.string :encrypted_password, :limit => 128
6
6
  t.string :salt, :limit => 128
7
- t.string :token, :limit => 128
8
- t.datetime :token_expires_at
7
+ t.string :confirmation_token, :limit => 128
8
+ t.string :remember_token, :limit => 128
9
+ t.datetime :remember_token_expires_at
9
10
  t.boolean :email_confirmed, :default => false, :null => false
10
11
  t.timestamps
11
12
  end
12
13
 
13
- add_index :users, [:id, :token]
14
+ add_index :users, [:id, :confirmation_token]
14
15
  add_index :users, :email
15
- add_index :users, :token
16
+ add_index :users, :remember_token
16
17
  end
17
18
 
18
19
  def self.down
@@ -1,36 +1,37 @@
1
1
  class ClearanceUpdateUsers < ActiveRecord::Migration
2
2
  def self.up
3
- <%
3
+ <%
4
4
  existing_columns = ActiveRecord::Base.connection.columns(:users).collect { |each| each.name }
5
5
  columns = [
6
6
  [:email, 't.string :email'],
7
7
  [:encrypted_password, 't.string :encrypted_password, :limit => 128'],
8
- [:salt, 't.string :salt, :limit => 128'],
9
- [:token, 't.string :token, :limit => 128'],
10
- [:token_expires_at, 't.datetime :token_expires_at'],
11
- [:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
12
- ].delete_if {|c| existing_columns.include?(c.first.to_s)}
8
+ [:salt, 't.string :salt, :limit => 128'],
9
+ [:confirmation_token, 't.string :confirmation_token, :limit => 128'],
10
+ [:remember_token, 't.string :remember_token, :limit => 128'],
11
+ [:remember_token_expires_at, 't.datetime :remember_token_expires_at'],
12
+ [:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
13
+ ].delete_if {|c| existing_columns.include?(c.first.to_s)}
13
14
  -%>
14
15
  change_table(:users) do |t|
15
16
  <% columns.each do |c| -%>
16
17
  <%= c.last %>
17
18
  <% end -%>
18
19
  end
19
-
20
+
20
21
  <%
21
22
  existing_indexes = ActiveRecord::Base.connection.indexes(:users)
22
23
  index_names = existing_indexes.collect { |each| each.name }
23
24
  new_indexes = [
24
- [:index_users_on_id_and_token, 'add_index :users, [:id, :token]'],
25
+ [:index_users_on_id_and_confirmation_token, 'add_index :users, [:id, :confirmation_token]'],
25
26
  [:index_users_on_email, 'add_index :users, :email'],
26
- [:index_users_on_token, 'add_index :users, :token']
27
+ [:index_users_on_remember_token, 'add_index :users, :remember_token']
27
28
  ].delete_if { |each| index_names.include?(each.first.to_s) }
28
29
  -%>
29
30
  <% new_indexes.each do |each| -%>
30
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? -%>
@@ -9,7 +9,7 @@ class ClearanceFeaturesGenerator < Rails::Generator::Base
9
9
  "features/step_definitions/factory_girl_steps.rb",
10
10
  "features/support/paths.rb",
11
11
  "features/sign_in.feature",
12
- "features/sign_out.feature",
12
+ "features/sign_out.feature",
13
13
  "features/sign_up.feature",
14
14
  "features/password_reset.feature"].each do |file|
15
15
  m.file file, file
@@ -30,13 +30,6 @@ Feature: Sign in
30
30
  And I sign in as "email@person.com/password"
31
31
  Then I should see "Signed in"
32
32
  And I should be signed in
33
-
34
- Scenario: User signs in and checks "remember me"
35
- Given I am signed up and confirmed as "email@person.com/password"
36
- When I go to the sign in page
37
- And I sign in with "remember me" as "email@person.com/password"
38
- Then I should see "Signed in"
39
- And I should be signed in
40
33
  When I return next time
41
34
  Then I should be signed in
42
35
 
@@ -10,14 +10,6 @@ Feature: Sign out
10
10
  And I sign out
11
11
  Then I should see "Signed out"
12
12
  And I should be signed out
13
-
14
- Scenario: User who was remembered signs out
15
- Given I am signed up and confirmed as "email@person.com/password"
16
- When I sign in with "remember me" as "email@person.com/password"
17
- Then I should be signed in
18
- And I sign out
19
- Then I should see "Signed out"
20
- And I should be signed out
21
13
  When I return next time
22
14
  Then I should be signed out
23
15
 
@@ -39,6 +39,11 @@ When /^session is cleared$/ do
39
39
  controller.instance_variable_set(:@_current_user, nil)
40
40
  end
41
41
 
42
+ Given /^I have signed in with "(.*)\/(.*)"$/ do |email, password|
43
+ Given %{I am signed up and confirmed as "#{email}/#{password}"}
44
+ And %{I sign in as "#{email}/#{password}"}
45
+ end
46
+
42
47
  # Emails
43
48
 
44
49
  Then /^a confirmation message should be sent to "(.*)"$/ do |email|
@@ -46,13 +51,14 @@ Then /^a confirmation message should be sent to "(.*)"$/ do |email|
46
51
  sent = ActionMailer::Base.deliveries.first
47
52
  assert_equal [user.email], sent.to
48
53
  assert_match /confirm/i, sent.subject
49
- assert !user.token.blank?
50
- assert_match /#{user.token}/, sent.body
54
+ assert !user.confirmation_token.blank?
55
+ assert_match /#{user.confirmation_token}/, sent.body
51
56
  end
52
57
 
53
58
  When /^I follow the confirmation link sent to "(.*)"$/ do |email|
54
59
  user = User.find_by_email(email)
55
- visit new_user_confirmation_path(:user_id => user, :token => user.token)
60
+ visit new_user_confirmation_path(:user_id => user,
61
+ :token => user.confirmation_token)
56
62
  end
57
63
 
58
64
  Then /^a password reset message should be sent to "(.*)"$/ do |email|
@@ -60,13 +66,14 @@ Then /^a password reset message should be sent to "(.*)"$/ do |email|
60
66
  sent = ActionMailer::Base.deliveries.first
61
67
  assert_equal [user.email], sent.to
62
68
  assert_match /password/i, sent.subject
63
- assert !user.token.blank?
64
- assert_match /#{user.token}/, sent.body
69
+ assert !user.confirmation_token.blank?
70
+ assert_match /#{user.confirmation_token}/, sent.body
65
71
  end
66
72
 
67
73
  When /^I follow the password reset link sent to "(.*)"$/ do |email|
68
74
  user = User.find_by_email(email)
69
- visit edit_user_password_path(:user_id => user, :token => user.token)
75
+ visit edit_user_password_path(:user_id => user,
76
+ :token => user.confirmation_token)
70
77
  end
71
78
 
72
79
  When /^I try to change the password of "(.*)" without token$/ do |email|
@@ -80,11 +87,10 @@ end
80
87
 
81
88
  # Actions
82
89
 
83
- When /^I sign in( with "remember me")? as "(.*)\/(.*)"$/ do |remember, email, password|
90
+ When /^I sign in as "(.*)\/(.*)"$/ do |email, password|
84
91
  When %{I go to the sign in page}
85
92
  And %{I fill in "Email" with "#{email}"}
86
93
  And %{I fill in "Password" with "#{password}"}
87
- And %{I check "Remember me"} if remember
88
94
  And %{I press "Sign In"}
89
95
  end
90
96
 
@@ -5,7 +5,7 @@
5
5
  </p>
6
6
 
7
7
  <% semantic_form_for(:user,
8
- :url => user_password_path(@user, :token => @user.token),
8
+ :url => user_password_path(@user, :token => @user.confirmation_token),
9
9
  :html => { :method => :put }) do |form| %>
10
10
  <%= form.error_messages %>
11
11
  <% form.inputs do -%>
@@ -4,7 +4,6 @@
4
4
  <% form.inputs do %>
5
5
  <%= form.input :email %>
6
6
  <%= form.input :password, :as => :password %>
7
- <%= form.input :remember_me, :as => :boolean, :required => false %>
8
7
  <% end %>
9
8
  <% form.buttons do %>
10
9
  <%= form.commit_button "Sign in" %>
@@ -1,7 +1,7 @@
1
1
  module Clearance
2
2
  module Authentication
3
3
 
4
- def self.included(controller)
4
+ def self.included(controller) # :nodoc:
5
5
  controller.send(:include, InstanceMethods)
6
6
 
7
7
  controller.class_eval do
@@ -11,34 +11,79 @@ module Clearance
11
11
  end
12
12
 
13
13
  module InstanceMethods
14
+ # User in the current cookie
15
+ #
16
+ # @return [User, nil]
14
17
  def current_user
15
- @_current_user ||= (user_from_cookie || user_from_session)
18
+ @_current_user ||= user_from_cookie
16
19
  end
17
20
 
21
+ # Is the current user signed in?
22
+ #
23
+ # @return [true, false]
18
24
  def signed_in?
19
25
  ! current_user.nil?
20
26
  end
21
27
 
28
+ # Is the current user signed out?
29
+ #
30
+ # @return [true, false]
22
31
  def signed_out?
23
32
  current_user.nil?
24
33
  end
25
34
 
26
- protected
27
-
35
+ # Deny the user access if they are signed out.
36
+ #
37
+ # @example
38
+ # before_filter :authenticate
28
39
  def authenticate
29
40
  deny_access unless signed_in?
30
41
  end
31
42
 
32
- def user_from_session
33
- if session[:user_id]
34
- return nil unless user = ::User.find_by_id(session[:user_id])
35
- return user if user.email_confirmed?
43
+ # Sign user in to cookie.
44
+ #
45
+ # @param [User]
46
+ #
47
+ # @example
48
+ # sign_in(@user)
49
+ def sign_in(user)
50
+ if user
51
+ user.remember_me!
52
+ cookies[:remember_token] = {
53
+ :value => user.remember_token,
54
+ :expires => user.remember_token_expires_at
55
+ }
36
56
  end
37
57
  end
38
58
 
59
+ # Sign user out of cookie.
60
+ #
61
+ # @param [User]
62
+ #
63
+ # @example
64
+ # sign_out(@user)
65
+ def sign_out(user)
66
+ user.forget_me! if user
67
+ cookies.delete(:remember_token)
68
+ reset_session
69
+ end
70
+
71
+ # Store the current location.
72
+ # Display a flash message if included.
73
+ # Redirect to sign in.
74
+ #
75
+ # @param [String] optional flash message to display to denied user
76
+ def deny_access(flash_message = nil)
77
+ store_location
78
+ flash[:failure] = flash_message if flash_message
79
+ redirect_to(new_session_url)
80
+ end
81
+
82
+ protected
83
+
39
84
  def user_from_cookie
40
85
  if token = cookies[:remember_token]
41
- return nil unless user = ::User.find_by_token(token)
86
+ return nil unless user = ::User.find_by_remember_token(token)
42
87
  return user if user.remember?
43
88
  end
44
89
  end
@@ -48,26 +93,8 @@ module Clearance
48
93
  sign_in(user)
49
94
  end
50
95
 
51
- def sign_in(user)
52
- if user
53
- session[:user_id] = user.id
54
- end
55
- end
56
-
57
- def remember?
58
- params[:session] && params[:session][:remember_me] == "1"
59
- end
60
-
61
- def remember(user)
62
- user.remember_me!
63
- cookies[:remember_token] = { :value => user.token,
64
- :expires => user.token_expires_at }
65
- end
66
-
67
- def forget(user)
68
- user.forget_me! if user
69
- cookies.delete(:remember_token)
70
- reset_session
96
+ def store_location
97
+ session[:return_to] = request.request_uri if request.get?
71
98
  end
72
99
 
73
100
  def redirect_back_or(default)
@@ -86,16 +113,6 @@ module Clearance
86
113
  def redirect_to_root
87
114
  redirect_to(root_url)
88
115
  end
89
-
90
- def store_location
91
- session[:return_to] = request.request_uri if request.get?
92
- end
93
-
94
- def deny_access(flash_message = nil, opts = {})
95
- store_location
96
- flash[:failure] = flash_message if flash_message
97
- redirect_to(new_session_url)
98
- end
99
116
  end
100
117
 
101
118
  end
@@ -3,6 +3,23 @@ require 'digest/sha1'
3
3
  module Clearance
4
4
  module User
5
5
 
6
+ # Hook for all Clearance::User modules.
7
+ #
8
+ # If you need to override parts of Clearance::User,
9
+ # extend and include à la carte.
10
+ #
11
+ # @example
12
+ # extend ClassMethods
13
+ # include InstanceMethods
14
+ # include AttrAccessor
15
+ # include Callbacks
16
+ #
17
+ # @see ClassMethods
18
+ # @see InstanceMethods
19
+ # @see AttrAccessible
20
+ # @see AttrAccessor
21
+ # @see Validations
22
+ # @see Callbacks
6
23
  def self.included(model)
7
24
  model.extend(ClassMethods)
8
25
 
@@ -14,6 +31,15 @@ module Clearance
14
31
  end
15
32
 
16
33
  module AttrAccessible
34
+ # Hook for attr_accessible white list.
35
+ #
36
+ # :email, :password, :password_confirmation
37
+ #
38
+ # Append other attributes that must be mass-assigned in your model.
39
+ #
40
+ # @example
41
+ # include Clearance::User
42
+ # attr_accessible :location, :gender
17
43
  def self.included(model)
18
44
  model.class_eval do
19
45
  attr_accessible :email, :password, :password_confirmation
@@ -22,6 +48,9 @@ module Clearance
22
48
  end
23
49
 
24
50
  module AttrAccessor
51
+ # Hook for attr_accessor virtual attributes.
52
+ #
53
+ # :password, :password_confirmation
25
54
  def self.included(model)
26
55
  model.class_eval do
27
56
  attr_accessor :password, :password_confirmation
@@ -30,6 +59,12 @@ module Clearance
30
59
  end
31
60
 
32
61
  module Validations
62
+ # Hook for validations.
63
+ #
64
+ # :email must be present, unique, formatted
65
+ #
66
+ # If password is required,
67
+ # :password must be present, confirmed
33
68
  def self.included(model)
34
69
  model.class_eval do
35
70
  validates_presence_of :email
@@ -43,50 +78,93 @@ module Clearance
43
78
  end
44
79
 
45
80
  module Callbacks
81
+ # Hook for callbacks.
82
+ #
83
+ # salt, token, password encryption are handled before_save.
46
84
  def self.included(model)
47
85
  model.class_eval do
48
- before_save :initialize_salt, :encrypt_password, :initialize_token
86
+ before_save :initialize_salt,
87
+ :encrypt_password,
88
+ :initialize_confirmation_token
49
89
  end
50
90
  end
51
91
  end
52
92
 
53
93
  module InstanceMethods
94
+ # Am I authenticated with given password?
95
+ #
96
+ # @param [String] plain-text password
97
+ # @return [true, false]
98
+ # @example
99
+ # user.authenticated?('password')
54
100
  def authenticated?(password)
55
101
  encrypted_password == encrypt(password)
56
102
  end
57
103
 
58
- def encrypt(string)
59
- generate_hash("--#{salt}--#{string}--")
60
- end
61
-
104
+ # Am I remembered?
105
+ #
106
+ # @return [true, false]
107
+ # @example
108
+ # user.remember?
62
109
  def remember?
63
- token_expires_at && Time.now.utc < token_expires_at
64
- end
65
-
110
+ remember_token &&
111
+ remember_token_expires_at &&
112
+ Time.now.utc < remember_token_expires_at
113
+ end
114
+
115
+ # Remember me for a year.
116
+ #
117
+ # @example
118
+ # user.remember_me!
119
+ # cookies[:remember_token] = {
120
+ # :value => user.remember_token,
121
+ # :expires => user.remember_token_expires_at
122
+ # }
66
123
  def remember_me!
67
- remember_me_until! 2.weeks.from_now.utc
124
+ remember_me_until! 1.year.from_now.utc
68
125
  end
69
126
 
127
+ # Forget me.
128
+ #
129
+ # @example
130
+ # user.forget_me!
70
131
  def forget_me!
71
- clear_token
132
+ self.remember_token = nil
133
+ self.remember_token_expires_at = nil
72
134
  save(false)
73
135
  end
74
136
 
137
+ # Confirm my email.
138
+ #
139
+ # @example
140
+ # user.confirm_email!
75
141
  def confirm_email!
76
- self.email_confirmed = true
77
- self.token = nil
142
+ self.email_confirmed = true
143
+ self.confirmation_token = nil
78
144
  save(false)
79
145
  end
80
146
 
147
+ # Mark my account as forgotten password.
148
+ #
149
+ # @example
150
+ # user.forgot_password!
81
151
  def forgot_password!
82
- generate_token
152
+ generate_confirmation_token
83
153
  save(false)
84
154
  end
85
155
 
156
+ # Update my password.
157
+ #
158
+ # @param [String, String] password and password confirmation
159
+ # @return [true, false] password was updated or not
160
+ # @example
161
+ # user.update_password('new-password', 'new-password')
86
162
  def update_password(new_password, new_password_confirmation)
87
163
  self.password = new_password
88
164
  self.password_confirmation = new_password_confirmation
89
- clear_token if valid?
165
+ if valid?
166
+ self.confirmation_token = nil
167
+ end
90
168
  save
91
169
  end
92
170
 
@@ -98,7 +176,7 @@ module Clearance
98
176
 
99
177
  def initialize_salt
100
178
  if new_record?
101
- self.salt = generate_hash("--#{Time.now.utc.to_s}--#{password}--")
179
+ self.salt = generate_hash("--#{Time.now.utc}--#{password}--")
102
180
  end
103
181
  end
104
182
 
@@ -107,18 +185,16 @@ module Clearance
107
185
  self.encrypted_password = encrypt(password)
108
186
  end
109
187
 
110
- def generate_token
111
- self.token = encrypt("--#{Time.now.utc.to_s}--#{password}--")
112
- self.token_expires_at = nil
188
+ def encrypt(string)
189
+ generate_hash("--#{salt}--#{string}--")
113
190
  end
114
191
 
115
- def clear_token
116
- self.token = nil
117
- self.token_expires_at = nil
192
+ def generate_confirmation_token
193
+ self.confirmation_token = encrypt("--#{Time.now.utc}--#{password}--")
118
194
  end
119
195
 
120
- def initialize_token
121
- generate_token if new_record?
196
+ def initialize_confirmation_token
197
+ generate_confirmation_token if new_record?
122
198
  end
123
199
 
124
200
  def password_required?
@@ -126,13 +202,19 @@ module Clearance
126
202
  end
127
203
 
128
204
  def remember_me_until!(time)
129
- self.token_expires_at = time
130
- self.token = encrypt("--#{token_expires_at}--#{password}--")
205
+ self.remember_token_expires_at = time
206
+ self.remember_token = encrypt("--#{time}--#{password}--")
131
207
  save(false)
132
208
  end
133
209
  end
134
210
 
135
211
  module ClassMethods
212
+ # Authenticate with email and password.
213
+ #
214
+ # @param [String, String] email and password
215
+ # @return [User, nil] authenticated user or nil
216
+ # @example
217
+ # User.authenticate("email@example.com", "password")
136
218
  def authenticate(email, password)
137
219
  return nil unless user = find_by_email(email)
138
220
  return user if user.authenticated?(password)
@@ -4,6 +4,7 @@ module Clearance
4
4
  # STATE OF AUTHENTICATION
5
5
 
6
6
  def should_be_signed_in_as(&block)
7
+ warn "[DEPRECATION] should_be_signed_in_as cannot be used in functional tests anymore now that it depends on cookies, which are unavailable until the next request."
7
8
  should "be signed in as #{block.bind(self).call}" do
8
9
  user = block.bind(self).call
9
10
  assert_not_nil user,
@@ -28,6 +29,7 @@ module Clearance
28
29
  end
29
30
 
30
31
  def should_not_be_signed_in
32
+ warn "[DEPRECATION] should_not_be_signed_in is no longer a valid test since we now store a remember_token in cookies, not user_id in session"
31
33
  should "not be signed in" do
32
34
  assert_nil session[:user_id]
33
35
  end
@@ -176,7 +178,7 @@ module Clearance
176
178
  warn "[DEPRECATION] should_display_a_password_update_form: not meant to be public, no longer used internally"
177
179
  should "have a form for the user's token, password, and password confirm" do
178
180
  update_path = ERB::Util.h(
179
- user_password_path(@user, :token => @user.token)
181
+ user_password_path(@user, :token => @user.confirmation_token)
180
182
  )
181
183
 
182
184
  assert_select 'form[action=?]', update_path do
@@ -213,8 +215,6 @@ module Clearance
213
215
  "session[email]", true, "There must be an email field"
214
216
  assert_select "input[type=password][name=?]",
215
217
  "session[password]", true, "There must be a password field"
216
- assert_select "input[type=checkbox][name=?]",
217
- "session[remember_me]", true, "There must be a 'remember me' check box"
218
218
  assert_select "input[type=submit]", true,
219
219
  "There must be a submit button"
220
220
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thoughtbot-clearance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Croak
@@ -24,7 +24,7 @@ autorequire:
24
24
  bindir: bin
25
25
  cert_chain: []
26
26
 
27
- date: 2009-08-03 21:00:00 -07:00
27
+ date: 2009-08-30 21:00:00 -07:00
28
28
  default_executable:
29
29
  dependencies: []
30
30
 
@@ -115,8 +115,6 @@ files:
115
115
  - rails/init.rb
116
116
  has_rdoc: true
117
117
  homepage: http://github.com/thoughtbot/clearance
118
- licenses: []
119
-
120
118
  post_install_message:
121
119
  rdoc_options: []
122
120
 
@@ -137,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
135
  requirements: []
138
136
 
139
137
  rubyforge_project:
140
- rubygems_version: 1.3.5
138
+ rubygems_version: 1.2.0
141
139
  signing_key:
142
140
  specification_version: 3
143
141
  summary: Rails authentication with email & password.