dacz-authuser 0.1.2

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 (46) hide show
  1. data/CHANGELOG.textile +2 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +123 -0
  4. data/Rakefile +72 -0
  5. data/TODO.textile +6 -0
  6. data/app/controllers/authuser/confirmations_controller.rb +48 -0
  7. data/app/controllers/authuser/passwords_controller.rb +69 -0
  8. data/app/controllers/authuser/sessions_controller.rb +50 -0
  9. data/app/controllers/authuser/users_controller.rb +31 -0
  10. data/app/models/authuser_mailer.rb +23 -0
  11. data/app/views/authuser_mailer/change_password.html.erb +7 -0
  12. data/app/views/authuser_mailer/confirmation.html.erb +2 -0
  13. data/app/views/passwords/edit.html.erb +23 -0
  14. data/app/views/passwords/new.html.erb +15 -0
  15. data/app/views/sessions/new.html.erb +28 -0
  16. data/app/views/users/_form.html.erb +13 -0
  17. data/app/views/users/new.html.erb +6 -0
  18. data/config/authuser_routes.rb +19 -0
  19. data/generators/authuser/USAGE +1 -0
  20. data/generators/authuser/authuser_generator.rb +48 -0
  21. data/generators/authuser/lib/insert_commands.rb +103 -0
  22. data/generators/authuser/lib/rake_commands.rb +22 -0
  23. data/generators/authuser/templates/README +22 -0
  24. data/generators/authuser/templates/config/initializers/authuser.rb +8 -0
  25. data/generators/authuser/templates/factories.rb +19 -0
  26. data/generators/authuser/templates/migrations/create_users.rb +26 -0
  27. data/generators/authuser/templates/migrations/update_users.rb +45 -0
  28. data/generators/authuser/templates/user.rb +3 -0
  29. data/generators/authuser_features/USAGE +1 -0
  30. data/generators/authuser_features/authuser_features_generator.rb +20 -0
  31. data/generators/authuser_features/templates/features/password_reset.feature +33 -0
  32. data/generators/authuser_features/templates/features/step_definitions/authuser_steps.rb +110 -0
  33. data/generators/authuser_features/templates/features/step_definitions/factory_girl_steps.rb +5 -0
  34. data/generators/authuser_features/templates/features/support/paths.rb +22 -0
  35. data/generators/authuser_features/templates/features/user_login.feature +42 -0
  36. data/generators/authuser_features/templates/features/user_logout.feature +23 -0
  37. data/generators/authuser_features/templates/features/user_register.feature +28 -0
  38. data/lib/authuser.rb +20 -0
  39. data/lib/authuser/authentication.rb +96 -0
  40. data/lib/authuser/extensions/errors.rb +4 -0
  41. data/lib/authuser/extensions/rescue.rb +1 -0
  42. data/lib/authuser/user.rb +143 -0
  43. data/lib/authuser/version.rb +7 -0
  44. data/rails/init.rb +1 -0
  45. data/shoulda_macros/authuser.rb +261 -0
  46. metadata +134 -0
@@ -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,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,5 @@
1
+ Factory.factories.each do |name, factory|
2
+ Given /^an? #{name} exists with an? (.*) of "([^"]*)"$/ do |attr, value|
3
+ Factory(name, attr.gsub(' ', '_') => value)
4
+ end
5
+ 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)
@@ -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,20 @@
1
+ require 'authuser/extensions/errors'
2
+ require 'authuser/extensions/rescue'
3
+
4
+ require 'authuser/authentication'
5
+ require 'authuser/user'
6
+ require 'authuser/version'
7
+
8
+ class ActionController::Routing::RouteSet
9
+ def load_routes_with_authuser!
10
+ lib_path = File.dirname(__FILE__)
11
+ authuser_routes = File.join(lib_path, *%w[.. config authuser_routes.rb])
12
+ unless configuration_files.include?(authuser_routes)
13
+ add_configuration_file(authuser_routes)
14
+ end
15
+ load_routes_without_authuser!
16
+ end
17
+
18
+ alias_method_chain :load_routes!, :authuser
19
+ end
20
+
@@ -0,0 +1,96 @@
1
+ module Authuser
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
@@ -0,0 +1,4 @@
1
+ module ActionController
2
+ class Forbidden < StandardError
3
+ end
4
+ end
@@ -0,0 +1 @@
1
+ ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
@@ -0,0 +1,143 @@
1
+ require 'digest/sha1'
2
+
3
+ module Authuser
4
+ module User
5
+
6
+ def self.included(model)
7
+ model.extend(ClassMethods)
8
+
9
+ model.send(:include, InstanceMethods)
10
+ model.send(:include, AttrAccessible)
11
+ model.send(:include, AttrAccessor)
12
+ model.send(:include, Validations)
13
+ model.send(:include, Callbacks)
14
+ end
15
+
16
+ module AttrAccessible
17
+ def self.included(model)
18
+ model.class_eval do
19
+ attr_accessible :email, :password, :password_confirmation
20
+ end
21
+ end
22
+ end
23
+
24
+ module AttrAccessor
25
+ def self.included(model)
26
+ model.class_eval do
27
+ attr_accessor :password, :password_confirmation
28
+ end
29
+ end
30
+ end
31
+
32
+ module Validations
33
+ def self.included(model)
34
+ model.class_eval do
35
+ validates_presence_of :email
36
+ validates_uniqueness_of :email, :case_sensitive => false
37
+ validates_format_of :email, :with => %r{.+@.+\..+}
38
+
39
+ validates_presence_of :password, :if => :password_required?
40
+ validates_confirmation_of :password, :if => :password_required?
41
+ end
42
+ end
43
+ end
44
+
45
+ module Callbacks
46
+ def self.included(model)
47
+ model.class_eval do
48
+ before_save :initialize_salt, :encrypt_password, :initialize_token
49
+ end
50
+ end
51
+ end
52
+
53
+ module InstanceMethods
54
+ def authenticated?(password)
55
+ encrypted_password == encrypt(password)
56
+ end
57
+
58
+ def encrypt(string)
59
+ generate_hash("--#{salt}--#{string}--")
60
+ end
61
+
62
+ def remember?
63
+ token_expires_at && Time.now.utc < token_expires_at
64
+ end
65
+
66
+ def remember_me!
67
+ remember_me_until! 2.weeks.from_now.utc
68
+ end
69
+
70
+ def forget_me!
71
+ clear_token
72
+ save(false)
73
+ end
74
+
75
+ def confirm_email!
76
+ self.email_confirmed = true
77
+ self.token = nil
78
+ save(false)
79
+ end
80
+
81
+ def forgot_password!
82
+ generate_token
83
+ save(false)
84
+ end
85
+
86
+ def update_password(new_password, new_password_confirmation)
87
+ self.password = new_password
88
+ self.password_confirmation = new_password_confirmation
89
+ clear_token if valid?
90
+ save
91
+ end
92
+
93
+ protected
94
+
95
+ def generate_hash(string)
96
+ Digest::SHA1.hexdigest(string)
97
+ end
98
+
99
+ def initialize_salt
100
+ if new_record?
101
+ self.salt = generate_hash("--#{Time.now.utc.to_s}--#{password}--")
102
+ end
103
+ end
104
+
105
+ def encrypt_password
106
+ return if password.blank?
107
+ self.encrypted_password = encrypt(password)
108
+ end
109
+
110
+ def generate_token
111
+ self.token = encrypt("--#{Time.now.utc.to_s}--#{password}--")
112
+ self.token_expires_at = nil
113
+ end
114
+
115
+ def clear_token
116
+ self.token = nil
117
+ self.token_expires_at = nil
118
+ end
119
+
120
+ def initialize_token
121
+ generate_token if new_record?
122
+ end
123
+
124
+ def password_required?
125
+ encrypted_password.blank? || !password.blank?
126
+ end
127
+
128
+ def remember_me_until!(time)
129
+ self.token_expires_at = time
130
+ self.token = encrypt("--#{token_expires_at}--#{password}--")
131
+ save(false)
132
+ end
133
+ end
134
+
135
+ module ClassMethods
136
+ def authenticate(email, password)
137
+ return nil unless user = find_by_email(email)
138
+ return user if user.authenticated?(password)
139
+ end
140
+ end
141
+
142
+ end
143
+ end