sorcery 0.1.0 → 0.1.4

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.
Files changed (44) hide show
  1. data/Gemfile +1 -2
  2. data/LICENSE.txt +1 -1
  3. data/README.rdoc +55 -42
  4. data/Rakefile +2 -12
  5. data/VERSION +1 -1
  6. data/lib/sorcery/controller/submodules/activity_logging.rb +45 -0
  7. data/lib/sorcery/controller/submodules/brute_force_protection.rb +8 -69
  8. data/lib/sorcery/controller/submodules/http_basic_auth.rb +60 -0
  9. data/lib/sorcery/controller/submodules/session_timeout.rb +4 -1
  10. data/lib/sorcery/controller.rb +13 -7
  11. data/lib/sorcery/crypto_providers/bcrypt.rb +2 -6
  12. data/lib/sorcery/model/submodules/activity_logging.rb +35 -0
  13. data/lib/sorcery/model/submodules/brute_force_protection.rb +72 -0
  14. data/lib/sorcery/model/submodules/remember_me.rb +3 -1
  15. data/lib/sorcery/model/submodules/reset_password.rb +93 -0
  16. data/lib/sorcery/model/submodules/user_activation.rb +8 -6
  17. data/lib/sorcery/model.rb +14 -8
  18. data/lib/sorcery.rb +5 -1
  19. data/sorcery.gemspec +221 -0
  20. data/spec/Gemfile +1 -1
  21. data/spec/Gemfile.lock +2 -2
  22. data/spec/rails3/Gemfile +3 -2
  23. data/spec/rails3/Gemfile.lock +4 -2
  24. data/spec/rails3/app_root/app/controllers/application_controller.rb +6 -1
  25. data/spec/rails3/app_root/config/routes.rb +1 -0
  26. data/spec/rails3/app_root/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
  27. data/spec/rails3/app_root/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
  28. data/spec/rails3/app_root/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
  29. data/spec/rails3/controller_activity_logging_spec.rb +84 -0
  30. data/spec/rails3/controller_brute_force_protection_spec.rb +24 -41
  31. data/spec/rails3/controller_http_basic_auth_spec.rb +50 -0
  32. data/spec/rails3/controller_session_timeout_spec.rb +1 -0
  33. data/spec/rails3/controller_spec.rb +3 -3
  34. data/spec/rails3/spec_helper.rb +39 -19
  35. data/spec/rails3/user_activation_spec.rb +7 -7
  36. data/spec/rails3/user_activity_logging_spec.rb +36 -0
  37. data/spec/rails3/user_brute_force_protection_spec.rb +76 -0
  38. data/spec/rails3/user_reset_password_spec.rb +198 -0
  39. data/spec/rails3/user_spec.rb +8 -4
  40. metadata +40 -64
  41. data/features/support/env.rb +0 -13
  42. data/lib/sorcery/model/submodules/password_reset.rb +0 -64
  43. data/spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb +0 -9
  44. data/spec/rails3/user_password_reset_spec.rb +0 -76
data/Gemfile CHANGED
@@ -6,13 +6,12 @@ source "http://rubygems.org"
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
- gem "rails", "3.0.3"
9
+ gem "rails", ">= 3.0.0"
10
10
  gem "rspec", "~> 2.3.0"
11
11
  gem 'rspec-rails'
12
12
  gem 'ruby-debug19'
13
13
  gem 'sqlite3-ruby', :require => 'sqlite3'
14
14
  gem "yard", "~> 0.6.0"
15
- gem "cucumber", ">= 0"
16
15
  gem "bundler", "~> 1.0.0"
17
16
  gem "jeweler", "~> 1.5.2"
18
17
  gem 'simplecov', '>= 0.3.8', :require => false # Will install simplecov-html as a dependency
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Noam Ben-Ari
1
+ Copyright (c) 2010 Noam Ben-Ari <mailto:nbenari@gmail.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -8,29 +8,60 @@ Crypto code taken almost unchanged from Authlogic.
8
8
 
9
9
  https://github.com/NoamB/sorcery-example-app
10
10
 
11
- == Current Features:
11
+ == Full Features List by module:
12
12
 
13
- * Basic Login/Logout.
14
- * Password encryption with configurable algorithm.
13
+ Core (see lib/sorcery/model/model.rb and lib/sorcery/controller/controller.rb):
14
+ * login/logout, optional redirect on login to where the user tried to reach before, configurable redirect for non-logged-in users.
15
+ * password encryption, algorithms: bcrypt(default), md5, sha1, sha256, sha512, aes256, custom(yours!), none. Configurable stretches and salt.
16
+ * configurable attribute names for username, password and email.
17
+
18
+ User Activation (see lib/sorcery/model/submodules/user_activation.rb):
15
19
  * User activation by email with optional success email.
20
+ * configurable attribute names.
21
+ * configurable mailer.
22
+ * Optionally prevent non-active users to login.
23
+
24
+ Reset Password (see lib/sorcery/model/submodules/reset_password.rb):
16
25
  * Reset password with email verification.
26
+ * configurable mailer, method name, and attribute name.
27
+ * configurable expiration.
28
+ * configurable time between emails (hammering protection).
29
+
30
+ Remember Me (see lib/sorcery/model/submodules/remember_me.rb):
17
31
  * Remember me with configurable expiration.
32
+ * configurable attribute names.
33
+
34
+ Session Timeout (see lib/sorcery/controller/submodules/session_timeout.rb):
18
35
  * Configurable session timeout.
36
+ * Optionally session timeout will be calculated from last user action.
37
+
38
+ Brute Force Protection (see lib/sorcery/model/submodules/brute_force_protection.rb):
19
39
  * Brute force login hammering protection.
40
+ * configurable logins before lock and lock duration.
41
+
42
+ Basic HTTP Authentication (see lib/sorcery/controller/submodules/http_basic_auth.rb):
43
+ * A before filter for requesting authentication with HTTP Basic.
44
+ * automatic login from HTTP Basic.
45
+ * automatic login is disabled if session key changed.
46
+
47
+ Activity Logging (see lib/sorcery/model/submodules/activity_logging.rb):
48
+ * automatic logging of last login, last logout and last activity time.
49
+ * an easy method of collecting the list of currently logged in users.
50
+ * configurable timeout by which to decide whether to include a user in the list of logged in users.
51
+
52
+ Other:
20
53
  * Modular design, load only the modules you need.
21
54
  * 100% TDD'd code, 100% test coverage.
22
55
 
23
- == Planned Features:
56
+ == Next Planned Features:
24
57
 
25
58
  I've got many plans which include:
26
- * Basic HTTP Authentication
27
- * Auto login
28
- * Hammering reset password protection
59
+ * Configurable Auto login on registration/activation
29
60
  * Other reset password strategies (security questions?)
61
+ * Other brute force protection strategies (captcha)
30
62
  * Sinatra support
31
63
  * Mongoid support
32
- * OmniAuth integration
33
- * Activity logging
64
+ * OAuth1 and OAuth2 support
34
65
  * Have an idea? Let me know, and it might get into the gem!
35
66
 
36
67
  == Project Goals:
@@ -50,9 +81,9 @@ Hopefully, I've achieved this. If not, let me know.
50
81
 
51
82
  == Installation:
52
83
 
53
- You can either git clone and then 'rake install',
84
+ You can either git clone and then 'rake install' to live on the edge (unstable),
54
85
 
55
- In the future will be available:
86
+ Or simply (stable):
56
87
 
57
88
  gem install sorcery
58
89
 
@@ -80,7 +111,7 @@ For example:
80
111
  2. app/models/user.rb (or another model of your choice)
81
112
 
82
113
  activate_sorcery! do |config|
83
- config.sorcery_mailer = MyMailer
114
+ config.user_activation_mailer = MyMailer
84
115
  config.username_attribute_name = :email
85
116
  end
86
117
 
@@ -92,45 +123,27 @@ For example:
92
123
 
93
124
  Also check the migrations in the example app to see what database fields are expected.
94
125
 
95
-
96
-
97
126
  The configuration options vary with the modules you've chosen to use.
98
127
 
99
- == Basic Configuration (in Model):
100
-
101
- see lib/sorcery/model.rb
102
-
103
- == User Activation Configuration (in Model):
104
-
105
- see lib/sorcery/model/submodules/user_activation.rb
106
-
107
- == Remember Me Configuration (in Model):
108
-
109
- see lib/sorcery/model/submodules/remember_me.rb
110
-
111
- == Password Reset Configuration (in Model):
112
-
113
- see lib/sorcery/model/submodules/password_reset.rb
114
-
115
-
116
-
117
- == Session Timeout Configuration (in Controller or config/application.rb):
118
-
119
- see lib/sorcery/controller/submodules/session_timeout.rb
120
-
121
- == Brute Force Protection Configuration (in Controller or config/application.rb):
122
-
123
- see lib/sorcery/controller/submodules/brute_force_protection.rb
124
128
 
129
+ == Contributing to sorcery
125
130
 
131
+ Your feedback is very welcome and will make this gem much much better for you, me and everyone else.
132
+ Besides feedback on code, features, suggestions and bug reports, you may want to actually make an impact on the code.
133
+ For this:
126
134
 
127
- == Contributing to sorcery
135
+ * Fork the project.
136
+ * Make your feature addition or bug fix.
137
+ * Add tests for it. This is important so I don’t break it in a future version unintentionally.
138
+ * Commit, do not mess with Rakefiles, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
139
+ * Send me a pull request. Bonus points for topic branches.
128
140
 
129
- I can use help of any kind, be it comments on code (code review), suggestions, features, bug reports, bug fixes and even a donation.
141
+ If you feel my work has made your life easier, and you would like to thank me through a donation, my paypal email is in the contact details.
130
142
 
131
143
  == Contact
132
144
 
133
- email: nbenari@gmail.com
145
+ email: nbenari@gmail.com ( also for paypal )
146
+ twitter: @nbenari
134
147
 
135
148
  == Copyright
136
149
 
data/Rakefile CHANGED
@@ -22,30 +22,20 @@ Jeweler::Tasks.new do |gem|
22
22
  # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
23
23
  # gem.add_runtime_dependency 'jabber4r', '> 0.1'
24
24
  # gem.add_development_dependency 'rspec', '> 1.2.3'
25
+ gem.add_runtime_dependency 'bcrypt-ruby', '~> 2.1.4'
25
26
  end
26
27
  Jeweler::RubygemsDotOrgTasks.new
27
28
 
28
29
  require 'rspec/core'
29
30
  require 'rspec/core/rake_task'
31
+
30
32
  RSpec::Core::RakeTask.new(:spec) do |spec|
31
33
  spec.pattern = FileList['spec/**/*_spec.rb']
32
34
  end
33
35
 
34
- RSpec::Core::RakeTask.new(:rcov) do |spec|
35
- spec.pattern = 'spec/**/*_spec.rb'
36
- spec.rcov = true
37
- end
38
-
39
- require 'cucumber/rake/task'
40
- Cucumber::Rake::Task.new(:features)
41
-
42
-
43
-
44
36
  require 'yard'
45
37
  YARD::Rake::YardocTask.new
46
38
 
47
-
48
- #task :default => :spec
49
39
  desc 'Default: Run all specs.'
50
40
  task :default => :all_specs
51
41
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.4
@@ -0,0 +1,45 @@
1
+ module Sorcery
2
+ module Controller
3
+ module Submodules
4
+ module ActivityLogging
5
+ def self.included(base)
6
+ base.send(:include, InstanceMethods)
7
+ Config.after_login << :register_login_time_to_db
8
+ Config.before_logout << :register_logout_time_to_db
9
+ base.after_filter :register_last_activity_time_to_db
10
+ end
11
+
12
+ module InstanceMethods
13
+ def logged_in_users
14
+ Config.user_class.logged_in_users
15
+ # A possible patch here:
16
+ # we'll add the logged_in_user to the users list if he's not in it (can happen when he was inactive for more than activity timeout):
17
+ #
18
+ # users.unshift!(logged_in_user) if logged_in? && users.find {|u| u.id == logged_in_user.id}.nil?
19
+ #
20
+ # disadvantages: can hurt performance.
21
+ end
22
+
23
+ protected
24
+
25
+ def register_login_time_to_db(user, credentials)
26
+ user.send(:"#{user.sorcery_config.last_login_at_attribute_name}=", Time.now.utc.to_s(:db))
27
+ user.save!(:validate => false)
28
+ end
29
+
30
+ def register_logout_time_to_db(user)
31
+ user.send(:"#{user.sorcery_config.last_logout_at_attribute_name}=", Time.now.utc.to_s(:db))
32
+ user.save!(:validate => false)
33
+ end
34
+
35
+ # we do not update activity on logout
36
+ def register_last_activity_time_to_db
37
+ return if !logged_in?
38
+ logged_in_user.send(:"#{logged_in_user.sorcery_config.last_activity_at_attribute_name}=", Time.now.utc.to_s(:db))
39
+ logged_in_user.save!(:validate => false)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -4,83 +4,22 @@ module Sorcery
4
4
  module BruteForceProtection
5
5
  def self.included(base)
6
6
  base.send(:include, InstanceMethods)
7
- Config.module_eval do
8
- class << self
9
- attr_accessor :login_retries_amount_allowed, # how many failed logins allowed.
10
- :login_retries_time_period, # the time after which the failed logins counter is reset.
11
- :login_ban_time_period, # how long the user should be banned. in seconds.
12
- :banned_action # what controller action should be called when a banned user tries.
13
-
14
- def merge_brute_force_protection_defaults!
15
- @defaults.merge!(:@login_retries_amount_allowed => 50,
16
- :@login_retries_time_period => 30,
17
- :@login_ban_time_period => 3600,
18
- :@banned_action => :default_banned_action)
19
- end
20
- end
21
- merge_brute_force_protection_defaults!
22
- end
23
- Config.after_failed_login << :check_failed_logins_limit_reached
24
- base.prepend_before_filter :deny_banned_user
7
+
8
+ Config.after_login << :reset_failed_logins_count!
9
+ Config.after_failed_login << :update_failed_logins_count!
25
10
  end
26
11
 
27
12
  module InstanceMethods
28
- def check_failed_logins_limit_reached(user, credentials)
29
- now = Time.now.utc
30
-
31
- # not banned
32
- if session[:first_failed_login_time]
33
- reset_failed_logins_if_time_passed(now)
34
- else
35
- session[:first_failed_login_time] = now
36
- end
37
- increment_failed_logins
38
- # ban
39
- ban_if_above_limit(now)
40
- end
41
13
 
42
14
  protected
43
15
 
44
- def release_ban_if_time_passed(now)
45
- if now - session[:ban_start_time] > Config.login_ban_time_period
46
- session[:banned] = nil
47
- session[:failed_logins] = 0
48
- return true
49
- end
50
- false
51
- end
52
-
53
- def increment_failed_logins
54
- session[:failed_logins] ||= 0
55
- session[:failed_logins] += 1
56
- end
57
-
58
- def reset_failed_logins_if_time_passed(now)
59
- if now - session[:first_failed_login_time] > Config.login_retries_time_period
60
- session[:failed_logins] = 0
61
- session[:first_failed_login_time] = now
62
- end
63
- end
64
-
65
- def ban_if_above_limit(now)
66
- if session[:failed_logins] > Config.login_retries_amount_allowed
67
- session[:banned] = true
68
- session[:ban_start_time] = now
69
- end
70
- end
71
-
72
- def deny_banned_user
73
- if session[:banned]
74
- now = Time.now.utc
75
- release_ban_if_time_passed(now)
76
- end
77
-
78
- # if still banned
79
- send(Config.banned_action) if session[:banned]
16
+ def update_failed_logins_count!(credentials)
17
+ user = User.where("#{User.sorcery_config.username_attribute_name} = ?", credentials[0]).first
18
+ user.register_failed_login! if user
80
19
  end
81
20
 
82
- def default_banned_action
83
- render :nothing => true
21
+ def reset_failed_logins_count!(user, credentials)
22
+ user.update_attributes!(User.sorcery_config.failed_logins_count_attribute_name => 0)
84
23
  end
85
24
  end
86
25
  end
@@ -0,0 +1,60 @@
1
+ module Sorcery
2
+ module Controller
3
+ module Submodules
4
+ module HttpBasicAuth
5
+ def self.included(base)
6
+ base.send(:include, InstanceMethods)
7
+ Config.module_eval do
8
+ class << self
9
+ attr_accessor :controller_to_realm_map # how many failed logins allowed.
10
+
11
+ def merge_http_basic_auth_defaults!
12
+ @defaults.merge!(:@controller_to_realm_map => {"application" => "Application"})
13
+ end
14
+ end
15
+ merge_http_basic_auth_defaults!
16
+ end
17
+ Config.login_sources << :login_from_basic_auth
18
+ end
19
+
20
+ module InstanceMethods
21
+
22
+ protected
23
+
24
+ # to be used as a before_filter.
25
+ # The method sets a session when requesting the user's credentials.
26
+ # This is a trick to overcome the way HTTP authentication works (explained below):
27
+ #
28
+ # Once the user fills the credentials once, the browser will always send it to the server when visiting the website, until the browser is closed.
29
+ # This causes wierd behaviour if the user logs out. The session is reset, yet the user is re-logged in by the before_filter calling 'login_from_basic_auth'.
30
+ # To overcome this, we set a session when requesting the password, which logout will reset, and that's how we know if we need to request for HTTP auth again.
31
+ def require_login_from_http_basic
32
+ (request_http_basic_authentication(realm_name_by_controller) and (session[:http_authentication_used] = true) and return) if (request.authorization.nil? || session[:http_authentication_used].nil?)
33
+ require_login
34
+ end
35
+
36
+ # given to main controller module as a login source callback
37
+ def login_from_basic_auth
38
+ authenticate_with_http_basic do |username, password|
39
+ @logged_in_user = (Config.user_class.authenticate(username, password) if session[:http_authentication_used]) || false
40
+ login_user(@logged_in_user) if @logged_in_user
41
+ @logged_in_user
42
+ end
43
+ end
44
+
45
+ def realm_name_by_controller
46
+ current_controller = self.class
47
+ while current_controller != ActionController::Base
48
+ result = Config.controller_to_realm_map[current_controller.controller_name]
49
+ return result if result
50
+ current_controller = self.class.superclass
51
+ end
52
+ nil
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -21,11 +21,13 @@ module Sorcery
21
21
  end
22
22
 
23
23
  module InstanceMethods
24
+ protected
25
+
24
26
  def register_login_time(user, credentials)
25
27
  session[:login_time] = session[:last_action_time] = Time.now.utc
26
28
  end
27
29
 
28
- # To be used as a before_filter, before authenticate
30
+ # To be used as a before_filter, before require_login
29
31
  def validate_session
30
32
  session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
31
33
  if session_to_use && (Time.now.utc - session_to_use > Config.session_timeout)
@@ -35,6 +37,7 @@ module Sorcery
35
37
  session[:last_action_time] = Time.now.utc
36
38
  end
37
39
  end
40
+
38
41
  end
39
42
  end
40
43
  end
@@ -12,7 +12,6 @@ module Sorcery
12
12
  end
13
13
  end
14
14
  Config.update!
15
- Config.user_class = User
16
15
  end
17
16
  end
18
17
 
@@ -31,7 +30,7 @@ module Sorcery
31
30
  # To be used as before_filter.
32
31
  # Will trigger auto-login attempts via the call to logged_in?
33
32
  # If all attempts to auto-login fail, the failure callback will be called.
34
- def require_user_login
33
+ def require_login
35
34
  if !logged_in?
36
35
  session[:user_wanted_url] = request.url if Config.save_user_wanted_url
37
36
  self.send(Config.not_authenticated_action)
@@ -46,13 +45,14 @@ module Sorcery
46
45
  after_login!(user, credentials)
47
46
  logged_in_user
48
47
  else
49
- after_failed_login!(user, credentials)
48
+ after_failed_login!(credentials)
50
49
  nil
51
50
  end
52
51
  end
53
52
 
54
53
  def logout
55
54
  if logged_in?
55
+ before_logout!(logged_in_user)
56
56
  reset_session
57
57
  after_logout!
58
58
  end
@@ -63,9 +63,9 @@ module Sorcery
63
63
  end
64
64
 
65
65
  # attempts to auto-login from the sources defined (session, basic_auth, cookie, etc.)
66
- # returns the logged in user if found, false if not (using old restful-authentication trick).
66
+ # returns the logged in user if found, false if not (using old restful-authentication trick, nil != false).
67
67
  def logged_in_user
68
- @logged_in_user ||= login_from_session || login_from_other_sources unless @logged_in_user == false # || login_from_basic_auth || )
68
+ @logged_in_user ||= login_from_session || login_from_other_sources unless @logged_in_user == false
69
69
  end
70
70
 
71
71
  def login_from_other_sources
@@ -94,8 +94,12 @@ module Sorcery
94
94
  Config.after_login.each {|c| self.send(c, user, credentials)}
95
95
  end
96
96
 
97
- def after_failed_login!(user, credentials)
98
- Config.after_failed_login.each {|c| self.send(c, user, credentials)}
97
+ def after_failed_login!(credentials)
98
+ Config.after_failed_login.each {|c| self.send(c, credentials)}
99
+ end
100
+
101
+ def before_logout!(user)
102
+ Config.before_logout.each {|c| self.send(c, user)}
99
103
  end
100
104
 
101
105
  def after_logout!
@@ -118,6 +122,7 @@ module Sorcery
118
122
  :login_sources,
119
123
  :after_login,
120
124
  :after_failed_login,
125
+ :before_logout,
121
126
  :after_logout,
122
127
  :after_config
123
128
 
@@ -130,6 +135,7 @@ module Sorcery
130
135
  :@login_sources => [],
131
136
  :@after_login => [],
132
137
  :@after_failed_login => [],
138
+ :@before_logout => [],
133
139
  :@after_logout => [],
134
140
  :@after_config => [],
135
141
  :@save_user_wanted_url => true
@@ -1,8 +1,4 @@
1
- begin
2
- require "bcrypt"
3
- rescue LoadError
4
- "sudo gem install bcrypt-ruby"
5
- end
1
+ require 'bcrypt'
6
2
 
7
3
  module Sorcery
8
4
  module CryptoProviders
@@ -46,7 +42,7 @@ module Sorcery
46
42
  # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
47
43
  # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
48
44
  def cost
49
- @cost ||= 10
45
+ @cost ||= 1
50
46
  end
51
47
  attr_writer :cost
52
48
  alias :stretches= :cost=
@@ -0,0 +1,35 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ module ActivityLogging
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.sorcery_config.class_eval do
8
+ attr_accessor :last_login_at_attribute_name, # last login attribute name.
9
+ :last_logout_at_attribute_name, # last logout attribute name.
10
+ :last_activity_at_attribute_name, # last activity attribute name.
11
+ :activity_timeout # how long since last activity is the user defined logged out?
12
+ end
13
+
14
+ base.sorcery_config.instance_eval do
15
+ @defaults.merge!(:@last_login_at_attribute_name => :last_login_at,
16
+ :@last_logout_at_attribute_name => :last_logout_at,
17
+ :@last_activity_at_attribute_name => :last_activity_at,
18
+ :@activity_timeout => 10.minutes)
19
+ reset!
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ # get all users with last_activity within timeout
25
+ def logged_in_users
26
+ config = sorcery_config
27
+ where("#{config.last_activity_at_attribute_name} IS NOT NULL") \
28
+ .where("#{config.last_logout_at_attribute_name} IS NULL OR #{config.last_activity_at_attribute_name} > #{config.last_logout_at_attribute_name}") \
29
+ .where("#{config.last_activity_at_attribute_name} > ? ", config.activity_timeout.seconds.ago)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ module BruteForceProtection
5
+ def self.included(base)
6
+ base.sorcery_config.class_eval do
7
+ attr_accessor :failed_logins_count_attribute_name, # failed logins attribute name.
8
+ :lock_expires_at_attribute_name, # this field indicates whether user is banned and when it will be active again.
9
+ :consecutive_login_retries_amount_allowed, # how many failed logins allowed.
10
+ :login_lock_time_period # how long the user should be banned. in seconds. 0 for permanent.
11
+ end
12
+
13
+ base.sorcery_config.instance_eval do
14
+ @defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
15
+ :@lock_expires_at_attribute_name => :lock_expires_at,
16
+ :@consecutive_login_retries_amount_allowed => 50,
17
+ :@login_lock_time_period => 3600)
18
+ reset!
19
+ end
20
+
21
+ base.class_eval do
22
+
23
+ end
24
+
25
+ base.sorcery_config.before_authenticate << :prevent_locked_user_login
26
+ base.extend(ClassMethods)
27
+ base.send(:include, InstanceMethods)
28
+ end
29
+
30
+ module ClassMethods
31
+ protected
32
+
33
+ end
34
+
35
+ module InstanceMethods
36
+ def register_failed_login!
37
+ config = sorcery_config
38
+ self.increment(config.failed_logins_count_attribute_name)
39
+ save!
40
+ self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_allowed
41
+ end
42
+
43
+ protected
44
+
45
+ def lock!
46
+ config = sorcery_config
47
+ self.update_attributes!(config.lock_expires_at_attribute_name => Time.now.utc + config.login_lock_time_period)
48
+ end
49
+
50
+ def unlock!
51
+ config = sorcery_config
52
+ self.update_attributes!(config.lock_expires_at_attribute_name => nil,
53
+ config.failed_logins_count_attribute_name => 0)
54
+ end
55
+
56
+ def unlocked?
57
+ config = sorcery_config
58
+ self.send(config.lock_expires_at_attribute_name).nil?
59
+ end
60
+
61
+ def prevent_locked_user_login
62
+ config = sorcery_config
63
+ if !self.unlocked?
64
+ self.unlock! if self.send(config.lock_expires_at_attribute_name) <= Time.now.utc
65
+ end
66
+ unlocked?
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -22,13 +22,15 @@ module Sorcery
22
22
  end
23
23
 
24
24
  module InstanceMethods
25
+ # You shouldn't really use this one - it's called by the controller's 'remember_me!' method.
25
26
  def remember_me!
26
27
  config = sorcery_config
27
28
  self.send(:"#{config.remember_me_token_attribute_name}=", generate_random_code)
28
29
  self.send(:"#{config.remember_me_token_expires_at_attribute_name}=", Time.now + config.remember_me_for)
29
30
  self.save!(:validate => false)
30
31
  end
31
-
32
+
33
+ # You shouldn't really use this one - it's called by the controller's 'forget_me!' method.
32
34
  def forget_me!
33
35
  config = sorcery_config
34
36
  self.send(:"#{config.remember_me_token_attribute_name}=", nil)