minimalist_authentication 1.2.0 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bda0fef1ab5c360af94e908ec11ba51059788808
4
- data.tar.gz: f691883871f612e2ed2f709736f3fb3eebf68d8b
3
+ metadata.gz: c706bf1ccf629eedb3a910b68a983ae2d4869f4d
4
+ data.tar.gz: 8d5f220c4f4ac6d91c445c3a946fc913ba5d1cbd
5
5
  SHA512:
6
- metadata.gz: 3fb4e425254e581abdf5e2fc9a7e461a4c5208513bcc122447acdf2db79e8dfd3db9b1595d58758b2c274ed667711863533e298ca469676c51091802273eb7bc
7
- data.tar.gz: b782a7c2a6070fa446636a040ac60b969014a17500e22d68fdca2332426163759dedb58843107b358096ff5896a3e2047c32c5ceaca120fed4e4cb3771a61c5a
6
+ metadata.gz: aeb1bfee96f489dd77baa1c3fa9059fdcc3e335cc9b50e387be72c5efd6f859f92c4827783e4a51722bf6482edfd0c1f961e43d6c65b93dd6f2467786aebb3d2
7
+ data.tar.gz: a0e7282735c59607dc0373a8940885659b8df6981ffeefe6e47535f99cc23b0058ea4084a9a9233f73562811179a208f29628b744afdefdd593e468b1760d33e
data/README.md CHANGED
@@ -16,12 +16,12 @@ $ bundle
16
16
 
17
17
  Create a user model with **email** for an identifier:
18
18
  ```bash
19
- bin/rails generate model user active:boolean email:string crypted_password:string salt:string last_logged_in_at:datetime
19
+ bin/rails generate model user active:boolean email:string password_hash:string last_logged_in_at:datetime
20
20
  ```
21
21
 
22
22
  OR create a user model with **username** for an identifier:
23
23
  ```bash
24
- bin/rails generate model user active:boolean username:string crypted_password:string salt:string last_logged_in_at:datetime
24
+ bin/rails generate model user active:boolean username:string password_hash:string last_logged_in_at:datetime
25
25
  ```
26
26
 
27
27
 
@@ -61,15 +61,47 @@ class ActiveSupport::TestCase
61
61
  end
62
62
  ```
63
63
 
64
- ## Example
64
+ ## Configuration
65
65
  Customize the configuration with an initializer. Create a **minimalist_authentication.rb** file in /Users/baldwina/git/brightways/config/initializers.
66
66
  ```ruby
67
67
  MinimalistAuthentication.configure do |configuration|
68
68
  configuration.user_model_name = 'CustomModelName' # default is '::User'
69
69
  configuration.session_key = :custom_session_key # default is ':user_id'
70
+ validate_email = true # default is true
71
+ validate_email_presence = true # default is true
70
72
  end
71
73
  ```
72
74
 
75
+
76
+ ## Fixtures
77
+ Use **MinimalistAuthentication::Password.create** to create a password for
78
+ fixture users.
79
+ ```yaml
80
+ example_user:
81
+ email: user@example.com
82
+ password_hash: <%= MinimalistAuthentication::Password.create('password') %>
83
+ ```
84
+
85
+
86
+ ## Conversions
87
+ Pre 2.0 versions of MinimalistAuthentication supported multiple hash algorithms
88
+ and stored the hashed password and salt as separate fields in the database
89
+ (crypted_password and salt). The current version of MinimalistAuthentication
90
+ uses BCrypt to hash passwords and stores the result in the **password_hash** field.
91
+
92
+ To convert from a pre 2.0 version add the **password_hash** to your user model
93
+ and run the conversion routine.
94
+ ```bash
95
+ bin/rails generate migration AddPasswordHashToUsers password_hash:string
96
+ ```
97
+ ```ruby
98
+ MinimalistAuthentication::Conversions::MergePasswordHash.run!
99
+ ```
100
+
101
+ When the conversion is complete the **crypted_password**, **salt**, and
102
+ **using_digest_version** fields can safely be removed.
103
+
104
+
73
105
  ## Build
74
106
  [![Build Status](https://travis-ci.org/wwidea/minimalist_authentication.svg?branch=master)](https://travis-ci.org/wwidea/minimalist_authentication)
75
107
 
@@ -1,7 +1,9 @@
1
1
  require 'minimalist_authentication/engine'
2
2
  require 'minimalist_authentication/configuration'
3
3
  require 'minimalist_authentication/user'
4
+ require 'minimalist_authentication/password'
4
5
  require 'minimalist_authentication/null_password'
5
6
  require 'minimalist_authentication/controller'
6
7
  require 'minimalist_authentication/sessions'
7
8
  require 'minimalist_authentication/test_helper'
9
+ require 'minimalist_authentication/conversions/merge_password_hash'
@@ -23,9 +23,20 @@ module MinimalistAuthentication
23
23
  # Defaults to '::User'
24
24
  attr_accessor :user_model_name
25
25
 
26
+ # Toggle all email validations.
27
+ # Defaults to true.
28
+ attr_accessor :validate_email
29
+
30
+ # Toggle email presence validation.
31
+ # Defaults to true.
32
+ # Note: validate_email_presence is only checked if validate_email is true.
33
+ attr_accessor :validate_email_presence
34
+
26
35
  def initialize
27
- self.user_model_name = '::User'
28
- self.session_key = :user_id
36
+ self.user_model_name = '::User'
37
+ self.session_key = :user_id
38
+ self.validate_email = true
39
+ self.validate_email_presence = true
29
40
  end
30
41
 
31
42
  # Returns the user_model class
@@ -0,0 +1,38 @@
1
+ module MinimalistAuthentication
2
+ module Conversions
3
+ class MergePasswordHash
4
+
5
+ class << self
6
+ def run!
7
+ user_model.where(using_digest_version: 3, password_hash: nil).each do |user|
8
+ new(user).update!
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def user_model
15
+ MinimalistAuthentication.configuration.user_model
16
+ end
17
+ end
18
+
19
+ attr_accessor :user
20
+
21
+ delegate :salt, :crypted_password, to: :user
22
+
23
+ def initialize(user)
24
+ self.user = user
25
+ end
26
+
27
+ def update!
28
+ user.update_column(:password_hash, merged_password_hash)
29
+ end
30
+
31
+ private
32
+
33
+ def merged_password_hash
34
+ "#{salt}#{crypted_password}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ module MinimalistAuthentication
2
+ class Password
3
+ class << self
4
+ # Create a bcrypt password hash with a calibrated cost factor.
5
+ def create(secret)
6
+ new ::BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost))
7
+ end
8
+
9
+ # Cache the calibrated bcrypt cost factor.
10
+ def cost
11
+ @bcrypt_cost ||= calibrate_cost
12
+ end
13
+
14
+ private
15
+
16
+ # Calibrates cost so that new user passwords can automatically take
17
+ # advantage of faster server hardware in the future.
18
+ # Sets cost to BCrypt::Engine::MIN_COST in the test environment
19
+ def calibrate_cost
20
+ ::Rails.env.test? ? ::BCrypt::Engine::MIN_COST : ::BCrypt::Engine.calibrate(750)
21
+ end
22
+ end
23
+
24
+ attr_accessor :bcrypt_password
25
+
26
+ # Returns a password object wrapping a valid BCrypt password or a NullPassword
27
+ def initialize(password_hash)
28
+ begin
29
+ self.bcrypt_password = ::BCrypt::Password.new(password_hash)
30
+ rescue ::BCrypt::Errors::InvalidHash
31
+ self.bcrypt_password = NullPassword.new
32
+ end
33
+ end
34
+
35
+ # Delegate methods to bcrypt_password
36
+ delegate :==, :to_s, :cost, to: :bcrypt_password
37
+
38
+ # Temporary access to checksum and salt for backwards compatibility
39
+ delegate :checksum, :salt, to: :bcrypt_password
40
+
41
+ # Checks if the password_hash cost factor is less than the current cost.
42
+ def stale?
43
+ cost < self.class.cost
44
+ end
45
+ end
46
+ end
@@ -4,7 +4,6 @@ module MinimalistAuthentication
4
4
  post session_path, params: { user: { email: users(user_fixture_name).email, password: password } }
5
5
  end
6
6
 
7
-
8
7
  def current_user
9
8
  @current_user ||= load_user_from_session
10
9
  end
@@ -7,58 +7,65 @@ module MinimalistAuthentication
7
7
  GUEST_USER_EMAIL = 'guest'
8
8
  EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
9
9
 
10
- # Recalibrates cost when class is loaded so that new user passwords
11
- # can automatically take advantage of faster server hardware in the
12
- # future for better encryption.
13
- # sets cost to BCrypt::Engine::MIN_COST in the test environment
14
- CALIBRATED_BCRYPT_COST = (::Rails.env.test? ? ::BCrypt::Engine::MIN_COST : ::BCrypt::Engine.calibrate(750))
15
-
16
10
  included do
11
+ # Stores the plain text password.
17
12
  attr_accessor :password
18
- before_save :encrypt_password
19
13
 
20
- # email validations
14
+ # Hashes and stores the password on save.
15
+ before_save :hash_password
16
+
17
+ # Email validations
21
18
  validates_presence_of :email, if: :validate_email_presence?
22
19
  validates_uniqueness_of :email, allow_blank: true, if: :validate_email?
23
20
  validates_format_of :email, allow_blank: true, with: EMAIL_REGEX, if: :validate_email?
24
21
 
25
- # password validations
26
- validates_presence_of :password, if: :password_required?
27
- validates_confirmation_of :password, if: :password_required?
28
- validates_length_of :password, within: 6..40, if: :password_required?
22
+ # Password validations
23
+ validates_presence_of :password, if: :validate_password?
24
+ validates_confirmation_of :password, if: :validate_password?
25
+ validates_length_of :password, within: 8..40, if: :validate_password?
29
26
 
27
+ # Active scope
30
28
  scope :active, ->(active = true) { where active: active }
31
29
  end
32
30
 
33
31
  module ClassMethods
32
+ # Authenticates a user form the params provied. Expects a params hash with
33
+ # email or username and passwod keys.
34
+ # Params examples:
35
+ # { email: 'user@example.com', password: 'abc123' }
36
+ # { username: 'user', password: 'abc123' }
37
+ # Returns user upon successful authentcation.
38
+ # Otherwise returns nil.
34
39
  def authenticate(params)
40
+ # extract email or username and the associated value
35
41
  field, value = params.to_h.select { |key, value| %w(email username).include?(key.to_s) && value.present? }.first
42
+ # return nil if field, value, or password is blank
36
43
  return if field.blank? || value.blank? || params[:password].blank?
44
+ # attempt to find the user using field and value
37
45
  user = active.where(field => value).first
46
+ # check if a user was found and if they can be authenticated
38
47
  return unless user && user.authenticated?(params[:password])
48
+ # return the authenticated user
39
49
  return user
40
50
  end
41
51
 
42
- def password_hash(password)
43
- ::BCrypt::Password.create(password, cost: calibrated_bcrypt_cost)
44
- end
45
-
46
- def calibrated_bcrypt_cost
47
- CALIBRATED_BCRYPT_COST
48
- end
49
-
52
+ # Returns a frozen user with the email set to GUEST_USER_EMAIL.
50
53
  def guest
51
- new(email: GUEST_USER_EMAIL)
54
+ new(email: GUEST_USER_EMAIL).freeze
52
55
  end
53
56
  end
54
57
 
58
+ # Returns true if the user is active.
55
59
  def active?
56
60
  active
57
61
  end
58
62
 
63
+ # Return true if password matches the hashed_password.
64
+ # If successful checks for an outdated password_hash and updates if
65
+ # necessary.
59
66
  def authenticated?(password)
60
- if bcrypt_password == password
61
- update_encryption(password) if bcrypt_password.cost < self.class.calibrated_bcrypt_cost
67
+ if password_object == password
68
+ update_hash!(password) if password_object.stale?
62
69
  return true
63
70
  end
64
71
 
@@ -70,54 +77,49 @@ module MinimalistAuthentication
70
77
  update_column(:last_logged_in_at, Time.current)
71
78
  end
72
79
 
80
+ # Check if user is a guest based on their email attribute
73
81
  def is_guest?
74
82
  email == GUEST_USER_EMAIL
75
83
  end
76
84
 
77
85
  private
78
86
 
79
- def password_required?
80
- active? && (crypted_password.blank? || !password.blank?)
81
- end
82
-
83
- def update_encryption(password)
87
+ # Set self.password to password, hash, and save
88
+ def update_hash!(password)
84
89
  self.password = password
85
- encrypt_password
90
+ hash_password
86
91
  save
87
92
  end
88
93
 
89
- def encrypt_password
94
+ # Hash password and store in hash_password unless password is blank.
95
+ def hash_password
90
96
  return if password.blank?
91
- password_hash = self.class.password_hash(password)
92
- self.salt = password_hash.salt
93
- self.crypted_password = password_hash.checksum
94
- end
95
-
96
- def bcrypt_password
97
- valid_hash? ? ::BCrypt::Password.new(password_hash) : null_password
98
- end
99
-
100
- def valid_hash?
101
- ::BCrypt::Password.valid_hash?(password_hash)
97
+ self.password_hash = Password.create(password)
102
98
  end
103
99
 
104
- def password_hash
105
- "#{salt}#{crypted_password}"
100
+ # Retuns a MinimalistAuthentication::Password object.
101
+ def password_object
102
+ Password.new(password_hash)
106
103
  end
107
104
 
108
- def null_password
109
- MinimalistAuthentication::NullPassword.new
105
+ # Requre password for active users that either do no have a password hash
106
+ # stored OR are attempting to set a new password.
107
+ def validate_password?
108
+ active? && (password_hash.blank? || password.present?)
110
109
  end
111
110
 
112
- # email validation
111
+ # Validate email for active users.
112
+ # Applications can turn off email validation by setting the validate_email
113
+ # configuration attribute to false.
113
114
  def validate_email?
114
- # allows applications to turn off all email validation
115
- active?
115
+ MinimalistAuthentication.configuration.validate_email && active?
116
116
  end
117
117
 
118
+ # Validate email presence for active users.
119
+ # Applications can turn offf email presence validation by setting
120
+ # validate_email_presence configuration attribute to false.
118
121
  def validate_email_presence?
119
- # allows applications to turn off email presence validation
120
- validate_email?
122
+ MinimalistAuthentication.configuration.validate_email_presence && validate_email?
121
123
  end
122
124
  end
123
125
  end
@@ -1,3 +1,3 @@
1
1
  module MinimalistAuthentication
2
- VERSION = '1.2.0'
2
+ VERSION = '2.0.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minimalist_authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Baldwin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-09-14 00:00:00.000000000 Z
12
+ date: 2017-09-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -77,8 +77,10 @@ files:
77
77
  - lib/minimalist_authentication.rb
78
78
  - lib/minimalist_authentication/configuration.rb
79
79
  - lib/minimalist_authentication/controller.rb
80
+ - lib/minimalist_authentication/conversions/merge_password_hash.rb
80
81
  - lib/minimalist_authentication/engine.rb
81
82
  - lib/minimalist_authentication/null_password.rb
83
+ - lib/minimalist_authentication/password.rb
82
84
  - lib/minimalist_authentication/sessions.rb
83
85
  - lib/minimalist_authentication/test_helper.rb
84
86
  - lib/minimalist_authentication/user.rb