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.
- data/Gemfile +1 -2
- data/LICENSE.txt +1 -1
- data/README.rdoc +55 -42
- data/Rakefile +2 -12
- data/VERSION +1 -1
- data/lib/sorcery/controller/submodules/activity_logging.rb +45 -0
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +8 -69
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +60 -0
- data/lib/sorcery/controller/submodules/session_timeout.rb +4 -1
- data/lib/sorcery/controller.rb +13 -7
- data/lib/sorcery/crypto_providers/bcrypt.rb +2 -6
- data/lib/sorcery/model/submodules/activity_logging.rb +35 -0
- data/lib/sorcery/model/submodules/brute_force_protection.rb +72 -0
- data/lib/sorcery/model/submodules/remember_me.rb +3 -1
- data/lib/sorcery/model/submodules/reset_password.rb +93 -0
- data/lib/sorcery/model/submodules/user_activation.rb +8 -6
- data/lib/sorcery/model.rb +14 -8
- data/lib/sorcery.rb +5 -1
- data/sorcery.gemspec +221 -0
- data/spec/Gemfile +1 -1
- data/spec/Gemfile.lock +2 -2
- data/spec/rails3/Gemfile +3 -2
- data/spec/rails3/Gemfile.lock +4 -2
- data/spec/rails3/app_root/app/controllers/application_controller.rb +6 -1
- data/spec/rails3/app_root/config/routes.rb +1 -0
- data/spec/rails3/app_root/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
- data/spec/rails3/app_root/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
- data/spec/rails3/app_root/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
- data/spec/rails3/controller_activity_logging_spec.rb +84 -0
- data/spec/rails3/controller_brute_force_protection_spec.rb +24 -41
- data/spec/rails3/controller_http_basic_auth_spec.rb +50 -0
- data/spec/rails3/controller_session_timeout_spec.rb +1 -0
- data/spec/rails3/controller_spec.rb +3 -3
- data/spec/rails3/spec_helper.rb +39 -19
- data/spec/rails3/user_activation_spec.rb +7 -7
- data/spec/rails3/user_activity_logging_spec.rb +36 -0
- data/spec/rails3/user_brute_force_protection_spec.rb +76 -0
- data/spec/rails3/user_reset_password_spec.rb +198 -0
- data/spec/rails3/user_spec.rb +8 -4
- metadata +40 -64
- data/features/support/env.rb +0 -13
- data/lib/sorcery/model/submodules/password_reset.rb +0 -64
- data/spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb +0 -9
- 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.
|
|
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
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
|
-
==
|
|
11
|
+
== Full Features List by module:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
83
|
-
|
|
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
|
|
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
|
data/lib/sorcery/controller.rb
CHANGED
|
@@ -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
|
|
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!(
|
|
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
|
|
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!(
|
|
98
|
-
Config.after_failed_login.each {|c| self.send(c,
|
|
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
|
-
|
|
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 ||=
|
|
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)
|