has_editable_password 0.1.1 → 0.2.1
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 +8 -8
- data/lib/has_editable_password.rb +53 -19
- data/lib/version.rb +1 -1
- data/spec/has_editable_password_spec.rb +23 -9
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            !binary "U0hBMQ==":
         | 
| 3 3 | 
             
              metadata.gz: !binary |-
         | 
| 4 | 
            -
                 | 
| 4 | 
            +
                ZjYyMDNiNzRhNWQ2MjI0NGI3YWU1MGEwZWI3ZjZiZTg2MjkxNjFhNQ==
         | 
| 5 5 | 
             
              data.tar.gz: !binary |-
         | 
| 6 | 
            -
                 | 
| 6 | 
            +
                NTIzMmE0ODliNzM4YjBhMTc3MjY5ZjI0NjViY2FiNDNkZjg5YTI5Mw==
         | 
| 7 7 | 
             
            SHA512:
         | 
| 8 8 | 
             
              metadata.gz: !binary |-
         | 
| 9 | 
            -
                 | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 9 | 
            +
                Y2Y4ODU1OTUzY2RhOTU1NjJkNzE0NDYzMTU5NmIwNmI2OTUyMjQ3ZjE3M2Q1
         | 
| 10 | 
            +
                M2MzMjIzZWUxOGIyOTU0NmQwNzVhNmNiNmIyMjFlMTJmYzdjOGI2YmUzN2I1
         | 
| 11 | 
            +
                ODg2MDM1YmE3OGE5MDc5MTEyYzM5OTBiZTcyOWQ3YmQ0ZmMwMmE=
         | 
| 12 12 | 
             
              data.tar.gz: !binary |-
         | 
| 13 | 
            -
                 | 
| 14 | 
            -
                 | 
| 15 | 
            -
                 | 
| 13 | 
            +
                YzNhMzQ5YmQ2MTE5YWZiNGE2MzQzODMwZjEyMzc3YTNkNzg5NDNkZTJhM2Iz
         | 
| 14 | 
            +
                NjBmMDFmNjI2ZjQ0YjczMmMzZWVjZmIyMDkwZjkzMDU4YmY3MjFhZWMzODZk
         | 
| 15 | 
            +
                MzYxZmJmMDI4YzE5NjY4NWQ1ZDdhNjY3MzcxNWYzMTRkMzMwODg=
         | 
| @@ -2,6 +2,9 @@ require 'active_support/concern' | |
| 2 2 | 
             
            require 'active_model/secure_password'
         | 
| 3 3 | 
             
            require 'securerandom'
         | 
| 4 4 |  | 
| 5 | 
            +
            ##
         | 
| 6 | 
            +
            # Just include this module into your model to have all of its nice features
         | 
| 7 | 
            +
            # :)
         | 
| 5 8 | 
             
            module HasEditablePassword
         | 
| 6 9 | 
             
              extend ActiveSupport::Concern
         | 
| 7 10 | 
             
              include ActiveModel::SecurePassword
         | 
| @@ -14,17 +17,13 @@ module HasEditablePassword | |
| 14 17 |  | 
| 15 18 | 
             
                validate :password_change, on: :update, if: :password_digest_changed?
         | 
| 16 19 |  | 
| 20 | 
            +
                ##
         | 
| 21 | 
            +
                # Overrides the has_secure_password implementation to provide nice features:
         | 
| 22 | 
            +
                # * Password backup
         | 
| 23 | 
            +
                # * Password update timestamp
         | 
| 17 24 | 
             
                def password=(value)
         | 
| 18 25 | 
             
                  @old_password_digest = password_digest unless @old_password_digest or password_digest.blank?
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  unless password_digest.blank? or password_digest_changed?
         | 
| 21 | 
            -
                    self.previous_password_digest = password_digest if respond_to? :previous_password_digest=
         | 
| 22 | 
            -
                  end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  unless password_digest_changed?
         | 
| 25 | 
            -
                    self.password_digest_updated = Time.now if respond_to? :password_digest_updated=
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
             | 
| 26 | 
            +
                  changing_password
         | 
| 28 27 | 
             
                  super(value)
         | 
| 29 28 | 
             
                end
         | 
| 30 29 | 
             
              end
         | 
| @@ -36,11 +35,11 @@ module HasEditablePassword | |
| 36 35 | 
             
              # +password_recovery_token_creation+ to the current time.
         | 
| 37 36 | 
             
              # Unless specified it calls +save+ to store the token in the database.
         | 
| 38 37 | 
             
              #
         | 
| 39 | 
            -
              #  | 
| 40 | 
            -
              # | 
| 41 | 
            -
              # | 
| 42 | 
            -
              #  | 
| 43 | 
            -
              # | 
| 38 | 
            +
              # * +:length+ - this is the length of the SecureRandom string generated
         | 
| 39 | 
            +
              # as the token. Since the token is base64_encoded it will be longer than
         | 
| 40 | 
            +
              # that. Default is 32.
         | 
| 41 | 
            +
              # * +:save+ - you can use this if you don't want save to be called.
         | 
| 42 | 
            +
              #     generate_recovery_token(save: false)
         | 
| 44 43 | 
             
              #
         | 
| 45 44 | 
             
              def generate_recovery_token(options = {})
         | 
| 46 45 | 
             
                token = SecureRandom.urlsafe_base64(options.delete(:length) || 32)
         | 
| @@ -51,11 +50,12 @@ module HasEditablePassword | |
| 51 50 | 
             
              end
         | 
| 52 51 |  | 
| 53 52 | 
             
              ##
         | 
| 54 | 
            -
              # Returns true if the + | 
| 53 | 
            +
              # Returns true if the +token+ matches with the stored one and the
         | 
| 55 54 | 
             
              # token creation time is less than 24 hours ago
         | 
| 56 55 | 
             
              #
         | 
| 57 | 
            -
               | 
| 58 | 
            -
             | 
| 56 | 
            +
              # If +token+ is +nil+, the stored token is compared with +@recovery_token+
         | 
| 57 | 
            +
              def valid_recovery_token?(token = nil)
         | 
| 58 | 
            +
                recovery_token_match?(token) and !recovery_token_expired?
         | 
| 59 59 | 
             
              end
         | 
| 60 60 |  | 
| 61 61 | 
             
              ##
         | 
| @@ -75,22 +75,56 @@ module HasEditablePassword | |
| 75 75 | 
             
              end
         | 
| 76 76 |  | 
| 77 77 | 
             
              private
         | 
| 78 | 
            +
              # True if the token has been updated more than 24 hours ago
         | 
| 78 79 | 
             
              def recovery_token_expired?
         | 
| 79 80 | 
             
                # 86400 = seconds in a day
         | 
| 80 81 | 
             
                (Time.now - self.password_recovery_token_creation).round >= 86400
         | 
| 81 82 | 
             
              end
         | 
| 82 83 |  | 
| 83 | 
            -
               | 
| 84 | 
            -
             | 
| 84 | 
            +
              ##
         | 
| 85 | 
            +
              # Compares password_recovery_token with:
         | 
| 86 | 
            +
              # * @recovery_token if +token+ is nil
         | 
| 87 | 
            +
              # * +token+ otherwise
         | 
| 88 | 
            +
              #
         | 
| 89 | 
            +
              # True if password_recovery_token matches.
         | 
| 90 | 
            +
              # False if password_recovery_token is nil
         | 
| 91 | 
            +
              # False if @recovery_token (or +token+) do not match the stored token
         | 
| 92 | 
            +
              def recovery_token_match?(token = nil)
         | 
| 93 | 
            +
                BCrypt::Password.new(self.password_recovery_token) == (token || @recovery_token)
         | 
| 85 94 | 
             
              rescue
         | 
| 86 95 | 
             
                false
         | 
| 87 96 | 
             
              end
         | 
| 88 97 |  | 
| 98 | 
            +
              ##
         | 
| 99 | 
            +
              # True if a valid recovery token or current password have been set
         | 
| 100 | 
            +
              #
         | 
| 89 101 | 
             
              def allow_password_change?
         | 
| 90 102 | 
             
                valid_recovery_token? or current_password_match?
         | 
| 91 103 | 
             
              end
         | 
| 92 104 |  | 
| 105 | 
            +
              ##
         | 
| 106 | 
            +
              # Validation called on :update when the password_digest is touched.
         | 
| 107 | 
            +
              # Sets an error on password unless the current_password or a valid recovery_token is set
         | 
| 93 108 | 
             
              def password_change
         | 
| 94 109 | 
             
                errors[:password] << 'Unauthorized to change the password' unless allow_password_change?
         | 
| 95 110 | 
             
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              def changing_password
         | 
| 113 | 
            +
                unless password_digest_changed?
         | 
| 114 | 
            +
                  update_previous_digest
         | 
| 115 | 
            +
                  update_digest_timestamp
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              def update_previous_digest
         | 
| 120 | 
            +
                if respond_to?(:previous_password_digest=) and !password_digest.blank?
         | 
| 121 | 
            +
                  self.previous_password_digest = password_digest
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
              def update_digest_timestamp
         | 
| 126 | 
            +
                if respond_to? :password_digest_updated=
         | 
| 127 | 
            +
                  self.password_digest_updated = Time.now
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
              end
         | 
| 96 130 | 
             
            end
         | 
    
        data/lib/version.rb
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            VERSION = '0. | 
| 1 | 
            +
            VERSION = '0.2.1'
         | 
| @@ -136,23 +136,37 @@ describe HasEditablePassword do | |
| 136 136 | 
             
                end
         | 
| 137 137 |  | 
| 138 138 | 
             
                context 'the creation is less than 24 hours ago' do
         | 
| 139 | 
            -
                   | 
| 140 | 
            -
                     | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 139 | 
            +
                  context 'the argument is nil' do
         | 
| 140 | 
            +
                    it 'returns false if the stored token is a random string' do
         | 
| 141 | 
            +
                      user.generate_recovery_token
         | 
| 142 | 
            +
                      user.recovery_token = "deadbeef"
         | 
| 143 | 
            +
                      expect(user.valid_recovery_token?).to be_false
         | 
| 144 | 
            +
                    end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                    context 'the stored token is a base64 string' do
         | 
| 147 | 
            +
                      it 'returns false if the token does not match' do
         | 
| 148 | 
            +
                        user.generate_recovery_token
         | 
| 149 | 
            +
                        user.recovery_token = SecureRandom.urlsafe_base64
         | 
| 150 | 
            +
                        expect(user.valid_recovery_token?).to be_false
         | 
| 151 | 
            +
                      end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                      it 'returns true if the token matches' do
         | 
| 154 | 
            +
                        token = user.generate_recovery_token
         | 
| 155 | 
            +
                        user.recovery_token = token
         | 
| 156 | 
            +
                        expect(user.valid_recovery_token?).to be_true
         | 
| 157 | 
            +
                      end
         | 
| 158 | 
            +
                    end
         | 
| 143 159 | 
             
                  end
         | 
| 144 160 |  | 
| 145 | 
            -
                  context 'the  | 
| 161 | 
            +
                  context 'the argument is not nil' do
         | 
| 146 162 | 
             
                    it 'returns false if the token does not match' do
         | 
| 147 163 | 
             
                      user.generate_recovery_token
         | 
| 148 | 
            -
                      user. | 
| 149 | 
            -
                      expect(user.valid_recovery_token?).to be_false
         | 
| 164 | 
            +
                      expect(user.valid_recovery_token?(SecureRandom.urlsafe_base64)).to be_false
         | 
| 150 165 | 
             
                    end
         | 
| 151 166 |  | 
| 152 167 | 
             
                    it 'returns true if the token matches' do
         | 
| 153 168 | 
             
                      token = user.generate_recovery_token
         | 
| 154 | 
            -
                      user. | 
| 155 | 
            -
                      expect(user.valid_recovery_token?).to be_true
         | 
| 169 | 
            +
                      expect(user.valid_recovery_token?(token)).to be_true
         | 
| 156 170 | 
             
                    end
         | 
| 157 171 | 
             
                  end
         | 
| 158 172 | 
             
                end
         |