shingara-devise 0.4.3
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/CHANGELOG.rdoc +119 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +253 -0
- data/Rakefile +45 -0
- data/TODO +5 -0
- data/app/controllers/confirmations_controller.rb +33 -0
- data/app/controllers/passwords_controller.rb +41 -0
- data/app/controllers/sessions_controller.rb +33 -0
- data/app/models/devise_mailer.rb +53 -0
- data/app/views/confirmations/new.html.erb +16 -0
- data/app/views/devise_mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise_mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/passwords/edit.html.erb +20 -0
- data/app/views/passwords/new.html.erb +16 -0
- data/app/views/sessions/new.html.erb +23 -0
- data/generators/devise/USAGE +5 -0
- data/generators/devise/devise_generator.rb +25 -0
- data/generators/devise/lib/route_devise.rb +32 -0
- data/generators/devise/templates/README +22 -0
- data/generators/devise/templates/migration.rb +20 -0
- data/generators/devise/templates/model.rb +5 -0
- data/generators/devise_install/USAGE +3 -0
- data/generators/devise_install/devise_install_generator.rb +9 -0
- data/generators/devise_install/templates/devise.rb +47 -0
- data/generators/devise_views/USAGE +3 -0
- data/generators/devise_views/devise_views_generator.rb +24 -0
- data/init.rb +2 -0
- data/lib/devise/controllers/filters.rb +111 -0
- data/lib/devise/controllers/helpers.rb +130 -0
- data/lib/devise/controllers/url_helpers.rb +49 -0
- data/lib/devise/encryptors/authlogic_sha512.rb +28 -0
- data/lib/devise/encryptors/clearance_sha1.rb +26 -0
- data/lib/devise/encryptors/restful_authentication_sha1.rb +29 -0
- data/lib/devise/encryptors/sha1.rb +34 -0
- data/lib/devise/encryptors/sha512.rb +34 -0
- data/lib/devise/failure.rb +36 -0
- data/lib/devise/hooks/confirmable.rb +11 -0
- data/lib/devise/hooks/rememberable.rb +27 -0
- data/lib/devise/locales/en.yml +18 -0
- data/lib/devise/mapping.rb +120 -0
- data/lib/devise/migrations.rb +57 -0
- data/lib/devise/models/authenticatable.rb +87 -0
- data/lib/devise/models/confirmable.rb +156 -0
- data/lib/devise/models/recoverable.rb +88 -0
- data/lib/devise/models/rememberable.rb +95 -0
- data/lib/devise/models/validatable.rb +36 -0
- data/lib/devise/models.rb +110 -0
- data/lib/devise/orm/mongo_mapper.rb +26 -0
- data/lib/devise/rails/routes.rb +109 -0
- data/lib/devise/rails/warden_compat.rb +26 -0
- data/lib/devise/rails.rb +17 -0
- data/lib/devise/strategies/authenticatable.rb +46 -0
- data/lib/devise/strategies/base.rb +24 -0
- data/lib/devise/strategies/rememberable.rb +35 -0
- data/lib/devise/version.rb +3 -0
- data/lib/devise/warden.rb +20 -0
- data/lib/devise.rb +130 -0
- data/test/controllers/filters_test.rb +103 -0
- data/test/controllers/helpers_test.rb +55 -0
- data/test/controllers/url_helpers_test.rb +47 -0
- data/test/devise_test.rb +72 -0
- data/test/encryptors_test.rb +28 -0
- data/test/failure_test.rb +34 -0
- data/test/integration/authenticatable_test.rb +195 -0
- data/test/integration/confirmable_test.rb +89 -0
- data/test/integration/recoverable_test.rb +131 -0
- data/test/integration/rememberable_test.rb +65 -0
- data/test/mailers/confirmation_instructions_test.rb +59 -0
- data/test/mailers/reset_password_instructions_test.rb +62 -0
- data/test/mapping_test.rb +101 -0
- data/test/models/authenticatable_test.rb +130 -0
- data/test/models/confirmable_test.rb +237 -0
- data/test/models/recoverable_test.rb +141 -0
- data/test/models/rememberable_test.rb +130 -0
- data/test/models/validatable_test.rb +99 -0
- data/test/models_test.rb +111 -0
- data/test/rails_app/app/controllers/admins_controller.rb +6 -0
- data/test/rails_app/app/controllers/application_controller.rb +10 -0
- data/test/rails_app/app/controllers/home_controller.rb +4 -0
- data/test/rails_app/app/controllers/users_controller.rb +7 -0
- data/test/rails_app/app/helpers/application_helper.rb +3 -0
- data/test/rails_app/app/models/account.rb +3 -0
- data/test/rails_app/app/models/admin.rb +3 -0
- data/test/rails_app/app/models/organizer.rb +3 -0
- data/test/rails_app/app/models/user.rb +3 -0
- data/test/rails_app/config/boot.rb +110 -0
- data/test/rails_app/config/environment.rb +41 -0
- data/test/rails_app/config/environments/development.rb +17 -0
- data/test/rails_app/config/environments/production.rb +28 -0
- data/test/rails_app/config/environments/test.rb +28 -0
- data/test/rails_app/config/initializers/new_rails_defaults.rb +21 -0
- data/test/rails_app/config/initializers/session_store.rb +15 -0
- data/test/rails_app/config/routes.rb +18 -0
- data/test/routes_test.rb +79 -0
- data/test/support/assertions_helper.rb +22 -0
- data/test/support/integration_tests_helper.rb +66 -0
- data/test/support/model_tests_helper.rb +51 -0
- data/test/test_helper.rb +40 -0
- metadata +161 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require "digest/sha2"
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
# Implements a way of adding different encryptions.
|
5
|
+
# The class should implement a self.digest method that taks the following params:
|
6
|
+
# - password
|
7
|
+
# - stretches: the number of times the encryption will be applied
|
8
|
+
# - salt: the password salt as defined by devise
|
9
|
+
# - pepper: Devise config option
|
10
|
+
#
|
11
|
+
module Encryptors
|
12
|
+
# = Sha512
|
13
|
+
# Uses the Sha512 hash algorithm to encrypt passwords.
|
14
|
+
class Sha512
|
15
|
+
|
16
|
+
# Gererates a default password digest based on salt, pepper and the
|
17
|
+
# incoming password.
|
18
|
+
def self.digest(password, stretches, salt, pepper)
|
19
|
+
digest = pepper
|
20
|
+
stretches.times { digest = self.secure_digest(salt, digest, password, pepper) }
|
21
|
+
digest
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Generate a Sha512 digest joining args. Generated token is something like
|
27
|
+
# --arg1--arg2--arg3--argN--
|
28
|
+
def self.secure_digest(*tokens)
|
29
|
+
::Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Devise
|
2
|
+
module Failure
|
3
|
+
mattr_accessor :default_url
|
4
|
+
|
5
|
+
# Failure application that will be called every time :warden is thrown from
|
6
|
+
# any strategy or hook. Responsible for redirect the user to the sign in
|
7
|
+
# page based on current scope and mapping. If no scope is given, redirect
|
8
|
+
# to the default_url.
|
9
|
+
def self.call(env)
|
10
|
+
options = env['warden.options']
|
11
|
+
scope = options[:scope]
|
12
|
+
params = case env['warden'].try(:message)
|
13
|
+
when Symbol
|
14
|
+
{ env['warden'].message => true }
|
15
|
+
when String
|
16
|
+
{ :message => env['warden'].message }
|
17
|
+
else
|
18
|
+
options[:params]
|
19
|
+
end
|
20
|
+
|
21
|
+
redirect_path = if mapping = Devise.mappings[scope]
|
22
|
+
"#{mapping.parsed_path}/#{mapping.path_names[:sign_in]}"
|
23
|
+
else
|
24
|
+
"/#{default_url}"
|
25
|
+
end
|
26
|
+
|
27
|
+
headers = {}
|
28
|
+
headers["Location"] = redirect_path
|
29
|
+
headers["Location"] << "?" << Rack::Utils.build_query(params) if params
|
30
|
+
headers["Content-Type"] = 'text/plain'
|
31
|
+
|
32
|
+
message = options[:message] || "You are being redirected to #{redirect_path}"
|
33
|
+
[302, headers, [message]]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Each time the user is set we verify if it is still able to really sign in.
|
2
|
+
# This is done by checking the time frame the user is able to sign in without
|
3
|
+
# confirming it's account. If the user has not confirmed it's account during
|
4
|
+
# this time frame, he/she will not able to sign in anymore.
|
5
|
+
Warden::Manager.after_set_user do |record, auth, options|
|
6
|
+
if record && record.respond_to?(:active?) && !record.active?
|
7
|
+
scope = options[:scope]
|
8
|
+
auth.logout(scope)
|
9
|
+
throw :warden, :scope => scope, :params => { :unconfirmed => true }
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# After authenticate hook to verify if the user in the given scope asked to be
|
2
|
+
# remembered while he does not sign out. Generates a new remember token for
|
3
|
+
# that specific user and adds a cookie with this user info to sign in this user
|
4
|
+
# automatically without asking for credentials. Refer to rememberable strategy
|
5
|
+
# for more info.
|
6
|
+
Warden::Manager.after_authentication do |record, auth, options|
|
7
|
+
scope = options[:scope]
|
8
|
+
remember_me = auth.params[scope].try(:fetch, :remember_me, nil)
|
9
|
+
|
10
|
+
if Devise::TRUE_VALUES.include?(remember_me) && record.respond_to?(:remember_me!)
|
11
|
+
record.remember_me!
|
12
|
+
auth.cookies['remember_token'] = {
|
13
|
+
:value => record.class.serialize_into_cookie(record),
|
14
|
+
:expires => record.remember_expires_at
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Before logout hook to forget the user in the given scope, only if rememberable
|
20
|
+
# is activated for this scope. Also clear remember token to ensure the user
|
21
|
+
# won't be remembered again.
|
22
|
+
Warden::Manager.before_logout do |record, auth, scope|
|
23
|
+
if record.respond_to?(:forget_me!)
|
24
|
+
record.forget_me!
|
25
|
+
auth.cookies.delete('remember_token')
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
en:
|
2
|
+
devise:
|
3
|
+
sessions:
|
4
|
+
signed_in: 'Signed in successfully.'
|
5
|
+
signed_out: 'Signed out successfully.'
|
6
|
+
unauthenticated: 'You need to sign in or sign up before continuing.'
|
7
|
+
unconfirmed: 'You have to confirm your account before continuing.'
|
8
|
+
invalid: 'Invalid email or password.'
|
9
|
+
passwords:
|
10
|
+
send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
|
11
|
+
updated: 'Your password was changed successfully. You are now signed in.'
|
12
|
+
confirmations:
|
13
|
+
send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
|
14
|
+
confirmed: 'Your account was successfully confirmed. You are now signed in.'
|
15
|
+
mailer:
|
16
|
+
confirmation_instructions: 'Confirmation instructions'
|
17
|
+
reset_password_instructions: 'Reset password instructions'
|
18
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Devise
|
2
|
+
# Responsible for handling devise mappings and routes configuration. Each
|
3
|
+
# resource configured by devise_for in routes is actually creating a mapping
|
4
|
+
# object. You can refer to devise_for in routes for usage options.
|
5
|
+
#
|
6
|
+
# The required value in devise_for is actually not used internally, but it's
|
7
|
+
# inflected to find all other values.
|
8
|
+
#
|
9
|
+
# map.devise_for :users
|
10
|
+
# mapping = Devise.mappings[:user]
|
11
|
+
#
|
12
|
+
# mapping.name #=> :user
|
13
|
+
# # is the scope used in controllers and warden, given in the route as :singular.
|
14
|
+
#
|
15
|
+
# mapping.as #=> "users"
|
16
|
+
# # how the mapping should be search in the path, given in the route as :as.
|
17
|
+
#
|
18
|
+
# mapping.to #=> User
|
19
|
+
# # is the class to be loaded from routes, given in the route as :class_name.
|
20
|
+
#
|
21
|
+
# mapping.for #=> [:authenticatable]
|
22
|
+
# # is the modules included in the class
|
23
|
+
#
|
24
|
+
class Mapping #:nodoc:
|
25
|
+
attr_reader :name, :as, :path_names, :path_prefix
|
26
|
+
|
27
|
+
# Loop through all mappings looking for a map that matches with the requested
|
28
|
+
# path (ie /users/sign_in). If a path prefix is given, it's taken into account.
|
29
|
+
def self.find_by_path(path)
|
30
|
+
Devise.mappings.each_value do |mapping|
|
31
|
+
route = path.split("/")[mapping.as_position]
|
32
|
+
return mapping if mapping.as == route.to_sym
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# Default url options which can be used as prefix.
|
38
|
+
def self.default_url_options
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(name, options) #:nodoc:
|
43
|
+
options.assert_valid_keys(:class_name, :as, :path_names, :singular, :path_prefix)
|
44
|
+
|
45
|
+
@as = (options[:as] || name).to_sym
|
46
|
+
@klass = (options[:class_name] || name.to_s.classify).to_s
|
47
|
+
@name = (options[:singular] || name.to_s.singularize).to_sym
|
48
|
+
@path_names = options[:path_names] || {}
|
49
|
+
@path_prefix = options[:path_prefix] || ""
|
50
|
+
@path_prefix << "/" unless @path_prefix[-1] == ?/
|
51
|
+
|
52
|
+
setup_path_names
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return modules for the mapping.
|
56
|
+
def for
|
57
|
+
@for ||= to.devise_modules
|
58
|
+
end
|
59
|
+
|
60
|
+
# Reload mapped class each time when cache_classes is false.
|
61
|
+
def to
|
62
|
+
return @to if @to
|
63
|
+
klass = @klass.constantize
|
64
|
+
@to = klass if Rails.configuration.cache_classes
|
65
|
+
klass
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check if the respective controller has a module in the mapping class.
|
69
|
+
def allows?(controller)
|
70
|
+
self.for.include?(CONTROLLERS[controller.to_sym])
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return in which position in the path prefix devise should find the as mapping.
|
74
|
+
def as_position
|
75
|
+
self.path_prefix.count("/")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the raw path using path_prefix and as.
|
79
|
+
def raw_path
|
80
|
+
path_prefix + as.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the parsed path. If you need meta information in your path_prefix,
|
84
|
+
# you should overwrite this method to use it. The only information supported
|
85
|
+
# by default is I18n.locale.
|
86
|
+
#
|
87
|
+
def parsed_path
|
88
|
+
returning raw_path do |path|
|
89
|
+
self.class.default_url_options.each do |key, value|
|
90
|
+
path.gsub!(key.inspect, value.to_s)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Create magic predicates for verifying what module is activated by this map.
|
96
|
+
# Example:
|
97
|
+
#
|
98
|
+
# def confirmable?
|
99
|
+
# self.for.include?(:confirmable)
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
ALL.each do |m|
|
103
|
+
class_eval <<-METHOD, __FILE__, __LINE__
|
104
|
+
def #{m}?
|
105
|
+
self.for.include?(:#{m})
|
106
|
+
end
|
107
|
+
METHOD
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Configure default path names, allowing the user overwrite defaults by
|
113
|
+
# passing a hash in :path_names.
|
114
|
+
def setup_path_names
|
115
|
+
[:sign_in, :sign_out, :password, :confirmation].each do |path_name|
|
116
|
+
@path_names[path_name] ||= path_name.to_s
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Devise
|
2
|
+
# Helpers to migration:
|
3
|
+
#
|
4
|
+
# create_table :accounts do |t|
|
5
|
+
# t.authenticatable
|
6
|
+
# t.confirmable
|
7
|
+
# t.recoverable
|
8
|
+
# t.rememberable
|
9
|
+
# t.timestamps
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# However this method does not add indexes. If you need them, here is the declaration:
|
13
|
+
#
|
14
|
+
# add_index "accounts", ["email"], :name => "email", :unique => true
|
15
|
+
# add_index "accounts", ["confirmation_token"], :name => "confirmation_token", :unique => true
|
16
|
+
# add_index "accounts", ["reset_password_token"], :name => "reset_password_token", :unique => true
|
17
|
+
#
|
18
|
+
module Migrations
|
19
|
+
|
20
|
+
# Creates email, encrypted_password and password_salt.
|
21
|
+
#
|
22
|
+
# == Options
|
23
|
+
# * :null when true, allow columns to be null
|
24
|
+
# * :encryptor The encryptor going to be used, necessary for setting the proper encrypter password length
|
25
|
+
#
|
26
|
+
def authenticatable(options={})
|
27
|
+
null = options[:null] || false
|
28
|
+
encryptor = options[:encryptor] || :sha1
|
29
|
+
|
30
|
+
string :email, :null => null, :limit => 100
|
31
|
+
string :encrypted_password, :null => null, :limit => Devise::ENCRYPTORS_LENGTH[encryptor]
|
32
|
+
string :password_salt, :null => null, :limit => 20
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates confirmation_token, confirmed_at and confirmation_sent_at.
|
36
|
+
#
|
37
|
+
def confirmable
|
38
|
+
string :confirmation_token, :limit => 20
|
39
|
+
datetime :confirmed_at
|
40
|
+
datetime :confirmation_sent_at
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates reset_password_token.
|
44
|
+
#
|
45
|
+
def recoverable
|
46
|
+
string :reset_password_token, :limit => 20
|
47
|
+
end
|
48
|
+
|
49
|
+
# Creates remember_token and remember_created_at.
|
50
|
+
#
|
51
|
+
def rememberable
|
52
|
+
string :remember_token, :limit => 20
|
53
|
+
datetime :remember_created_at
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'devise/strategies/authenticatable'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Authenticable Module, responsible for encrypting password and validating
|
7
|
+
# authenticity of a user while signing in.
|
8
|
+
#
|
9
|
+
# Configuration:
|
10
|
+
#
|
11
|
+
# You can overwrite configuration values by setting in globally in Devise,
|
12
|
+
# using devise method or overwriting the respective instance method.
|
13
|
+
#
|
14
|
+
# pepper: encryption key used for creating encrypted password. Each time
|
15
|
+
# password changes, it's gonna be encrypted again, and this key
|
16
|
+
# is added to the password and salt to create a secure hash.
|
17
|
+
# Always use `rake secret' to generate a new key.
|
18
|
+
#
|
19
|
+
# stretches: defines how many times the password will be encrypted.
|
20
|
+
#
|
21
|
+
# Examples:
|
22
|
+
#
|
23
|
+
# User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
|
24
|
+
# User.find(1).valid_password?('password123') # returns true/false
|
25
|
+
#
|
26
|
+
module Authenticatable
|
27
|
+
def self.included(base)
|
28
|
+
base.class_eval do
|
29
|
+
extend ClassMethods
|
30
|
+
|
31
|
+
attr_reader :password
|
32
|
+
attr_accessor :password_confirmation
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Regenerates password salt and encrypted password each time password is
|
37
|
+
# setted.
|
38
|
+
def password=(new_password)
|
39
|
+
@password = new_password
|
40
|
+
self.password_salt = friendly_token
|
41
|
+
self.encrypted_password = password_digest(@password)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Verifies whether an incoming_password (ie from login) is the user
|
45
|
+
# password.
|
46
|
+
def valid_password?(incoming_password)
|
47
|
+
password_digest(incoming_password) == encrypted_password
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
# Digests the password using the configured encryptor
|
53
|
+
def password_digest(password)
|
54
|
+
encryptor.digest(password, stretches, password_salt, pepper)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Generate a friendly string randomically to be used as token.
|
58
|
+
def friendly_token
|
59
|
+
ActiveSupport::SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
module ClassMethods
|
63
|
+
# Authenticate a user based on email and password. Returns the
|
64
|
+
# authenticated user if it's valid or nil.
|
65
|
+
# Attributes are :email and :password
|
66
|
+
def authenticate(attributes={})
|
67
|
+
authenticatable = find_by_email(attributes[:email])
|
68
|
+
authenticatable if authenticatable.try(:valid_password?, attributes[:password])
|
69
|
+
end
|
70
|
+
|
71
|
+
# Attempt to find a user by it's email. If not user is found, returns a
|
72
|
+
# new user with an email not found error.
|
73
|
+
def find_or_initialize_with_error_by_email(email)
|
74
|
+
perishable = find_or_initialize_by_email(email)
|
75
|
+
if perishable.new_record?
|
76
|
+
perishable.errors.add(:email, :not_found, :default => 'not found')
|
77
|
+
end
|
78
|
+
perishable
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Devise::Models.config(self, :pepper)
|
83
|
+
Devise::Models.config(self, :stretches)
|
84
|
+
Devise::Models.config(self, :encryptor)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'devise/hooks/confirmable'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
|
6
|
+
# Confirmable is responsible to verify if an account is already confirmed to
|
7
|
+
# sign in, and to send emails with confirmation instructions.
|
8
|
+
# Confirmation instructions are sent to the user email after creating a
|
9
|
+
# record, after updating it's email and also when manually requested by
|
10
|
+
# a new confirmation instruction request.
|
11
|
+
# Whenever the user update it's email, his account is automatically unconfirmed,
|
12
|
+
# it means it won't be able to sign in again without confirming the account
|
13
|
+
# again through the email that was sent.
|
14
|
+
#
|
15
|
+
# Configuration:
|
16
|
+
#
|
17
|
+
# confirm_within: the time you want the user will have to confirm it's account
|
18
|
+
# without blocking his access. When confirm_within is zero, the
|
19
|
+
# user won't be able to sign in without confirming. You can
|
20
|
+
# use this to let your user access some features of your
|
21
|
+
# application without confirming the account, but blocking it
|
22
|
+
# after a certain period (ie 7 days). By default confirm_within is
|
23
|
+
# zero, it means users always have to confirm to sign in.
|
24
|
+
#
|
25
|
+
# Examples:
|
26
|
+
#
|
27
|
+
# User.find(1).confirm! # returns true unless it's already confirmed
|
28
|
+
# User.find(1).confirmed? # true/false
|
29
|
+
# User.find(1).send_confirmation_instructions # manually send instructions
|
30
|
+
# User.find(1).reset_confirmation! # reset confirmation status and send instructions
|
31
|
+
module Confirmable
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.class_eval do
|
35
|
+
extend ClassMethods
|
36
|
+
|
37
|
+
before_create :generate_confirmation_token
|
38
|
+
after_create :send_confirmation_instructions
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
43
|
+
# is already confirmed, add en error to email field
|
44
|
+
def confirm!
|
45
|
+
unless_confirmed do
|
46
|
+
self.confirmation_token = nil
|
47
|
+
self.confirmed_at = Time.now
|
48
|
+
save(false)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Verifies whether a user is confirmed or not
|
53
|
+
def confirmed?
|
54
|
+
!new_record? && confirmed_at?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Send confirmation instructions by email
|
58
|
+
def send_confirmation_instructions
|
59
|
+
::DeviseMailer.deliver_confirmation_instructions(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Remove confirmation date and send confirmation instructions, to ensure
|
63
|
+
# after sending these instructions the user won't be able to sign in without
|
64
|
+
# confirming it's account
|
65
|
+
def reset_confirmation!
|
66
|
+
unless_confirmed do
|
67
|
+
generate_confirmation_token
|
68
|
+
save(false)
|
69
|
+
send_confirmation_instructions
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Verify whether a user is active to sign in or not. If the user is
|
74
|
+
# already confirmed, it should never be blocked. Otherwise we need to
|
75
|
+
# calculate if the confirm time has not expired for this user, in other
|
76
|
+
# words, if the confirmation is still valid.
|
77
|
+
def active?
|
78
|
+
confirmed? || confirmation_period_valid?
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
# Checks if the confirmation for the user is within the limit time.
|
84
|
+
# We do this by calculating if the difference between today and the
|
85
|
+
# confirmation sent date does not exceed the confirm in time configured.
|
86
|
+
# Confirm_in is a model configuration, must always be an integer value.
|
87
|
+
#
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# # confirm_within = 1.day and confirmation_sent_at = today
|
91
|
+
# confirmation_period_valid? # returns true
|
92
|
+
#
|
93
|
+
# # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
|
94
|
+
# confirmation_period_valid? # returns true
|
95
|
+
#
|
96
|
+
# # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
|
97
|
+
# confirmation_period_valid? # returns false
|
98
|
+
#
|
99
|
+
# # confirm_within = 0.days
|
100
|
+
# confirmation_period_valid? # will always return false
|
101
|
+
#
|
102
|
+
def confirmation_period_valid?
|
103
|
+
confirmation_sent_at? &&
|
104
|
+
(Time.now.utc - confirmation_sent_at.utc) < confirm_within
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks whether the record is confirmed or not, yielding to the block
|
108
|
+
# if it's already confirmed, otherwise adds an error to email.
|
109
|
+
def unless_confirmed
|
110
|
+
unless confirmed?
|
111
|
+
yield
|
112
|
+
else
|
113
|
+
errors.add(:email, :already_confirmed, :default => 'already confirmed')
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generates a new random token for confirmation, and stores the time
|
119
|
+
# this token is being generated
|
120
|
+
def generate_confirmation_token
|
121
|
+
self.confirmed_at = nil
|
122
|
+
self.confirmation_token = friendly_token
|
123
|
+
self.confirmation_sent_at = Time.now.utc
|
124
|
+
end
|
125
|
+
|
126
|
+
module ClassMethods
|
127
|
+
|
128
|
+
# Attempt to find a user by it's email. If a record is found, send new
|
129
|
+
# confirmation instructions to it. If not user is found, returns a new user
|
130
|
+
# with an email not found error.
|
131
|
+
# Options must contain the user email
|
132
|
+
def send_confirmation_instructions(attributes={})
|
133
|
+
confirmable = find_or_initialize_with_error_by_email(attributes[:email])
|
134
|
+
confirmable.reset_confirmation! unless confirmable.new_record?
|
135
|
+
confirmable
|
136
|
+
end
|
137
|
+
|
138
|
+
# Find a user by it's confirmation token and try to confirm it.
|
139
|
+
# If no user is found, returns a new user with an error.
|
140
|
+
# If the user is already confirmed, create an error for the user
|
141
|
+
# Options must have the confirmation_token
|
142
|
+
def confirm!(attributes={})
|
143
|
+
confirmable = find_or_initialize_by_confirmation_token(attributes[:confirmation_token])
|
144
|
+
if confirmable.new_record?
|
145
|
+
confirmable.errors.add(:confirmation_token, :invalid)
|
146
|
+
else
|
147
|
+
confirmable.confirm!
|
148
|
+
end
|
149
|
+
confirmable
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
Devise::Models.config(self, :confirm_within)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Devise
|
2
|
+
module Models
|
3
|
+
|
4
|
+
# Recoverable takes care of reseting the user password and send reset instructions
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# # resets the user password and save the record, true if valid passwords are given, otherwise false
|
8
|
+
# User.find(1).reset_password!('password123', 'password123')
|
9
|
+
# # only resets the user password, without saving the record
|
10
|
+
# user = User.find(1)
|
11
|
+
# user.reset_password('password123', 'password123')
|
12
|
+
# # creates a new token and send it with instructions about how to reset the password
|
13
|
+
# User.find(1).send_reset_password_instructions
|
14
|
+
module Recoverable
|
15
|
+
def self.included(base)
|
16
|
+
base.class_eval do
|
17
|
+
extend ClassMethods
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Update password
|
22
|
+
def reset_password(new_password, new_password_confirmation)
|
23
|
+
self.password = new_password
|
24
|
+
self.password_confirmation = new_password_confirmation
|
25
|
+
end
|
26
|
+
|
27
|
+
# Update password saving the record and clearing token. Returns true if
|
28
|
+
# the passwords are valid and the record was saved, false otherwise.
|
29
|
+
def reset_password!(new_password, new_password_confirmation)
|
30
|
+
reset_password(new_password, new_password_confirmation)
|
31
|
+
clear_reset_password_token if valid?
|
32
|
+
save
|
33
|
+
end
|
34
|
+
|
35
|
+
# Resets reset password token and send reset password instructions by email
|
36
|
+
def send_reset_password_instructions
|
37
|
+
generate_reset_password_token!
|
38
|
+
::DeviseMailer.deliver_reset_password_instructions(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# Generates a new random token for reset password
|
44
|
+
def generate_reset_password_token
|
45
|
+
self.reset_password_token = friendly_token
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resets the reset password token with and save the record without
|
49
|
+
# validating
|
50
|
+
def generate_reset_password_token!
|
51
|
+
generate_reset_password_token && save(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Removes reset_password token
|
55
|
+
def clear_reset_password_token
|
56
|
+
self.reset_password_token = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
|
61
|
+
# Attempt to find a user by it's email. If a record is found, send new
|
62
|
+
# password instructions to it. If not user is found, returns a new user
|
63
|
+
# with an email not found error.
|
64
|
+
# Attributes must contain the user email
|
65
|
+
def send_reset_password_instructions(attributes={})
|
66
|
+
recoverable = find_or_initialize_with_error_by_email(attributes[:email])
|
67
|
+
recoverable.send_reset_password_instructions unless recoverable.new_record?
|
68
|
+
recoverable
|
69
|
+
end
|
70
|
+
|
71
|
+
# Attempt to find a user by it's reset_password_token to reset it's
|
72
|
+
# password. If a user is found, reset it's password and automatically
|
73
|
+
# try saving the record. If not user is found, returns a new user
|
74
|
+
# containing an error in reset_password_token attribute.
|
75
|
+
# Attributes must contain reset_password_token, password and confirmation
|
76
|
+
def reset_password!(attributes={})
|
77
|
+
recoverable = find_or_initialize_by_reset_password_token(attributes[:reset_password_token])
|
78
|
+
if recoverable.new_record?
|
79
|
+
recoverable.errors.add(:reset_password_token, :invalid)
|
80
|
+
else
|
81
|
+
recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
|
82
|
+
end
|
83
|
+
recoverable
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|