authenticate 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +27 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +59 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +11 -11
- data/README.md +37 -4
- data/Rakefile +2 -4
- data/app/controllers/authenticate/passwords_controller.rb +3 -3
- data/app/controllers/authenticate/sessions_controller.rb +4 -4
- data/app/controllers/authenticate/users_controller.rb +5 -7
- data/app/mailers/authenticate_mailer.rb +6 -8
- data/authenticate.gemspec +8 -9
- data/lib/authenticate.rb +1 -1
- data/lib/authenticate/callbacks/authenticatable.rb +1 -2
- data/lib/authenticate/callbacks/brute_force.rb +1 -3
- data/lib/authenticate/callbacks/lifetimed.rb +2 -1
- data/lib/authenticate/callbacks/timeoutable.rb +3 -2
- data/lib/authenticate/callbacks/trackable.rb +1 -1
- data/lib/authenticate/configuration.rb +11 -7
- data/lib/authenticate/controller.rb +32 -23
- data/lib/authenticate/crypto/bcrypt.rb +3 -3
- data/lib/authenticate/debug.rb +7 -7
- data/lib/authenticate/engine.rb +4 -2
- data/lib/authenticate/lifecycle.rb +12 -22
- data/lib/authenticate/login_status.rb +4 -3
- data/lib/authenticate/model/brute_force.rb +4 -6
- data/lib/authenticate/model/db_password.rb +5 -14
- data/lib/authenticate/model/email.rb +7 -9
- data/lib/authenticate/model/lifetimed.rb +1 -2
- data/lib/authenticate/model/password_reset.rb +1 -3
- data/lib/authenticate/model/timeoutable.rb +14 -15
- data/lib/authenticate/model/trackable.rb +5 -4
- data/lib/authenticate/model/username.rb +3 -5
- data/lib/authenticate/modules.rb +37 -39
- data/lib/authenticate/session.rb +15 -23
- data/lib/authenticate/token.rb +3 -0
- data/lib/authenticate/user.rb +2 -6
- data/lib/authenticate/version.rb +1 -1
- data/lib/generators/authenticate/controllers/controllers_generator.rb +1 -2
- data/lib/generators/authenticate/helpers.rb +1 -2
- data/lib/generators/authenticate/install/install_generator.rb +31 -32
- data/lib/generators/authenticate/install/templates/authenticate.rb +0 -1
- data/lib/generators/authenticate/routes/routes_generator.rb +1 -2
- data/lib/generators/authenticate/views/USAGE +3 -2
- data/lib/generators/authenticate/views/views_generator.rb +1 -2
- data/spec/controllers/passwords_controller_spec.rb +5 -7
- data/spec/controllers/secured_controller_spec.rb +6 -6
- data/spec/controllers/sessions_controller_spec.rb +2 -2
- data/spec/controllers/users_controller_spec.rb +4 -4
- data/spec/features/brute_force_spec.rb +0 -2
- data/spec/features/max_session_lifetime_spec.rb +0 -1
- data/spec/features/password_reset_spec.rb +10 -19
- data/spec/features/password_update_spec.rb +0 -2
- data/spec/features/sign_out_spec.rb +0 -1
- data/spec/features/sign_up_spec.rb +0 -1
- data/spec/features/timeoutable_spec.rb +0 -1
- data/spec/model/brute_force_spec.rb +2 -3
- data/spec/model/configuration_spec.rb +2 -7
- data/spec/model/db_password_spec.rb +4 -6
- data/spec/model/email_spec.rb +1 -3
- data/spec/model/lifetimed_spec.rb +0 -3
- data/spec/model/modules_spec.rb +22 -0
- data/spec/model/password_reset_spec.rb +3 -10
- data/spec/model/session_spec.rb +4 -5
- data/spec/model/timeoutable_spec.rb +0 -1
- data/spec/model/token_spec.rb +1 -3
- data/spec/model/trackable_spec.rb +1 -2
- data/spec/model/user_spec.rb +0 -1
- data/spec/orm/active_record.rb +1 -1
- data/spec/spec_helper.rb +3 -11
- data/spec/support/controllers/controller_helpers.rb +1 -2
- data/spec/support/features/feature_helpers.rb +2 -4
- metadata +29 -26
@@ -1,6 +1,10 @@
|
|
1
|
+
# Authenticate
|
1
2
|
module Authenticate
|
3
|
+
#
|
4
|
+
# Configuration for Authenticate.
|
5
|
+
#
|
2
6
|
class Configuration
|
3
|
-
|
7
|
+
#
|
4
8
|
# ActiveRecord model class name that represents your user. Specify as a String.
|
5
9
|
#
|
6
10
|
# Defaults to '::User'.
|
@@ -224,12 +228,11 @@ module Authenticate
|
|
224
228
|
# @return [Boolean]
|
225
229
|
attr_accessor :debug
|
226
230
|
|
227
|
-
|
228
231
|
def initialize
|
229
232
|
# Defaults
|
230
233
|
@debug = false
|
231
234
|
@cookie_name = 'authenticate_session_token'
|
232
|
-
@cookie_expiration =
|
235
|
+
@cookie_expiration = -> { 1.year.from_now.utc }
|
233
236
|
@cookie_domain = nil
|
234
237
|
@cookie_path = '/'
|
235
238
|
@secure_cookie = false
|
@@ -286,10 +289,12 @@ module Authenticate
|
|
286
289
|
modules << :brute_force if @max_consecutive_bad_logins_allowed
|
287
290
|
modules
|
288
291
|
end
|
289
|
-
|
290
292
|
end # end of Configuration class
|
291
|
-
|
292
|
-
|
293
|
+
#
|
294
|
+
# Access to Authenticate's configuration, e.g.:
|
295
|
+
#
|
296
|
+
# puts Authenticate.configuration.redirect_url
|
297
|
+
#
|
293
298
|
def self.configuration
|
294
299
|
@configuration ||= Configuration.new
|
295
300
|
end
|
@@ -301,5 +306,4 @@ module Authenticate
|
|
301
306
|
def self.configure
|
302
307
|
yield configuration
|
303
308
|
end
|
304
|
-
|
305
309
|
end
|
@@ -1,4 +1,27 @@
|
|
1
1
|
module Authenticate
|
2
|
+
#
|
3
|
+
# The authenticate controller methods.
|
4
|
+
#
|
5
|
+
# Typically, you include this concern into your ApplicationController. A basic implementation might look like this:
|
6
|
+
#
|
7
|
+
# class ApplicationController < ActionController::Base
|
8
|
+
# include Authenticate::Controller
|
9
|
+
# before_action :require_authentication
|
10
|
+
# protect_from_forgery with: :exception
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Methods, generally called from authenticate's app controllers:
|
14
|
+
# * authenticate(params) - validate a user's identity
|
15
|
+
# * login(user, &block) - complete login after validating a user's identity, creating an Authenticate session
|
16
|
+
# * logout - log a user out, invalidating their Authenticate session.
|
17
|
+
#
|
18
|
+
# Action/Filter:
|
19
|
+
# * require_authentication - restrict access to authenticated users, often from ApplicationController
|
20
|
+
#
|
21
|
+
# Helpers, used anywhere:
|
22
|
+
# * current_user - get the current user from the current Authenticate session.
|
23
|
+
# * authenticated? - has the user been logged in?
|
24
|
+
#
|
2
25
|
module Controller
|
3
26
|
extend ActiveSupport::Concern
|
4
27
|
include Debug
|
@@ -8,7 +31,6 @@ module Authenticate
|
|
8
31
|
attr_writer :authenticate_session
|
9
32
|
end
|
10
33
|
|
11
|
-
|
12
34
|
# Validate a user's identity with (typically) email/ID & password, and return the User if valid, or nil.
|
13
35
|
# After calling this, call login(user) to complete the process.
|
14
36
|
def authenticate(params)
|
@@ -16,14 +38,12 @@ module Authenticate
|
|
16
38
|
Authenticate.configuration.user_model_class.authenticate(credentials)
|
17
39
|
end
|
18
40
|
|
19
|
-
|
20
41
|
# Complete the user's sign in process: after calling authenticate, or after user creates account.
|
21
42
|
# Runs all valid callbacks and sends the user a session token.
|
22
43
|
def login(user, &block)
|
23
44
|
authenticate_session.login user, &block
|
24
45
|
end
|
25
46
|
|
26
|
-
|
27
47
|
# Log the user out. Typically used in session controller.
|
28
48
|
#
|
29
49
|
# class SessionsController < ActionController::Base
|
@@ -37,7 +57,6 @@ module Authenticate
|
|
37
57
|
authenticate_session.deauthenticate
|
38
58
|
end
|
39
59
|
|
40
|
-
|
41
60
|
# Use this filter as a before_action to restrict controller actions to authenticated users.
|
42
61
|
# Consider using in application_controller to restrict access to all controllers.
|
43
62
|
#
|
@@ -53,18 +72,14 @@ module Authenticate
|
|
53
72
|
#
|
54
73
|
def require_authentication
|
55
74
|
debug 'Controller::require_authentication'
|
56
|
-
unless authenticated?
|
57
|
-
unauthorized
|
58
|
-
end
|
59
|
-
|
75
|
+
unauthorized unless authenticated?
|
60
76
|
message = catch(:failure) do
|
61
77
|
current_user = authenticate_session.current_user
|
62
|
-
Authenticate.lifecycle.run_callbacks(:after_set_user, current_user, authenticate_session,
|
78
|
+
Authenticate.lifecycle.run_callbacks(:after_set_user, current_user, authenticate_session, event: :set_user)
|
63
79
|
end
|
64
80
|
unauthorized(message) if message
|
65
81
|
end
|
66
82
|
|
67
|
-
|
68
83
|
# Has the user been logged in? Exposed as a helper, can be called from views.
|
69
84
|
#
|
70
85
|
# <% if authenticated? %>
|
@@ -77,7 +92,6 @@ module Authenticate
|
|
77
92
|
authenticate_session.authenticated?
|
78
93
|
end
|
79
94
|
|
80
|
-
|
81
95
|
# Get the current user from the current Authenticate session.
|
82
96
|
# Exposed as a helper , can be called from controllers, views, and other helpers.
|
83
97
|
#
|
@@ -103,9 +117,7 @@ module Authenticate
|
|
103
117
|
authenticate_session.deauthenticate
|
104
118
|
respond_to do |format|
|
105
119
|
format.any(:js, :json, :xml) { head :unauthorized }
|
106
|
-
format.any {
|
107
|
-
redirect_unauthorized(msg)
|
108
|
-
}
|
120
|
+
format.any { redirect_unauthorized(msg) }
|
109
121
|
end
|
110
122
|
end
|
111
123
|
|
@@ -113,7 +125,7 @@ module Authenticate
|
|
113
125
|
store_location
|
114
126
|
|
115
127
|
if flash_message
|
116
|
-
flash[:notice] = flash_message
|
128
|
+
flash[:notice] = flash_message # TODO: use locales
|
117
129
|
end
|
118
130
|
|
119
131
|
if authenticated?
|
@@ -123,13 +135,11 @@ module Authenticate
|
|
123
135
|
end
|
124
136
|
end
|
125
137
|
|
126
|
-
|
127
138
|
def redirect_back_or(default)
|
128
139
|
redirect_to(stored_location || default)
|
129
140
|
clear_stored_location
|
130
141
|
end
|
131
142
|
|
132
|
-
|
133
143
|
# Used as the redirect location when {#unauthorized} is called and there is a
|
134
144
|
# currently signed in user.
|
135
145
|
#
|
@@ -152,11 +162,11 @@ module Authenticate
|
|
152
162
|
def store_location
|
153
163
|
if request.get?
|
154
164
|
value = {
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
165
|
+
expires: nil,
|
166
|
+
httponly: true,
|
167
|
+
path: nil,
|
168
|
+
secure: Authenticate.configuration.secure_cookie,
|
169
|
+
value: request.original_fullpath
|
160
170
|
}
|
161
171
|
cookies[:authenticate_return_to] = value
|
162
172
|
end
|
@@ -173,6 +183,5 @@ module Authenticate
|
|
173
183
|
def authenticate_session
|
174
184
|
@authenticate_session ||= Authenticate::Session.new(request, cookies)
|
175
185
|
end
|
176
|
-
|
177
186
|
end
|
178
187
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Authenticate
|
2
2
|
module Crypto
|
3
|
-
|
3
|
+
#
|
4
4
|
# All crypto providers must implement encrypt(secret) and match?(secret, encrypted)
|
5
5
|
module BCrypt
|
6
6
|
require 'bcrypt'
|
@@ -20,11 +20,11 @@ module Authenticate
|
|
20
20
|
|
21
21
|
def cost=(val)
|
22
22
|
if val < ::BCrypt::Engine::MIN_COST
|
23
|
-
|
23
|
+
msg = "bcrypt cost cannot be set below the engine's min cost (#{::BCrypt::Engine::MIN_COST})"
|
24
|
+
raise ArgumentError.new(msg), msg
|
24
25
|
end
|
25
26
|
@cost = val
|
26
27
|
end
|
27
|
-
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
data/lib/authenticate/debug.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module Authenticate
|
2
|
+
#
|
3
|
+
# Simple debug output for gem.
|
4
|
+
#
|
2
5
|
module Debug
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
|
5
|
-
|
6
8
|
def debug(msg)
|
7
|
-
if defined?(Rails) && defined?(Rails.logger)
|
8
|
-
Rails.logger.info msg.to_s
|
9
|
-
|
10
|
-
puts msg.to_s
|
9
|
+
if defined?(Rails) && defined?(Rails.logger) && Authenticate.configuration.debug
|
10
|
+
Rails.logger.info msg.to_s
|
11
|
+
elsif Authenticate.configuration.debug
|
12
|
+
puts msg.to_s
|
11
13
|
end
|
12
14
|
end
|
13
|
-
|
14
|
-
|
15
15
|
end
|
16
16
|
end
|
data/lib/authenticate/engine.rb
CHANGED
@@ -2,8 +2,11 @@ require 'authenticate'
|
|
2
2
|
require 'rails'
|
3
3
|
|
4
4
|
module Authenticate
|
5
|
+
#
|
6
|
+
# Authenticate Rails engine.
|
7
|
+
# Filter password, token, from spewing out.
|
8
|
+
#
|
5
9
|
class Engine < ::Rails::Engine
|
6
|
-
|
7
10
|
initializer 'authenticate.filter' do |app|
|
8
11
|
app.config.filter_parameters += [:password, :token]
|
9
12
|
end
|
@@ -12,6 +15,5 @@ module Authenticate
|
|
12
15
|
g.test_framework :rspec
|
13
16
|
g.fixture_replacement :factory_girl, dir: 'spec/factories'
|
14
17
|
end
|
15
|
-
|
16
18
|
end
|
17
19
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# Authenticate Lifecycle methods within
|
1
2
|
module Authenticate
|
2
|
-
|
3
|
+
#
|
3
4
|
# Lifecycle stores and runs callbacks for authorization events.
|
4
5
|
#
|
5
6
|
# Heavily borrowed from warden (https://github.com/hassox/warden).
|
@@ -37,22 +38,22 @@ module Authenticate
|
|
37
38
|
#
|
38
39
|
class Lifecycle
|
39
40
|
include Debug
|
40
|
-
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@conditions = [:only, :except, :event].freeze
|
44
|
+
end
|
41
45
|
|
42
46
|
# This callback is triggered after the first time a user is set during per-hit authorization, or during login.
|
43
47
|
def after_set_user(options = {}, method = :push, &block)
|
44
48
|
add_callback(after_set_user_callbacks, options, method, &block)
|
45
49
|
end
|
46
50
|
|
47
|
-
|
48
|
-
|
49
51
|
# A callback to run after the user successfully authenticates, during the login process.
|
50
52
|
# Mechanically identical to [#after_set_user].
|
51
53
|
def after_authentication(options = {}, method = :push, &block)
|
52
54
|
add_callback(after_authentication_callbacks, options, method, &block)
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
57
|
# Run callbacks of the given kind.
|
57
58
|
#
|
58
59
|
# * kind - :authenticate or :after_set_user
|
@@ -64,27 +65,16 @@ module Authenticate
|
|
64
65
|
def run_callbacks(kind, *args) # args - |user, session, opts|
|
65
66
|
# Last callback arg MUST be a Hash
|
66
67
|
options = args.last
|
67
|
-
|
68
|
-
|
69
|
-
# each callback has 'conditions' stored with it
|
70
|
-
send("#{kind}_callbacks").each do |callback, conditions|
|
71
|
-
conditions = conditions.dup # make a copy, we mutate it
|
72
|
-
debug "Lifecycle.running callback -- #{conditions.inspect}"
|
73
|
-
conditions.delete_if {|key, _val| !@@conditions.include? key}
|
74
|
-
# debug "conditions after filter:#{conditions.inspect}"
|
68
|
+
send("#{kind}_callbacks").each do |callback, conditions| # each callback has 'conditions' stored with it
|
69
|
+
conditions = conditions.dup.delete_if { |key, _val| !@conditions.include? key }
|
75
70
|
invalid = conditions.find do |key, value|
|
76
|
-
# debug "!!!!!!! conditions key:#{key} value:#{value} options[key]:#{options[key].inspect}"
|
77
|
-
# debug("!value.include?(options[key]):#{!value.include?(options[key])}") if value.is_a?(Array)
|
78
71
|
value.is_a?(Array) ? !value.include?(options[key]) : (value != options[key])
|
79
72
|
end
|
80
|
-
debug "Lifecycle.callback invalid? #{invalid.inspect}"
|
81
73
|
callback.call(*args) unless invalid
|
82
74
|
end
|
83
|
-
debug "FINISHED Lifecycle.run_callbacks #{kind}"
|
84
75
|
nil
|
85
76
|
end
|
86
77
|
|
87
|
-
|
88
78
|
def prepend_after_authentication(options = {}, &block)
|
89
79
|
after_authentication(options, :unshift, &block)
|
90
80
|
end
|
@@ -97,7 +87,6 @@ module Authenticate
|
|
97
87
|
callbacks.send(method, [block, options])
|
98
88
|
end
|
99
89
|
|
100
|
-
|
101
90
|
# set event: to run callback on based on options
|
102
91
|
def process_opts(options)
|
103
92
|
if options.key?(:only)
|
@@ -108,7 +97,6 @@ module Authenticate
|
|
108
97
|
options
|
109
98
|
end
|
110
99
|
|
111
|
-
|
112
100
|
def after_set_user_callbacks
|
113
101
|
@after_set_user_callbacks ||= []
|
114
102
|
end
|
@@ -118,7 +106,9 @@ module Authenticate
|
|
118
106
|
end
|
119
107
|
end
|
120
108
|
|
121
|
-
|
109
|
+
# Invoke lifecycle methods. Example:
|
110
|
+
# Authenticate.lifecycle.run_callbacks(:after_set_user, current_user, authenticate_session, { event: :set_user })
|
111
|
+
#
|
122
112
|
def self.lifecycle
|
123
113
|
@lifecycle ||= Lifecycle.new
|
124
114
|
end
|
@@ -126,4 +116,4 @@ module Authenticate
|
|
126
116
|
def self.lifecycle=(lifecycle)
|
127
117
|
@lifecycle = lifecycle
|
128
118
|
end
|
129
|
-
end
|
119
|
+
end
|
@@ -1,14 +1,17 @@
|
|
1
1
|
module Authenticate
|
2
|
-
|
2
|
+
#
|
3
3
|
# Indicate login attempt was successful. Allows caller to supply a block to login() predicated on success?
|
4
|
+
#
|
4
5
|
class Success
|
5
6
|
def success?
|
6
7
|
true
|
7
8
|
end
|
8
9
|
end
|
9
10
|
|
11
|
+
#
|
10
12
|
# Indicate login attempt was a failure, with a message.
|
11
13
|
# Allows caller to supply a block to login() predicated on success?
|
14
|
+
#
|
12
15
|
class Failure
|
13
16
|
# The reason the sign in failed.
|
14
17
|
attr_reader :message
|
@@ -22,6 +25,4 @@ module Authenticate
|
|
22
25
|
false
|
23
26
|
end
|
24
27
|
end
|
25
|
-
|
26
28
|
end
|
27
|
-
|
@@ -2,8 +2,7 @@ require 'authenticate/callbacks/brute_force'
|
|
2
2
|
|
3
3
|
module Authenticate
|
4
4
|
module Model
|
5
|
-
|
6
|
-
|
5
|
+
#
|
7
6
|
# Protect from brute force attacks. Lock accounts that have too many failed consecutive logins.
|
8
7
|
# Todo: email user to allow unlocking via a token.
|
9
8
|
#
|
@@ -37,7 +36,6 @@ module Authenticate
|
|
37
36
|
[:failed_logins_count, :lock_expires_at]
|
38
37
|
end
|
39
38
|
|
40
|
-
|
41
39
|
def register_failed_login!
|
42
40
|
self.failed_logins_count ||= 0
|
43
41
|
self.failed_logins_count += 1
|
@@ -45,11 +43,11 @@ module Authenticate
|
|
45
43
|
end
|
46
44
|
|
47
45
|
def lock!
|
48
|
-
|
46
|
+
update_attribute(:lock_expires_at, Time.now.utc + lockout_period)
|
49
47
|
end
|
50
48
|
|
51
49
|
def unlock!
|
52
|
-
|
50
|
+
update_attributes(failed_logins_count: 0, lock_expires_at: nil)
|
53
51
|
end
|
54
52
|
|
55
53
|
def locked?
|
@@ -57,7 +55,7 @@ module Authenticate
|
|
57
55
|
end
|
58
56
|
|
59
57
|
def unlocked?
|
60
|
-
|
58
|
+
lock_expires_at.nil?
|
61
59
|
end
|
62
60
|
|
63
61
|
private
|
@@ -1,9 +1,8 @@
|
|
1
1
|
require 'authenticate/crypto/bcrypt'
|
2
2
|
|
3
|
-
|
4
3
|
module Authenticate
|
5
4
|
module Model
|
6
|
-
|
5
|
+
#
|
7
6
|
# Encrypts and stores a password in the database to validate the authenticity of a user while logging in.
|
8
7
|
#
|
9
8
|
# Authenticate can plug in any crypto provider, but currently only features BCrypt.
|
@@ -37,28 +36,23 @@ module Authenticate
|
|
37
36
|
attr_accessor :password_changing
|
38
37
|
validates :password,
|
39
38
|
presence: true,
|
40
|
-
length:{ in: password_length },
|
39
|
+
length: { in: password_length },
|
41
40
|
unless: :skip_password_validation?
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
43
|
def password_match?(password)
|
47
|
-
match?(password,
|
44
|
+
match?(password, encrypted_password)
|
48
45
|
end
|
49
46
|
|
50
47
|
def password=(new_password)
|
51
48
|
@password = new_password
|
52
|
-
|
53
|
-
if new_password.present?
|
54
|
-
self.encrypted_password = encrypt(new_password)
|
55
|
-
end
|
49
|
+
self.encrypted_password = encrypt(new_password) if new_password.present?
|
56
50
|
end
|
57
51
|
|
58
52
|
private
|
59
53
|
|
54
|
+
# Class methods for database password management.
|
60
55
|
module ClassMethods
|
61
|
-
|
62
56
|
# We only have one crypto provider at the moment, but look up the provider in the config.
|
63
57
|
def crypto_provider
|
64
58
|
Authenticate.configuration.crypto_provider || Authenticate::Crypto::BCrypt
|
@@ -69,13 +63,10 @@ module Authenticate
|
|
69
63
|
end
|
70
64
|
end
|
71
65
|
|
72
|
-
|
73
66
|
# If we already have an encrypted password and it's not changing, skip the validation.
|
74
67
|
def skip_password_validation?
|
75
68
|
encrypted_password.present? && !password_changing
|
76
69
|
end
|
77
|
-
|
78
70
|
end
|
79
71
|
end
|
80
72
|
end
|
81
|
-
|