devise_password_history 0.1.1 → 0.1.2
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.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/devise_password_history.gemspec +21 -0
- data/lib/devise_password_history/generators/install_generator.rb +35 -0
- data/lib/devise_password_history/generators/templates/migrations/create_old_passwords.rb +15 -0
- data/lib/devise_password_history/generators/templates/models/old_password.rb +3 -0
- data/lib/devise_password_history/models/password_history.rb +87 -0
- data/lib/devise_password_history/version.rb +3 -0
- data/lib/devise_password_history.rb +22 -0
- metadata +16 -5
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2014 Ryan Heath
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# DevisePasswordHistory
|
|
2
|
+
|
|
3
|
+
This extension provides password history support for Devise,
|
|
4
|
+
which allows you to prevent users from re-using the same password
|
|
5
|
+
they've used in the past (the actual limit is configurable).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
gem 'devise_password_history'
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install devise_password_history
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
After installation, you need to "install" the extension into your
|
|
24
|
+
app via the following command:
|
|
25
|
+
|
|
26
|
+
$ bundle exec rails g devise_password_history:install
|
|
27
|
+
|
|
28
|
+
That generator will do three things:
|
|
29
|
+
|
|
30
|
+
1. Modifies the `config/initializers/devise.rb` file with two new config options:
|
|
31
|
+
- `config.deny_old_passwords`: turns the validations on/off
|
|
32
|
+
- `config.password_history_count`: the threshold of how many passwords to store
|
|
33
|
+
2. Creates an `OldPassword` polymorphic model in `app/models`
|
|
34
|
+
3. Creates the migration for the `old_passwords` table
|
|
35
|
+
|
|
36
|
+
So once you run the generator, you just need to:
|
|
37
|
+
|
|
38
|
+
$ bundle exec rake db:migrate
|
|
39
|
+
|
|
40
|
+
Now the extension has been installed. To use it, you tell `devise` like with
|
|
41
|
+
any of the other extensions:
|
|
42
|
+
|
|
43
|
+
class User < ActiveRecord::Base
|
|
44
|
+
devise :database_authenticatable, :password_history
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
## Contributing
|
|
48
|
+
|
|
49
|
+
1. Fork it
|
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'devise_password_history/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "devise_password_history"
|
|
8
|
+
gem.version = DevisePasswordHistory::VERSION
|
|
9
|
+
gem.authors = ["Ryan Heath"]
|
|
10
|
+
gem.email = ["ryan@rpheath.com"]
|
|
11
|
+
gem.description = %q{Maintains password history and ensures old passwords aren't reused}
|
|
12
|
+
gem.summary = %q{Password history support for devise}
|
|
13
|
+
gem.homepage = ""
|
|
14
|
+
|
|
15
|
+
gem.add_dependency("devise", ["~> 2.2.0"])
|
|
16
|
+
|
|
17
|
+
gem.files = `git ls-files`.split($/)
|
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test)/})
|
|
20
|
+
gem.require_paths = ["lib"]
|
|
21
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/base"
|
|
3
|
+
|
|
4
|
+
module DevisePasswordHistory
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include Rails::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
10
|
+
|
|
11
|
+
desc "Install the devise password history extension"
|
|
12
|
+
|
|
13
|
+
def self.next_migration_number(path)
|
|
14
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S").to_i.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_configs
|
|
18
|
+
inject_into_file "config/initializers/devise.rb", "\n # ==> Password History\n" +
|
|
19
|
+
" # How many old passwords to keep and validate against\n" +
|
|
20
|
+
" config.password_history_count = 8\n\n" +
|
|
21
|
+
" # Toggles behavior for Deny/Allow old passwords\n" +
|
|
22
|
+
" config.deny_old_passwords = true\n\n" +
|
|
23
|
+
"", :before => /end[\s|\n|]+\Z/
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def copy_models
|
|
27
|
+
copy_file "models/old_password.rb", "app/models/old_password.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def copy_migrations
|
|
31
|
+
migration_template "migrations/create_old_passwords.rb", "db/migrate/create_old_passwords.rb"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateOldPasswords < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :old_passwords do |t|
|
|
4
|
+
t.string :encrypted_password, :null => false
|
|
5
|
+
t.string :password_salt
|
|
6
|
+
t.string :password_history_type, :null => false
|
|
7
|
+
t.integer :password_history_id, :null => false
|
|
8
|
+
t.datetime :created_at
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.down
|
|
13
|
+
drop_table :old_passwords
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
module PasswordHistory
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
Devise::Models.config(self, :password_history_count, :deny_old_passwords)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
has_many :old_passwords, :as => :password_history, :dependent => :destroy
|
|
14
|
+
before_update :store_old_password
|
|
15
|
+
validate :validate_old_passwords
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# validation applied here
|
|
19
|
+
def validate_old_passwords
|
|
20
|
+
if self.encrypted_password_changed? && self.old_password_being_used?
|
|
21
|
+
self.errors.add(:password, "has been used already (you can't use your last #{self.class.password_history_count} passwords)")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def old_password_being_used?
|
|
26
|
+
if active_password_history_support?
|
|
27
|
+
if self.password.present?
|
|
28
|
+
# we need to go through each of the old passwords
|
|
29
|
+
# and check to see if the new password would authenticate
|
|
30
|
+
# the user (via valid_password?); if so that indicates
|
|
31
|
+
# the password has been used in the past
|
|
32
|
+
self.old_passwords.each do |old_pw|
|
|
33
|
+
temp = self.class.new
|
|
34
|
+
temp.encrypted_password = old_pw.encrypted_password
|
|
35
|
+
temp.password_salt = old_pw.password_salt
|
|
36
|
+
|
|
37
|
+
# return true if this password "passes" (authenticates)
|
|
38
|
+
return true if temp.valid_password?(self.password)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# otherwise, we're safe to let this
|
|
44
|
+
# password go through
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
# make sure this is turned on in the Devise config
|
|
50
|
+
def should_deny_old_passwords?
|
|
51
|
+
self.class.deny_old_passwords.is_a?(TrueClass)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# count needs to be above zero
|
|
55
|
+
def valid_password_history_count?
|
|
56
|
+
self.class.password_history_count > 0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# is this extension configured properly (active)?
|
|
60
|
+
def active_password_history_support?
|
|
61
|
+
self.should_deny_old_passwords? && self.valid_password_history_count?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# stores the old password in the database;
|
|
65
|
+
# removes passwords past the configured threshold
|
|
66
|
+
def store_old_password
|
|
67
|
+
# only do this if the password has been modified
|
|
68
|
+
if self.encrypted_password_changed?
|
|
69
|
+
if self.valid_password_history_count?
|
|
70
|
+
# record the old password
|
|
71
|
+
old_pw = self.old_passwords.new
|
|
72
|
+
old_pw.encrypted_password = self.encrypted_password_change.first
|
|
73
|
+
old_pw.password_salt = self.password_salt_change.first if self.password_salt_change.present?
|
|
74
|
+
old_pw.save!
|
|
75
|
+
|
|
76
|
+
# wipe out passwords beyond our desired count
|
|
77
|
+
expired_passwords = self.old_passwords.order(:id).reverse_order.offset(self.class.password_history_count)
|
|
78
|
+
expired_passwords.destroy_all if expired_passwords.present?
|
|
79
|
+
else
|
|
80
|
+
# if the count is zero, no password history
|
|
81
|
+
self.old_passwords.destroy_all
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
|
|
3
|
+
require "devise"
|
|
4
|
+
require "devise_password_history/version"
|
|
5
|
+
require "devise_password_history/generators/install_generator"
|
|
6
|
+
|
|
7
|
+
# adds out config options to Devise
|
|
8
|
+
# (see config/initializers/devise.rb)
|
|
9
|
+
module Devise
|
|
10
|
+
# How many old passwords to save
|
|
11
|
+
mattr_accessor :password_history_count
|
|
12
|
+
@@password_history_count = 8
|
|
13
|
+
|
|
14
|
+
# Toggles the behavior of denying/allowing old passwords
|
|
15
|
+
mattr_accessor :deny_old_passwords
|
|
16
|
+
@@deny_old_passwords = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module DevisePasswordHistory; end
|
|
20
|
+
|
|
21
|
+
# makes this module available to the `devise` command
|
|
22
|
+
Devise.add_module :password_history, :model => "devise_password_history/models/password_history"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise_password_history
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 31
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 1
|
|
9
|
-
-
|
|
10
|
-
version: 0.1.
|
|
9
|
+
- 2
|
|
10
|
+
version: 0.1.2
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Ryan Heath
|
|
@@ -43,8 +43,19 @@ extensions: []
|
|
|
43
43
|
|
|
44
44
|
extra_rdoc_files: []
|
|
45
45
|
|
|
46
|
-
files:
|
|
47
|
-
|
|
46
|
+
files:
|
|
47
|
+
- .gitignore
|
|
48
|
+
- Gemfile
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- README.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- devise_password_history.gemspec
|
|
53
|
+
- lib/devise_password_history.rb
|
|
54
|
+
- lib/devise_password_history/generators/install_generator.rb
|
|
55
|
+
- lib/devise_password_history/generators/templates/migrations/create_old_passwords.rb
|
|
56
|
+
- lib/devise_password_history/generators/templates/models/old_password.rb
|
|
57
|
+
- lib/devise_password_history/models/password_history.rb
|
|
58
|
+
- lib/devise_password_history/version.rb
|
|
48
59
|
has_rdoc: true
|
|
49
60
|
homepage: ""
|
|
50
61
|
licenses: []
|