joshuaclayton-clearance 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.textile +135 -0
- data/LICENSE +21 -0
- data/README.textile +113 -0
- data/Rakefile +73 -0
- data/TODO.textile +6 -0
- data/app/controllers/clearance/confirmations_controller.rb +46 -0
- data/app/controllers/clearance/passwords_controller.rb +65 -0
- data/app/controllers/clearance/sessions_controller.rb +45 -0
- data/app/controllers/clearance/users_controller.rb +29 -0
- data/app/models/clearance_mailer.rb +19 -0
- data/app/views/clearance_mailer/change_password.html.erb +7 -0
- data/app/views/clearance_mailer/confirmation.html.erb +2 -0
- data/app/views/passwords/edit.html.erb +23 -0
- data/app/views/passwords/new.html.erb +15 -0
- data/app/views/sessions/new.html.erb +28 -0
- data/app/views/users/_form.html.erb +13 -0
- data/app/views/users/new.html.erb +6 -0
- data/config/routes.rb +19 -0
- data/generators/clearance/USAGE +1 -0
- data/generators/clearance/clearance_generator.rb +41 -0
- data/generators/clearance/lib/insert_commands.rb +103 -0
- data/generators/clearance/lib/rake_commands.rb +22 -0
- data/generators/clearance/templates/README +22 -0
- data/generators/clearance/templates/factories.rb +13 -0
- data/generators/clearance/templates/migrations/create_users.rb +20 -0
- data/generators/clearance/templates/migrations/update_users.rb +41 -0
- data/generators/clearance/templates/user.rb +3 -0
- data/generators/clearance_features/USAGE +1 -0
- data/generators/clearance_features/clearance_features_generator.rb +20 -0
- data/generators/clearance_features/templates/features/password_reset.feature +33 -0
- data/generators/clearance_features/templates/features/sign_in.feature +42 -0
- data/generators/clearance_features/templates/features/sign_out.feature +23 -0
- data/generators/clearance_features/templates/features/sign_up.feature +28 -0
- data/generators/clearance_features/templates/features/step_definitions/clearance_steps.rb +110 -0
- data/generators/clearance_features/templates/features/step_definitions/factory_girl_steps.rb +5 -0
- data/generators/clearance_features/templates/features/support/paths.rb +22 -0
- data/lib/clearance.rb +5 -0
- data/lib/clearance/authentication.rb +96 -0
- data/lib/clearance/extensions/errors.rb +4 -0
- data/lib/clearance/extensions/rescue.rb +1 -0
- data/lib/clearance/user.rb +115 -0
- data/rails/init.rb +1 -0
- data/shoulda_macros/clearance.rb +261 -0
- metadata +129 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
class ClearanceUpdateUsers < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
<%
|
4
|
+
existing_columns = ActiveRecord::Base.connection.columns(:users).collect { |each| each.name }
|
5
|
+
columns = [
|
6
|
+
[:email, 't.string :email'],
|
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)}
|
13
|
+
-%>
|
14
|
+
change_table(:users) do |t|
|
15
|
+
<% columns.each do |c| -%>
|
16
|
+
<%= c.last %>
|
17
|
+
<% end -%>
|
18
|
+
end
|
19
|
+
|
20
|
+
<%
|
21
|
+
existing_indexes = ActiveRecord::Base.connection.indexes(:users)
|
22
|
+
index_names = existing_indexes.collect { |each| each.name }
|
23
|
+
new_indexes = [
|
24
|
+
[:index_users_on_id_and_token, 'add_index :users, [:id, :token]'],
|
25
|
+
[:index_users_on_email, 'add_index :users, :email'],
|
26
|
+
[:index_users_on_token, 'add_index :users, :token']
|
27
|
+
].delete_if { |each| index_names.include?(each.first.to_s) }
|
28
|
+
-%>
|
29
|
+
<% new_indexes.each do |each| -%>
|
30
|
+
<%= each.last %>
|
31
|
+
<% end -%>
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.down
|
35
|
+
change_table(:users) do |t|
|
36
|
+
<% unless columns.empty? -%>
|
37
|
+
t.remove <%= columns.collect { |each| ":#{each.first}" }.join(',') %>
|
38
|
+
<% end -%>
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
script/generate clearance_features
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class ClearanceFeaturesGenerator < Rails::Generator::Base
|
2
|
+
|
3
|
+
def manifest
|
4
|
+
record do |m|
|
5
|
+
m.directory File.join("features", "step_definitions")
|
6
|
+
m.directory File.join("features", "support")
|
7
|
+
|
8
|
+
["features/step_definitions/clearance_steps.rb",
|
9
|
+
"features/step_definitions/factory_girl_steps.rb",
|
10
|
+
"features/support/paths.rb",
|
11
|
+
"features/sign_in.feature",
|
12
|
+
"features/sign_out.feature",
|
13
|
+
"features/sign_up.feature",
|
14
|
+
"features/password_reset.feature"].each do |file|
|
15
|
+
m.file file, file
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Password reset
|
2
|
+
In order to sign in even if user forgot their password
|
3
|
+
A user
|
4
|
+
Should be able to reset it
|
5
|
+
|
6
|
+
Scenario: User is not signed up
|
7
|
+
Given no user exists with an email of "email@person.com"
|
8
|
+
When I request password reset link to be sent to "email@person.com"
|
9
|
+
Then I should see "Unknown email"
|
10
|
+
|
11
|
+
Scenario: User is signed up and requests password reset
|
12
|
+
Given I signed up with "email@person.com/password"
|
13
|
+
When I request password reset link to be sent to "email@person.com"
|
14
|
+
Then I should see "instructions for changing your password"
|
15
|
+
And a password reset message should be sent to "email@person.com"
|
16
|
+
|
17
|
+
Scenario: User is signed up updated his password and types wrong confirmation
|
18
|
+
Given I signed up with "email@person.com/password"
|
19
|
+
When I follow the password reset link sent to "email@person.com"
|
20
|
+
And I update my password with "newpassword/wrongconfirmation"
|
21
|
+
Then I should see error messages
|
22
|
+
And I should be signed out
|
23
|
+
|
24
|
+
Scenario: User is signed up and updates his password
|
25
|
+
Given I signed up with "email@person.com/password"
|
26
|
+
When I follow the password reset link sent to "email@person.com"
|
27
|
+
And I update my password with "newpassword/newpassword"
|
28
|
+
Then I should be signed in
|
29
|
+
When I sign out
|
30
|
+
Then I should be signed out
|
31
|
+
And I sign in as "email@person.com/newpassword"
|
32
|
+
Then I should be signed in
|
33
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Feature: Sign in
|
2
|
+
In order to get access to protected sections of the site
|
3
|
+
A user
|
4
|
+
Should be able to sign in
|
5
|
+
|
6
|
+
Scenario: User is not signed up
|
7
|
+
Given no user exists with an email of "email@person.com"
|
8
|
+
When I go to the sign in page
|
9
|
+
And I sign in as "email@person.com/password"
|
10
|
+
Then I should see "Bad email or password"
|
11
|
+
And I should be signed out
|
12
|
+
|
13
|
+
Scenario: User is not confirmed
|
14
|
+
Given I signed up with "email@person.com/password"
|
15
|
+
When I go to the sign in page
|
16
|
+
And I sign in as "email@person.com/password"
|
17
|
+
Then I should see "User has not confirmed email"
|
18
|
+
And I should be signed out
|
19
|
+
|
20
|
+
Scenario: User enters wrong password
|
21
|
+
Given I am signed up and confirmed as "email@person.com/password"
|
22
|
+
When I go to the sign in page
|
23
|
+
And I sign in as "email@person.com/wrongpassword"
|
24
|
+
Then I should see "Bad email or password"
|
25
|
+
And I should be signed out
|
26
|
+
|
27
|
+
Scenario: User signs in successfully
|
28
|
+
Given I am signed up and confirmed as "email@person.com/password"
|
29
|
+
When I go to the sign in page
|
30
|
+
And I sign in as "email@person.com/password"
|
31
|
+
Then I should see "Signed in"
|
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
|
+
When I return next time
|
41
|
+
Then I should be signed in
|
42
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: Sign out
|
2
|
+
To protect my account from unauthorized access
|
3
|
+
A signed in user
|
4
|
+
Should be able to sign out
|
5
|
+
|
6
|
+
Scenario: User signs out
|
7
|
+
Given I am signed up and confirmed as "email@person.com/password"
|
8
|
+
When I sign in as "email@person.com/password"
|
9
|
+
Then I should be signed in
|
10
|
+
And I sign out
|
11
|
+
Then I should see "Signed out"
|
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
|
+
When I return next time
|
22
|
+
Then I should be signed out
|
23
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Feature: Sign up
|
2
|
+
In order to get access to protected sections of the site
|
3
|
+
A user
|
4
|
+
Should be able to sign up
|
5
|
+
|
6
|
+
Scenario: User signs up with invalid data
|
7
|
+
When I go to the sign up page
|
8
|
+
And I fill in "Email" with "invalidemail"
|
9
|
+
And I fill in "Password" with "password"
|
10
|
+
And I fill in "Confirm password" with ""
|
11
|
+
And I press "Sign Up"
|
12
|
+
Then I should see error messages
|
13
|
+
|
14
|
+
Scenario: User signs up with valid data
|
15
|
+
When I go to the sign up page
|
16
|
+
And I fill in "Email" with "email@person.com"
|
17
|
+
And I fill in "Password" with "password"
|
18
|
+
And I fill in "Confirm password" with "password"
|
19
|
+
And I press "Sign Up"
|
20
|
+
Then I should see "instructions for confirming"
|
21
|
+
And a confirmation message should be sent to "email@person.com"
|
22
|
+
|
23
|
+
Scenario: User confirms his account
|
24
|
+
Given I signed up with "email@person.com/password"
|
25
|
+
When I follow the confirmation link sent to "email@person.com"
|
26
|
+
Then I should see "Confirmed email and signed in"
|
27
|
+
And I should be signed in
|
28
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# General
|
2
|
+
|
3
|
+
Then /^I should see error messages$/ do
|
4
|
+
assert_match /error(s)? prohibited/m, response.body
|
5
|
+
end
|
6
|
+
|
7
|
+
# Database
|
8
|
+
|
9
|
+
Given /^no user exists with an email of "(.*)"$/ do |email|
|
10
|
+
assert_nil User.find_by_email(email)
|
11
|
+
end
|
12
|
+
|
13
|
+
Given /^I signed up with "(.*)\/(.*)"$/ do |email, password|
|
14
|
+
user = Factory :user,
|
15
|
+
:email => email,
|
16
|
+
:password => password,
|
17
|
+
:password_confirmation => password
|
18
|
+
end
|
19
|
+
|
20
|
+
Given /^I am signed up and confirmed as "(.*)\/(.*)"$/ do |email, password|
|
21
|
+
user = Factory :email_confirmed_user,
|
22
|
+
:email => email,
|
23
|
+
:password => password,
|
24
|
+
:password_confirmation => password
|
25
|
+
end
|
26
|
+
|
27
|
+
# Session
|
28
|
+
|
29
|
+
Then /^I should be signed in$/ do
|
30
|
+
assert controller.signed_in?
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^I should be signed out$/ do
|
34
|
+
assert ! controller.signed_in?
|
35
|
+
end
|
36
|
+
|
37
|
+
When /^session is cleared$/ do
|
38
|
+
request.reset_session
|
39
|
+
controller.instance_variable_set(:@_current_user, nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Emails
|
43
|
+
|
44
|
+
Then /^a confirmation message should be sent to "(.*)"$/ do |email|
|
45
|
+
user = User.find_by_email(email)
|
46
|
+
sent = ActionMailer::Base.deliveries.first
|
47
|
+
assert_equal [user.email], sent.to
|
48
|
+
assert_match /confirm/i, sent.subject
|
49
|
+
assert !user.token.blank?
|
50
|
+
assert_match /#{user.token}/, sent.body
|
51
|
+
end
|
52
|
+
|
53
|
+
When /^I follow the confirmation link sent to "(.*)"$/ do |email|
|
54
|
+
user = User.find_by_email(email)
|
55
|
+
visit new_user_confirmation_path(:user_id => user, :token => user.token)
|
56
|
+
end
|
57
|
+
|
58
|
+
Then /^a password reset message should be sent to "(.*)"$/ do |email|
|
59
|
+
user = User.find_by_email(email)
|
60
|
+
sent = ActionMailer::Base.deliveries.first
|
61
|
+
assert_equal [user.email], sent.to
|
62
|
+
assert_match /password/i, sent.subject
|
63
|
+
assert !user.token.blank?
|
64
|
+
assert_match /#{user.token}/, sent.body
|
65
|
+
end
|
66
|
+
|
67
|
+
When /^I follow the password reset link sent to "(.*)"$/ do |email|
|
68
|
+
user = User.find_by_email(email)
|
69
|
+
visit edit_user_password_path(:user_id => user, :token => user.token)
|
70
|
+
end
|
71
|
+
|
72
|
+
When /^I try to change the password of "(.*)" without token$/ do |email|
|
73
|
+
user = User.find_by_email(email)
|
74
|
+
visit edit_user_password_path(:user_id => user)
|
75
|
+
end
|
76
|
+
|
77
|
+
Then /^I should be forbidden$/ do
|
78
|
+
assert_response :forbidden
|
79
|
+
end
|
80
|
+
|
81
|
+
# Actions
|
82
|
+
|
83
|
+
When /^I sign in( with "remember me")? as "(.*)\/(.*)"$/ do |remember, email, password|
|
84
|
+
When %{I go to the sign in page}
|
85
|
+
And %{I fill in "Email" with "#{email}"}
|
86
|
+
And %{I fill in "Password" with "#{password}"}
|
87
|
+
And %{I check "Remember me"} if remember
|
88
|
+
And %{I press "Sign In"}
|
89
|
+
end
|
90
|
+
|
91
|
+
When /^I sign out$/ do
|
92
|
+
visit '/session', :delete
|
93
|
+
end
|
94
|
+
|
95
|
+
When /^I request password reset link to be sent to "(.*)"$/ do |email|
|
96
|
+
When %{I go to the password reset request page}
|
97
|
+
And %{I fill in "Email address" with "#{email}"}
|
98
|
+
And %{I press "Reset password"}
|
99
|
+
end
|
100
|
+
|
101
|
+
When /^I update my password with "(.*)\/(.*)"$/ do |password, confirmation|
|
102
|
+
And %{I fill in "Choose password" with "#{password}"}
|
103
|
+
And %{I fill in "Confirm password" with "#{confirmation}"}
|
104
|
+
And %{I press "Save this password"}
|
105
|
+
end
|
106
|
+
|
107
|
+
When /^I return next time$/ do
|
108
|
+
When %{session is cleared}
|
109
|
+
And %{I go to the homepage}
|
110
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
def path_to(page_name)
|
3
|
+
case page_name
|
4
|
+
|
5
|
+
when /the homepage/i
|
6
|
+
root_path
|
7
|
+
when /the sign up page/i
|
8
|
+
new_user_path
|
9
|
+
when /the sign in page/i
|
10
|
+
new_session_path
|
11
|
+
when /the password reset request page/i
|
12
|
+
new_password_path
|
13
|
+
|
14
|
+
# Add more page name => path mappings here
|
15
|
+
|
16
|
+
else
|
17
|
+
raise "Can't find mapping from \"#{page_name}\" to a path."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
World(NavigationHelpers)
|
data/lib/clearance.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Authentication
|
3
|
+
|
4
|
+
def self.included(controller)
|
5
|
+
controller.send(:include, InstanceMethods)
|
6
|
+
|
7
|
+
controller.class_eval do
|
8
|
+
helper_method :current_user
|
9
|
+
helper_method :signed_in?
|
10
|
+
|
11
|
+
hide_action :current_user, :signed_in?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
def current_user
|
17
|
+
@_current_user ||= (user_from_cookie || user_from_session)
|
18
|
+
end
|
19
|
+
|
20
|
+
def signed_in?
|
21
|
+
! current_user.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def authenticate
|
27
|
+
deny_access unless signed_in?
|
28
|
+
end
|
29
|
+
|
30
|
+
def user_from_session
|
31
|
+
if session[:user_id]
|
32
|
+
return nil unless user = ::User.find_by_id(session[:user_id])
|
33
|
+
return user if user.email_confirmed?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def user_from_cookie
|
38
|
+
if token = cookies[:remember_token]
|
39
|
+
return nil unless user = ::User.find_by_token(token)
|
40
|
+
return user if user.remember?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sign_user_in(user)
|
45
|
+
sign_in(user)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sign_in(user)
|
49
|
+
if user
|
50
|
+
session[:user_id] = user.id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remember?
|
55
|
+
params[:session] && params[:session][:remember_me] == "1"
|
56
|
+
end
|
57
|
+
|
58
|
+
def remember(user)
|
59
|
+
user.remember_me!
|
60
|
+
cookies[:remember_token] = { :value => user.token,
|
61
|
+
:expires => user.token_expires_at }
|
62
|
+
end
|
63
|
+
|
64
|
+
def forget(user)
|
65
|
+
user.forget_me! if user
|
66
|
+
cookies.delete :remember_token
|
67
|
+
reset_session
|
68
|
+
end
|
69
|
+
|
70
|
+
def redirect_back_or(default)
|
71
|
+
session[:return_to] ||= params[:return_to]
|
72
|
+
if session[:return_to]
|
73
|
+
redirect_to(session[:return_to])
|
74
|
+
else
|
75
|
+
redirect_to(default)
|
76
|
+
end
|
77
|
+
session[:return_to] = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def redirect_to_root
|
81
|
+
redirect_to root_url
|
82
|
+
end
|
83
|
+
|
84
|
+
def store_location
|
85
|
+
session[:return_to] = request.request_uri if request.get?
|
86
|
+
end
|
87
|
+
|
88
|
+
def deny_access(flash_message = nil, opts = {})
|
89
|
+
store_location
|
90
|
+
flash[:failure] = flash_message if flash_message
|
91
|
+
redirect_to new_session_url
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|