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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +27 -0
  3. data/CHANGELOG.md +6 -0
  4. data/CONTRIBUTING.md +59 -0
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +11 -11
  7. data/README.md +37 -4
  8. data/Rakefile +2 -4
  9. data/app/controllers/authenticate/passwords_controller.rb +3 -3
  10. data/app/controllers/authenticate/sessions_controller.rb +4 -4
  11. data/app/controllers/authenticate/users_controller.rb +5 -7
  12. data/app/mailers/authenticate_mailer.rb +6 -8
  13. data/authenticate.gemspec +8 -9
  14. data/lib/authenticate.rb +1 -1
  15. data/lib/authenticate/callbacks/authenticatable.rb +1 -2
  16. data/lib/authenticate/callbacks/brute_force.rb +1 -3
  17. data/lib/authenticate/callbacks/lifetimed.rb +2 -1
  18. data/lib/authenticate/callbacks/timeoutable.rb +3 -2
  19. data/lib/authenticate/callbacks/trackable.rb +1 -1
  20. data/lib/authenticate/configuration.rb +11 -7
  21. data/lib/authenticate/controller.rb +32 -23
  22. data/lib/authenticate/crypto/bcrypt.rb +3 -3
  23. data/lib/authenticate/debug.rb +7 -7
  24. data/lib/authenticate/engine.rb +4 -2
  25. data/lib/authenticate/lifecycle.rb +12 -22
  26. data/lib/authenticate/login_status.rb +4 -3
  27. data/lib/authenticate/model/brute_force.rb +4 -6
  28. data/lib/authenticate/model/db_password.rb +5 -14
  29. data/lib/authenticate/model/email.rb +7 -9
  30. data/lib/authenticate/model/lifetimed.rb +1 -2
  31. data/lib/authenticate/model/password_reset.rb +1 -3
  32. data/lib/authenticate/model/timeoutable.rb +14 -15
  33. data/lib/authenticate/model/trackable.rb +5 -4
  34. data/lib/authenticate/model/username.rb +3 -5
  35. data/lib/authenticate/modules.rb +37 -39
  36. data/lib/authenticate/session.rb +15 -23
  37. data/lib/authenticate/token.rb +3 -0
  38. data/lib/authenticate/user.rb +2 -6
  39. data/lib/authenticate/version.rb +1 -1
  40. data/lib/generators/authenticate/controllers/controllers_generator.rb +1 -2
  41. data/lib/generators/authenticate/helpers.rb +1 -2
  42. data/lib/generators/authenticate/install/install_generator.rb +31 -32
  43. data/lib/generators/authenticate/install/templates/authenticate.rb +0 -1
  44. data/lib/generators/authenticate/routes/routes_generator.rb +1 -2
  45. data/lib/generators/authenticate/views/USAGE +3 -2
  46. data/lib/generators/authenticate/views/views_generator.rb +1 -2
  47. data/spec/controllers/passwords_controller_spec.rb +5 -7
  48. data/spec/controllers/secured_controller_spec.rb +6 -6
  49. data/spec/controllers/sessions_controller_spec.rb +2 -2
  50. data/spec/controllers/users_controller_spec.rb +4 -4
  51. data/spec/features/brute_force_spec.rb +0 -2
  52. data/spec/features/max_session_lifetime_spec.rb +0 -1
  53. data/spec/features/password_reset_spec.rb +10 -19
  54. data/spec/features/password_update_spec.rb +0 -2
  55. data/spec/features/sign_out_spec.rb +0 -1
  56. data/spec/features/sign_up_spec.rb +0 -1
  57. data/spec/features/timeoutable_spec.rb +0 -1
  58. data/spec/model/brute_force_spec.rb +2 -3
  59. data/spec/model/configuration_spec.rb +2 -7
  60. data/spec/model/db_password_spec.rb +4 -6
  61. data/spec/model/email_spec.rb +1 -3
  62. data/spec/model/lifetimed_spec.rb +0 -3
  63. data/spec/model/modules_spec.rb +22 -0
  64. data/spec/model/password_reset_spec.rb +3 -10
  65. data/spec/model/session_spec.rb +4 -5
  66. data/spec/model/timeoutable_spec.rb +0 -1
  67. data/spec/model/token_spec.rb +1 -3
  68. data/spec/model/trackable_spec.rb +1 -2
  69. data/spec/model/user_spec.rb +0 -1
  70. data/spec/orm/active_record.rb +1 -1
  71. data/spec/spec_helper.rb +3 -11
  72. data/spec/support/controllers/controller_helpers.rb +1 -2
  73. data/spec/support/features/feature_helpers.rb +2 -4
  74. 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 the user if authenticated
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
- self.password_reset_sent_at && self.password_reset_sent_at.utc >= reset_within.ago.utc
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
- extend ActiveSupport::Concern
29
+ extend ActiveSupport::Concern
31
30
 
32
- def self.required_fields(_klass)
33
- [:last_access_at]
34
- end
31
+ def self.required_fields(_klass)
32
+ [:last_access_at]
33
+ end
35
34
 
36
- # Checks whether the user session has expired based on configured time.
37
- def timedout?
38
- return false if timeout_in.nil?
39
- return false if last_access_at.nil?
40
- last_access_at <= timeout_in.ago
41
- end
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
- private
42
+ private
44
43
 
45
- def timeout_in
46
- Authenticate.configuration.timeout_in
47
- end
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, new_current = self.current_sign_in_at, Time.now.utc
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, new_current = self.current_sign_in_ip, request.remote_ip
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 the user if authenticated
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
@@ -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
- # def self.required_fields(klass)
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
- constants = []
30
+ modules_to_include = []
28
31
  Authenticate.configuration.modules.each do |mod|
29
- # puts "load_modules about to load #{mod.to_s}"
30
- require "authenticate/model/#{mod.to_s}" if mod.is_a?(Symbol)
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
- constants << mod
36
+ modules_to_include << mod
33
37
  end
34
- check_fields constants
35
- constants.each { |mod|
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 module_symbol
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. If it does not
48
- # have all required fields, huck an exception.
49
- def check_fields modules
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 = self.new
53
+ instance = new
52
54
  modules.each do |mod|
53
55
  if mod.respond_to?(:required_fields)
54
- mod.required_fields(self).each do |field|
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
- # fail MissingAttribute.new(failed_attributes)
62
- Rails.logger.warn "The following attribute(s) is (are) missing on your user model: #{failed_attributes.join(", ")}"
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
- "The following attribute(s) is (are) missing on your user model: #{@attributes.join(", ")}"
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
-
@@ -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, &block)
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, { event: :authentication })
28
- Authenticate.lifecycle.run_callbacks(:after_authentication, @current_user, self, { event: :authentication })
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
- path: Authenticate.configuration.cookie_path,
91
- secure: Authenticate.configuration.secure_cookie,
92
- httponly: Authenticate.configuration.cookie_http_only,
93
- value: @current_user.session_token,
94
- expires: Authenticate.configuration.cookie_expiration.call
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
- # @cookies.signed[cookie_name] = cookie_hash
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
-
@@ -1,4 +1,7 @@
1
1
  module Authenticate
2
+ #
3
+ # A secure token, consisting of a big random number.
4
+ #
2
5
  class Token
3
6
  def self.new
4
7
  SecureRandom.hex(20).encode('UTF-8')
@@ -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
-