clearance 0.8.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of clearance might be problematic. Click here for more details.

Files changed (52) hide show
  1. data/CHANGELOG.textile +194 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +123 -0
  4. data/Rakefile +103 -0
  5. data/TODO.textile +6 -0
  6. data/app/controllers/clearance/confirmations_controller.rb +75 -0
  7. data/app/controllers/clearance/passwords_controller.rb +84 -0
  8. data/app/controllers/clearance/sessions_controller.rb +66 -0
  9. data/app/controllers/clearance/users_controller.rb +35 -0
  10. data/app/models/clearance_mailer.rb +23 -0
  11. data/app/views/clearance_mailer/change_password.html.erb +9 -0
  12. data/app/views/clearance_mailer/confirmation.html.erb +5 -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 +24 -0
  16. data/app/views/users/_form.html.erb +13 -0
  17. data/app/views/users/new.html.erb +6 -0
  18. data/config/clearance_routes.rb +30 -0
  19. data/generators/clearance/USAGE +1 -0
  20. data/generators/clearance/clearance_generator.rb +41 -0
  21. data/generators/clearance/lib/insert_commands.rb +33 -0
  22. data/generators/clearance/lib/rake_commands.rb +22 -0
  23. data/generators/clearance/templates/README +22 -0
  24. data/generators/clearance/templates/factories.rb +13 -0
  25. data/generators/clearance/templates/migrations/create_users.rb +21 -0
  26. data/generators/clearance/templates/migrations/update_users.rb +41 -0
  27. data/generators/clearance/templates/user.rb +3 -0
  28. data/generators/clearance_features/USAGE +1 -0
  29. data/generators/clearance_features/clearance_features_generator.rb +20 -0
  30. data/generators/clearance_features/templates/features/password_reset.feature +33 -0
  31. data/generators/clearance_features/templates/features/sign_in.feature +35 -0
  32. data/generators/clearance_features/templates/features/sign_out.feature +15 -0
  33. data/generators/clearance_features/templates/features/sign_up.feature +45 -0
  34. data/generators/clearance_features/templates/features/step_definitions/clearance_steps.rb +116 -0
  35. data/generators/clearance_features/templates/features/step_definitions/factory_girl_steps.rb +5 -0
  36. data/generators/clearance_features/templates/features/support/paths.rb +22 -0
  37. data/generators/clearance_views/USAGE +0 -0
  38. data/generators/clearance_views/clearance_views_generator.rb +27 -0
  39. data/generators/clearance_views/templates/formtastic/passwords/edit.html.erb +21 -0
  40. data/generators/clearance_views/templates/formtastic/passwords/new.html.erb +15 -0
  41. data/generators/clearance_views/templates/formtastic/sessions/new.html.erb +21 -0
  42. data/generators/clearance_views/templates/formtastic/users/_inputs.html.erb +6 -0
  43. data/generators/clearance_views/templates/formtastic/users/new.html.erb +10 -0
  44. data/lib/clearance.rb +6 -0
  45. data/lib/clearance/authentication.rb +125 -0
  46. data/lib/clearance/extensions/errors.rb +6 -0
  47. data/lib/clearance/extensions/rescue.rb +3 -0
  48. data/lib/clearance/extensions/routes.rb +14 -0
  49. data/lib/clearance/user.rb +199 -0
  50. data/rails/init.rb +1 -0
  51. data/shoulda_macros/clearance.rb +266 -0
  52. metadata +120 -0
@@ -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)
File without changes
@@ -0,0 +1,27 @@
1
+ class ClearanceViewsGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ strategy = "formtastic"
6
+ template_strategy = "erb"
7
+
8
+ m.directory File.join("app", "views", "users")
9
+ m.file "#{strategy}/users/new.html.#{template_strategy}",
10
+ "app/views/users/new.html.#{template_strategy}"
11
+ m.file "#{strategy}/users/_inputs.html.#{template_strategy}",
12
+ "app/views/users/_inputs.html.#{template_strategy}"
13
+
14
+ m.directory File.join("app", "views", "sessions")
15
+ m.file "#{strategy}/sessions/new.html.#{template_strategy}",
16
+ "app/views/sessions/new.html.#{template_strategy}"
17
+
18
+ m.directory File.join("app", "views", "passwords")
19
+ m.file "#{strategy}/passwords/new.html.#{template_strategy}",
20
+ "app/views/passwords/new.html.#{template_strategy}"
21
+ m.file "#{strategy}/passwords/edit.html.#{template_strategy}",
22
+ "app/views/passwords/edit.html.#{template_strategy}"
23
+ end
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,21 @@
1
+ <h2>Change your password</h2>
2
+
3
+ <p>
4
+ Your password has been reset. Choose a new password below.
5
+ </p>
6
+
7
+ <% semantic_form_for(:user,
8
+ :url => user_password_path(@user, :token => @user.confirmation_token),
9
+ :html => { :method => :put }) do |form| %>
10
+ <%= form.error_messages %>
11
+ <% form.inputs do -%>
12
+ <%= form.input :password, :as => :password,
13
+ :label => "Choose password" %>
14
+ <%= form.input :password_confirmation, :as => :password,
15
+ :label => "Confirm password" %>
16
+ <% end -%>
17
+ <% form.buttons do -%>
18
+ <%= form.commit_button "Save this password" %>
19
+ <% end -%>
20
+ <% end %>
21
+
@@ -0,0 +1,15 @@
1
+ <h2>Reset your password</h2>
2
+
3
+ <p>
4
+ We will email you a link to reset your password.
5
+ </p>
6
+
7
+ <% semantic_form_for :password, :url => passwords_path do |form| -%>
8
+ <% form.inputs do -%>
9
+ <%= form.input :email, :label => "Email address" %>
10
+ <% end -%>
11
+ <% form.buttons do -%>
12
+ <%= form.commit_button "Reset password" %>
13
+ <% end -%>
14
+ <% end -%>
15
+
@@ -0,0 +1,21 @@
1
+ <h2>Sign in</h2>
2
+
3
+ <% semantic_form_for :session, :url => session_path do |form| %>
4
+ <% form.inputs do %>
5
+ <%= form.input :email %>
6
+ <%= form.input :password, :as => :password %>
7
+ <% end %>
8
+ <% form.buttons do %>
9
+ <%= form.commit_button "Sign in" %>
10
+ <% end %>
11
+ <% end %>
12
+
13
+ <ul>
14
+ <li>
15
+ <%= link_to "Sign up", new_user_path %>
16
+ </li>
17
+ <li>
18
+ <%= link_to "Forgot password?", new_password_path %>
19
+ </li>
20
+ </ul>
21
+
@@ -0,0 +1,6 @@
1
+ <% form.inputs do %>
2
+ <%= form.input :email %>
3
+ <%= form.input :password %>
4
+ <%= form.input :password_confirmation, :label => "Confirm password" %>
5
+ <% end %>
6
+
@@ -0,0 +1,10 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <% semantic_form_for @user do |form| %>
4
+ <%= form.error_messages %>
5
+ <%= render :partial => "/users/inputs", :locals => { :form => form } %>
6
+ <% form.buttons do %>
7
+ <%= form.commit_button "Sign up" %>
8
+ <% end %>
9
+ <% end %>
10
+
data/lib/clearance.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'clearance/extensions/errors'
2
+ require 'clearance/extensions/rescue'
3
+ require 'clearance/extensions/routes'
4
+
5
+ require 'clearance/authentication'
6
+ require 'clearance/user'
@@ -0,0 +1,125 @@
1
+ module Clearance
2
+ module Authentication
3
+
4
+ def self.included(controller) # :nodoc:
5
+ controller.send(:include, InstanceMethods)
6
+
7
+ controller.class_eval do
8
+ helper_method :current_user, :signed_in?, :signed_out?
9
+ hide_action :current_user, :signed_in?, :signed_out?
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ # User in the current cookie
15
+ #
16
+ # @return [User, nil]
17
+ def current_user
18
+ @_current_user ||= user_from_cookie
19
+ end
20
+
21
+ # Set the current user
22
+ #
23
+ # @param [User]
24
+ def current_user=(user)
25
+ @_current_user = user
26
+ end
27
+
28
+ # Is the current user signed in?
29
+ #
30
+ # @return [true, false]
31
+ def signed_in?
32
+ ! current_user.nil?
33
+ end
34
+
35
+ # Is the current user signed out?
36
+ #
37
+ # @return [true, false]
38
+ def signed_out?
39
+ current_user.nil?
40
+ end
41
+
42
+ # Deny the user access if they are signed out.
43
+ #
44
+ # @example
45
+ # before_filter :authenticate
46
+ def authenticate
47
+ deny_access unless signed_in?
48
+ end
49
+
50
+ # Sign user in to cookie.
51
+ #
52
+ # @param [User]
53
+ #
54
+ # @example
55
+ # sign_in(@user)
56
+ def sign_in(user)
57
+ if user
58
+ user.remember_me!
59
+ cookies[:remember_token] = {
60
+ :value => user.remember_token,
61
+ :expires => 1.year.from_now.utc
62
+ }
63
+ current_user = user
64
+ end
65
+ end
66
+
67
+ # Sign user out of cookie.
68
+ #
69
+ # @example
70
+ # sign_out
71
+ def sign_out
72
+ cookies.delete(:remember_token)
73
+ current_user = nil
74
+ end
75
+
76
+ # Store the current location.
77
+ # Display a flash message if included.
78
+ # Redirect to sign in.
79
+ #
80
+ # @param [String] optional flash message to display to denied user
81
+ def deny_access(flash_message = nil)
82
+ store_location
83
+ flash[:failure] = flash_message if flash_message
84
+ redirect_to(new_session_url)
85
+ end
86
+
87
+ protected
88
+
89
+ def user_from_cookie
90
+ if token = cookies[:remember_token]
91
+ ::User.find_by_remember_token(token)
92
+ end
93
+ end
94
+
95
+ def sign_user_in(user)
96
+ warn "[DEPRECATION] sign_user_in: unnecessary. use sign_in(user) instead."
97
+ sign_in(user)
98
+ end
99
+
100
+ def store_location
101
+ if request.get?
102
+ session[:return_to] = request.request_uri
103
+ end
104
+ end
105
+
106
+ def redirect_back_or(default)
107
+ redirect_to(return_to || default)
108
+ clear_return_to
109
+ end
110
+
111
+ def return_to
112
+ session[:return_to] || params[:return_to]
113
+ end
114
+
115
+ def clear_return_to
116
+ session[:return_to] = nil
117
+ end
118
+
119
+ def redirect_to_root
120
+ redirect_to(root_url)
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,6 @@
1
+ if defined?(ActionController)
2
+ module ActionController
3
+ class Forbidden < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ if defined?(ActionController::Base)
2
+ ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
3
+ end
@@ -0,0 +1,14 @@
1
+ if defined?(ActionController::Routing::RouteSet)
2
+ class ActionController::Routing::RouteSet
3
+ def load_routes_with_clearance!
4
+ lib_path = File.dirname(__FILE__)
5
+ clearance_routes = File.join(lib_path, *%w[.. .. .. config clearance_routes.rb])
6
+ unless configuration_files.include?(clearance_routes)
7
+ add_configuration_file(clearance_routes)
8
+ end
9
+ load_routes_without_clearance!
10
+ end
11
+
12
+ alias_method_chain :load_routes!, :clearance
13
+ end
14
+ end
@@ -0,0 +1,199 @@
1
+ require 'digest/sha1'
2
+
3
+ module Clearance
4
+ module User
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
23
+ def self.included(model)
24
+ model.extend(ClassMethods)
25
+
26
+ model.send(:include, InstanceMethods)
27
+ model.send(:include, AttrAccessible)
28
+ model.send(:include, AttrAccessor)
29
+ model.send(:include, Validations)
30
+ model.send(:include, Callbacks)
31
+ end
32
+
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
43
+ def self.included(model)
44
+ model.class_eval do
45
+ attr_accessible :email, :password, :password_confirmation
46
+ end
47
+ end
48
+ end
49
+
50
+ module AttrAccessor
51
+ # Hook for attr_accessor virtual attributes.
52
+ #
53
+ # :password, :password_confirmation
54
+ def self.included(model)
55
+ model.class_eval do
56
+ attr_accessor :password, :password_confirmation
57
+ end
58
+ end
59
+ end
60
+
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
68
+ def self.included(model)
69
+ model.class_eval do
70
+ validates_presence_of :email
71
+ validates_uniqueness_of :email, :case_sensitive => false
72
+ validates_format_of :email, :with => %r{.+@.+\..+}
73
+
74
+ validates_presence_of :password, :if => :password_required?
75
+ validates_confirmation_of :password, :if => :password_required?
76
+ end
77
+ end
78
+ end
79
+
80
+ module Callbacks
81
+ # Hook for callbacks.
82
+ #
83
+ # salt, token, password encryption are handled before_save.
84
+ def self.included(model)
85
+ model.class_eval do
86
+ before_save :initialize_salt,
87
+ :encrypt_password,
88
+ :initialize_confirmation_token
89
+ end
90
+ end
91
+ end
92
+
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')
100
+ def authenticated?(password)
101
+ encrypted_password == encrypt(password)
102
+ end
103
+
104
+ # Remember me for a year.
105
+ #
106
+ # @example
107
+ # user.remember_me!
108
+ # cookies[:remember_token] = {
109
+ # :value => user.remember_token,
110
+ # :expires => user.remember_token_expires_at
111
+ # }
112
+ def remember_me!
113
+ self.remember_token = encrypt("--#{Time.now.utc}--#{password}--")
114
+ save(false)
115
+ end
116
+
117
+ # Confirm my email.
118
+ #
119
+ # @example
120
+ # user.confirm_email!
121
+ def confirm_email!
122
+ self.email_confirmed = true
123
+ self.confirmation_token = nil
124
+ save(false)
125
+ end
126
+
127
+ # Mark my account as forgotten password.
128
+ #
129
+ # @example
130
+ # user.forgot_password!
131
+ def forgot_password!
132
+ generate_confirmation_token
133
+ save(false)
134
+ end
135
+
136
+ # Update my password.
137
+ #
138
+ # @param [String, String] password and password confirmation
139
+ # @return [true, false] password was updated or not
140
+ # @example
141
+ # user.update_password('new-password', 'new-password')
142
+ def update_password(new_password, new_password_confirmation)
143
+ self.password = new_password
144
+ self.password_confirmation = new_password_confirmation
145
+ if valid?
146
+ self.confirmation_token = nil
147
+ end
148
+ save
149
+ end
150
+
151
+ protected
152
+
153
+ def generate_hash(string)
154
+ Digest::SHA1.hexdigest(string)
155
+ end
156
+
157
+ def initialize_salt
158
+ if new_record?
159
+ self.salt = generate_hash("--#{Time.now.utc}--#{password}--")
160
+ end
161
+ end
162
+
163
+ def encrypt_password
164
+ return if password.blank?
165
+ self.encrypted_password = encrypt(password)
166
+ end
167
+
168
+ def encrypt(string)
169
+ generate_hash("--#{salt}--#{string}--")
170
+ end
171
+
172
+ def generate_confirmation_token
173
+ self.confirmation_token = encrypt("--#{Time.now.utc}--#{password}--")
174
+ end
175
+
176
+ def initialize_confirmation_token
177
+ generate_confirmation_token if new_record?
178
+ end
179
+
180
+ def password_required?
181
+ encrypted_password.blank? || !password.blank?
182
+ end
183
+ end
184
+
185
+ module ClassMethods
186
+ # Authenticate with email and password.
187
+ #
188
+ # @param [String, String] email and password
189
+ # @return [User, nil] authenticated user or nil
190
+ # @example
191
+ # User.authenticate("email@example.com", "password")
192
+ def authenticate(email, password)
193
+ return nil unless user = find_by_email(email)
194
+ return user if user.authenticated?(password)
195
+ end
196
+ end
197
+
198
+ end
199
+ end