devise-secure_password 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +44 -0
- data/Dockerfile.prev +44 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +280 -0
- data/LICENSE.txt +21 -0
- data/README.md +326 -0
- data/Rakefile +11 -0
- data/app/controllers/devise/passwords_with_policy_controller.rb +52 -0
- data/app/views/devise/passwords_with_policy/edit.html.erb +16 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/config/locales/en.yml +71 -0
- data/devise-secure_password.gemspec +57 -0
- data/docker-entrypoint.sh +6 -0
- data/gemfiles/rails-5_0_6.gemfile +17 -0
- data/gemfiles/rails-5_1_4.gemfile +16 -0
- data/lib/devise/secure_password.rb +70 -0
- data/lib/devise/secure_password/controllers/active_helpers.rb +40 -0
- data/lib/devise/secure_password/controllers/devise_helpers.rb +64 -0
- data/lib/devise/secure_password/hooks/password_requires_regular_updates.rb +5 -0
- data/lib/devise/secure_password/models/password_disallows_frequent_changes.rb +60 -0
- data/lib/devise/secure_password/models/password_disallows_frequent_reuse.rb +71 -0
- data/lib/devise/secure_password/models/password_has_required_content.rb +131 -0
- data/lib/devise/secure_password/models/password_requires_regular_updates.rb +56 -0
- data/lib/devise/secure_password/models/previous_password.rb +20 -0
- data/lib/devise/secure_password/routes.rb +11 -0
- data/lib/devise/secure_password/version.rb +5 -0
- data/lib/generators/devise/secure_password/install_generator.rb +30 -0
- data/lib/generators/devise/templates/README.txt +21 -0
- data/lib/generators/devise/templates/secure_password.rb +43 -0
- data/lib/support/string/character_counter.rb +53 -0
- data/pkg/devise-secure_password-1.0.0.gem +0 -0
- 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,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__)
|