devise-secure_password 1.0.0

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