headstart 0.1.0
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.
- data/LICENSE +20 -0
- data/README.rdoc +117 -0
- data/Rakefile +95 -0
- data/VERSION +1 -0
- data/app/controllers/headstart/confirmations_controller.rb +76 -0
- data/app/controllers/headstart/impersonations_controller.rb +44 -0
- data/app/controllers/headstart/passwords_controller.rb +93 -0
- data/app/controllers/headstart/sessions_controller.rb +76 -0
- data/app/controllers/headstart/users_controller.rb +85 -0
- data/app/models/deliver_change_password_job.rb +19 -0
- data/app/models/deliver_welcome_job.rb +17 -0
- data/app/models/generic_mailer.rb +31 -0
- data/app/models/headstart_mailer.rb +28 -0
- data/app/models/impersonation.rb +26 -0
- data/app/models/mimi_mailer.rb +30 -0
- data/app/views/generic_mailer/change_password.html.erb +9 -0
- data/app/views/generic_mailer/confirmation.html.erb +5 -0
- data/app/views/generic_mailer/welcome.html.erb +1 -0
- data/app/views/impersonations/index.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 +48 -0
- data/app/views/users/_form.html.erb +21 -0
- data/app/views/users/edit.html.erb +6 -0
- data/app/views/users/new.html.erb +6 -0
- data/app/views/users/show.html.erb +8 -0
- data/generators/headstart/USAGE +1 -0
- data/generators/headstart/headstart_generator.rb +86 -0
- data/generators/headstart/lib/insert_commands.rb +33 -0
- data/generators/headstart/lib/rake_commands.rb +22 -0
- data/generators/headstart/templates/README +20 -0
- data/generators/headstart/templates/app/controllers/sessions_controller.rb +6 -0
- data/generators/headstart/templates/app/views/sessions/index.html.erb +1 -0
- data/generators/headstart/templates/application.html.erb +75 -0
- data/generators/headstart/templates/factories.rb +23 -0
- data/generators/headstart/templates/headstart.rb +25 -0
- data/generators/headstart/templates/headstart.yml +45 -0
- data/generators/headstart/templates/layout.css +353 -0
- data/generators/headstart/templates/migrations/create_users.rb +26 -0
- data/generators/headstart/templates/migrations/update_users.rb +44 -0
- data/generators/headstart/templates/report.css +69 -0
- data/generators/headstart/templates/reset.css +1 -0
- data/generators/headstart/templates/style.css +31 -0
- data/generators/headstart/templates/text.css +1 -0
- data/generators/headstart/templates/user.rb +3 -0
- data/generators/headstart/templates/xd_receiver.html +10 -0
- data/generators/headstart/templates/xd_receiver_ssl.html +10 -0
- data/generators/headstart_admin/USAGE +1 -0
- data/generators/headstart_admin/headstart_admin_generator.rb +32 -0
- data/generators/headstart_admin/lib/insert_commands.rb +33 -0
- data/generators/headstart_admin/templates/README +16 -0
- data/generators/headstart_admin/templates/app/controllers/admin/admin_controller.rb +17 -0
- data/generators/headstart_admin/templates/app/controllers/admin/users_controller.rb +52 -0
- data/generators/headstart_admin/templates/app/views/admin/admin/index.html.erb +2 -0
- data/generators/headstart_admin/templates/app/views/admin/users/_form.html.erb +25 -0
- data/generators/headstart_admin/templates/app/views/admin/users/edit.html.erb +6 -0
- data/generators/headstart_admin/templates/app/views/admin/users/index.html.erb +7 -0
- data/generators/headstart_admin/templates/app/views/admin/users/new.html.erb +6 -0
- data/generators/headstart_admin/templates/app/views/admin/users/show.html.erb +10 -0
- data/generators/headstart_admin/templates/test/integration/admin/users_test.rb +201 -0
- data/generators/headstart_tests/USAGE +1 -0
- data/generators/headstart_tests/headstart_tests_generator.rb +21 -0
- data/generators/headstart_tests/templates/README +58 -0
- data/generators/headstart_tests/templates/test/integration/edit_profile_test.rb +35 -0
- data/generators/headstart_tests/templates/test/integration/facebook_test.rb +61 -0
- data/generators/headstart_tests/templates/test/integration/impersonation_test.rb +39 -0
- data/generators/headstart_tests/templates/test/integration/password_reset_test.rb +128 -0
- data/generators/headstart_tests/templates/test/integration/sign_in_test.rb +66 -0
- data/generators/headstart_tests/templates/test/integration/sign_out_test.rb +28 -0
- data/generators/headstart_tests/templates/test/integration/sign_up_test.rb +47 -0
- data/lib/headstart/authentication.rb +138 -0
- data/lib/headstart/configuration.rb +34 -0
- data/lib/headstart/extensions/errors.rb +6 -0
- data/lib/headstart/extensions/rescue.rb +5 -0
- data/lib/headstart/routes.rb +67 -0
- data/lib/headstart/user.rb +279 -0
- data/lib/headstart.rb +7 -0
- data/rails/init.rb +4 -0
- data/shoulda_macros/headstart.rb +244 -0
- data/test/controllers/passwords_controller_test.rb +184 -0
- data/test/controllers/sessions_controller_test.rb +129 -0
- data/test/controllers/users_controller_test.rb +57 -0
- data/test/models/headstart_mailer_test.rb +52 -0
- data/test/models/impersonation_test.rb +25 -0
- data/test/models/user_test.rb +213 -0
- data/test/rails_root/app/controllers/accounts_controller.rb +10 -0
- data/test/rails_root/app/controllers/application_controller.rb +6 -0
- data/test/rails_root/app/helpers/application_helper.rb +5 -0
- data/test/rails_root/app/helpers/confirmations_helper.rb +2 -0
- data/test/rails_root/app/helpers/passwords_helper.rb +2 -0
- data/test/rails_root/config/boot.rb +110 -0
- data/test/rails_root/config/environment.rb +22 -0
- data/test/rails_root/config/environments/development.rb +19 -0
- data/test/rails_root/config/environments/production.rb +1 -0
- data/test/rails_root/config/environments/test.rb +37 -0
- data/test/rails_root/config/initializers/inflections.rb +10 -0
- data/test/rails_root/config/initializers/mime_types.rb +5 -0
- data/test/rails_root/config/initializers/requires.rb +13 -0
- data/test/rails_root/config/initializers/time_formats.rb +4 -0
- data/test/rails_root/config/routes.rb +9 -0
- data/test/rails_root/public/dispatch.rb +10 -0
- data/test/rails_root/script/create_project.rb +52 -0
- data/test/rails_root/test/functional/accounts_controller_test.rb +23 -0
- data/test/test_helper.rb +21 -0
- metadata +232 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class SignInTest < ActionController::IntegrationTest
|
|
4
|
+
|
|
5
|
+
context 'Signing in as a User' do
|
|
6
|
+
|
|
7
|
+
context 'who is not in the system' do
|
|
8
|
+
|
|
9
|
+
should 'see a failure message' do
|
|
10
|
+
sign_in_as('someone@somewhere.com', 'password')
|
|
11
|
+
assert_match(/Bad email or password/, response.body)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
should 'not be signed in' do
|
|
15
|
+
sign_in_as "someone@somewhere.com", 'password'
|
|
16
|
+
assert !controller.signed_in?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context 'when confirmed' do
|
|
22
|
+
|
|
23
|
+
setup do
|
|
24
|
+
@user = Factory(:user, :email => "bob@bob.bob", :password => "password")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should 'be signed in' do
|
|
28
|
+
sign_in_as 'bob@bob.bob', 'password'
|
|
29
|
+
assert controller.signed_in?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
should 'see "Signed In"' do
|
|
33
|
+
sign_in_as 'bob@bob.bob', 'password'
|
|
34
|
+
assert_match %r{Signed in}, @response.body
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
should 'be signed in on subsequent requests' do
|
|
38
|
+
sign_in_as 'bob@bob.bob', 'password'
|
|
39
|
+
reset_session
|
|
40
|
+
visit root_url
|
|
41
|
+
assert controller.signed_in?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when confirmed but with bad credentials' do
|
|
47
|
+
|
|
48
|
+
setup do
|
|
49
|
+
@user = Factory(:user, :email => 'bob@bob.bob', :password => 'password')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
should 'not be signed in' do
|
|
53
|
+
sign_in_as 'bob@bob.bob', 'badpassword'
|
|
54
|
+
assert !controller.signed_in?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
should 'see "Bad email or password"' do
|
|
58
|
+
sign_in_as 'bob@bob.bob', 'badpassword'
|
|
59
|
+
assert_match /Bad email or password/, response.body
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class SignOutTest < ActionController::IntegrationTest
|
|
4
|
+
|
|
5
|
+
context 'Signing out as a user' do
|
|
6
|
+
|
|
7
|
+
should 'see "Signed out"' do
|
|
8
|
+
sign_up(:email => 'bob@bob.bob')
|
|
9
|
+
sign_out
|
|
10
|
+
assert_match(/Signed out/, response.body)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
should 'be signed out' do
|
|
14
|
+
sign_up(:email => 'bob@bob.bob')
|
|
15
|
+
sign_out
|
|
16
|
+
assert !controller.signed_in?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
should 'be signed out when I return' do
|
|
20
|
+
sign_up(:email => 'bob@bob.bob')
|
|
21
|
+
sign_out
|
|
22
|
+
visit root_url
|
|
23
|
+
assert !controller.signed_in?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class SignUpTest < ActionController::IntegrationTest
|
|
4
|
+
|
|
5
|
+
context 'Signing up as a new user' do
|
|
6
|
+
|
|
7
|
+
setup do
|
|
8
|
+
ActionMailer::Base.deliveries.clear
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
teardown do
|
|
12
|
+
ActionMailer::Base.deliveries.clear
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'with invalid data' do
|
|
16
|
+
|
|
17
|
+
should 'show error messages' do
|
|
18
|
+
sign_up(:email => 'invalidemail', :password_confirmation => '', :first_name => '', :last_name => '')
|
|
19
|
+
assert_match /Email is invalid/, response.body
|
|
20
|
+
assert_match /First name.*blank/, response.body
|
|
21
|
+
assert_match /Last name.*blank/, response.body
|
|
22
|
+
assert_match /Password doesn't match confirmation/, response.body
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with valid data' do
|
|
28
|
+
|
|
29
|
+
should 'sign in the user' do
|
|
30
|
+
sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
|
|
31
|
+
assert controller.signed_in?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
should 'send a welcome email' do
|
|
35
|
+
sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
|
|
36
|
+
user = User.find_by_email('bob@bob.bob')
|
|
37
|
+
Delayed::Job.work_off
|
|
38
|
+
sent = ActionMailer::Base.deliveries.last
|
|
39
|
+
assert_equal user.email, sent.recipients
|
|
40
|
+
assert_match /welcome/i, sent.subject
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Headstart
|
|
2
|
+
module Authentication
|
|
3
|
+
|
|
4
|
+
def self.included(controller) # :nodoc:
|
|
5
|
+
controller.send(:include, InstanceMethods)
|
|
6
|
+
controller.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def self.extended(controller)
|
|
11
|
+
controller.helper_method :current_user, :signed_in?,
|
|
12
|
+
:signed_out?, :impersonating?
|
|
13
|
+
controller.hide_action :current_user, :current_user=,
|
|
14
|
+
:signed_in?, :signed_out?,
|
|
15
|
+
:sign_in, :sign_out,
|
|
16
|
+
:authenticate, :deny_access,
|
|
17
|
+
:impersonating?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module InstanceMethods
|
|
22
|
+
# User in the current cookie
|
|
23
|
+
#
|
|
24
|
+
# @return [User, nil]
|
|
25
|
+
def current_user
|
|
26
|
+
@_current_user ||= user_from_cookie
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Set the current user
|
|
30
|
+
#
|
|
31
|
+
# @param [User]
|
|
32
|
+
def current_user=(user)
|
|
33
|
+
@_current_user = user
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Is the current user signed in?
|
|
37
|
+
#
|
|
38
|
+
# @return [true, false]
|
|
39
|
+
def signed_in?
|
|
40
|
+
! current_user.nil?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Is the current user signed out?
|
|
44
|
+
#
|
|
45
|
+
# @return [true, false]
|
|
46
|
+
def signed_out?
|
|
47
|
+
current_user.nil?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Deny the user access if they are signed out.
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# before_filter :authenticate
|
|
54
|
+
def authenticate
|
|
55
|
+
deny_access unless signed_in?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Sign user in to cookie.
|
|
59
|
+
#
|
|
60
|
+
# @param [User]
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# sign_in(@user)
|
|
64
|
+
def sign_in(user)
|
|
65
|
+
if user
|
|
66
|
+
cookies[:remember_token] = {
|
|
67
|
+
:value => user.remember_token,
|
|
68
|
+
:expires => 1.year.from_now.utc
|
|
69
|
+
}
|
|
70
|
+
self.current_user = user
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Sign user out of cookie.
|
|
75
|
+
#
|
|
76
|
+
# @example
|
|
77
|
+
# sign_out
|
|
78
|
+
def sign_out
|
|
79
|
+
current_user.reset_remember_token! if current_user
|
|
80
|
+
cookies.delete(:remember_token)
|
|
81
|
+
self.current_user = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Store the current location and redirect to sign in.
|
|
85
|
+
# Display a failure flash message if included.
|
|
86
|
+
#
|
|
87
|
+
# @param [String] optional flash message to display to denied user
|
|
88
|
+
def deny_access(flash_message = nil)
|
|
89
|
+
store_location
|
|
90
|
+
flash[:failure] = flash_message if flash_message
|
|
91
|
+
redirect_to(sign_in_url)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def impersonating?
|
|
95
|
+
!session[:admin_user_id].blank?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
protected
|
|
100
|
+
|
|
101
|
+
def user_from_cookie
|
|
102
|
+
if token = cookies[:remember_token]
|
|
103
|
+
::User.find_by_remember_token(token)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def sign_user_in(user)
|
|
108
|
+
warn "[DEPRECATION] sign_user_in: unnecessary. use sign_in(user) instead."
|
|
109
|
+
sign_in(user)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def store_location
|
|
113
|
+
if request.get?
|
|
114
|
+
session[:return_to] = request.request_uri
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def redirect_back_or(default)
|
|
119
|
+
redirect_to(return_to || default)
|
|
120
|
+
clear_return_to
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def return_to
|
|
124
|
+
session[:return_to] || params[:return_to]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def clear_return_to
|
|
128
|
+
session[:return_to] = nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def redirect_to_root
|
|
132
|
+
redirect_to('/')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Headstart
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_accessor :mailer_sender
|
|
4
|
+
attr_accessor :impersonation_hash
|
|
5
|
+
attr_accessor :use_facebook_connect
|
|
6
|
+
attr_accessor :facebook_api_key
|
|
7
|
+
attr_accessor :facebook_secret_key
|
|
8
|
+
attr_accessor :use_delayed_job
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@mailer_sender = 'donotreply@example.com'
|
|
12
|
+
@impersonation_hash = 'e76e05e1ddf74560ffb64c02a1c1b26c'
|
|
13
|
+
@user_facebook_connect = false
|
|
14
|
+
@use_delayed_job = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :configuration
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Configure Headstart someplace sensible,
|
|
23
|
+
# like config/initializers/headstart.rb
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# Headstart.configure do |config|
|
|
27
|
+
# config.mailer_sender = 'donotreply@example.com'
|
|
28
|
+
# config.impersonation_hash = 'abc123def456...'
|
|
29
|
+
# end
|
|
30
|
+
def self.configure
|
|
31
|
+
self.configuration ||= Configuration.new
|
|
32
|
+
yield(configuration)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
if defined?(ActionDispatch::ShowExceptions) # Rails 3
|
|
2
|
+
ActionDispatch::ShowExceptions.rescue_responses.update('ActionController::Forbidden' => :forbidden)
|
|
3
|
+
elsif defined?(ActionController::Base)
|
|
4
|
+
ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
|
|
5
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Headstart
|
|
2
|
+
class Routes
|
|
3
|
+
|
|
4
|
+
# In your application's config/routes.rb, draw Headstart's routes:
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# map.resources :posts
|
|
8
|
+
# Headstart::Routes.draw(map)
|
|
9
|
+
#
|
|
10
|
+
# If you need to override a Headstart route, invoke your app route
|
|
11
|
+
# earlier in the file so Rails' router short-circuits when it finds
|
|
12
|
+
# your route:
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# map.resources :users, :only => [:new, :create]
|
|
16
|
+
# Headstart::Routes.draw(map)
|
|
17
|
+
def self.draw(map)
|
|
18
|
+
map.resources :passwords,
|
|
19
|
+
:controller => 'headstart/passwords',
|
|
20
|
+
:only => [:new, :create]
|
|
21
|
+
|
|
22
|
+
map.resource :session,
|
|
23
|
+
:controller => 'headstart/sessions',
|
|
24
|
+
:only => [:new, :create, :destroy]
|
|
25
|
+
|
|
26
|
+
map.resources :users, :controller => 'headstart/users' do |users|
|
|
27
|
+
users.resource :password,
|
|
28
|
+
:controller => 'headstart/passwords',
|
|
29
|
+
:only => [:create, :edit, :update]
|
|
30
|
+
|
|
31
|
+
users.resource :confirmation,
|
|
32
|
+
:controller => 'headstart/confirmations',
|
|
33
|
+
:only => [:new, :create]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
map.resource :impersonation,
|
|
37
|
+
:controller => 'headstart/impersonations',
|
|
38
|
+
:only => [:create, :destroy]
|
|
39
|
+
map.resources :impersonations,
|
|
40
|
+
:controller => 'headstart/impersonations',
|
|
41
|
+
:only => :index
|
|
42
|
+
|
|
43
|
+
map.sign_up 'sign_up',
|
|
44
|
+
:controller => 'headstart/users',
|
|
45
|
+
:action => 'new'
|
|
46
|
+
map.sign_in 'sign_in',
|
|
47
|
+
:controller => 'headstart/sessions',
|
|
48
|
+
:action => 'new'
|
|
49
|
+
map.fb_connect 'fb_connect',
|
|
50
|
+
:controller => 'headstart/sessions',
|
|
51
|
+
:action => 'create'
|
|
52
|
+
map.fb_disconnect 'fb_disconnect',
|
|
53
|
+
:controller => 'headstart/users',
|
|
54
|
+
:action => 'facebook_remove'
|
|
55
|
+
map.sign_out 'sign_out',
|
|
56
|
+
:controller => 'headstart/sessions',
|
|
57
|
+
:action => 'destroy',
|
|
58
|
+
:method => :delete
|
|
59
|
+
map.admin 'admin',
|
|
60
|
+
:controller => '/admin/admin',
|
|
61
|
+
:action => :index
|
|
62
|
+
|
|
63
|
+
map.root :controller => "sessions", :action => 'index'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
|
|
3
|
+
module Headstart
|
|
4
|
+
module User
|
|
5
|
+
|
|
6
|
+
Admin = 'admin'
|
|
7
|
+
|
|
8
|
+
# Hook for all Headstart::User modules.
|
|
9
|
+
#
|
|
10
|
+
# If you need to override parts of Headstart::User,
|
|
11
|
+
# extend and include à la carte.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# extend ClassMethods
|
|
15
|
+
# include InstanceMethods
|
|
16
|
+
# include AttrAccessor
|
|
17
|
+
# include Callbacks
|
|
18
|
+
#
|
|
19
|
+
# @see ClassMethods
|
|
20
|
+
# @see InstanceMethods
|
|
21
|
+
# @see AttrAccessible
|
|
22
|
+
# @see AttrAccessor
|
|
23
|
+
# @see Validations
|
|
24
|
+
# @see Callbacks
|
|
25
|
+
def self.included(model)
|
|
26
|
+
model.extend(ClassMethods)
|
|
27
|
+
|
|
28
|
+
model.send(:include, InstanceMethods)
|
|
29
|
+
model.send(:include, AttrAccessor)
|
|
30
|
+
model.send(:include, Validations)
|
|
31
|
+
model.send(:include, Callbacks)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module AttrAccessor
|
|
35
|
+
# Hook for attr_accessor virtual attributes.
|
|
36
|
+
#
|
|
37
|
+
# :password, :password_confirmation
|
|
38
|
+
def self.included(model)
|
|
39
|
+
model.class_eval do
|
|
40
|
+
attr_accessor :password, :password_confirmation
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Validations
|
|
46
|
+
# Hook for validations.
|
|
47
|
+
#
|
|
48
|
+
# :email must be present, unique, formatted
|
|
49
|
+
#
|
|
50
|
+
# If password is required,
|
|
51
|
+
# :password must be present, confirmed
|
|
52
|
+
def self.included(model)
|
|
53
|
+
model.class_eval do
|
|
54
|
+
validates_presence_of :email, :unless => :email_optional?
|
|
55
|
+
validates_uniqueness_of :email, :case_sensitive => false, :allow_blank => true
|
|
56
|
+
validates_format_of :email, :with => %r{.+@.+\..+}, :allow_blank => true
|
|
57
|
+
|
|
58
|
+
validates_presence_of :password, :unless => :password_optional?
|
|
59
|
+
validates_confirmation_of :password, :unless => :password_optional?
|
|
60
|
+
|
|
61
|
+
validates_presence_of :first_name, :last_name
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module Callbacks
|
|
67
|
+
# Hook for callbacks.
|
|
68
|
+
#
|
|
69
|
+
# salt, token, password encryption are handled before_save.
|
|
70
|
+
def self.included(model)
|
|
71
|
+
model.class_eval do
|
|
72
|
+
before_save :initialize_salt,
|
|
73
|
+
:encrypt_password
|
|
74
|
+
before_create :generate_confirmation_token,
|
|
75
|
+
:generate_remember_token
|
|
76
|
+
after_create :send_welcome_email, :unless => :suppress_receive_welcome_email?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module InstanceMethods
|
|
82
|
+
# Am I authenticated with given password?
|
|
83
|
+
#
|
|
84
|
+
# @param [String] plain-text password
|
|
85
|
+
# @return [true, false]
|
|
86
|
+
# @example
|
|
87
|
+
# user.authenticated?('password')
|
|
88
|
+
def authenticated?(password)
|
|
89
|
+
encrypted_password == encrypt(password)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Don't send welcome email if email already confirmed, or
|
|
93
|
+
# use is a facebook connect user
|
|
94
|
+
def suppress_receive_welcome_email?
|
|
95
|
+
return true if email_confirmed?
|
|
96
|
+
if self.facebook_uid.present?
|
|
97
|
+
self.email_confirmed = true
|
|
98
|
+
self.confirmation_token = nil
|
|
99
|
+
self.save
|
|
100
|
+
return true
|
|
101
|
+
end
|
|
102
|
+
return false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Set the remember token.
|
|
106
|
+
#
|
|
107
|
+
# @deprecated Use {#reset_remember_token!} instead
|
|
108
|
+
def remember_me!
|
|
109
|
+
warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
|
|
110
|
+
reset_remember_token!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Reset the remember token.
|
|
114
|
+
#
|
|
115
|
+
# @example
|
|
116
|
+
# user.reset_remember_token!
|
|
117
|
+
def reset_remember_token!
|
|
118
|
+
generate_remember_token
|
|
119
|
+
save(false)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Confirm my email.
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# user.confirm_email!
|
|
126
|
+
def confirm_email!
|
|
127
|
+
self.email_confirmed = true
|
|
128
|
+
self.confirmation_token = nil
|
|
129
|
+
save(false)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Mark my account as forgotten password.
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# user.forgot_password!
|
|
136
|
+
def forgot_password!
|
|
137
|
+
generate_password_reset_token
|
|
138
|
+
save(false)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Update my password.
|
|
142
|
+
#
|
|
143
|
+
# @param [String, String] password and password confirmation
|
|
144
|
+
# @return [true, false] password was updated or not
|
|
145
|
+
# @example
|
|
146
|
+
# user.update_password('new-password', 'new-password')
|
|
147
|
+
def update_password(new_password, new_password_confirmation)
|
|
148
|
+
self.password = new_password
|
|
149
|
+
self.password_confirmation = new_password_confirmation
|
|
150
|
+
if valid?
|
|
151
|
+
self.password_reset_token = nil
|
|
152
|
+
end
|
|
153
|
+
save
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def facebook_user?
|
|
157
|
+
!self.facebook_uid.blank?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
##
|
|
161
|
+
# Returns +true+ if the user is an admin.
|
|
162
|
+
#
|
|
163
|
+
def admin?
|
|
164
|
+
self.role == Admin
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
##
|
|
168
|
+
# Returns the user's full name.
|
|
169
|
+
#
|
|
170
|
+
def name
|
|
171
|
+
"#{self.first_name} #{self.last_name}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
protected
|
|
175
|
+
|
|
176
|
+
def generate_hash(string)
|
|
177
|
+
Digest::SHA1.hexdigest(string)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def initialize_salt
|
|
181
|
+
if new_record?
|
|
182
|
+
self.salt = generate_hash("--#{Time.now.utc}--#{password}--#{rand}--")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def encrypt_password
|
|
187
|
+
return if password.blank?
|
|
188
|
+
self.encrypted_password = encrypt(password)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def encrypt(string)
|
|
192
|
+
generate_hash("--#{salt}--#{string}--")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def generate_confirmation_token
|
|
196
|
+
self.confirmation_token = encrypt("--#{Time.now.utc}--#{password}--#{rand}--")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def generate_password_reset_token
|
|
200
|
+
self.password_reset_token = encrypt("--#{Time.now.utc}--#{password}--#{rand}--")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def generate_remember_token
|
|
204
|
+
self.remember_token = encrypt("--#{Time.now.utc}--#{encrypted_password}--#{id}--#{rand}--")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Always false. Override to allow other forms of authentication
|
|
208
|
+
# (username, facebook, etc).
|
|
209
|
+
# @return [Boolean] true if the email field be left blank for this user
|
|
210
|
+
def email_optional?
|
|
211
|
+
false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# True if the password has been set and the password is not being
|
|
215
|
+
# updated. Override to allow other forms of # authentication (username,
|
|
216
|
+
# facebook, etc).
|
|
217
|
+
# @return [Boolean] true if the password field can be left blank for this user
|
|
218
|
+
def password_optional?
|
|
219
|
+
facebook_user? || (encrypted_password.present? && password.blank?)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def password_required?
|
|
223
|
+
# warn "[DEPRECATION] password_required?: use !password_optional? instead"
|
|
224
|
+
!password_optional?
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def send_welcome_email
|
|
228
|
+
if Headstart.configuration.use_delayed_job
|
|
229
|
+
Delayed::Job.enqueue DeliverWelcomeJob.new(self.id)
|
|
230
|
+
else
|
|
231
|
+
if user = ::User.find_by_id(self.id)
|
|
232
|
+
HeadstartMailer.deliver_welcome(user)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def send_confirmation_email
|
|
238
|
+
HeadstartMailer.deliver_confirmation self
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
module ClassMethods
|
|
244
|
+
# Authenticate with email and password.
|
|
245
|
+
#
|
|
246
|
+
# @param [String, String] email and password
|
|
247
|
+
# @return [User, nil] authenticated user or nil
|
|
248
|
+
# @example
|
|
249
|
+
# User.authenticate("email@example.com", "password")
|
|
250
|
+
def authenticate(email, password)
|
|
251
|
+
return nil unless user = find_by_email(email)
|
|
252
|
+
return user if user.authenticated?(password)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def find_facebook_user(facebook_session, facebook_uid)
|
|
256
|
+
return nil unless Headstart.configuration.use_facebook_connect && facebook_session && facebook_uid
|
|
257
|
+
|
|
258
|
+
begin
|
|
259
|
+
facebook_user = MiniFB::Session.new(Headstart.configuration.facebook_api_key,
|
|
260
|
+
Headstart.configuration.facebook_secret_key,
|
|
261
|
+
facebook_session, facebook_uid).user
|
|
262
|
+
rescue MiniFB::FaceBookError
|
|
263
|
+
facebook_user = nil
|
|
264
|
+
end
|
|
265
|
+
return nil unless facebook_user
|
|
266
|
+
|
|
267
|
+
user = ::User.find_by_facebook_uid(facebook_uid) || ::User.find_by_email(facebook_user['email']) || ::User.new
|
|
268
|
+
user.tap do |user|
|
|
269
|
+
user.facebook_uid = facebook_uid
|
|
270
|
+
user.email = facebook_user['email']
|
|
271
|
+
user.first_name = facebook_user['first_name']
|
|
272
|
+
user.last_name = facebook_user['last_name']
|
|
273
|
+
user.save
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
end
|
|
279
|
+
end
|
data/lib/headstart.rb
ADDED
data/rails/init.rb
ADDED