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
@@ -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
-