authenticate 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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
-