tonkapark-clearance 0.6.9.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.textile +11 -5
- data/README.textile +38 -37
- data/Rakefile +41 -19
- data/TODO.textile +4 -3
- data/app/controllers/clearance/confirmations_controller.rb +27 -6
- data/app/controllers/clearance/passwords_controller.rb +1 -1
- data/app/controllers/clearance/sessions_controller.rb +1 -1
- data/app/controllers/clearance/users_controller.rb +3 -4
- data/app/models/clearance_mailer.rb +3 -25
- data/app/views/passwords/edit.html.erb +12 -14
- data/app/views/passwords/new.html.erb +9 -10
- data/app/views/sessions/new.html.erb +26 -1
- data/app/views/users/_form.html.erb +13 -0
- data/app/views/users/new.html.erb +5 -3
- data/clearance.gemspec +20 -31
- data/config/clearance_routes.rb +7 -9
- data/generators/clearance/clearance_generator.rb +3 -30
- data/generators/clearance/templates/README +17 -17
- data/generators/clearance/templates/migrations/create_users.rb +0 -3
- data/generators/clearance/templates/migrations/update_users.rb +1 -4
- data/generators/clearance/templates/user.rb +0 -1
- data/generators/clearance_features/clearance_features_generator.rb +4 -4
- data/generators/clearance_features/templates/features/sign_up.feature +17 -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 +22 -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/authentication.rb +70 -32
- data/lib/clearance/user.rb +87 -32
- data/lib/clearance.rb +0 -1
- data/shoulda_macros/clearance.rb +6 -0
- metadata +23 -32
- data/app/controllers/clearance/invitations_controller.rb +0 -40
- data/app/views/clearance_mailer/invitation.html.erb +0 -3
- data/app/views/invitations/index.html.erb +0 -19
- data/app/views/invitations/new.html.erb +0 -15
- data/app/views/users/_user.html.erb +0 -28
- data/generators/clearance/templates/invitation.rb +0 -4
- data/generators/clearance/templates/migrations/create_invitations.rb +0 -20
- data/generators/clearance/templates/migrations/update_invitations.rb +0 -40
- data/lib/clearance/invitation.rb +0 -87
data/config/clearance_routes.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
ActionController::Routing::Routes.draw do |map|
|
2
2
|
map.resources :passwords,
|
3
3
|
:controller => 'clearance/passwords',
|
4
|
-
:only => [:create]
|
4
|
+
:only => [:new, :create]
|
5
5
|
|
6
6
|
map.resource :session,
|
7
7
|
:controller => 'clearance/sessions',
|
@@ -17,19 +17,17 @@ ActionController::Routing::Routes.draw do |map|
|
|
17
17
|
:only => [:new, :create]
|
18
18
|
end
|
19
19
|
|
20
|
-
map.
|
21
|
-
:controller => 'clearance/
|
22
|
-
:
|
23
|
-
|
24
|
-
map.recover_password '/recover_password', :controller =>'clearance/passwords', :action => 'new'
|
20
|
+
map.recover_password 'recover_password',
|
21
|
+
:controller => 'clearance/passwords',
|
22
|
+
:action => 'new'
|
25
23
|
|
26
|
-
map.
|
24
|
+
map.sign_up 'sign_up',
|
27
25
|
:controller => 'clearance/users',
|
28
26
|
:action => 'new'
|
29
|
-
map.
|
27
|
+
map.sign_in 'sign_in',
|
30
28
|
:controller => 'clearance/sessions',
|
31
29
|
:action => 'new'
|
32
|
-
map.
|
30
|
+
map.sign_out 'sign_out',
|
33
31
|
:controller => 'clearance/sessions',
|
34
32
|
:action => 'destroy',
|
35
33
|
:method => :delete
|
@@ -12,7 +12,6 @@ class ClearanceGenerator < Rails::Generator::Base
|
|
12
12
|
user_model = "app/models/user.rb"
|
13
13
|
if File.exists?(user_model)
|
14
14
|
m.insert_into user_model, "include Clearance::User"
|
15
|
-
m.insert_into user_model, "belongs_to :invitation"
|
16
15
|
else
|
17
16
|
m.directory File.join("app", "models")
|
18
17
|
m.file "user.rb", user_model
|
@@ -21,35 +20,17 @@ class ClearanceGenerator < Rails::Generator::Base
|
|
21
20
|
m.directory File.join("test", "factories")
|
22
21
|
m.file "factories.rb", "test/factories/clearance.rb"
|
23
22
|
|
24
|
-
m.migration_template "migrations/#{
|
23
|
+
m.migration_template "migrations/#{migration_name}.rb",
|
25
24
|
'db/migrate',
|
26
|
-
:migration_file_name => "clearance_#{
|
25
|
+
:migration_file_name => "clearance_#{migration_name}"
|
27
26
|
|
28
|
-
m.directory File.join("config")
|
29
|
-
m.file "clearance.yml", 'config/clearance.yml'
|
30
|
-
|
31
|
-
invitation_model = "app/models/invitation.rb"
|
32
|
-
if File.exists?(invitation_model)
|
33
|
-
m.insert_into invitation_model, "include Clearance::Invitation"
|
34
|
-
m.insert_into invitation_model, "belongs_to :sender, :class_name => 'User', :foreign_key => :sender_id"
|
35
|
-
else
|
36
|
-
m.directory File.join("app", "models")
|
37
|
-
m.file "invitation.rb", invitation_model
|
38
|
-
end
|
39
|
-
|
40
|
-
m.migration_template "migrations/#{invitation_migration_name}.rb",
|
41
|
-
'db/migrate',
|
42
|
-
:migration_file_name => "clearance_#{invitation_migration_name}"
|
43
|
-
|
44
|
-
|
45
|
-
|
46
27
|
m.readme "README"
|
47
28
|
end
|
48
29
|
end
|
49
30
|
|
50
31
|
private
|
51
32
|
|
52
|
-
def
|
33
|
+
def migration_name
|
53
34
|
if ActiveRecord::Base.connection.table_exists?(:users)
|
54
35
|
'update_users'
|
55
36
|
else
|
@@ -57,12 +38,4 @@ class ClearanceGenerator < Rails::Generator::Base
|
|
57
38
|
end
|
58
39
|
end
|
59
40
|
|
60
|
-
def invitation_migration_name
|
61
|
-
if ActiveRecord::Base.connection.table_exists?(:invitations)
|
62
|
-
'update_invitations'
|
63
|
-
else
|
64
|
-
'create_invitations'
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
41
|
end
|
@@ -1,22 +1,22 @@
|
|
1
1
|
|
2
2
|
*******************************************************************************
|
3
|
-
|
3
|
+
|
4
4
|
Ok, enough fancy automatic stuff. Time for some old school monkey copy-pasting.
|
5
|
-
|
6
|
-
1.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
5
|
+
|
6
|
+
1. Define a HOST constant in your environments files.
|
7
|
+
In config/environments/test.rb and config/environments/development.rb it can be:
|
8
|
+
|
9
|
+
HOST = "localhost"
|
10
|
+
|
11
|
+
In production.rb it must be the actual host your application is deployed to.
|
12
|
+
The constant is used by mailers to generate URLs in emails.
|
13
|
+
|
14
|
+
2. In config/environment.rb:
|
15
|
+
|
16
|
+
DO_NOT_REPLY = "donotreply@example.com"
|
17
|
+
|
18
|
+
3. Define root_url to *something* in your config/routes.rb:
|
19
|
+
|
20
20
|
map.root :controller => 'home'
|
21
|
-
|
21
|
+
|
22
22
|
*******************************************************************************
|
@@ -7,9 +7,6 @@ class ClearanceCreateUsers < ActiveRecord::Migration
|
|
7
7
|
t.string :token, :limit => 128
|
8
8
|
t.datetime :token_expires_at
|
9
9
|
t.boolean :email_confirmed, :default => false, :null => false
|
10
|
-
t.boolean :admin, :default => false, :null => false
|
11
|
-
t.integer :invitation_id
|
12
|
-
t.integer :invitation_limit
|
13
10
|
t.timestamps
|
14
11
|
end
|
15
12
|
|
@@ -8,10 +8,7 @@ class ClearanceUpdateUsers < ActiveRecord::Migration
|
|
8
8
|
[:salt, 't.string :salt, :limit => 128'],
|
9
9
|
[:token, 't.string :token, :limit => 128'],
|
10
10
|
[:token_expires_at, 't.datetime :token_expires_at'],
|
11
|
-
[:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
|
12
|
-
[:admin, 't.boolean :admin, :default => false, :null => false'],
|
13
|
-
[:invitation_id, 't.integer :invitation_id'],
|
14
|
-
[:invitation_limit, 't.integer :invitation_limit']
|
11
|
+
[:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
|
15
12
|
].delete_if {|c| existing_columns.include?(c.first.to_s)}
|
16
13
|
-%>
|
17
14
|
change_table(:users) do |t|
|
@@ -1,20 +1,20 @@
|
|
1
1
|
class ClearanceFeaturesGenerator < Rails::Generator::Base
|
2
|
-
|
2
|
+
|
3
3
|
def manifest
|
4
4
|
record do |m|
|
5
5
|
m.directory File.join("features", "step_definitions")
|
6
6
|
m.directory File.join("features", "support")
|
7
|
-
|
7
|
+
|
8
8
|
["features/step_definitions/clearance_steps.rb",
|
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
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
end
|
@@ -26,3 +26,20 @@ Feature: Sign up
|
|
26
26
|
Then I should see "Confirmed email and signed in"
|
27
27
|
And I should be signed in
|
28
28
|
|
29
|
+
Scenario: Signed in user clicks confirmation link again
|
30
|
+
Given I signed up with "email@person.com/password"
|
31
|
+
When I follow the confirmation link sent to "email@person.com"
|
32
|
+
Then I should be signed in
|
33
|
+
When I follow the confirmation link sent to "email@person.com"
|
34
|
+
Then I should see "Confirmed email and signed in"
|
35
|
+
And I should be signed in
|
36
|
+
|
37
|
+
Scenario: Signed out user clicks confirmation link again
|
38
|
+
Given I signed up with "email@person.com/password"
|
39
|
+
When I follow the confirmation link sent to "email@person.com"
|
40
|
+
Then I should be signed in
|
41
|
+
When I sign out
|
42
|
+
And I follow the confirmation link sent to "email@person.com"
|
43
|
+
Then I should see "Already confirmed email. Please sign in."
|
44
|
+
And I should be signed out
|
45
|
+
|
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.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,22 @@
|
|
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
|
+
<%= form.input :remember_me, :as => :boolean, :required => false %>
|
8
|
+
<% end %>
|
9
|
+
<% form.buttons do %>
|
10
|
+
<%= form.commit_button "Sign in" %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<ul>
|
15
|
+
<li>
|
16
|
+
<%= link_to "Sign up", new_user_path %>
|
17
|
+
</li>
|
18
|
+
<li>
|
19
|
+
<%= link_to "Forgot password?", new_password_path %>
|
20
|
+
</li>
|
21
|
+
</ul>
|
22
|
+
|
@@ -1,32 +1,94 @@
|
|
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
|
8
|
-
helper_method :current_user
|
9
|
-
|
10
|
-
|
11
|
-
hide_action :current_user, :signed_in?
|
8
|
+
helper_method :current_user, :signed_in?, :signed_out?
|
9
|
+
hide_action :current_user, :signed_in?, :signed_out?
|
12
10
|
end
|
13
11
|
end
|
14
12
|
|
15
13
|
module InstanceMethods
|
14
|
+
# User in the current session or remember me cookie
|
15
|
+
#
|
16
|
+
# @return [User, nil]
|
16
17
|
def current_user
|
17
18
|
@_current_user ||= (user_from_cookie || user_from_session)
|
18
19
|
end
|
19
20
|
|
21
|
+
# Is the current user signed in?
|
22
|
+
#
|
23
|
+
# @return [true, false]
|
20
24
|
def signed_in?
|
21
25
|
! current_user.nil?
|
22
26
|
end
|
23
27
|
|
24
|
-
|
28
|
+
# Is the current user signed out?
|
29
|
+
#
|
30
|
+
# @return [true, false]
|
31
|
+
def signed_out?
|
32
|
+
current_user.nil?
|
33
|
+
end
|
25
34
|
|
35
|
+
# Deny the user access if they are signed out.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# before_filter :authenticate
|
26
39
|
def authenticate
|
27
40
|
deny_access unless signed_in?
|
28
41
|
end
|
29
42
|
|
43
|
+
# Sign user in to session.
|
44
|
+
#
|
45
|
+
# @param [User]
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# sign_in(@user)
|
49
|
+
def sign_in(user)
|
50
|
+
if user
|
51
|
+
session[:user_id] = user.id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Remember user with cookie.
|
56
|
+
#
|
57
|
+
# @param [User]
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# remember(@user)
|
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
|
+
# Forget user. Should only be used if developer overrides sign out.
|
68
|
+
#
|
69
|
+
# @param [User]
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# forget(@user)
|
73
|
+
def forget(user)
|
74
|
+
user.forget_me! if user
|
75
|
+
cookies.delete(:remember_token)
|
76
|
+
reset_session
|
77
|
+
end
|
78
|
+
|
79
|
+
# Store the current location.
|
80
|
+
# Display a flash message if included.
|
81
|
+
# Redirect to sign in.
|
82
|
+
#
|
83
|
+
# @param [String] optional flash message to display to denied user
|
84
|
+
def deny_access(flash_message = nil)
|
85
|
+
store_location
|
86
|
+
flash[:failure] = flash_message if flash_message
|
87
|
+
redirect_to(sign_in_url)
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
30
92
|
def user_from_session
|
31
93
|
if session[:user_id]
|
32
94
|
return nil unless user = ::User.find_by_id(session[:user_id])
|
@@ -46,26 +108,12 @@ module Clearance
|
|
46
108
|
sign_in(user)
|
47
109
|
end
|
48
110
|
|
49
|
-
def sign_in(user)
|
50
|
-
if user
|
51
|
-
session[:user_id] = user.id
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
111
|
def remember?
|
56
112
|
params[:session] && params[:session][:remember_me] == "1"
|
57
113
|
end
|
58
114
|
|
59
|
-
def
|
60
|
-
|
61
|
-
cookies[:remember_token] = { :value => user.token,
|
62
|
-
:expires => user.token_expires_at }
|
63
|
-
end
|
64
|
-
|
65
|
-
def forget(user)
|
66
|
-
user.forget_me! if user
|
67
|
-
cookies.delete(:remember_token)
|
68
|
-
reset_session
|
115
|
+
def store_location
|
116
|
+
session[:return_to] = request.request_uri if request.get?
|
69
117
|
end
|
70
118
|
|
71
119
|
def redirect_back_or(default)
|
@@ -84,16 +132,6 @@ module Clearance
|
|
84
132
|
def redirect_to_root
|
85
133
|
redirect_to(root_url)
|
86
134
|
end
|
87
|
-
|
88
|
-
def store_location
|
89
|
-
session[:return_to] = request.request_uri if request.get?
|
90
|
-
end
|
91
|
-
|
92
|
-
def deny_access(flash_message = nil, opts = {})
|
93
|
-
store_location
|
94
|
-
flash[:failure] = flash_message if flash_message
|
95
|
-
redirect_to(signin_url)
|
96
|
-
end
|
97
135
|
end
|
98
136
|
|
99
137
|
end
|
data/lib/clearance/user.rb
CHANGED
@@ -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,14 +31,26 @@ 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
|
-
attr_accessible :email, :password, :password_confirmation
|
45
|
+
attr_accessible :email, :password, :password_confirmation
|
20
46
|
end
|
21
47
|
end
|
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
|
@@ -38,69 +73,91 @@ module Clearance
|
|
38
73
|
|
39
74
|
validates_presence_of :password, :if => :password_required?
|
40
75
|
validates_confirmation_of :password, :if => :password_required?
|
41
|
-
|
42
|
-
validates_presence_of :invitation_id, :message => 'is required', :if => Proc.new { |u| u.new_record?}
|
43
|
-
validates_uniqueness_of :invitation_id, :if => Proc.new { |u| u.new_record?}
|
44
76
|
end
|
45
77
|
end
|
46
78
|
end
|
47
79
|
|
48
80
|
module Callbacks
|
81
|
+
# Hook for callbacks.
|
82
|
+
#
|
83
|
+
# salt, token, password encryption are handled before_save.
|
49
84
|
def self.included(model)
|
50
85
|
model.class_eval do
|
51
|
-
before_save :initialize_salt, :encrypt_password, :initialize_token
|
52
|
-
before_create :set_invitation_limit, :redeemed
|
86
|
+
before_save :initialize_salt, :encrypt_password, :initialize_token
|
53
87
|
end
|
54
88
|
end
|
55
89
|
end
|
56
90
|
|
57
91
|
module InstanceMethods
|
92
|
+
# Am I authenticated with given password?
|
93
|
+
#
|
94
|
+
# @param [String] plain-text password
|
95
|
+
# @return [true, false]
|
96
|
+
# @example
|
97
|
+
# user.authenticated?('password')
|
58
98
|
def authenticated?(password)
|
59
99
|
encrypted_password == encrypt(password)
|
60
100
|
end
|
61
101
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
102
|
+
# Am I remembered?
|
103
|
+
#
|
104
|
+
# @return [true, false]
|
105
|
+
# @example
|
106
|
+
# user.remember?
|
66
107
|
def remember?
|
67
108
|
token_expires_at && Time.now.utc < token_expires_at
|
68
109
|
end
|
69
110
|
|
111
|
+
# Remember me for two weeks.
|
112
|
+
#
|
113
|
+
# @example
|
114
|
+
# user.remember_me!
|
115
|
+
# cookies[:remember_token] = { :value => user.token,
|
116
|
+
# :expires => user.token_expires_at }
|
70
117
|
def remember_me!
|
71
118
|
remember_me_until! 2.weeks.from_now.utc
|
72
119
|
end
|
73
120
|
|
121
|
+
# Forget me.
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# user.forget_me!
|
74
125
|
def forget_me!
|
75
126
|
clear_token
|
76
127
|
save(false)
|
77
128
|
end
|
78
129
|
|
130
|
+
# Confirm my email.
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# user.confirm_email!
|
79
134
|
def confirm_email!
|
80
|
-
self.email_confirmed
|
81
|
-
self.token
|
135
|
+
self.email_confirmed = true
|
136
|
+
self.token = nil
|
82
137
|
save(false)
|
83
138
|
end
|
84
139
|
|
140
|
+
# Mark my account as forgotten password.
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# user.forgot_password!
|
85
144
|
def forgot_password!
|
86
145
|
generate_token
|
87
146
|
save(false)
|
88
147
|
end
|
89
148
|
|
149
|
+
# Update my password.
|
150
|
+
#
|
151
|
+
# @param [String, String] password and password confirmation
|
152
|
+
# @return [true, false] password was updated or not
|
153
|
+
# @example
|
154
|
+
# user.update_password('new-password', 'new-password')
|
90
155
|
def update_password(new_password, new_password_confirmation)
|
91
156
|
self.password = new_password
|
92
157
|
self.password_confirmation = new_password_confirmation
|
93
158
|
clear_token if valid?
|
94
159
|
save
|
95
160
|
end
|
96
|
-
|
97
|
-
def invitation_token
|
98
|
-
invitation.token if invitation
|
99
|
-
end
|
100
|
-
|
101
|
-
def invitation_token=(token)
|
102
|
-
self.invitation = ::Invitation.find_by_token(token)
|
103
|
-
end
|
104
161
|
|
105
162
|
protected
|
106
163
|
|
@@ -119,6 +176,10 @@ module Clearance
|
|
119
176
|
self.encrypted_password = encrypt(password)
|
120
177
|
end
|
121
178
|
|
179
|
+
def encrypt(string)
|
180
|
+
generate_hash("--#{salt}--#{string}--")
|
181
|
+
end
|
182
|
+
|
122
183
|
def generate_token
|
123
184
|
self.token = encrypt("--#{Time.now.utc.to_s}--#{password}--")
|
124
185
|
self.token_expires_at = nil
|
@@ -142,21 +203,15 @@ module Clearance
|
|
142
203
|
self.token = encrypt("--#{token_expires_at}--#{password}--")
|
143
204
|
save(false)
|
144
205
|
end
|
145
|
-
|
146
|
-
def downcase_email
|
147
|
-
self.email = email.to_s.downcase
|
148
|
-
end
|
149
|
-
|
150
|
-
def set_invitation_limit
|
151
|
-
self.invitation_limit = 5
|
152
|
-
end
|
153
|
-
|
154
|
-
def redeemed
|
155
|
-
invitation.redeemed! if invitation
|
156
|
-
end
|
157
206
|
end
|
158
207
|
|
159
208
|
module ClassMethods
|
209
|
+
# Authenticate with email and password.
|
210
|
+
#
|
211
|
+
# @param [String, String] email and password
|
212
|
+
# @return [User, nil] authenticated user or nil
|
213
|
+
# @example
|
214
|
+
# User.authenticate("email@example.com", "password")
|
160
215
|
def authenticate(email, password)
|
161
216
|
return nil unless user = find_by_email(email)
|
162
217
|
return user if user.authenticated?(password)
|