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.
- data/CHANGELOG.textile +194 -0
- data/LICENSE +21 -0
- data/README.textile +123 -0
- data/Rakefile +103 -0
- data/TODO.textile +6 -0
- data/app/controllers/clearance/confirmations_controller.rb +75 -0
- data/app/controllers/clearance/passwords_controller.rb +84 -0
- data/app/controllers/clearance/sessions_controller.rb +66 -0
- data/app/controllers/clearance/users_controller.rb +35 -0
- data/app/models/clearance_mailer.rb +23 -0
- data/app/views/clearance_mailer/change_password.html.erb +9 -0
- data/app/views/clearance_mailer/confirmation.html.erb +5 -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 +24 -0
- data/app/views/users/_form.html.erb +13 -0
- data/app/views/users/new.html.erb +6 -0
- data/config/clearance_routes.rb +30 -0
- data/generators/clearance/USAGE +1 -0
- data/generators/clearance/clearance_generator.rb +41 -0
- data/generators/clearance/lib/insert_commands.rb +33 -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 +21 -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 +35 -0
- data/generators/clearance_features/templates/features/sign_out.feature +15 -0
- data/generators/clearance_features/templates/features/sign_up.feature +45 -0
- data/generators/clearance_features/templates/features/step_definitions/clearance_steps.rb +116 -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/generators/clearance_views/USAGE +0 -0
- data/generators/clearance_views/clearance_views_generator.rb +27 -0
- data/generators/clearance_views/templates/formtastic/passwords/edit.html.erb +21 -0
- data/generators/clearance_views/templates/formtastic/passwords/new.html.erb +15 -0
- data/generators/clearance_views/templates/formtastic/sessions/new.html.erb +21 -0
- data/generators/clearance_views/templates/formtastic/users/_inputs.html.erb +6 -0
- data/generators/clearance_views/templates/formtastic/users/new.html.erb +10 -0
- data/lib/clearance.rb +6 -0
- data/lib/clearance/authentication.rb +125 -0
- data/lib/clearance/extensions/errors.rb +6 -0
- data/lib/clearance/extensions/rescue.rb +3 -0
- data/lib/clearance/extensions/routes.rb +14 -0
- data/lib/clearance/user.rb +199 -0
- data/rails/init.rb +1 -0
- data/shoulda_macros/clearance.rb +266 -0
- 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
|
+
|
data/lib/clearance.rb
ADDED
@@ -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,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
|