authkit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/FEATURES.md +73 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +168 -0
- data/Rakefile +60 -0
- data/authkit.gemspec +27 -0
- data/config/database.yml.example +19 -0
- data/lib/authkit.rb +5 -0
- data/lib/authkit/engine.rb +7 -0
- data/lib/authkit/version.rb +3 -0
- data/lib/generators/authkit/USAGE +18 -0
- data/lib/generators/authkit/install_generator.rb +113 -0
- data/lib/generators/authkit/templates/app/controllers/application_controller.rb +94 -0
- data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +25 -0
- data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +29 -0
- data/lib/generators/authkit/templates/app/controllers/password_reset_controller.rb +29 -0
- data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +35 -0
- data/lib/generators/authkit/templates/app/controllers/users_controller.rb +89 -0
- data/lib/generators/authkit/templates/app/models/user.rb +170 -0
- data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +16 -0
- data/lib/generators/authkit/templates/app/views/password_reset/show.html.erb +12 -0
- data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +13 -0
- data/lib/generators/authkit/templates/app/views/users/edit.html.erb +58 -0
- data/lib/generators/authkit/templates/app/views/users/new.html.erb +58 -0
- data/lib/generators/authkit/templates/db/migrate/add_authkit_fields_to_users.rb +110 -0
- data/lib/generators/authkit/templates/db/migrate/create_users.rb +17 -0
- data/lib/generators/authkit/templates/lib/email_format_validator.rb +11 -0
- data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +188 -0
- data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +80 -0
- data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +98 -0
- data/lib/generators/authkit/templates/spec/controllers/password_reset_controller_spec.rb +87 -0
- data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +111 -0
- data/lib/generators/authkit/templates/spec/controllers/users_controller_spec.rb +195 -0
- data/lib/generators/authkit/templates/spec/models/user_spec.rb +268 -0
- data/spec/spec_helper.rb +16 -0
- metadata +165 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
|
4
|
+
module Authkit
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "An auth system for your Rails app"
|
9
|
+
|
10
|
+
def self.source_root
|
11
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_authkit
|
15
|
+
generate_migration("create_users")
|
16
|
+
generate_migration("add_authkit_fields_to_users")
|
17
|
+
|
18
|
+
# Ensure the destination structure
|
19
|
+
empty_directory "app"
|
20
|
+
empty_directory "app/models"
|
21
|
+
empty_directory "app/controllers"
|
22
|
+
empty_directory "app/views"
|
23
|
+
empty_directory "app/views/users"
|
24
|
+
empty_directory "app/views/sessions"
|
25
|
+
empty_directory "app/views/password_reset"
|
26
|
+
empty_directory "app/views/password_change"
|
27
|
+
empty_directory "spec"
|
28
|
+
empty_directory "spec/models"
|
29
|
+
empty_directory "spec/controllers"
|
30
|
+
empty_directory "lib"
|
31
|
+
|
32
|
+
# Fill out some templates (for now, this is just straight copy)
|
33
|
+
template "app/models/user.rb", "app/models/user.rb"
|
34
|
+
template "app/controllers/users_controller.rb", "app/controllers/users_controller.rb"
|
35
|
+
template "app/controllers/sessions_controller.rb", "app/controllers/sessions_controller.rb"
|
36
|
+
template "app/controllers/password_reset_controller.rb", "app/controllers/password_reset_controller.rb"
|
37
|
+
template "app/controllers/password_change_controller.rb", "app/controllers/password_change_controller.rb"
|
38
|
+
template "app/controllers/email_confirmation_controller.rb", "app/controllers/email_confirmation_controller.rb"
|
39
|
+
|
40
|
+
template "spec/models/user_spec.rb", "spec/models/user_spec.rb"
|
41
|
+
template "spec/controllers/application_controller_spec.rb", "spec/controllers/application_controller_spec.rb"
|
42
|
+
template "spec/controllers/users_controller_spec.rb", "spec/controllers/users_controller_spec.rb"
|
43
|
+
template "spec/controllers/sessions_controller_spec.rb", "spec/controllers/sessions_controller_spec.rb"
|
44
|
+
template "spec/controllers/password_reset_controller_spec.rb", "spec/controllers/password_reset_controller_spec.rb"
|
45
|
+
template "spec/controllers/password_change_controller_spec.rb", "spec/controllers/password_change_controller_spec.rb"
|
46
|
+
template "spec/controllers/email_confirmation_controller_spec.rb", "spec/controllers/email_confirmation_controller_spec.rb"
|
47
|
+
|
48
|
+
template "lib/email_format_validator.rb", "lib/email_format_validator.rb"
|
49
|
+
|
50
|
+
# Don't treat these like templates
|
51
|
+
copy_file "app/views/users/new.html.erb", "app/views/users/new.html.erb"
|
52
|
+
copy_file "app/views/users/edit.html.erb", "app/views/users/edit.html.erb"
|
53
|
+
copy_file "app/views/sessions/new.html.erb", "app/views/sessions/new.html.erb"
|
54
|
+
copy_file "app/views/password_reset/show.html.erb", "app/views/password_reset/show.html.erb"
|
55
|
+
copy_file "app/views/password_change/show.html.erb", "app/views/password_change/show.html.erb"
|
56
|
+
|
57
|
+
# We don't want to override this file and may have a protected section
|
58
|
+
insert_at_end_of_class "app/controllers/application_controller.rb", "app/controllers/application_controller.rb"
|
59
|
+
|
60
|
+
# Need a temp root
|
61
|
+
route "root 'welcome#index'"
|
62
|
+
|
63
|
+
# Setup the routes
|
64
|
+
route "get '/email/confirm/:token', to: 'email_confirmation#show', as: :confirm"
|
65
|
+
|
66
|
+
route "post '/password/reset', to: 'password_reset#create'"
|
67
|
+
route "get '/password/reset', to: 'password_reset#show', as: :password_reset"
|
68
|
+
route "post '/password/change/:token', to: 'password_change#create'"
|
69
|
+
route "get '/password/change/:token', to: 'password_change#show', as: :password_change"
|
70
|
+
|
71
|
+
route "get '/signup', to: 'users#new', as: :signup"
|
72
|
+
route "get '/logout', to: 'sessions#destroy', as: :logout"
|
73
|
+
route "get '/login', to: 'sessions#new', as: :login"
|
74
|
+
|
75
|
+
route "put '/account', to: 'users#update'"
|
76
|
+
route "get '/account', to: 'users#edit', as: :user"
|
77
|
+
|
78
|
+
route "resources :sessions, only: [:new, :create, :destroy]"
|
79
|
+
route "resources :users, only: [:new, :create]"
|
80
|
+
|
81
|
+
# Support for has_secure_password and has_one_time_password
|
82
|
+
gem "active_model_otp"
|
83
|
+
gem "bcrypt-ruby", '~> 3.0.0'
|
84
|
+
|
85
|
+
# RSpec needs to be in the development group to be used in generators
|
86
|
+
gem_group :test, :development do
|
87
|
+
gem "rspec-rails"
|
88
|
+
gem "shoulda-matchers"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.next_migration_number(dirname)
|
93
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def insert_at_end_of_class(filename, source)
|
99
|
+
source = File.expand_path(find_in_source_paths(source.to_s))
|
100
|
+
context = instance_eval('binding')
|
101
|
+
content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
|
102
|
+
insert_into_file "app/controllers/application_controller.rb", "#{content}\n", before: /end\n*\z/
|
103
|
+
end
|
104
|
+
|
105
|
+
def generate_migration(filename)
|
106
|
+
if self.class.migration_exists?("db/migrate", "#{filename}")
|
107
|
+
say_status "skipped", "Migration #{filename}.rb already exists"
|
108
|
+
else
|
109
|
+
migration_template "db/migrate/#{filename}.rb", "db/migrate/#{filename}.rb"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
before_filter :set_time_zone
|
3
|
+
|
4
|
+
helper_method :logged_in?, :current_user
|
5
|
+
|
6
|
+
# It is very unlikely that this exception will be created under normal
|
7
|
+
# circumstances. Unique validations are handled in Rails, but they are
|
8
|
+
# also enforced at the database level to guarantee data integrity. In
|
9
|
+
# certain cases (double-clicking a save link, multiple distributed servers)
|
10
|
+
# it is possible to get past the Rails validation in which case the
|
11
|
+
# database throws an exception.
|
12
|
+
rescue_from ActiveRecord::RecordNotUnique, with: :record_not_unique
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def current_user
|
17
|
+
return @current_user if defined?(@current_user)
|
18
|
+
@current_user ||= User.find_by(session[:user_id]) if session[:user_id]
|
19
|
+
@current_user ||= User.user_from_remember_token(cookies.signed[:remember]) unless cookies.signed[:remember].blank?
|
20
|
+
session[:user_id] = @current_user.id if @current_user
|
21
|
+
session[:time_zone] = @current_user.time_zone if @current_user
|
22
|
+
set_time_zone
|
23
|
+
|
24
|
+
@current_user
|
25
|
+
end
|
26
|
+
|
27
|
+
def allow_tracking?
|
28
|
+
"#{request.headers['X-Do-Not-Track']}" != '1' && "#{request.headers['DNT']}" != '1'
|
29
|
+
end
|
30
|
+
|
31
|
+
def logged_in?
|
32
|
+
!!current_user
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_login
|
36
|
+
deny_user(nil, login_path) unless logged_in?
|
37
|
+
end
|
38
|
+
|
39
|
+
def require_token
|
40
|
+
deny_user("Invalid token", root_path) unless @user = User.user_from_token(params[:token])
|
41
|
+
end
|
42
|
+
|
43
|
+
def login(user)
|
44
|
+
@current_user = user
|
45
|
+
current_user.track_sign_in(request.remote_ip) if allow_tracking?
|
46
|
+
current_user.set_token(:remember_token)
|
47
|
+
set_remember_cookie
|
48
|
+
reset_session
|
49
|
+
session[:user_id] = current_user.id
|
50
|
+
session[:time_zone] = current_user.time_zone
|
51
|
+
set_time_zone
|
52
|
+
current_user
|
53
|
+
end
|
54
|
+
|
55
|
+
def logout
|
56
|
+
current_user.clear_remember_token if current_user
|
57
|
+
cookies.delete(:remember)
|
58
|
+
reset_session
|
59
|
+
@current_user = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_time_zone
|
63
|
+
Time.zone = session[:time_zone] if session[:time_zone].present?
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_remember_cookie
|
67
|
+
cookies.permanent.signed[:remember] = {
|
68
|
+
value: current_user.remember_token,
|
69
|
+
secure: Rails.env.production?
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def redirect_back_or_default
|
74
|
+
redirect_to(session.delete(:return_url) || root_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def deny_user(message=nil, location=nil)
|
78
|
+
location ||= (logged_in? ? root_path : login_path)
|
79
|
+
|
80
|
+
session[:return_url] = request.fullpath
|
81
|
+
respond_to do |format|
|
82
|
+
format.json { render(status: 403, nothing: true) }
|
83
|
+
format.html do
|
84
|
+
flash[:error] = message || "Sorry, you must be logged in to do that"
|
85
|
+
redirect_to(location)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
def record_not_unique
|
93
|
+
respond_with(nil, location: root_path, status: 422)
|
94
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class EmailConfirmationController < ApplicationController
|
2
|
+
before_filter :require_token
|
3
|
+
|
4
|
+
respond_to :html
|
5
|
+
|
6
|
+
def show
|
7
|
+
if @user.email_confirmed
|
8
|
+
login(@user)
|
9
|
+
flash[:notice] = "Thanks for confirming your email address"
|
10
|
+
respond_to do |format|
|
11
|
+
format.json { head :no_content }
|
12
|
+
format.html { redirect_to root_path }
|
13
|
+
end
|
14
|
+
else
|
15
|
+
respond_to do |format|
|
16
|
+
format.json { render json: { status: 'error', errors: @user.errors }.to_json, status: 422 }
|
17
|
+
format.html {
|
18
|
+
flash[:error] = "Could not confirm email address because it is already in use"
|
19
|
+
redirect_to root_path
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class PasswordChangeController < ApplicationController
|
2
|
+
before_filter :require_token
|
3
|
+
|
4
|
+
def show
|
5
|
+
respond_to do |format|
|
6
|
+
format.json { head :no_content }
|
7
|
+
format.html
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
if @user.change_password(params[:password], params[:password_confirmation])
|
13
|
+
login(@user)
|
14
|
+
|
15
|
+
respond_to do |format|
|
16
|
+
format.json { head :no_content }
|
17
|
+
format.html {
|
18
|
+
flash.now[:notice] = "Password updated successfully"
|
19
|
+
redirect_to(root_path)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
else
|
23
|
+
respond_to do |format|
|
24
|
+
format.json { render json: { status: 'error', errors: @user.errors }.to_json, status: 422 }
|
25
|
+
format.html { render :show }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class PasswordResetController < ApplicationController
|
2
|
+
def show
|
3
|
+
end
|
4
|
+
|
5
|
+
def create
|
6
|
+
username_or_email = "#{params[:email]}".downcase
|
7
|
+
user = User.find_by_username_or_email(username_or_email) if username_or_email.present?
|
8
|
+
|
9
|
+
if user && user.send_reset_password
|
10
|
+
logout
|
11
|
+
|
12
|
+
respond_to do |format|
|
13
|
+
format.json { head :no_content }
|
14
|
+
format.html {
|
15
|
+
flash[:notice] = "We've sent an email which can be used to change your password"
|
16
|
+
redirect_to login_path
|
17
|
+
}
|
18
|
+
end
|
19
|
+
else
|
20
|
+
respond_to do |format|
|
21
|
+
format.json { render json: { errors: ["Invalid user name or email"], status: "error" }, status: 422 }
|
22
|
+
format.html {
|
23
|
+
flash.now[:error] = "Invalid user name or email"
|
24
|
+
render :show
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class SessionsController < ApplicationController
|
2
|
+
# Login
|
3
|
+
def new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
username_or_email = "#{params[:email]}".downcase
|
8
|
+
user = User.find_by_username_or_email(username_or_email) if username_or_email.present?
|
9
|
+
|
10
|
+
if user && user.authenticate(params[:password])
|
11
|
+
login(user)
|
12
|
+
respond_to do |format|
|
13
|
+
format.json { head :no_content }
|
14
|
+
format.html { redirect_back_or_default }
|
15
|
+
end
|
16
|
+
else
|
17
|
+
respond_to do |format|
|
18
|
+
format.json { render json: { errors: ["Invalid user name or password"], status: "error" }, status: 422 }
|
19
|
+
format.html {
|
20
|
+
flash.now[:error] = "Invalid user name or password"
|
21
|
+
render :new
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Logout
|
28
|
+
def destroy
|
29
|
+
logout
|
30
|
+
respond_to do |format|
|
31
|
+
format.json { head :no_content }
|
32
|
+
format.html { redirect_to root_path }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class UsersController < ApplicationController
|
2
|
+
before_filter :require_login, only: [:edit, :update]
|
3
|
+
|
4
|
+
respond_to :html, :json
|
5
|
+
|
6
|
+
# Signup
|
7
|
+
def new
|
8
|
+
@user = User.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
@user = User.new(user_create_params)
|
13
|
+
if @user.save
|
14
|
+
@user.send_confirmation
|
15
|
+
login(@user)
|
16
|
+
respond_to do |format|
|
17
|
+
format.json { head :no_content }
|
18
|
+
format.html { redirect_to root_path }
|
19
|
+
end
|
20
|
+
else
|
21
|
+
respond_to do |format|
|
22
|
+
format.json { render json: { status: 'error', errors: @user.errors }.to_json, status: 422 }
|
23
|
+
format.html { render :new }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def edit
|
29
|
+
@user = current_user
|
30
|
+
end
|
31
|
+
|
32
|
+
def update
|
33
|
+
@user = current_user
|
34
|
+
|
35
|
+
orig_confirmation_email = @user.confirmation_email
|
36
|
+
|
37
|
+
if @user.update_attributes(user_update_params)
|
38
|
+
# Send a new email confirmation if the user updated their email address
|
39
|
+
if @user.confirmation_email.present? &&
|
40
|
+
@user.confirmation_email != @user.email &&
|
41
|
+
@user.confirmation_email != orig_confirmation_email
|
42
|
+
@user.send_confirmation
|
43
|
+
end
|
44
|
+
respond_to do |format|
|
45
|
+
format.json { head :no_content }
|
46
|
+
format.html { redirect_to @user }
|
47
|
+
end
|
48
|
+
else
|
49
|
+
respond_to do |format|
|
50
|
+
format.json { render json: { status: 'error', errors: @user.errors }.to_json, status: 422 }
|
51
|
+
format.html { render :edit }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# It would be nice to find a strategy to merge these. The only difference is that
|
59
|
+
# when signing up you are setting the email, and when changing your settings you
|
60
|
+
# are setting the confirmation email.
|
61
|
+
|
62
|
+
def user_create_params
|
63
|
+
params.require(:user).permit(
|
64
|
+
:email,
|
65
|
+
:username,
|
66
|
+
:password,
|
67
|
+
:password_confirmation,
|
68
|
+
:first_name,
|
69
|
+
:last_name,
|
70
|
+
:bio,
|
71
|
+
:website,
|
72
|
+
:phone_number,
|
73
|
+
:time_zone)
|
74
|
+
end
|
75
|
+
|
76
|
+
def user_update_params
|
77
|
+
params.require(:user).permit(
|
78
|
+
:confirmation_email,
|
79
|
+
:username,
|
80
|
+
:password,
|
81
|
+
:password_confirmation,
|
82
|
+
:first_name,
|
83
|
+
:last_name,
|
84
|
+
:bio,
|
85
|
+
:website,
|
86
|
+
:phone_number,
|
87
|
+
:time_zone)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'email_format_validator'
|
2
|
+
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
has_secure_password
|
5
|
+
has_one_time_password
|
6
|
+
|
7
|
+
# Uncomment if you are not using strong params (note, that email is only permitted on
|
8
|
+
# signup and confirmation_email is only permitted on update):
|
9
|
+
#
|
10
|
+
# attr_accessible :username,
|
11
|
+
# :email,
|
12
|
+
# :confirmation_email,
|
13
|
+
# :password,
|
14
|
+
# :password_confirmation,
|
15
|
+
# :time_zone,
|
16
|
+
# :first_name,
|
17
|
+
# :last_name,
|
18
|
+
# :bio,
|
19
|
+
# :website,
|
20
|
+
# :phone_number
|
21
|
+
|
22
|
+
before_validation :downcase_email
|
23
|
+
before_validation :set_confirmation_email
|
24
|
+
|
25
|
+
# Whenever the password is set, validate (not only on create)
|
26
|
+
validates :password, presence: true, confirmation: true, length: {minimum: 6}, if: :password_set?
|
27
|
+
validates :username, presence: true, uniqueness: {case_sensitive: false}
|
28
|
+
validates :email, email_format: true, presence: true, uniqueness: true
|
29
|
+
validates :confirmation_email, email_format: true, presence: true
|
30
|
+
|
31
|
+
# Confirm emails check for existing emails for uniqueness as a convenience
|
32
|
+
validate :confirmation_email_uniqueness, if: :confirmation_email_set?
|
33
|
+
|
34
|
+
def self.user_from_token(token)
|
35
|
+
verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_token)
|
36
|
+
id = verifier.verify(token)
|
37
|
+
User.find_by_id(id)
|
38
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# The tokens created by this method have unique indexes but they are digests of the
|
43
|
+
# id which is unique. Because of this we shouldn't see a conflict. If we do, however
|
44
|
+
# we want the ActiveRecord::StatementInvalid or ActiveRecord::RecordNotUnique exeception
|
45
|
+
# to bubble up.
|
46
|
+
def set_token(field)
|
47
|
+
return unless self.persisted?
|
48
|
+
verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_token)
|
49
|
+
self.send("#{field}_created_at=", Time.now)
|
50
|
+
self.send("#{field}=", verifier.generate(self.id))
|
51
|
+
self.save
|
52
|
+
end
|
53
|
+
|
54
|
+
# These methods are a little redundant, but give you the opportunity to
|
55
|
+
# insert expiry for any of these token based authentication strategies.
|
56
|
+
# For example:
|
57
|
+
#
|
58
|
+
# def self.user_from_remember_token(token)
|
59
|
+
# user = user_from_token(token)
|
60
|
+
# user = nil if user && user.remember_token_created_at < 30.days.ago
|
61
|
+
# user
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
def self.user_from_remember_token(token)
|
65
|
+
user_from_token(token)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.user_from_reset_password_token(token)
|
69
|
+
user_from_token(token)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.user_from_confirmation_token(token)
|
73
|
+
user_from_token(token)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.user_from_unlock_token(token)
|
77
|
+
user_from_token(token)
|
78
|
+
end
|
79
|
+
|
80
|
+
def display_name
|
81
|
+
[first_name, last_name].compact.join(" ")
|
82
|
+
end
|
83
|
+
|
84
|
+
def track_sign_in(ip)
|
85
|
+
self.sign_in_count += 1
|
86
|
+
self.last_sign_in_at = self.current_sign_in_at
|
87
|
+
self.last_sign_in_ip = self.current_sign_in_ip
|
88
|
+
self.current_sign_in_at = Time.now
|
89
|
+
self.current_sign_in_ip = ip
|
90
|
+
self.save
|
91
|
+
end
|
92
|
+
|
93
|
+
def clear_remember_token
|
94
|
+
self.remember_token = nil
|
95
|
+
self.remember_token_created_at = nil
|
96
|
+
self.save
|
97
|
+
end
|
98
|
+
|
99
|
+
def send_reset_password
|
100
|
+
return false unless set_token(:reset_password_token)
|
101
|
+
|
102
|
+
# TODO: insert your mailer logic here
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def send_confirmation
|
107
|
+
return false unless set_token(:confirmation_token)
|
108
|
+
|
109
|
+
# TODO: insert your mailer logic here
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
def email_confirmed
|
114
|
+
return false if self.confirmation_token.blank? || self.confirmation_email.blank?
|
115
|
+
|
116
|
+
self.email = self.confirmation_email
|
117
|
+
|
118
|
+
# Don't nil out the token unless the changes are valid as it may be
|
119
|
+
# needed again (when re-rendering the form, for instance)
|
120
|
+
if valid?
|
121
|
+
self.confirmation_token = nil
|
122
|
+
self.confirmation_token_created_at = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
self.save
|
126
|
+
end
|
127
|
+
|
128
|
+
def change_password(password, password_confirmation)
|
129
|
+
self.password = password
|
130
|
+
self.password_confirmation = password_confirmation
|
131
|
+
|
132
|
+
# Don't nil out the token unless the changes are valid as it may be
|
133
|
+
# needed again (when re-rendering the form, for instance)
|
134
|
+
if valid?
|
135
|
+
self.reset_password_token = nil
|
136
|
+
self.reset_password_token_created_at = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
self.save
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def password_set?
|
145
|
+
self.password.present?
|
146
|
+
end
|
147
|
+
|
148
|
+
def downcase_email
|
149
|
+
self.email = self.email.downcase if self.email
|
150
|
+
end
|
151
|
+
|
152
|
+
def set_confirmation_email
|
153
|
+
self.confirmation_email = self.email if self.confirmation_email.blank?
|
154
|
+
end
|
155
|
+
|
156
|
+
def confirmation_email_set?
|
157
|
+
confirmation_email.present? && confirmation_email_changed? && confirmation_email != email
|
158
|
+
end
|
159
|
+
|
160
|
+
# It is possible that a user will change their email, not confirm, and then
|
161
|
+
# sign up for the service again using the same email. If they later go to confirm
|
162
|
+
# the email change on the first account it will fail because the email will be
|
163
|
+
# used by the new signup. Though this is problematic it avoids the larger problem of
|
164
|
+
# users blocking new user signups by changing their email address to something they
|
165
|
+
# don't control. This check is just for convenience and does not need to
|
166
|
+
# guarantee uniqueness.
|
167
|
+
def confirmation_email_uniqueness
|
168
|
+
errors.add(:confirmation_email, :taken, value: email) if User.where('email = ?', confirmation_email).count > 0
|
169
|
+
end
|
170
|
+
end
|