authenticate 0.3.1 → 0.3.2
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.
- 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
|
-
|