revise 0.0.1
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/lib/padrino/revise.rb +8 -0
- data/lib/revise.rb +110 -0
- data/lib/revise/controllers/accounts.rb +59 -0
- data/lib/revise/controllers/confirmations.rb +26 -0
- data/lib/revise/controllers/invitations.rb +55 -0
- data/lib/revise/controllers/main.rb +13 -0
- data/lib/revise/controllers/recovery.rb +60 -0
- data/lib/revise/controllers/sessions.rb +22 -0
- data/lib/revise/core_ext/module.rb +52 -0
- data/lib/revise/core_ext/string.rb +14 -0
- data/lib/revise/helpers/authentication.rb +21 -0
- data/lib/revise/helpers/core.rb +48 -0
- data/lib/revise/inviter.rb +40 -0
- data/lib/revise/locale/en.yml +11 -0
- data/lib/revise/mailers/confirmable.rb +18 -0
- data/lib/revise/mailers/invitable.rb +18 -0
- data/lib/revise/mailers/recoverable.rb +18 -0
- data/lib/revise/models.rb +99 -0
- data/lib/revise/models/authenticatable.rb +137 -0
- data/lib/revise/models/confirmable.rb +236 -0
- data/lib/revise/models/database_authenticatable.rb +107 -0
- data/lib/revise/models/invitable.rb +237 -0
- data/lib/revise/models/recoverable.rb +99 -0
- data/lib/revise/orm/mongo_mapper.rb +2 -0
- data/lib/revise/param_filter.rb +41 -0
- data/test/controllers/accounts_test.rb +148 -0
- data/test/controllers/invitations_test.rb +105 -0
- data/test/controllers/sessions_test.rb +35 -0
- data/test/factories/account.rb +15 -0
- data/test/models/account_test.rb +45 -0
- data/test/models/invitation_test.rb +423 -0
- data/test/test.rake +13 -0
- data/test/test_config.rb +23 -0
- metadata +181 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module Revise
|
2
|
+
module Helpers
|
3
|
+
module Core
|
4
|
+
def revise_for(*resources)
|
5
|
+
Revise.app = self
|
6
|
+
|
7
|
+
options = resources.extract_options!
|
8
|
+
|
9
|
+
resources.each do |resource|
|
10
|
+
begin
|
11
|
+
if Revise::MODULES.has_key?(resource)
|
12
|
+
models = Revise::MODULES[resource]
|
13
|
+
models.each do |m|
|
14
|
+
model = Revise::Models.const_get(m)
|
15
|
+
add_helpers(model)
|
16
|
+
add_controllers(model)
|
17
|
+
add_mailers(model)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
Padrino.logger.error "Hey man #{resource} doesn't exist"
|
21
|
+
end
|
22
|
+
rescue Exception => e
|
23
|
+
Padrino.logger.error "Failed to load: #{resource} Because #{e.message()}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def add_helpers(model)
|
30
|
+
model::HELPERS.each do |helper|
|
31
|
+
self.helpers(Revise::Helpers.const_get(helper))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_controllers(model)
|
36
|
+
model::CONTROLLERS.each do |controller|
|
37
|
+
self.send(:extend, Revise::Controllers.const_get(controller))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_mailers(model)
|
42
|
+
model::MAILERS.each do |mailer|
|
43
|
+
self.send(:extend, Revise::Mailers.const_get(mailer))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Revise
|
2
|
+
module Inviter
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend ClassMethods
|
7
|
+
attr_writer :invitation_limit unless respond_to? :invitation_limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def invitation_limit
|
11
|
+
self[:invitation_limit] || self.class.invitation_limit
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return true if this user has invitations left to send
|
15
|
+
def has_invitations_left?
|
16
|
+
if self.class.invitation_limit.present? || self.respond_to?(:invitation_limit)
|
17
|
+
if invitation_limit
|
18
|
+
return invitation_limit > 0
|
19
|
+
else
|
20
|
+
return self.class.invitation_limit > 0 if self.class.invitation_limit.present?
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
else
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def decrement_invitation_limit!
|
30
|
+
if self.class.invitation_limit.present?
|
31
|
+
self.invitation_limit ||= self.class.invitation_limit
|
32
|
+
self.update_attribute(:invitation_limit, invitation_limit - 1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
Revise::Models.config(self, :invitation_limit)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
en:
|
2
|
+
revise:
|
3
|
+
recoverable:
|
4
|
+
reset_password_instructions:
|
5
|
+
subject: "Reset Password Instructions For %{domain}"
|
6
|
+
confirmable:
|
7
|
+
confirmation_instructions:
|
8
|
+
subject: "Confirm Your Email At %{domain}"
|
9
|
+
invitable:
|
10
|
+
invation_instructions:
|
11
|
+
subject: "You've Been Invited To %{domain}"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Revise
|
2
|
+
module Mailers
|
3
|
+
module Confirmable
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.mailer :confirmable do
|
6
|
+
email :confirmation_instructions do |name, email, confirmation_token|
|
7
|
+
from Revise.mailer_from
|
8
|
+
to email
|
9
|
+
subject t('revise.confirmable.confirmation_instructions.subject', :domain => ENV['DOMAIN'])
|
10
|
+
locals :name => name, :email => email, :confirmation_token => confirmation_token
|
11
|
+
render 'revise/confirmation_instructions'
|
12
|
+
content_type :html
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Revise
|
2
|
+
module Mailers
|
3
|
+
module Invitable
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.mailer :invitable do
|
6
|
+
email :invitation_instructions do |name, email, invitation_token|
|
7
|
+
from Revise.mailer_from
|
8
|
+
to email
|
9
|
+
subject t('revise.invitable.invitation_instructions.subject', :domain => ENV['DOMAIN'])
|
10
|
+
locals :name => name, :email => email, :invitation_token => invitation_token
|
11
|
+
render 'revise/invitation_instructions'
|
12
|
+
content_type :html
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Revise
|
2
|
+
module Mailers
|
3
|
+
module Recoverable
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.mailer :recoverable do
|
6
|
+
email :reset_password_instructions do |name, email, reset_password_token|
|
7
|
+
from Revise.mailer_from
|
8
|
+
to email
|
9
|
+
subject t('revise.recoverable.reset_password_instructions.subject', :domain => ENV['DOMAIN'])
|
10
|
+
locals :name => name, :email => email, :reset_password_token => reset_password_token
|
11
|
+
render 'revise/reset_password_instructions'
|
12
|
+
content_type :html
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Revise
|
2
|
+
module Models
|
3
|
+
class MissingAttribute < StandardError
|
4
|
+
def initialize(attributes)
|
5
|
+
@attributes = attributes
|
6
|
+
end
|
7
|
+
|
8
|
+
def message
|
9
|
+
"The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.config(mod, *accessors)
|
14
|
+
class << mod; attr_accessor :available_configs; end
|
15
|
+
mod.available_configs = accessors
|
16
|
+
|
17
|
+
accessors.each do |accessor|
|
18
|
+
mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
19
|
+
def #{accessor}
|
20
|
+
if defined?(@#{accessor})
|
21
|
+
@#{accessor}
|
22
|
+
elsif superclass.respond_to?(:#{accessor})
|
23
|
+
superclass.#{accessor}
|
24
|
+
else
|
25
|
+
Revise.#{accessor}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def #{accessor}=(value)
|
30
|
+
@#{accessor} = value
|
31
|
+
end
|
32
|
+
METHOD
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.check_fields!(klass)
|
37
|
+
failed_attributes = []
|
38
|
+
instance = klass.new
|
39
|
+
|
40
|
+
klass.revise_modules.each do |mod|
|
41
|
+
constant = const_get(mod.to_s.classify)
|
42
|
+
|
43
|
+
if constant.respond_to?(:required_fields)
|
44
|
+
constant.required_fields(klass).each do |field|
|
45
|
+
failed_attributes << field unless instance.respond_to?(field)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
ActiveSupport::Deprecation.warn "The module #{mod} doesn't implement self.required_fields(klass). " \
|
49
|
+
"Devise uses required_fields to warn developers of any missing fields in their models. " \
|
50
|
+
"Please implement #{mod}.required_fields(klass) that returns an array of symbols with the required fields."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if failed_attributes.any?
|
55
|
+
fail Revise::Models::MissingAttribute.new(failed_attributes)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def revise(*modules)
|
60
|
+
options = modules.extract_options!.dup
|
61
|
+
|
62
|
+
plural_name = self.model_name.plural.to_sym
|
63
|
+
Revise::MODULES[plural_name] = []
|
64
|
+
|
65
|
+
revise_modules_hook! do
|
66
|
+
include Revise::Models::Authenticatable
|
67
|
+
|
68
|
+
modules.each do |m|
|
69
|
+
mod = Revise::Models.const_get(m.to_s.classify)
|
70
|
+
|
71
|
+
if mod.const_defined?("ClassMethods")
|
72
|
+
class_mod = mod.const_get("ClassMethods")
|
73
|
+
extend class_mod
|
74
|
+
|
75
|
+
if class_mod.respond_to?(:available_configs)
|
76
|
+
available_configs = class_mod.available_configs
|
77
|
+
available_configs.each do |config|
|
78
|
+
next unless options.key?(config)
|
79
|
+
send(:"#{config}=", options.delete(config))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
include mod
|
85
|
+
|
86
|
+
Revise::MODULES[plural_name] << m.to_s.classify
|
87
|
+
end
|
88
|
+
|
89
|
+
options.each { |key, value| send(:"#{key}=", value) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def revise_modules_hook!
|
94
|
+
yield
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
require 'revise/models/authenticatable'
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Revise
|
2
|
+
module Models
|
3
|
+
module Authenticatable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
MAILERS = []
|
7
|
+
HELPERS = ['Authentication']
|
8
|
+
CONTROLLERS = ['Main', 'Sessions', 'Accounts']
|
9
|
+
|
10
|
+
BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at, :role,
|
11
|
+
:remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
|
12
|
+
:last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
|
13
|
+
:remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token, :role,
|
14
|
+
:roles]
|
15
|
+
|
16
|
+
included do
|
17
|
+
before_validation :downcase_keys
|
18
|
+
before_validation :strip_whitespace
|
19
|
+
attr_accessor :skip_email
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.required_fields(klass)
|
23
|
+
[:role]
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid_for_authentication?
|
27
|
+
block_given? ? yield : true
|
28
|
+
end
|
29
|
+
|
30
|
+
def unauthenticated_message
|
31
|
+
:invalid
|
32
|
+
end
|
33
|
+
|
34
|
+
def active_for_authentication?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def inactive_message
|
39
|
+
:inactive
|
40
|
+
end
|
41
|
+
|
42
|
+
def password_required?
|
43
|
+
encrypted_password.blank? || password.present?
|
44
|
+
end
|
45
|
+
|
46
|
+
def role?(role)
|
47
|
+
return false unless self.respond_to?(:role)
|
48
|
+
return self.role.to_sym == role.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
array = %w(serializable_hash)
|
52
|
+
array << "to_xml"
|
53
|
+
|
54
|
+
array.each do |method|
|
55
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
56
|
+
def #{method}(options=nil)
|
57
|
+
options ||= {}
|
58
|
+
options[:except] = Array(options[:except])
|
59
|
+
|
60
|
+
if options[:force_except]
|
61
|
+
options[:except].concat Array(options[:force_except])
|
62
|
+
else
|
63
|
+
options[:except].concat BLACKLIST_FOR_SERIALIZATION
|
64
|
+
end
|
65
|
+
super(options)
|
66
|
+
end
|
67
|
+
RUBY
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def send_revise_notification(resource, notification, *attributes)
|
73
|
+
return false if self.email.blank?
|
74
|
+
Revise.app.deliver(resource, notification, *attributes) unless @skip_email
|
75
|
+
end
|
76
|
+
|
77
|
+
def downcase_keys
|
78
|
+
self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def strip_whitespace
|
82
|
+
self.class.strip_whitespace_keys.each { |k| self[k].try(:strip!) }
|
83
|
+
end
|
84
|
+
|
85
|
+
module ClassMethods
|
86
|
+
Revise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
|
87
|
+
:case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage)
|
88
|
+
|
89
|
+
def find_for_authentication(conditions)
|
90
|
+
find_first_by_auth_conditions(conditions)
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_first_by_auth_conditions(conditions)
|
94
|
+
to_adapter.find_first revise_param_filter.filter(conditions)
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
|
98
|
+
find_or_initialize_with_errors([attribute], { attribute => value }, error)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Find an initialize a group of attributes based on a list of required attributes.
|
102
|
+
def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
|
103
|
+
attributes = attributes.slice(*required_attributes)
|
104
|
+
attributes.delete_if { |key, value| value.blank? }
|
105
|
+
|
106
|
+
if attributes.size == required_attributes.size
|
107
|
+
record = find_first_by_auth_conditions(attributes)
|
108
|
+
end
|
109
|
+
|
110
|
+
unless record
|
111
|
+
record = new
|
112
|
+
|
113
|
+
required_attributes.each do |key|
|
114
|
+
value = attributes[key]
|
115
|
+
record.send("#{key}=", value)
|
116
|
+
record.errors.add(key, value.present? ? error : :blank)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
record
|
121
|
+
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
def revise_param_filter
|
125
|
+
@revise_param_filter ||= Revise::ParamFilter.new(case_insensitive_keys, strip_whitespace_keys)
|
126
|
+
end
|
127
|
+
|
128
|
+
def generate_token(column)
|
129
|
+
loop do
|
130
|
+
token = String.friendly_token
|
131
|
+
break token unless to_adapter.find_first({ column => token })
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Revise
|
2
|
+
module Models
|
3
|
+
module Confirmable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
MAILERS = ['Confirmable']
|
7
|
+
HELPERS = []
|
8
|
+
CONTROLLERS = ['Confirmations']
|
9
|
+
|
10
|
+
included do
|
11
|
+
before_create :generate_confirmation_token, :if => :confirmation_required?
|
12
|
+
after_create :send_on_create_confirmation_instructions, :if => :confirmation_required?
|
13
|
+
before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
|
14
|
+
after_update :send_confirmation_instructions, :if => :reconfirmation_required?
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.required_fields(klass)
|
18
|
+
required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
|
19
|
+
required_methods << :unconfirmed_email if klass.reconfirmable
|
20
|
+
required_methods
|
21
|
+
end
|
22
|
+
|
23
|
+
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
24
|
+
# is already confirmed, add an error to email field. If the user is invalid
|
25
|
+
# add errors
|
26
|
+
def confirm!
|
27
|
+
pending_any_confirmation do
|
28
|
+
if confirmation_period_expired?
|
29
|
+
self.errors.add(:confirmation_period_expired, 'Token expired')
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
self.confirmation_token = nil
|
34
|
+
self.confirmed_at = Time.now.utc
|
35
|
+
|
36
|
+
if self.class.reconfirmable && unconfirmed_email.present?
|
37
|
+
skip_reconfirmation!
|
38
|
+
self.email = unconfirmed_email
|
39
|
+
self.unconfirmed_email = nil
|
40
|
+
|
41
|
+
# We need to validate in such cases to enforce e-mail uniqueness
|
42
|
+
save(:validate => true)
|
43
|
+
else
|
44
|
+
save(:validate => false)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Verifies whether a user is confirmed or not
|
50
|
+
def confirmed?
|
51
|
+
!!confirmed_at
|
52
|
+
end
|
53
|
+
|
54
|
+
def pending_reconfirmation?
|
55
|
+
self.class.reconfirmable && unconfirmed_email.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Send confirmation instructions by email
|
59
|
+
def send_confirmation_instructions
|
60
|
+
self.confirmation_token = nil if reconfirmation_required?
|
61
|
+
@reconfirmation_required = false
|
62
|
+
|
63
|
+
generate_confirmation_token! if self.confirmation_token.blank?
|
64
|
+
send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Resend confirmation token. This method does not need to generate a new token.
|
68
|
+
def resend_confirmation_token
|
69
|
+
pending_any_confirmation do
|
70
|
+
self.confirmation_token = nil if confirmation_period_expired?
|
71
|
+
send_confirmation_instructions
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Overwrites active_for_authentication? for confirmation
|
76
|
+
# by verifying whether a user is active to sign in or not. If the user
|
77
|
+
# is already confirmed, it should never be blocked. Otherwise we need to
|
78
|
+
# calculate if the confirm time has not expired for this user.
|
79
|
+
def active_for_authentication?
|
80
|
+
super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
|
81
|
+
end
|
82
|
+
|
83
|
+
# The message to be shown if the account is inactive.
|
84
|
+
def inactive_message
|
85
|
+
!confirmed? ? :unconfirmed : super
|
86
|
+
end
|
87
|
+
|
88
|
+
# If you don't want confirmation to be sent on create, neither a code
|
89
|
+
# to be generated, call skip_confirmation!
|
90
|
+
def skip_confirmation!
|
91
|
+
self.confirmed_at = Time.now.utc
|
92
|
+
end
|
93
|
+
|
94
|
+
# If you don't want reconfirmation to be sent, neither a code
|
95
|
+
# to be generated, call skip_reconfirmation!
|
96
|
+
def skip_reconfirmation!
|
97
|
+
@bypass_postpone = true
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
# A callback method used to deliver confirmation
|
103
|
+
# instructions on creation. This can be overriden
|
104
|
+
# in models to map to a nice sign up e-mail.
|
105
|
+
def send_on_create_confirmation_instructions
|
106
|
+
send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Callback to overwrite if confirmation is required or not.
|
110
|
+
def confirmation_required?
|
111
|
+
!confirmed?
|
112
|
+
end
|
113
|
+
|
114
|
+
# Checks if the confirmation for the user is within the limit time.
|
115
|
+
# We do this by calculating if the difference between today and the
|
116
|
+
# confirmation sent date does not exceed the confirm in time configured.
|
117
|
+
# Confirm_within is a model configuration, must always be an integer value.
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
#
|
121
|
+
# # allow_unconfirmed_access_for = 1.day and confirmation_sent_at = today
|
122
|
+
# confirmation_period_valid? # returns true
|
123
|
+
#
|
124
|
+
# # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 4.days.ago
|
125
|
+
# confirmation_period_valid? # returns true
|
126
|
+
#
|
127
|
+
# # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 5.days.ago
|
128
|
+
# confirmation_period_valid? # returns false
|
129
|
+
#
|
130
|
+
# # allow_unconfirmed_access_for = 0.days
|
131
|
+
# confirmation_period_valid? # will always return false
|
132
|
+
#
|
133
|
+
def confirmation_period_valid?
|
134
|
+
confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
|
135
|
+
end
|
136
|
+
|
137
|
+
# Checks if the user confirmation happens before the token becomes invalid
|
138
|
+
# Examples:
|
139
|
+
#
|
140
|
+
# # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
|
141
|
+
# confirmation_period_expired? # returns false
|
142
|
+
#
|
143
|
+
# # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
|
144
|
+
# confirmation_period_expired? # returns true
|
145
|
+
#
|
146
|
+
# # confirm_within = nil
|
147
|
+
# confirmation_period_expired? # will always return false
|
148
|
+
#
|
149
|
+
def confirmation_period_expired?
|
150
|
+
self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
|
151
|
+
end
|
152
|
+
|
153
|
+
# Checks whether the record requires any confirmation.
|
154
|
+
def pending_any_confirmation
|
155
|
+
if (!confirmed? || pending_reconfirmation?)
|
156
|
+
yield
|
157
|
+
else
|
158
|
+
self.errors.add(:email, :already_confirmed)
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generates a new random token for confirmation, and stores the time
|
164
|
+
# this token is being generated
|
165
|
+
def generate_confirmation_token
|
166
|
+
self.confirmation_token = self.class.confirmation_token
|
167
|
+
self.confirmation_sent_at = Time.now.utc
|
168
|
+
end
|
169
|
+
|
170
|
+
def generate_confirmation_token!
|
171
|
+
generate_confirmation_token && save(:validate => false)
|
172
|
+
end
|
173
|
+
|
174
|
+
def after_password_reset
|
175
|
+
super
|
176
|
+
confirm! unless confirmed?
|
177
|
+
end
|
178
|
+
|
179
|
+
def postpone_email_change_until_confirmation
|
180
|
+
@reconfirmation_required = true
|
181
|
+
self.unconfirmed_email = self.email
|
182
|
+
self.email = self.email_was
|
183
|
+
end
|
184
|
+
|
185
|
+
def postpone_email_change?
|
186
|
+
postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
|
187
|
+
@bypass_postpone = nil
|
188
|
+
postpone
|
189
|
+
end
|
190
|
+
|
191
|
+
def reconfirmation_required?
|
192
|
+
self.class.reconfirmable && @reconfirmation_required
|
193
|
+
end
|
194
|
+
|
195
|
+
module ClassMethods
|
196
|
+
# Attempt to find a user by its email. If a record is found, send new
|
197
|
+
# confirmation instructions to it. If not, try searching for a user by unconfirmed_email
|
198
|
+
# field. If no user is found, returns a new user with an email not found error.
|
199
|
+
# Options must contain the user email
|
200
|
+
def send_confirmation_instructions(attributes={})
|
201
|
+
confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
|
202
|
+
unless confirmable.try(:persisted?)
|
203
|
+
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
|
204
|
+
end
|
205
|
+
confirmable.resend_confirmation_token if confirmable.persisted?
|
206
|
+
confirmable
|
207
|
+
end
|
208
|
+
|
209
|
+
# Find a user by its confirmation token and try to confirm it.
|
210
|
+
# If no user is found, returns a new user with an error.
|
211
|
+
# If the user is already confirmed, create an error for the user
|
212
|
+
# Options must have the confirmation_token
|
213
|
+
def confirm_by_token(confirmation_token)
|
214
|
+
confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
|
215
|
+
confirmable.confirm! if confirmable.persisted?
|
216
|
+
confirmable
|
217
|
+
end
|
218
|
+
|
219
|
+
# Generate a token checking if one does not already exist in the database.
|
220
|
+
def confirmation_token
|
221
|
+
generate_token(:confirmation_token)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Find a record for confirmation by unconfirmed email field
|
225
|
+
def find_by_unconfirmed_email_with_errors(attributes = {})
|
226
|
+
unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
|
227
|
+
unconfirmed_attributes = attributes.symbolize_keys
|
228
|
+
unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
|
229
|
+
find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
|
230
|
+
end
|
231
|
+
|
232
|
+
Revise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|