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
@@ -2,7 +2,7 @@ require 'email_validator'
|
|
2
2
|
|
3
3
|
module Authenticate
|
4
4
|
module Model
|
5
|
-
|
5
|
+
#
|
6
6
|
# Use :email as the identifier for the user. Email must be unique.
|
7
7
|
#
|
8
8
|
# = Columns
|
@@ -18,7 +18,7 @@ module Authenticate
|
|
18
18
|
#
|
19
19
|
# = Class Methods
|
20
20
|
# * credentials(params) - return the credentials required for authorization by email
|
21
|
-
# * authenticate(credentials) - find user with given email, validate their password, return
|
21
|
+
# * authenticate(credentials) - find user with given email, validate their password, return user if authenticated
|
22
22
|
# * normalize_email(email) - clean up the given email and return it.
|
23
23
|
# * find_by_credentials(credentials) - find and return the user with the email address in the credentials
|
24
24
|
#
|
@@ -37,9 +37,11 @@ module Authenticate
|
|
37
37
|
uniqueness: { allow_blank: true }
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
# Class methods for authenticating using email as the user identifier.
|
41
41
|
module ClassMethods
|
42
|
-
|
42
|
+
# Retrieve credentials from params.
|
43
|
+
#
|
44
|
+
# @return [id, pw]
|
43
45
|
def credentials(params)
|
44
46
|
return [] if params.nil? || params[:session].nil?
|
45
47
|
[params[:session][:email], params[:session][:password]]
|
@@ -54,18 +56,14 @@ module Authenticate
|
|
54
56
|
email = credentials[0]
|
55
57
|
find_by_normalized_email(email)
|
56
58
|
end
|
57
|
-
|
58
59
|
end
|
59
60
|
|
60
|
-
# Sets the email on this instance to the value returned by
|
61
|
-
# {.normalize_email}
|
61
|
+
# Sets the email on this instance to the value returned by class method #normalize_email
|
62
62
|
#
|
63
63
|
# @return [String]
|
64
64
|
def normalize_email
|
65
65
|
self.email = self.class.normalize_email(email)
|
66
66
|
end
|
67
67
|
end
|
68
|
-
|
69
68
|
end
|
70
69
|
end
|
71
|
-
|
@@ -2,7 +2,7 @@ require 'authenticate/callbacks/lifetimed'
|
|
2
2
|
|
3
3
|
module Authenticate
|
4
4
|
module Model
|
5
|
-
|
5
|
+
#
|
6
6
|
# Imposes a maximum allowed lifespan on a user's session, after which the session is expired and requires
|
7
7
|
# re-authentication.
|
8
8
|
#
|
@@ -41,7 +41,6 @@ module Authenticate
|
|
41
41
|
def max_session_lifetime
|
42
42
|
Authenticate.configuration.max_session_lifetime
|
43
43
|
end
|
44
|
-
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Authenticate
|
2
2
|
module Model
|
3
|
-
|
4
3
|
# Support 'forgot my password' functionality.
|
5
4
|
#
|
6
5
|
# = Columns
|
@@ -68,7 +67,7 @@ module Authenticate
|
|
68
67
|
def reset_password_period_valid?
|
69
68
|
reset_within = Authenticate.configuration.reset_password_within
|
70
69
|
return true if reset_within.nil?
|
71
|
-
|
70
|
+
password_reset_sent_at && password_reset_sent_at.utc >= reset_within.ago.utc
|
72
71
|
end
|
73
72
|
|
74
73
|
private
|
@@ -77,7 +76,6 @@ module Authenticate
|
|
77
76
|
self.password_reset_token = nil
|
78
77
|
self.password_reset_sent_at = nil
|
79
78
|
end
|
80
|
-
|
81
79
|
end
|
82
80
|
end
|
83
81
|
end
|
@@ -2,7 +2,6 @@ require 'authenticate/callbacks/timeoutable'
|
|
2
2
|
|
3
3
|
module Authenticate
|
4
4
|
module Model
|
5
|
-
|
6
5
|
# Expire user sessions that have not been accessed within a certain period of time.
|
7
6
|
# Expired users will be asked for credentials again.
|
8
7
|
#
|
@@ -27,24 +26,24 @@ module Authenticate
|
|
27
26
|
# * timeout_in - look up timeout period in config, @return [ActiveSupport::CoreExtensions::Numeric::Time]
|
28
27
|
#
|
29
28
|
module Timeoutable
|
30
|
-
|
29
|
+
extend ActiveSupport::Concern
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def self.required_fields(_klass)
|
32
|
+
[:last_access_at]
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
# Checks whether the user session has expired based on configured time.
|
36
|
+
def timedout?
|
37
|
+
return false if timeout_in.nil?
|
38
|
+
return false if last_access_at.nil?
|
39
|
+
last_access_at <= timeout_in.ago
|
40
|
+
end
|
42
41
|
|
43
|
-
|
42
|
+
private
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
def timeout_in
|
45
|
+
Authenticate.configuration.timeout_in
|
46
|
+
end
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
@@ -2,7 +2,7 @@ require 'authenticate/callbacks/trackable'
|
|
2
2
|
|
3
3
|
module Authenticate
|
4
4
|
module Model
|
5
|
-
|
5
|
+
#
|
6
6
|
# Track information about your user sign ins. This module is always enabled.
|
7
7
|
#
|
8
8
|
# = Methods
|
@@ -24,11 +24,13 @@ module Authenticate
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def update_tracked_fields(request)
|
27
|
-
old_current
|
27
|
+
old_current = current_sign_in_at
|
28
|
+
new_current = Time.now.utc
|
28
29
|
self.last_sign_in_at = old_current || new_current
|
29
30
|
self.current_sign_in_at = new_current
|
30
31
|
|
31
|
-
old_current
|
32
|
+
old_current = current_sign_in_ip
|
33
|
+
new_current = request.remote_ip
|
32
34
|
self.last_sign_in_ip = old_current || new_current
|
33
35
|
self.current_sign_in_ip = new_current
|
34
36
|
|
@@ -40,7 +42,6 @@ module Authenticate
|
|
40
42
|
update_tracked_fields(request)
|
41
43
|
save(validate: false)
|
42
44
|
end
|
43
|
-
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Authenticate
|
2
2
|
module Model
|
3
|
-
|
3
|
+
#
|
4
4
|
# Use :username as the identifier for the user. Username must be unique.
|
5
5
|
#
|
6
6
|
# = Columns
|
@@ -11,7 +11,7 @@ module Authenticate
|
|
11
11
|
#
|
12
12
|
# = class methods
|
13
13
|
# * credentials(params) - return the credentials required for authorization by username
|
14
|
-
# * authenticate(credentials) - find user with given username, validate their password, return
|
14
|
+
# * authenticate(credentials) - find user with given username, validate their password, return user if authenticated
|
15
15
|
# * find_by_credentials(credentials) - find and return the user with the username in the credentials
|
16
16
|
#
|
17
17
|
module Username
|
@@ -28,6 +28,7 @@ module Authenticate
|
|
28
28
|
uniqueness: { allow_blank: true }
|
29
29
|
end
|
30
30
|
|
31
|
+
# Class methods for managing username-based authentication
|
31
32
|
module ClassMethods
|
32
33
|
def credentials(params)
|
33
34
|
[params[:session][:username], params[:session][:password]]
|
@@ -42,10 +43,7 @@ module Authenticate
|
|
42
43
|
username = credentials[0]
|
43
44
|
find_by_username username
|
44
45
|
end
|
45
|
-
|
46
46
|
end
|
47
|
-
|
48
47
|
end
|
49
|
-
|
50
48
|
end
|
51
49
|
end
|
data/lib/authenticate/modules.rb
CHANGED
@@ -1,80 +1,78 @@
|
|
1
1
|
module Authenticate
|
2
|
+
#
|
3
|
+
# Modules injects Authenticate modules into the app User model.
|
4
|
+
#
|
5
|
+
# Any module being loaded into User can optionally define a class method `required_fields(klass)` defining
|
6
|
+
# any required attributes in the User model. For example, the :username module declares:
|
7
|
+
#
|
8
|
+
# module Username
|
9
|
+
# extend ActiveSupport::Concern
|
10
|
+
#
|
11
|
+
# def self.required_fields(klass)
|
12
|
+
# [:username]
|
13
|
+
# end
|
14
|
+
# ...
|
15
|
+
#
|
16
|
+
# If the model class is missing a required field, Authenticate will fail with a MissingAttribute error.
|
17
|
+
# The error will declare what required fields are missing.
|
2
18
|
module Modules
|
3
19
|
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
# Module to help Authenticate's user model load Authenticate modules.
|
6
|
-
#
|
7
|
-
# All Authenticate modules implement ActiveSupport::Concern.
|
8
|
-
#
|
9
|
-
# Modules can optionally define a class method to define what attributes they require present
|
10
|
-
# in the user model. For example, :username declares:
|
11
|
-
#
|
12
|
-
# module Username
|
13
|
-
# extend ActiveSupport::Concern
|
14
20
|
#
|
15
|
-
#
|
16
|
-
# [:username]
|
17
|
-
# end
|
18
|
-
# ...
|
21
|
+
# Class methods injected into User model.
|
19
22
|
#
|
20
|
-
# If the model class is missing a required field, Authenticate will fail with a MissingAttribute error.
|
21
|
-
# The error will declare what required fields are missing.
|
22
23
|
module ClassMethods
|
23
|
-
|
24
|
+
#
|
24
25
|
# Load all modules declared in Authenticate.configuration.modules.
|
25
26
|
# Requires them, then loads as a constant, then checks fields, and finally includes.
|
27
|
+
#
|
28
|
+
# @raise MissingAttribute if attributes required by Authenticate are missing.
|
26
29
|
def load_modules
|
27
|
-
|
30
|
+
modules_to_include = []
|
28
31
|
Authenticate.configuration.modules.each do |mod|
|
29
|
-
#
|
30
|
-
|
32
|
+
# The built-in modules are referred to by symbol. Additional module classes (constants) can be added
|
33
|
+
# via Authenticate.configuration.modules.
|
34
|
+
require "authenticate/model/#{mod}" if mod.is_a?(Symbol)
|
31
35
|
mod = load_constant(mod) if mod.is_a?(Symbol)
|
32
|
-
|
36
|
+
modules_to_include << mod
|
33
37
|
end
|
34
|
-
check_fields
|
35
|
-
|
36
|
-
include mod
|
37
|
-
}
|
38
|
+
check_fields modules_to_include
|
39
|
+
modules_to_include.each { |mod| include mod }
|
38
40
|
end
|
39
41
|
|
40
42
|
private
|
41
43
|
|
42
|
-
def load_constant
|
44
|
+
def load_constant(module_symbol)
|
43
45
|
Authenticate::Model.const_get(module_symbol.to_s.classify)
|
44
46
|
end
|
45
47
|
|
46
48
|
# For each module, look at the fields it requires. Ensure the User
|
47
|
-
# model including the module has the required fields.
|
48
|
-
#
|
49
|
-
def check_fields
|
49
|
+
# model including the module has the required fields.
|
50
|
+
# @raise MissingAttribute if required attributes are missing.
|
51
|
+
def check_fields(modules)
|
50
52
|
failed_attributes = []
|
51
|
-
instance =
|
53
|
+
instance = new
|
52
54
|
modules.each do |mod|
|
53
55
|
if mod.respond_to?(:required_fields)
|
54
|
-
mod.required_fields(self).each
|
55
|
-
failed_attributes << field unless instance.respond_to?(field)
|
56
|
-
end
|
56
|
+
mod.required_fields(self).each { |field| failed_attributes << field unless instance.respond_to?(field) }
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
60
|
if failed_attributes.any?
|
61
|
-
|
62
|
-
|
61
|
+
raise MissingAttribute.new(failed_attributes),
|
62
|
+
"Required attribute are missing on your user model: #{failed_attributes.join(', ')}"
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
66
65
|
end
|
67
66
|
|
67
|
+
# Thrown if required attributes are missing.
|
68
68
|
class MissingAttribute < StandardError
|
69
69
|
def initialize(attributes)
|
70
70
|
@attributes = attributes
|
71
71
|
end
|
72
72
|
|
73
73
|
def message
|
74
|
-
"
|
74
|
+
"Required attributes are missing on your user model: #{@attributes.join(', ')}"
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
78
77
|
end
|
79
78
|
end
|
80
|
-
|
data/lib/authenticate/session.rb
CHANGED
@@ -2,6 +2,7 @@ require 'authenticate/login_status'
|
|
2
2
|
require 'authenticate/debug'
|
3
3
|
|
4
4
|
module Authenticate
|
5
|
+
# Represents an Authenticate session.
|
5
6
|
class Session
|
6
7
|
include Debug
|
7
8
|
|
@@ -16,19 +17,19 @@ module Authenticate
|
|
16
17
|
|
17
18
|
# Finish user login process, *after* the user has been authenticated.
|
18
19
|
# Called when user creates an account or signs back into the app.
|
20
|
+
# Runs all callbacks checking for any login failure. If a login failure
|
21
|
+
# occurs, user is NOT logged in.
|
19
22
|
#
|
20
23
|
# @return [User]
|
21
|
-
def login(user
|
22
|
-
debug 'session.login()'
|
24
|
+
def login(user)
|
23
25
|
@current_user = user
|
24
26
|
@current_user.generate_session_token if user.present?
|
25
27
|
|
26
28
|
message = catch(:failure) do
|
27
|
-
Authenticate.lifecycle.run_callbacks(:after_set_user, @current_user, self,
|
28
|
-
Authenticate.lifecycle.run_callbacks(:after_authentication, @current_user, self,
|
29
|
+
Authenticate.lifecycle.run_callbacks(:after_set_user, @current_user, self, event: :authentication)
|
30
|
+
Authenticate.lifecycle.run_callbacks(:after_authentication, @current_user, self, event: :authentication)
|
29
31
|
end
|
30
32
|
|
31
|
-
debug "session.login after lifecycle callbacks, message: #{message}"
|
32
33
|
status = message.present? ? Failure.new(message) : Success.new
|
33
34
|
if status.success?
|
34
35
|
@current_user.save
|
@@ -37,9 +38,7 @@ module Authenticate
|
|
37
38
|
@current_user = nil
|
38
39
|
end
|
39
40
|
|
40
|
-
if block_given?
|
41
|
-
block.call(status)
|
42
|
-
end
|
41
|
+
yield(status) if block_given?
|
43
42
|
end
|
44
43
|
|
45
44
|
# Get the user represented by this session.
|
@@ -47,9 +46,7 @@ module Authenticate
|
|
47
46
|
# @return [User]
|
48
47
|
def current_user
|
49
48
|
debug 'session.current_user'
|
50
|
-
if @session_token.present?
|
51
|
-
@current_user ||= load_user
|
52
|
-
end
|
49
|
+
@current_user ||= load_user if @session_token.present?
|
53
50
|
@current_user
|
54
51
|
end
|
55
52
|
|
@@ -66,9 +63,7 @@ module Authenticate
|
|
66
63
|
# @return [void]
|
67
64
|
def deauthenticate
|
68
65
|
# nuke session_token in db
|
69
|
-
if current_user.present?
|
70
|
-
current_user.reset_session_token!
|
71
|
-
end
|
66
|
+
current_user.reset_session_token! if current_user.present?
|
72
67
|
|
73
68
|
# nuke notion of current_user
|
74
69
|
@current_user = nil
|
@@ -87,16 +82,15 @@ module Authenticate
|
|
87
82
|
|
88
83
|
def write_cookie
|
89
84
|
cookie_hash = {
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
85
|
+
path: Authenticate.configuration.cookie_path,
|
86
|
+
secure: Authenticate.configuration.secure_cookie,
|
87
|
+
httponly: Authenticate.configuration.cookie_http_only,
|
88
|
+
value: @current_user.session_token,
|
89
|
+
expires: Authenticate.configuration.cookie_expiration.call
|
95
90
|
}
|
96
91
|
cookie_hash[:domain] = Authenticate.configuration.cookie_domain if Authenticate.configuration.cookie_domain
|
97
|
-
#
|
92
|
+
# Consider adding an option for a signed cookie
|
98
93
|
@cookies[cookie_name] = cookie_hash
|
99
|
-
debug 'session.write_cookie WROTE COOKIE I HOPE. Cookie guts:' + @cookies[cookie_name].inspect
|
100
94
|
end
|
101
95
|
|
102
96
|
def cookie_name
|
@@ -106,7 +100,5 @@ module Authenticate
|
|
106
100
|
def load_user
|
107
101
|
Authenticate.configuration.user_model_class.where(session_token: @session_token).first
|
108
102
|
end
|
109
|
-
|
110
103
|
end
|
111
104
|
end
|
112
|
-
|
data/lib/authenticate/token.rb
CHANGED
data/lib/authenticate/user.rb
CHANGED
@@ -3,7 +3,6 @@ require 'authenticate/token'
|
|
3
3
|
require 'authenticate/callbacks/authenticatable'
|
4
4
|
|
5
5
|
module Authenticate
|
6
|
-
|
7
6
|
# Required to be included in your configured user class, which is `User` by
|
8
7
|
# default, but can be changed with {Configuration#user_model=}.
|
9
8
|
#
|
@@ -52,8 +51,8 @@ module Authenticate
|
|
52
51
|
save validate: false
|
53
52
|
end
|
54
53
|
|
54
|
+
# Class methods added to users.
|
55
55
|
module ClassMethods
|
56
|
-
|
57
56
|
def normalize_email(email)
|
58
57
|
email.to_s.downcase.gsub(/\s+/, '')
|
59
58
|
end
|
@@ -62,9 +61,6 @@ module Authenticate
|
|
62
61
|
def find_by_normalized_email(email)
|
63
62
|
find_by_email normalize_email(email)
|
64
63
|
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
64
|
+
end
|
68
65
|
end
|
69
66
|
end
|
70
|
-
|