minimalist_authentication 1.2.0 → 2.0.0

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