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
@@ -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 = -> { 1.year.from_now.utc }
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, {event: :set_user })
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 # TODO use locales
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
- expires: nil,
156
- httponly: true,
157
- path: nil,
158
- secure: Authenticate.configuration.secure_cookie,
159
- value: request.original_fullpath
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
- raise ArgumentError.new("bcrypt cost cannot be set below the engine's min cost (#{::BCrypt::Engine::MIN_COST})")
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
@@ -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 if Authenticate.configuration.debug
9
- else
10
- puts msg.to_s if Authenticate.configuration.debug
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
@@ -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
- @@conditions = [:only, :except, :event]
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
- debug "START Lifecycle.run_callbacks kind:#{kind} options:#{options.inspect}"
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
- self.update_attribute(:lock_expires_at, Time.now.utc + lockout_period)
46
+ update_attribute(:lock_expires_at, Time.now.utc + lockout_period)
49
47
  end
50
48
 
51
49
  def unlock!
52
- self.update_attributes({failed_logins_count: 0, lock_expires_at: nil})
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
- self.lock_expires_at.nil?
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, self.encrypted_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
-