devise-secure_password 1.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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +74 -0
  3. data/Dockerfile +44 -0
  4. data/Dockerfile.prev +44 -0
  5. data/Gemfile +13 -0
  6. data/Gemfile.lock +280 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +326 -0
  9. data/Rakefile +11 -0
  10. data/app/controllers/devise/passwords_with_policy_controller.rb +52 -0
  11. data/app/views/devise/passwords_with_policy/edit.html.erb +16 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +6 -0
  14. data/config/locales/en.yml +71 -0
  15. data/devise-secure_password.gemspec +57 -0
  16. data/docker-entrypoint.sh +6 -0
  17. data/gemfiles/rails-5_0_6.gemfile +17 -0
  18. data/gemfiles/rails-5_1_4.gemfile +16 -0
  19. data/lib/devise/secure_password.rb +70 -0
  20. data/lib/devise/secure_password/controllers/active_helpers.rb +40 -0
  21. data/lib/devise/secure_password/controllers/devise_helpers.rb +64 -0
  22. data/lib/devise/secure_password/hooks/password_requires_regular_updates.rb +5 -0
  23. data/lib/devise/secure_password/models/password_disallows_frequent_changes.rb +60 -0
  24. data/lib/devise/secure_password/models/password_disallows_frequent_reuse.rb +71 -0
  25. data/lib/devise/secure_password/models/password_has_required_content.rb +131 -0
  26. data/lib/devise/secure_password/models/password_requires_regular_updates.rb +56 -0
  27. data/lib/devise/secure_password/models/previous_password.rb +20 -0
  28. data/lib/devise/secure_password/routes.rb +11 -0
  29. data/lib/devise/secure_password/version.rb +5 -0
  30. data/lib/generators/devise/secure_password/install_generator.rb +30 -0
  31. data/lib/generators/devise/templates/README.txt +21 -0
  32. data/lib/generators/devise/templates/secure_password.rb +43 -0
  33. data/lib/support/string/character_counter.rb +53 -0
  34. data/pkg/devise-secure_password-1.0.0.gem +0 -0
  35. metadata +471 -0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017-2018, Valimail, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # Devise Secure Password Extension
2
+
3
+ [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
4
+
5
+ The __Devise Secure Password Extension__ is a user account password policy enforcement gem that can be
6
+ added to a Rails project to enforce password policies. The gem is implemented as an extension to the Rails
7
+ [devise](https://github.com/plataformatec/devise) authentication solution gem and requires that __devise__ is installed
8
+ as well.
9
+
10
+ ## Build Status
11
+
12
+ | Service | rails 5.1.4 |
13
+ |:-----------|:-----------:|
14
+ | Circle CI | [![Circle CI](https://circleci.com/gh/ValiMail/devise-secure_password/tree/master.svg?style=shield&circle-token=cd173d5f9d2944a9b14737c2d4339b20b08565cf)]() |
15
+
16
+ ## Overview
17
+
18
+ The __Devise Secure Password Extension__ is composed of the following modules:
19
+
20
+ - __password_has_required_content__: require that passwords consist of a specific number (configurable) of letters,
21
+ numbers, and special characters (symbols)
22
+ - __password_disallows_frequent_reuse__: prevent the reuse of a number (configurable) of previous passwords when a user
23
+ changes their password
24
+ - __password_disallows_frequent_changes__: prevent the user from changing their password more than once within a time
25
+ duration (configurable)
26
+ - __password_requires_regular_updates__: require that a user change their password following a time duration
27
+ (configurable)
28
+
29
+ ## Compatibility
30
+
31
+ The goal of this project is to provide compatibility for officially supported stable releases of [Ruby](https://www.ruby-lang.org/en/downloads/)
32
+ and [Ruby on Rails](http://guides.rubyonrails.org/maintenance_policy.html). More specifically, the following releases
33
+ are currently supported by the __Devise Secure Password Extension__:
34
+
35
+ - Ruby on Rails: __5.1.Z__, __5.0.Z__ (current and previous stable release)
36
+ - Ruby: __2.5.0__, __2.4.3__ (current and previous stable release)
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's Gemfile:
41
+
42
+ ```ruby
43
+ gem 'devise', '~> 4.2'
44
+ gem 'devise-secure_password', '~> 1.0.0'
45
+ ```
46
+
47
+ And then execute:
48
+
49
+ ```shell
50
+ prompt> bundle
51
+ ```
52
+
53
+ Or install it yourself as:
54
+
55
+ ```shell
56
+ prompt> gem install devise-secure_password
57
+ ```
58
+
59
+ Finally, run the generator:
60
+
61
+ ```shell
62
+ prompt> rails generate devise:secure_password:install
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ### Configuration
68
+
69
+ The __Devise Secure Password Extension__ exposes configuration parameters as outlined below. Commented out configuration
70
+ parameters reflect the default settings.
71
+
72
+ ```ruby
73
+ Devise.setup do |config|
74
+ # ==> Configuration for the Devise Secure Password extension
75
+ # Module: password_has_required_content
76
+ #
77
+ # Configure password content requirements including the number of uppercase,
78
+ # lowercase, number, and special characters that are required. To configure the
79
+ # minimum and maximum length refer to the Devise config.password_length
80
+ # standard configuration parameter.
81
+
82
+ # Passwords consist of at least one uppercase letter (latin A-Z):
83
+ # config.password_required_uppercase_count = 1
84
+
85
+ # Passwords consist of at least one lowercase characters (latin a-z):
86
+ # config.password_required_lowercase_count = 1
87
+
88
+ # Passwords consist of at least one number (0-9):
89
+ # config.password_required_number_count = 1
90
+
91
+ # Passwords consist of at least one special character (!@#$%^&*()_+-=[]{}|'):
92
+ # config.password_required_special_character_count = 1
93
+
94
+ # ==> Configuration for the Devise Secure Password extension
95
+ # Module: password_disallows_frequent_reuse
96
+ #
97
+ # Passwords cannot be reused. A user's last 24 password hashes are saved:
98
+ # config.password_previously_used_count = 24
99
+
100
+ # ==> Configuration for the Devise Secure Password extension
101
+ # Module: password_disallows_frequent_changes
102
+ # *Requires* password_disallows_frequent_reuse
103
+ #
104
+ # Passwords cannot be changed more frequently than once per day:
105
+ # config.password_minimum_age = 1.day
106
+
107
+ # ==> Configuration for the Devise Secure Password extension
108
+ # Module: password_requires_regular_updates
109
+ # *Requires* password_disallows_frequent_reuse
110
+ #
111
+ # Passwords must be changed every 60 days:
112
+ # config.password_maximum_age = 60.days
113
+ end
114
+ ```
115
+
116
+ Enable the __Devise Secure Password Extension__ enforcement in your Devise model(s):
117
+
118
+ ```ruby
119
+ devise :password_has_required_content, :password_disallows_frequent_reuse,
120
+ :password_disallows_frequent_changes, :password_requires_regular_updates
121
+ ```
122
+
123
+ Usually, you would append these after your selection of Devise modules. So your configuration will more likely look like
124
+ the following:
125
+
126
+ ```ruby
127
+ class User < ApplicationRecord
128
+ # Include default devise modules. Others available are:
129
+ # :confirmable, :lockable, :timeoutable and :omniauthable
130
+ devise :database_authenticatable, :registerable,
131
+ :recoverable, :rememberable, :trackable, :validatable,
132
+ :password_has_required_content, :password_disallows_frequent_reuse,
133
+ :password_disallows_frequent_changes, :password_requires_regular_updates
134
+ ...
135
+ <YOUR USER MODEL CONTENT FOLLOWS>
136
+ end
137
+ ```
138
+
139
+ >NOTE: Both `:password_disallows_frequent_changes` and `:password_requires_regular_updates` are dependent upon the
140
+ previous passwords memorization implemented by the `:password_disallows_frequent_reuse` module.
141
+
142
+ ### Database migration
143
+
144
+ The following database migration needs to be applied:
145
+
146
+ ```shell
147
+ prompt> rails generate migration create_previous_passwords salt:string encrypted_password:string user:references
148
+ ```
149
+
150
+ Edit the resulting file to disallow null values for the hash and to add indexes for both hash and user_id fields:
151
+
152
+ ```ruby
153
+ class CreatePreviousPasswords < ActiveRecord::Migration[5.1]
154
+ def change
155
+ create_table :previous_passwords do |t|
156
+ t.string :salt, null: false
157
+ t.string :encrypted_password, null: false
158
+ t.references :user, foreign_key: true
159
+
160
+ t.timestamps
161
+ end
162
+
163
+ add_index :previous_passwords, :encrypted_password
164
+ add_index :previous_passwords, [:user_id, :created_at]
165
+ end
166
+ end
167
+
168
+ ```
169
+
170
+ And then:
171
+
172
+ ```shell
173
+ prompt> bundle exec rake db:migrate
174
+ ```
175
+
176
+ <a name="running-tests"></a>
177
+
178
+ ## Running Tests
179
+
180
+ This document assumes that you already have a [functioning ruby install](https://rvm.io/).
181
+
182
+ ### Default Rails target
183
+
184
+ The __Devise Secure Password Extension__ provides compatibility for officially supported stable releases of Ruby on
185
+ Rails. To configure and test the default target (the most-recent supported Rails release):
186
+
187
+ ```bash
188
+ prompt> bundle
189
+ prompt> bundle exec rake
190
+ ```
191
+
192
+ ### Selecting an alternate Rails target
193
+
194
+ To determine the Ruby on Rails versions supported by this release, run the following commands:
195
+
196
+ ```bash
197
+ prompt> gem install flay ruby2ruby rubucop rspec
198
+ prompt> rake test:spec:targets
199
+
200
+ Available Rails targets: 5.0.6, 5.1.4
201
+ ```
202
+
203
+ Reconfigure the project by specifying the correct Gemfile when running bundler, followed by running tests:
204
+
205
+ ```bash
206
+ prompt> BUNDLE_GEMFILE=gemfiles/rails-5_0_6.gemfile bundle
207
+ prompt> BUNDLE_GEMFILE=gemfiles/rails-5_0_6.gemfile bundle exec rake
208
+ ```
209
+
210
+ The only time you need to define the `BUNDLE_GEMFILE` environment variable is when testing a non-default target.
211
+
212
+ ### Testing with code coverage (SimpleCov)
213
+
214
+ SimpleCov tests are enabled by defining the `test:spec:coverage` rake task:
215
+
216
+ ```bash
217
+ prompt> bundle exec rake test:spec:coverage
218
+ ```
219
+
220
+ A brief summary will be output at the end of the run but a more extensive eport will be saved in the `coverage`
221
+ directory (under the top-level project directory).
222
+
223
+ ### Testing with headless Chrome
224
+
225
+ You will need to install the [ChromeDriver >= v2.3.4](https://sites.google.com/a/chromium.org/chromedriver/downloads)
226
+ for testing.
227
+
228
+ ```bash
229
+ prompt> brew install chromedriver
230
+ ```
231
+
232
+ >NOTE: __ChromeDriver__ < 2.33 has a bug for testing clickable targets; therefore, install >= 2.3.4.
233
+
234
+ You can always install [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) by downloading and then
235
+ unpacking into the `/usr/local/bin` directory.
236
+
237
+ ### Testing inside the spec/rails-app-X_y_z
238
+
239
+ To debug from inside of the dummy rails-app you will need to first install the rails bin stubs and then perform a db
240
+ migration:
241
+
242
+ ```bash
243
+ prompt> cd spec/rails-app-X_y_z
244
+ prompt> rake app:update:bin
245
+ prompt> RAILS_ENV=development bundle exec rake db:migrate
246
+ ```
247
+
248
+ Remember, the dummy app is not meant to be a full featured rails app: there is just enough functionality to test the
249
+ gem feature set.
250
+
251
+ ### Running benchmarks
252
+
253
+ Available benchmarks can be run as follows:
254
+
255
+ ```bash
256
+ prompt> bundle exec rake test:benchmark
257
+ ```
258
+
259
+ Benchmarks are run within an RSpec context but are not run along with other tests as benchmarks merely seek to measure
260
+ performance and not enforce set performance targets.
261
+
262
+ ### Screenshots
263
+
264
+ Failing tests that invoke the JavaScript driver will result in both the failing html along with a screenshot of the
265
+ page output to be saved in the `spec/rails-app-X_y_z/tmp/capybara` snapshot directory.
266
+
267
+ >NOTE: On __circleci__ the snapshots will be captured as artifacts.
268
+
269
+ The snapshot directory will be pruned automatically between runs.
270
+
271
+ ## Docker
272
+
273
+ This repository includes a [Dockerfile](https://docs.docker.com/engine/reference/builder/) to facilitate testing in and
274
+ using [Docker](https://www.docker.com/).
275
+
276
+ To start the container simply build and launch the image:
277
+
278
+ ```bash
279
+ prompt> docker build -t secure-password-dev .
280
+ prompt> docker run -it --rm secure-password-dev /bin/bash
281
+ ```
282
+
283
+ The above `docker run` command will start the container, connect you to the command line within the project home
284
+ directory where you can issue the tests as documented in the [Running Tests](#running-tests) section above. When you exit
285
+ the shell, the container will be removed.
286
+
287
+ ### Running tests in a Docker container
288
+
289
+ The Docker container is derived from the latest [circleci/ruby](https://hub.docker.com/r/circleci/ruby/) image. It is
290
+ critical that you update the bundler inside of the Docker image as the `circleci` user (i.e. the default user) before
291
+ initiating any development work including tests.
292
+
293
+ ```bash
294
+ prompt> gem update bundler
295
+ ```
296
+
297
+ ## Contributing
298
+
299
+ Bug reports and pull requests are welcome on GitHub at https://github.com/valimail/devise-secure_password. This project
300
+ is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
301
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
302
+
303
+ ### Basic guidelines for contributors
304
+
305
+ 1 Fork it
306
+
307
+ 2 Create your feature branch (`git checkout -b my-new-feature`)
308
+
309
+ 3 Commit your changes (`git commit -am 'Add some feature'`)
310
+
311
+ 4 Push to the branch (`git push origin my-new-feature`)
312
+
313
+ 5 Create new Pull Request
314
+
315
+ >NOTE: Contributions should always be based on the `master` branch. You may be asked to [rebase](https://git-scm.com/docs/git-rebase)
316
+ your contributions on the tip of the `master` branch, this is normal and is to be expected if the `master` branch has
317
+ moved ahead since your pull request was opened, discussed, and accepted.
318
+
319
+ ## License
320
+
321
+ The __Devise Secure Password Extension__ gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
322
+
323
+ ## Code of Conduct
324
+
325
+ Everyone interacting in the __Devise Secure Password Extension__ project’s codebases and issue trackers is expected to
326
+ follow the [code of conduct](https://github.com/valimail/devise-secure_password/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake'
4
+
5
+ # load all rake tasks
6
+ Dir.glob(File.expand_path('lib/tasks/*.rake', __dir__)).each do |file|
7
+ import file
8
+ end
9
+
10
+ # require 'yard'
11
+ # YARD::Rake::YardocTask.new
@@ -0,0 +1,52 @@
1
+ module Devise
2
+ class PasswordsWithPolicyController < DeviseController
3
+ before_action :authenticate_scope!
4
+
5
+ def edit
6
+ self.resource = resource_class.new
7
+ if warden.session(scope_name)['secure_password_expired']
8
+ resource.errors.add(:base, "#{error_string_for_password_expired}.")
9
+ end
10
+ render :edit
11
+ end
12
+
13
+ def update
14
+ if resource.update_with_password(password_params)
15
+ prepare_for_redirect
16
+ redirect_to stored_location_for(scope_name) || :root
17
+ else
18
+ clean_up_passwords(resource)
19
+ render :edit
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def authenticate_scope!
26
+ send(:"authenticate_#{scope_name}!")
27
+ self.resource = send("current_#{scope_name}")
28
+ end
29
+
30
+ def alert_string_for_password_updated
31
+ I18n.t('secure_password.password_requires_regular_updates.alerts.messages.password_updated')
32
+ end
33
+
34
+ def error_string_for_password_expired
35
+ return 'password expired' unless warden.user.class.respond_to?(:password_maximum_age)
36
+ I18n.t(
37
+ 'secure_password.password_requires_regular_updates.errors.messages.password_expired',
38
+ timeframe: distance_of_time_in_words(warden.user.class.password_maximum_age)
39
+ )
40
+ end
41
+
42
+ def password_params
43
+ params.require(scope_name).permit(:current_password, :password, :password_confirmation)
44
+ end
45
+
46
+ def prepare_for_redirect
47
+ warden.session(scope_name)[:secure_password_expired] = false
48
+ flash[:notice] = alert_string_for_password_updated
49
+ bypass_sign_in resource, scope: scope_name
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ <h2><%= t('.titles.section_title') %></h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: [resource_name, :password_with_policy], html: { method: :put }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <p><%= f.label :current_password, t('.labels.current_password') %><br />
7
+ <%= f.password_field :current_password %></p>
8
+
9
+ <p><%= f.label :password, t('.labels.new_password') %><br />
10
+ <%= f.password_field :password %></p>
11
+
12
+ <p><%= f.label :password_confirmation, t('.labels.confirm_new_password') %><br />
13
+ <%= f.password_field :password_confirmation %></p>
14
+
15
+ <p><%= f.submit t('.buttons.change_my_password') %></p>
16
+ <% end %>
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "devise/secure_password"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)