cb-sorcery 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +56 -0
- data/.rspec +1 -0
- data/.travis.yml +40 -0
- data/CHANGELOG.md +263 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +360 -0
- data/Rakefile +6 -0
- data/gemfiles/active_record-rails40.gemfile +7 -0
- data/gemfiles/active_record-rails41.gemfile +7 -0
- data/lib/generators/sorcery/USAGE +22 -0
- data/lib/generators/sorcery/helpers.rb +40 -0
- data/lib/generators/sorcery/install_generator.rb +95 -0
- data/lib/generators/sorcery/templates/initializer.rb +451 -0
- data/lib/generators/sorcery/templates/migration/activity_logging.rb +10 -0
- data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +9 -0
- data/lib/generators/sorcery/templates/migration/core.rb +13 -0
- data/lib/generators/sorcery/templates/migration/external.rb +12 -0
- data/lib/generators/sorcery/templates/migration/remember_me.rb +8 -0
- data/lib/generators/sorcery/templates/migration/reset_password.rb +9 -0
- data/lib/generators/sorcery/templates/migration/user_activation.rb +9 -0
- data/lib/sorcery.rb +85 -0
- data/lib/sorcery/adapters/active_record_adapter.rb +120 -0
- data/lib/sorcery/adapters/base_adapter.rb +30 -0
- data/lib/sorcery/controller.rb +157 -0
- data/lib/sorcery/controller/config.rb +65 -0
- data/lib/sorcery/controller/submodules/activity_logging.rb +82 -0
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +38 -0
- data/lib/sorcery/controller/submodules/external.rb +199 -0
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +74 -0
- data/lib/sorcery/controller/submodules/remember_me.rb +81 -0
- data/lib/sorcery/controller/submodules/session_timeout.rb +56 -0
- data/lib/sorcery/crypto_providers/aes256.rb +51 -0
- data/lib/sorcery/crypto_providers/bcrypt.rb +97 -0
- data/lib/sorcery/crypto_providers/common.rb +35 -0
- data/lib/sorcery/crypto_providers/md5.rb +19 -0
- data/lib/sorcery/crypto_providers/sha1.rb +28 -0
- data/lib/sorcery/crypto_providers/sha256.rb +36 -0
- data/lib/sorcery/crypto_providers/sha512.rb +36 -0
- data/lib/sorcery/engine.rb +21 -0
- data/lib/sorcery/model.rb +183 -0
- data/lib/sorcery/model/config.rb +96 -0
- data/lib/sorcery/model/submodules/activity_logging.rb +70 -0
- data/lib/sorcery/model/submodules/brute_force_protection.rb +125 -0
- data/lib/sorcery/model/submodules/external.rb +100 -0
- data/lib/sorcery/model/submodules/remember_me.rb +62 -0
- data/lib/sorcery/model/submodules/reset_password.rb +131 -0
- data/lib/sorcery/model/submodules/user_activation.rb +149 -0
- data/lib/sorcery/model/temporary_token.rb +30 -0
- data/lib/sorcery/protocols/certs/ca-bundle.crt +5182 -0
- data/lib/sorcery/protocols/oauth.rb +42 -0
- data/lib/sorcery/protocols/oauth2.rb +47 -0
- data/lib/sorcery/providers/base.rb +27 -0
- data/lib/sorcery/providers/facebook.rb +63 -0
- data/lib/sorcery/providers/github.rb +51 -0
- data/lib/sorcery/providers/google.rb +51 -0
- data/lib/sorcery/providers/jira.rb +77 -0
- data/lib/sorcery/providers/linkedin.rb +66 -0
- data/lib/sorcery/providers/liveid.rb +53 -0
- data/lib/sorcery/providers/twitter.rb +59 -0
- data/lib/sorcery/providers/vk.rb +63 -0
- data/lib/sorcery/providers/xing.rb +64 -0
- data/lib/sorcery/railties/tasks.rake +6 -0
- data/lib/sorcery/test_helpers/internal.rb +78 -0
- data/lib/sorcery/test_helpers/internal/rails.rb +68 -0
- data/lib/sorcery/test_helpers/rails/controller.rb +21 -0
- data/lib/sorcery/test_helpers/rails/integration.rb +26 -0
- data/lib/sorcery/version.rb +3 -0
- data/sorcery.gemspec +34 -0
- data/spec/active_record/user_activation_spec.rb +18 -0
- data/spec/active_record/user_activity_logging_spec.rb +17 -0
- data/spec/active_record/user_brute_force_protection_spec.rb +16 -0
- data/spec/active_record/user_oauth_spec.rb +16 -0
- data/spec/active_record/user_remember_me_spec.rb +16 -0
- data/spec/active_record/user_reset_password_spec.rb +16 -0
- data/spec/active_record/user_spec.rb +37 -0
- data/spec/controllers/controller_activity_logging_spec.rb +124 -0
- data/spec/controllers/controller_brute_force_protection_spec.rb +43 -0
- data/spec/controllers/controller_http_basic_auth_spec.rb +68 -0
- data/spec/controllers/controller_oauth2_spec.rb +407 -0
- data/spec/controllers/controller_oauth_spec.rb +240 -0
- data/spec/controllers/controller_remember_me_spec.rb +117 -0
- data/spec/controllers/controller_session_timeout_spec.rb +80 -0
- data/spec/controllers/controller_spec.rb +215 -0
- data/spec/orm/active_record.rb +21 -0
- data/spec/rails_app/app/active_record/authentication.rb +3 -0
- data/spec/rails_app/app/active_record/user.rb +5 -0
- data/spec/rails_app/app/active_record/user_provider.rb +3 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +265 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/sorcery_mailer.rb +32 -0
- data/spec/rails_app/app/views/application/index.html.erb +17 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_email.text.erb +9 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_needed_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.text.erb +9 -0
- data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.html.erb +16 -0
- data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.text.erb +8 -0
- data/spec/rails_app/app/views/sorcery_mailer/send_unlock_token_email.text.erb +1 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +56 -0
- data/spec/rails_app/config/boot.rb +4 -0
- data/spec/rails_app/config/database.yml +22 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +12 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +48 -0
- data/spec/rails_app/db/migrate/activation/20101224223622_add_activation_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +19 -0
- data/spec/rails_app/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +13 -0
- data/spec/rails_app/db/migrate/core/20101224223620_create_users.rb +16 -0
- data/spec/rails_app/db/migrate/external/20101224223628_create_authentications_and_user_providers.rb +22 -0
- data/spec/rails_app/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +15 -0
- data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
- data/spec/rails_app/db/schema.rb +23 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/shared_examples/user_activation_shared_examples.rb +242 -0
- data/spec/shared_examples/user_activity_logging_shared_examples.rb +97 -0
- data/spec/shared_examples/user_brute_force_protection_shared_examples.rb +156 -0
- data/spec/shared_examples/user_oauth_shared_examples.rb +36 -0
- data/spec/shared_examples/user_remember_me_shared_examples.rb +57 -0
- data/spec/shared_examples/user_reset_password_shared_examples.rb +263 -0
- data/spec/shared_examples/user_shared_examples.rb +467 -0
- data/spec/sorcery_crypto_providers_spec.rb +198 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +41 -0
- metadata +350 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Model
|
3
|
+
module Submodules
|
4
|
+
# This submodule helps you login users from external providers such as Twitter.
|
5
|
+
# This is the model part which handles finding the user using access tokens.
|
6
|
+
# For the controller options see Sorcery::Controller::External.
|
7
|
+
#
|
8
|
+
# Socery assumes (read: requires) you will create external users in the same table where
|
9
|
+
# you keep your regular users,
|
10
|
+
# but that you will have a separate table for keeping their external authentication data,
|
11
|
+
# and that that separate table has a few rows for each user, facebook and twitter
|
12
|
+
# for example (a one-to-many relationship).
|
13
|
+
#
|
14
|
+
# External users will have a null crypted_password field, since we do not hold their password.
|
15
|
+
# They will not be sent activation emails on creation.
|
16
|
+
module External
|
17
|
+
def self.included(base)
|
18
|
+
base.sorcery_config.class_eval do
|
19
|
+
attr_accessor :authentications_class,
|
20
|
+
:authentications_user_id_attribute_name,
|
21
|
+
:provider_attribute_name,
|
22
|
+
:provider_uid_attribute_name
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
base.sorcery_config.instance_eval do
|
27
|
+
@defaults.merge!(:@authentications_class => nil,
|
28
|
+
:@authentications_user_id_attribute_name => :user_id,
|
29
|
+
:@provider_attribute_name => :provider,
|
30
|
+
:@provider_uid_attribute_name => :uid)
|
31
|
+
|
32
|
+
reset!
|
33
|
+
end
|
34
|
+
|
35
|
+
base.send(:include, InstanceMethods)
|
36
|
+
base.extend(ClassMethods)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
# takes a provider and uid and finds a user by them.
|
42
|
+
def load_from_provider(provider,uid)
|
43
|
+
config = sorcery_config
|
44
|
+
authentication = config.authentications_class.sorcery_adapter.find_by_oauth_credentials(provider, uid)
|
45
|
+
user = sorcery_adapter.find_by_id(authentication.send(config.authentications_user_id_attribute_name)) if authentication
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_and_validate_from_provider(provider, uid, attrs)
|
49
|
+
user = new(attrs)
|
50
|
+
user.send(sorcery_config.authentications_class.to_s.downcase.pluralize).build(
|
51
|
+
sorcery_config.provider_uid_attribute_name => uid,
|
52
|
+
sorcery_config.provider_attribute_name => provider
|
53
|
+
)
|
54
|
+
saved = user.sorcery_adapter.save
|
55
|
+
[user, saved]
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_from_provider(provider, uid, attrs)
|
59
|
+
user = new
|
60
|
+
attrs.each do |k,v|
|
61
|
+
user.send(:"#{k}=", v)
|
62
|
+
end
|
63
|
+
|
64
|
+
if block_given?
|
65
|
+
return false unless yield user
|
66
|
+
end
|
67
|
+
|
68
|
+
sorcery_adapter.transaction do
|
69
|
+
user.sorcery_adapter.save(:validate => false)
|
70
|
+
sorcery_config.authentications_class.create!(
|
71
|
+
sorcery_config.authentications_user_id_attribute_name => user.id,
|
72
|
+
sorcery_config.provider_attribute_name => provider,
|
73
|
+
sorcery_config.provider_uid_attribute_name => uid
|
74
|
+
)
|
75
|
+
end
|
76
|
+
user
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
def add_provider_to_user(provider, uid)
|
82
|
+
authentications = sorcery_config.authentications_class.name.underscore.pluralize
|
83
|
+
# first check to see if user has a particular authentication already
|
84
|
+
if sorcery_adapter.find_authentication_by_oauth_credentials(authentications, provider, uid).nil?
|
85
|
+
user = send(authentications).build(sorcery_config.provider_uid_attribute_name => uid,
|
86
|
+
sorcery_config.provider_attribute_name => provider)
|
87
|
+
user.sorcery_adapter.save(validate: false)
|
88
|
+
else
|
89
|
+
user = false
|
90
|
+
end
|
91
|
+
|
92
|
+
user
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Model
|
3
|
+
module Submodules
|
4
|
+
# The Remember Me submodule takes care of setting the user's cookie so that he will
|
5
|
+
# be automatically logged in to the site on every visit,
|
6
|
+
# until the cookie expires.
|
7
|
+
module RememberMe
|
8
|
+
def self.included(base)
|
9
|
+
base.sorcery_config.class_eval do
|
10
|
+
attr_accessor :remember_me_token_attribute_name, # the attribute in the model class.
|
11
|
+
:remember_me_token_expires_at_attribute_name, # the expires attribute in the model class.
|
12
|
+
:remember_me_for # how long in seconds to remember.
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
base.sorcery_config.instance_eval do
|
17
|
+
@defaults.merge!(:@remember_me_token_attribute_name => :remember_me_token,
|
18
|
+
:@remember_me_token_expires_at_attribute_name => :remember_me_token_expires_at,
|
19
|
+
:@remember_me_for => 7 * 60 * 60 * 24)
|
20
|
+
|
21
|
+
reset!
|
22
|
+
end
|
23
|
+
|
24
|
+
base.send(:include, InstanceMethods)
|
25
|
+
base.sorcery_config.after_config << :define_remember_me_fields
|
26
|
+
|
27
|
+
base.extend(ClassMethods)
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
protected
|
32
|
+
|
33
|
+
def define_remember_me_fields
|
34
|
+
sorcery_adapter.define_field sorcery_config.remember_me_token_attribute_name, String
|
35
|
+
sorcery_adapter.define_field sorcery_config.remember_me_token_expires_at_attribute_name, Time
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module InstanceMethods
|
41
|
+
# You shouldn't really use this one yourself - it's called by the controller's 'remember_me!' method.
|
42
|
+
def remember_me!
|
43
|
+
config = sorcery_config
|
44
|
+
self.sorcery_adapter.update_attributes(config.remember_me_token_attribute_name => TemporaryToken.generate_random_token,
|
45
|
+
config.remember_me_token_expires_at_attribute_name => Time.now.in_time_zone + config.remember_me_for)
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_remember_me_token?
|
49
|
+
self.send(sorcery_config.remember_me_token_attribute_name).present?
|
50
|
+
end
|
51
|
+
|
52
|
+
# You shouldn't really use this one yourself - it's called by the controller's 'forget_me!' method.
|
53
|
+
def forget_me!
|
54
|
+
config = sorcery_config
|
55
|
+
self.sorcery_adapter.update_attributes(config.remember_me_token_attribute_name => nil,
|
56
|
+
config.remember_me_token_expires_at_attribute_name => nil)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Model
|
3
|
+
module Submodules
|
4
|
+
# This submodule adds the ability to reset password via email confirmation.
|
5
|
+
# When the user requests an email is sent to him with a url.
|
6
|
+
# The url includes a token, which is also saved with the user's record in the db.
|
7
|
+
# The token has configurable expiration.
|
8
|
+
# When the user clicks the url in the email, providing the token has not yet expired,
|
9
|
+
# he will be able to reset his password via a form.
|
10
|
+
#
|
11
|
+
# When using this submodule, supplying a mailer is mandatory.
|
12
|
+
module ResetPassword
|
13
|
+
def self.included(base)
|
14
|
+
base.sorcery_config.class_eval do
|
15
|
+
attr_accessor :reset_password_token_attribute_name, # reset password code attribute name.
|
16
|
+
:reset_password_token_expires_at_attribute_name, # expires at attribute name.
|
17
|
+
:reset_password_email_sent_at_attribute_name, # when was email sent, used for hammering
|
18
|
+
# protection.
|
19
|
+
|
20
|
+
:reset_password_mailer, # mailer class. Needed.
|
21
|
+
|
22
|
+
:reset_password_mailer_disabled, # when true sorcery will not automatically
|
23
|
+
# email password reset details and allow you to
|
24
|
+
# manually handle how and when email is sent
|
25
|
+
|
26
|
+
:reset_password_email_method_name, # reset password email method on your
|
27
|
+
# mailer class.
|
28
|
+
|
29
|
+
:reset_password_expiration_period, # how many seconds before the reset request
|
30
|
+
# expires. nil for never expires.
|
31
|
+
|
32
|
+
:reset_password_time_between_emails # hammering protection, how long to wait
|
33
|
+
# before allowing another email to be sent.
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
base.sorcery_config.instance_eval do
|
38
|
+
@defaults.merge!(:@reset_password_token_attribute_name => :reset_password_token,
|
39
|
+
:@reset_password_token_expires_at_attribute_name => :reset_password_token_expires_at,
|
40
|
+
:@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
|
41
|
+
:@reset_password_mailer => nil,
|
42
|
+
:@reset_password_mailer_disabled => false,
|
43
|
+
:@reset_password_email_method_name => :reset_password_email,
|
44
|
+
:@reset_password_expiration_period => nil,
|
45
|
+
:@reset_password_time_between_emails => 5 * 60 )
|
46
|
+
|
47
|
+
reset!
|
48
|
+
end
|
49
|
+
|
50
|
+
base.extend(ClassMethods)
|
51
|
+
|
52
|
+
base.sorcery_config.after_config << :validate_mailer_defined
|
53
|
+
base.sorcery_config.after_config << :define_reset_password_fields
|
54
|
+
|
55
|
+
base.send(:include, InstanceMethods)
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
# Find user by token, also checks for expiration.
|
61
|
+
# Returns the user if token found and is valid.
|
62
|
+
def load_from_reset_password_token(token)
|
63
|
+
token_attr_name = @sorcery_config.reset_password_token_attribute_name
|
64
|
+
token_expiration_date_attr = @sorcery_config.reset_password_token_expires_at_attribute_name
|
65
|
+
load_from_token(token, token_attr_name, token_expiration_date_attr)
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# This submodule requires the developer to define his own mailer class to be used by it
|
71
|
+
# when reset_password_mailer_disabled is false
|
72
|
+
def validate_mailer_defined
|
73
|
+
msg = "To use reset_password submodule, you must define a mailer (config.reset_password_mailer = YourMailerClass)."
|
74
|
+
raise ArgumentError, msg if @sorcery_config.reset_password_mailer == nil and @sorcery_config.reset_password_mailer_disabled == false
|
75
|
+
end
|
76
|
+
|
77
|
+
def define_reset_password_fields
|
78
|
+
sorcery_adapter.define_field sorcery_config.reset_password_token_attribute_name, String
|
79
|
+
sorcery_adapter.define_field sorcery_config.reset_password_token_expires_at_attribute_name, Time
|
80
|
+
sorcery_adapter.define_field sorcery_config.reset_password_email_sent_at_attribute_name, Time
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
module InstanceMethods
|
86
|
+
# generates a reset code with expiration
|
87
|
+
def generate_reset_password_token!
|
88
|
+
config = sorcery_config
|
89
|
+
attributes = {config.reset_password_token_attribute_name => TemporaryToken.generate_random_token,
|
90
|
+
config.reset_password_email_sent_at_attribute_name => Time.now.in_time_zone}
|
91
|
+
attributes[config.reset_password_token_expires_at_attribute_name] = Time.now.in_time_zone + config.reset_password_expiration_period if config.reset_password_expiration_period
|
92
|
+
|
93
|
+
self.sorcery_adapter.update_attributes(attributes)
|
94
|
+
end
|
95
|
+
|
96
|
+
# generates a reset code with expiration and sends an email to the user.
|
97
|
+
def deliver_reset_password_instructions!
|
98
|
+
config = sorcery_config
|
99
|
+
# hammering protection
|
100
|
+
return false if config.reset_password_time_between_emails.present? && self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.seconds.ago.utc
|
101
|
+
self.class.sorcery_adapter.transaction do
|
102
|
+
generate_reset_password_token!
|
103
|
+
send_reset_password_email! unless config.reset_password_mailer_disabled
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Clears token and tries to update the new password for the user.
|
108
|
+
def change_password!(new_password)
|
109
|
+
clear_reset_password_token
|
110
|
+
self.send(:"#{sorcery_config.password_attribute_name}=", new_password)
|
111
|
+
sorcery_adapter.save
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
|
116
|
+
def send_reset_password_email!
|
117
|
+
generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Clears the token.
|
121
|
+
def clear_reset_password_token
|
122
|
+
config = sorcery_config
|
123
|
+
self.send(:"#{config.reset_password_token_attribute_name}=", nil)
|
124
|
+
self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", nil) if config.reset_password_expiration_period
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Model
|
3
|
+
module Submodules
|
4
|
+
# This submodule adds the ability to make the user activate his account via email
|
5
|
+
# or any other way in which he can recieve an activation code.
|
6
|
+
# with the activation code the user may activate his account.
|
7
|
+
# When using this submodule, supplying a mailer is mandatory.
|
8
|
+
module UserActivation
|
9
|
+
def self.included(base)
|
10
|
+
base.sorcery_config.class_eval do
|
11
|
+
attr_accessor :activation_state_attribute_name, # the attribute name to hold activation state
|
12
|
+
# (active/pending).
|
13
|
+
|
14
|
+
:activation_token_attribute_name, # the attribute name to hold activation code
|
15
|
+
# (sent by email).
|
16
|
+
|
17
|
+
:activation_token_expires_at_attribute_name, # the attribute name to hold activation code
|
18
|
+
# expiration date.
|
19
|
+
|
20
|
+
:activation_token_expiration_period, # how many seconds before the activation code
|
21
|
+
# expires. nil for never expires.
|
22
|
+
|
23
|
+
:user_activation_mailer, # your mailer class. Required when
|
24
|
+
# activation_mailer_disabled == false.
|
25
|
+
|
26
|
+
:activation_mailer_disabled, # when true sorcery will not automatically
|
27
|
+
# email activation details and allow you to
|
28
|
+
# manually handle how and when email is sent
|
29
|
+
|
30
|
+
:activation_needed_email_method_name, # activation needed email method on your
|
31
|
+
# mailer class.
|
32
|
+
|
33
|
+
:activation_success_email_method_name, # activation success email method on your
|
34
|
+
# mailer class.
|
35
|
+
|
36
|
+
:prevent_non_active_users_to_login # do you want to prevent or allow users that
|
37
|
+
# did not activate by email to login?
|
38
|
+
end
|
39
|
+
|
40
|
+
base.sorcery_config.instance_eval do
|
41
|
+
@defaults.merge!(:@activation_state_attribute_name => :activation_state,
|
42
|
+
:@activation_token_attribute_name => :activation_token,
|
43
|
+
:@activation_token_expires_at_attribute_name => :activation_token_expires_at,
|
44
|
+
:@activation_token_expiration_period => nil,
|
45
|
+
:@user_activation_mailer => nil,
|
46
|
+
:@activation_mailer_disabled => false,
|
47
|
+
:@activation_needed_email_method_name => :activation_needed_email,
|
48
|
+
:@activation_success_email_method_name => :activation_success_email,
|
49
|
+
:@prevent_non_active_users_to_login => true)
|
50
|
+
reset!
|
51
|
+
end
|
52
|
+
|
53
|
+
base.class_eval do
|
54
|
+
# don't setup activation if no password supplied - this user is created automatically
|
55
|
+
sorcery_adapter.define_callback :before, :create, :setup_activation, :if => Proc.new { |user| user.send(sorcery_config.password_attribute_name).present? }
|
56
|
+
# don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
|
57
|
+
sorcery_adapter.define_callback :after, :create, :send_activation_needed_email!, :if => :send_activation_needed_email?
|
58
|
+
end
|
59
|
+
|
60
|
+
base.sorcery_config.after_config << :validate_mailer_defined
|
61
|
+
base.sorcery_config.after_config << :define_user_activation_fields
|
62
|
+
base.sorcery_config.before_authenticate << :prevent_non_active_login
|
63
|
+
|
64
|
+
base.extend(ClassMethods)
|
65
|
+
base.send(:include, InstanceMethods)
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
# Find user by token, also checks for expiration.
|
72
|
+
# Returns the user if token found and is valid.
|
73
|
+
def load_from_activation_token(token)
|
74
|
+
token_attr_name = @sorcery_config.activation_token_attribute_name
|
75
|
+
token_expiration_date_attr = @sorcery_config.activation_token_expires_at_attribute_name
|
76
|
+
load_from_token(token, token_attr_name, token_expiration_date_attr)
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
# This submodule requires the developer to define his own mailer class to be used by it
|
82
|
+
# when activation_mailer_disabled is false
|
83
|
+
def validate_mailer_defined
|
84
|
+
msg = "To use user_activation submodule, you must define a mailer (config.user_activation_mailer = YourMailerClass)."
|
85
|
+
raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil and @sorcery_config.activation_mailer_disabled == false
|
86
|
+
end
|
87
|
+
|
88
|
+
def define_user_activation_fields
|
89
|
+
self.class_eval do
|
90
|
+
sorcery_adapter.define_field sorcery_config.activation_state_attribute_name, String
|
91
|
+
sorcery_adapter.define_field sorcery_config.activation_token_attribute_name, String
|
92
|
+
sorcery_adapter.define_field sorcery_config.activation_token_expires_at_attribute_name, Time
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module InstanceMethods
|
98
|
+
def setup_activation
|
99
|
+
config = sorcery_config
|
100
|
+
generated_activation_token = TemporaryToken.generate_random_token
|
101
|
+
self.send(:"#{config.activation_token_attribute_name}=", generated_activation_token)
|
102
|
+
self.send(:"#{config.activation_state_attribute_name}=", "pending")
|
103
|
+
self.send(:"#{config.activation_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.activation_token_expiration_period) if config.activation_token_expiration_period
|
104
|
+
end
|
105
|
+
|
106
|
+
# clears activation code, sets the user as 'active' and optionaly sends a success email.
|
107
|
+
def activate!
|
108
|
+
config = sorcery_config
|
109
|
+
self.send(:"#{config.activation_token_attribute_name}=", nil)
|
110
|
+
self.send(:"#{config.activation_state_attribute_name}=", "active")
|
111
|
+
send_activation_success_email! if send_activation_success_email?
|
112
|
+
sorcery_adapter.save(:validate => false, :raise_on_failure => true)
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
# called automatically after user initial creation.
|
118
|
+
def send_activation_needed_email!
|
119
|
+
generic_send_email(:activation_needed_email_method_name, :user_activation_mailer)
|
120
|
+
end
|
121
|
+
|
122
|
+
def send_activation_success_email!
|
123
|
+
generic_send_email(:activation_success_email_method_name, :user_activation_mailer)
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_activation_success_email?
|
127
|
+
!external? && (
|
128
|
+
!(sorcery_config.activation_success_email_method_name.nil? ||
|
129
|
+
sorcery_config.activation_mailer_disabled == true)
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def send_activation_needed_email?
|
134
|
+
!external? && (
|
135
|
+
!(sorcery_config.activation_needed_email_method_name.nil? ||
|
136
|
+
sorcery_config.activation_mailer_disabled == true)
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
def prevent_non_active_login
|
141
|
+
config = sorcery_config
|
142
|
+
config.prevent_non_active_users_to_login ? self.send(config.activation_state_attribute_name) == "active" : true
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Sorcery
|
4
|
+
module Model
|
5
|
+
# This module encapsulates the logic for temporary token.
|
6
|
+
# A temporary token is created to identify a user in scenarios
|
7
|
+
# such as reseting password and activating the user by email.
|
8
|
+
module TemporaryToken
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Random code, used for salt and temporary tokens.
|
14
|
+
def self.generate_random_token
|
15
|
+
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def load_from_token(token, token_attr_name, token_expiration_date_attr)
|
20
|
+
return nil if token.blank?
|
21
|
+
user = sorcery_adapter.find_by_token(token_attr_name,token)
|
22
|
+
if !user.blank? && !user.send(token_expiration_date_attr).nil?
|
23
|
+
return Time.now.in_time_zone < user.send(token_expiration_date_attr) ? user : nil
|
24
|
+
end
|
25
|
+
user
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|