devision 0.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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/devision.gemspec +29 -0
- data/lib/devision.rb +47 -0
- data/lib/devision/configuration_options.rb +118 -0
- data/lib/devision/models/authenticatable.rb +44 -0
- data/lib/devision/models/config.rb +32 -0
- data/lib/devision/models/confirmable.rb +86 -0
- data/lib/devision/models/database_authenticatable.rb +81 -0
- data/lib/devision/models/lockable.rb +67 -0
- data/lib/devision/models/recoverable.rb +137 -0
- data/lib/devision/models/rememberable.rb +114 -0
- data/lib/devision/token_generator.rb +72 -0
- data/lib/devision/version.rb +13 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 43c30705818f3d105f21c7270c92e7f1824225b0
|
4
|
+
data.tar.gz: aa8bb3b3b720f5336b42f13e7ff1ad18450c76cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c9da084babbbe7878ca794570626f27b8d4b15d02b91c1b209be41de3edcca642809bc0ad96fc908abfd1752a9628e75cb7f1517c06a916850853beabf974bfc
|
7
|
+
data.tar.gz: 73f23e9e1aeb2f8c8bcabd1444a9c97dabe0745938e641fdc099e2b3c37ca5b340a5c7e729218a3031c260cbad66b6615892de251bbfcc783ae01cf3f2e4930f
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
*.sw[op]
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 John Faucett
|
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,39 @@
|
|
1
|
+
# Devision
|
2
|
+
|
3
|
+
This is a gem that is heavily based off of Devise, think of it as Devise's little cousin.
|
4
|
+
Basically, it provides a lot of the functionality but doesn't make many assumptions about
|
5
|
+
how you want to want to structure your app, especially on the session handling end.
|
6
|
+
|
7
|
+
Here's some of the things it does not provide.
|
8
|
+
1. No routing
|
9
|
+
2. No automatic mailing or setup for mailers
|
10
|
+
3. No controller helpers and session/cookie stuff.
|
11
|
+
|
12
|
+
So what does it provide, is probably your next question. It basically covers just the domain
|
13
|
+
model end, things like reset_password, etc.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'devision'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install devision
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
TODO: Write usage instructions here
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it ( https://github.com/[my-github-username]/devision/fork )
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/devision.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$:.unshift(lib) unless $:.include?(lib)
|
4
|
+
require 'devision/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'devision'
|
8
|
+
spec.version = Devision::VERSION::STRING
|
9
|
+
spec.authors = ['John Faucett']
|
10
|
+
spec.email = ['jwaterfaucett@gmail.com']
|
11
|
+
spec.summary = 'Devise on a Diet'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
20
|
+
spec.add_development_dependency 'rake'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
23
|
+
|
24
|
+
spec.add_dependency 'railties', '>= 3.2.6', '< 5'
|
25
|
+
spec.add_dependency 'orm_adapter', '~> 0.5'
|
26
|
+
spec.add_dependency 'bcrypt', '~> 3.1'
|
27
|
+
spec.add_dependency 'thread_safe', '~> 0.3'
|
28
|
+
|
29
|
+
end
|
data/lib/devision.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'orm_adapter'
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module Devision
|
7
|
+
autoload :TokenGenerator, 'devision/token_generator'
|
8
|
+
|
9
|
+
module Models
|
10
|
+
autoload :Config, 'devision/models/config'
|
11
|
+
autoload :Confirmable, 'devision/models/confirmable'
|
12
|
+
autoload :DatabaseAuthenticatable, 'devision/models/database_authenticatable'
|
13
|
+
autoload :Recoverable, 'devision/models/recoverable'
|
14
|
+
autoload :Rememberable, 'devision/models/rememberable'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Devision configuration settings (for initializers)
|
18
|
+
require 'devision/configuration_options'
|
19
|
+
|
20
|
+
|
21
|
+
# Generate a user friendly 'nice' string randomly to be used as token.
|
22
|
+
def self.nice_token
|
23
|
+
SecureRandom.urlsafe_base64(15).tr('lIO0', 'sxyz')
|
24
|
+
end
|
25
|
+
|
26
|
+
# constant-time comparison algorithm to prevent timing attacks
|
27
|
+
def self.secure_compare(a, b)
|
28
|
+
return false if a.blank? || b.blank? || a.bytesize != b.bytesize
|
29
|
+
l = a.unpack "C#{a.bytesize}"
|
30
|
+
|
31
|
+
res = 0
|
32
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
33
|
+
res == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
# Digests a password using bcrypt.
|
37
|
+
def self.bcrypt(klass, password)
|
38
|
+
::BCrypt::Password.create("#{password}#{klass.pepper}", cost: klass.stretches).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
# Default way to setup Devision. Just call this in an initializer
|
42
|
+
def self.setup
|
43
|
+
yield(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Devision
|
2
|
+
|
3
|
+
# =========================
|
4
|
+
# AUTHENTICATABLE OPTIONS
|
5
|
+
# =========================
|
6
|
+
|
7
|
+
# Keys used when authenticating a user.
|
8
|
+
mattr_accessor :authentication_keys
|
9
|
+
@@authentication_keys = [ :email ]
|
10
|
+
|
11
|
+
# =========================
|
12
|
+
# RECOVERABLE OPTIONS
|
13
|
+
# =========================
|
14
|
+
|
15
|
+
# Defines which key will be used when recovering the password for an account
|
16
|
+
mattr_accessor :reset_password_keys
|
17
|
+
@@reset_password_keys = [ :email ]
|
18
|
+
|
19
|
+
# Time interval you can reset your password with a reset password key
|
20
|
+
mattr_accessor :reset_password_within
|
21
|
+
@@reset_password_within = 6.hours
|
22
|
+
|
23
|
+
|
24
|
+
# Email regex used to validate email formats. It simply asserts that
|
25
|
+
# an one (and only one) @ exists in the given string. This is mainly
|
26
|
+
# to give user feedback and not to assert the e-mail validity.
|
27
|
+
mattr_accessor :email_regexp
|
28
|
+
@@email_regexp = /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
|
29
|
+
|
30
|
+
# Range validation for password length
|
31
|
+
mattr_accessor :password_length
|
32
|
+
@@password_length = 6..128
|
33
|
+
|
34
|
+
# ======================
|
35
|
+
# REMEMBERABLE OPTIONS
|
36
|
+
# ======================
|
37
|
+
|
38
|
+
# Custom domain or key for cookies. Not set by default
|
39
|
+
# mattr_accessor :rememberable_options
|
40
|
+
# @@rememberable_options = {}
|
41
|
+
|
42
|
+
# The time the user will be remembered without asking for credentials again.
|
43
|
+
mattr_accessor :remember_for
|
44
|
+
@@remember_for = 2.weeks
|
45
|
+
|
46
|
+
# If true, extends the user's remember period when remembered via cookie.
|
47
|
+
mattr_accessor :extend_remember_period
|
48
|
+
@@extend_remember_period = false
|
49
|
+
|
50
|
+
# ========================
|
51
|
+
# CONFIRMABLE OPTIONS
|
52
|
+
# ========================
|
53
|
+
|
54
|
+
# Time interval you can access your account before confirming your account.
|
55
|
+
# nil - allows unconfirmed access for unlimited time
|
56
|
+
mattr_accessor :allow_unconfirmed_access_for
|
57
|
+
@@allow_unconfirmed_access_for = 0.days
|
58
|
+
|
59
|
+
# Time interval the confirmation token is valid. nil = unlimited
|
60
|
+
mattr_accessor :confirm_within
|
61
|
+
@@confirm_within = nil
|
62
|
+
|
63
|
+
# Defines which key will be used when confirming an account.
|
64
|
+
mattr_accessor :confirmation_keys
|
65
|
+
@@confirmation_keys = [ :email ]
|
66
|
+
|
67
|
+
# Defines if email should be reconfirmable.
|
68
|
+
# False by default for backwards compatibility.
|
69
|
+
mattr_accessor :reconfirmable
|
70
|
+
@@reconfirmable = false
|
71
|
+
|
72
|
+
# ====================
|
73
|
+
# LOCKABLE OPTIONS
|
74
|
+
# ====================
|
75
|
+
|
76
|
+
# Defines which strategy can be used to lock an account.
|
77
|
+
# Values: :failed_attempts, :none
|
78
|
+
mattr_accessor :lock_strategy
|
79
|
+
@@lock_strategy = :failed_attempts
|
80
|
+
|
81
|
+
# Defines which key will be used when locking and unlocking an account
|
82
|
+
mattr_accessor :unlock_keys
|
83
|
+
@@unlock_keys = [ :email ]
|
84
|
+
|
85
|
+
# Defines which strategy can be used to unlock an account.
|
86
|
+
# Values: :email, :time, :both
|
87
|
+
mattr_accessor :unlock_strategy
|
88
|
+
@@unlock_strategy = :both
|
89
|
+
|
90
|
+
# Number of authentication tries before locking an account
|
91
|
+
mattr_accessor :maximum_attempts
|
92
|
+
@@maximum_attempts = 20
|
93
|
+
|
94
|
+
# Time interval to unlock the account if :time is defined as unlock_strategy.
|
95
|
+
mattr_accessor :unlock_in
|
96
|
+
@@unlock_in = 1.hour
|
97
|
+
|
98
|
+
# ======================
|
99
|
+
# ENCRYPTION OPTIONS
|
100
|
+
# ======================
|
101
|
+
|
102
|
+
# Stores the token generator
|
103
|
+
mattr_accessor :token_generator
|
104
|
+
@@token_generator = nil
|
105
|
+
|
106
|
+
# Secret key used by the key generator
|
107
|
+
mattr_accessor :secret_key
|
108
|
+
@@secret_key = nil
|
109
|
+
|
110
|
+
# The number of times to encrypt password.
|
111
|
+
mattr_accessor :stretches
|
112
|
+
@@stretches = 10
|
113
|
+
|
114
|
+
# Used to encrypt password. Please generate one with rake secret.
|
115
|
+
mattr_accessor :pepper
|
116
|
+
@@pepper = nil
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Devision
|
2
|
+
module Authenticatable
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def self.required_fields(klass)
|
7
|
+
[]
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def find_first_by_auth_conditions(tainted_conditions, options = {})
|
14
|
+
to_adapter.find_first(tainted_conditions.merge(options))
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_or_initialize_with_error_by(attribute, value, error=:invalid)
|
18
|
+
find_or_initialize_with_errors([attribute], { attribute => value }, error)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
|
22
|
+
attributes = attributes.slice(*required_attributes)
|
23
|
+
attributes.delete_if { |key, value| value.blank? }
|
24
|
+
|
25
|
+
if attributes.size == required_attributes.size
|
26
|
+
record = find_first_by_auth_conditions(attributes)
|
27
|
+
end
|
28
|
+
|
29
|
+
unless record
|
30
|
+
record = new
|
31
|
+
required_attributes.each do |key|
|
32
|
+
value = attributes[key]
|
33
|
+
record.public_send("#{key}=", value)
|
34
|
+
record.errors.add(key, value.present? ? error : :blank)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
record
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Devision
|
2
|
+
module Models
|
3
|
+
|
4
|
+
module Config
|
5
|
+
|
6
|
+
def self.add_options(mod, *accessors)
|
7
|
+
class << mod; attr_accessor :available_configs; end
|
8
|
+
mod.available_configs = accessors
|
9
|
+
|
10
|
+
accessors.each do |accessor|
|
11
|
+
mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
12
|
+
def #{accessor}
|
13
|
+
if defined?(@#{accessor})
|
14
|
+
@#{accessor}
|
15
|
+
elsif superclass.respond_to?(:#{accessor})
|
16
|
+
superclass.#{accessor}
|
17
|
+
else
|
18
|
+
Devise.#{accessor}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{accessor}=(value)
|
23
|
+
@#{accessor} = value
|
24
|
+
end
|
25
|
+
METHOD
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end # Config
|
30
|
+
|
31
|
+
end # Models
|
32
|
+
end # Devision
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Devision
|
2
|
+
module Models
|
3
|
+
module Confirmable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Fields required on the target Model
|
7
|
+
def self.required_fields(klass)
|
8
|
+
[:confirmation_token, :confirmed_at, :confirmation_sent_at]
|
9
|
+
end
|
10
|
+
|
11
|
+
included do
|
12
|
+
|
13
|
+
attr_reader :raw_confirmation_token
|
14
|
+
|
15
|
+
def confirm!
|
16
|
+
pending_any_confirmation do
|
17
|
+
if confirmation_period_expired?
|
18
|
+
self.errors.add(:email, :confirmation_period_expired)
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
self.confirmation_token = nil
|
22
|
+
self.confirmed_at = Time.now.utc
|
23
|
+
save(validate: false)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def confirmed?
|
28
|
+
!!confirmed_at
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_confirmation_tokens
|
32
|
+
raw, enc = Devision.token_generator.generate(self.class, :confirmation_token)
|
33
|
+
@raw_confirmation_token = raw
|
34
|
+
self.confirmation_token = enc
|
35
|
+
self.confirmation_sent_at = Time.now.utc
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_confirmation_tokens!
|
39
|
+
generate_confirmation_tokens && save(validate: false)
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear_confirmation_token
|
43
|
+
@raw_confirmation_token = nil
|
44
|
+
self.confirmation_token = nil
|
45
|
+
self.confirmation_sent_at = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks whether the record requires any confirmation.
|
49
|
+
def pending_any_confirmation
|
50
|
+
if !confirmed?
|
51
|
+
yield
|
52
|
+
else
|
53
|
+
self.errors.add(:email, :already_confirmed)
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def confirmation_period_expired?
|
59
|
+
self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
|
60
|
+
end
|
61
|
+
|
62
|
+
def confirmation_period_valid?
|
63
|
+
self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
module ClassMethods
|
69
|
+
|
70
|
+
def confirm_by_token(raw_confirmation_token)
|
71
|
+
original_token = raw_confirmation_token
|
72
|
+
saved_token = Devision.token_generator.digest(self, :confirmation_token, original_token)
|
73
|
+
|
74
|
+
confirmable = find_or_initialize_by(confirmation_token: saved_token)
|
75
|
+
confirmable.confirm! if confirmable.persisted?
|
76
|
+
confirmable.confirmation_token = original_token
|
77
|
+
confirmable
|
78
|
+
end
|
79
|
+
|
80
|
+
Devision::Models::Config.add_options(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end # Confirmable
|
85
|
+
end # Models
|
86
|
+
end # Devision
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'bcrypt'
|
2
|
+
|
3
|
+
module Devision
|
4
|
+
|
5
|
+
module Models
|
6
|
+
# Authenticatable Module, responsible for encrypting password and validating
|
7
|
+
# authenticity of a user while signing in.
|
8
|
+
#
|
9
|
+
# == Options
|
10
|
+
#
|
11
|
+
# == Examples
|
12
|
+
#
|
13
|
+
# User.find(1).valid_password?('password123') # returns true/false
|
14
|
+
#
|
15
|
+
module DatabaseAuthenticatable
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
# Fields required on the target Model
|
19
|
+
def self.required_fields(klass)
|
20
|
+
[:encrypted_password] + klass.authentication_keys
|
21
|
+
end
|
22
|
+
|
23
|
+
included do
|
24
|
+
attr_reader :password, :current_password
|
25
|
+
attr_accessor :password_confirmation
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generates password encryption based on the given value.
|
29
|
+
def password=(new_password)
|
30
|
+
@password = new_password
|
31
|
+
self.encrypted_password = password_digest(@password) if @password.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Verifies whether an password (ie from sign in) is the user password.
|
35
|
+
def valid_password?(password)
|
36
|
+
return false if encrypted_password.blank?
|
37
|
+
bcrypt = ::BCrypt::Password.new(encrypted_password)
|
38
|
+
password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
|
39
|
+
Devision.secure_compare(password, encrypted_password)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set password and password confirmation to nil
|
43
|
+
def clean_up_passwords
|
44
|
+
self.password = self.password_confirmation = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# A callback initiated after successfully authenticating. This can be
|
48
|
+
# used to insert your own logic that is only run after the user successfully
|
49
|
+
# authenticates.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
#
|
53
|
+
# def after_database_authentication
|
54
|
+
# self.update_attribute(:invite_code, nil)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def after_database_authentication
|
58
|
+
end
|
59
|
+
|
60
|
+
# A reliable way to expose the salt regardless of the implementation.
|
61
|
+
def authenticatable_salt
|
62
|
+
encrypted_password[0,29] if encrypted_password
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# Digests the password using bcrypt. Custom encryption should override
|
68
|
+
# this method to apply their own algorithm.
|
69
|
+
#
|
70
|
+
# See https://github.com/plataformatec/devise-encryptable for examples
|
71
|
+
# of other encryption engines.
|
72
|
+
def password_digest(password)
|
73
|
+
Devision.bcrypt(self.class, password)
|
74
|
+
end
|
75
|
+
|
76
|
+
module ClassMethods
|
77
|
+
Devision::Models::Config.add_options(self, :pepper, :stretches)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Devision
|
2
|
+
module Lockable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, to: 'self.class'
|
6
|
+
|
7
|
+
def self.required_fields(klass)
|
8
|
+
attributes = []
|
9
|
+
attributes << :failed_attempts if klass.lock_strategy_enabled?(:failed_attempts)
|
10
|
+
attributes << :locked_at if klass.unlock_strategy_enabled?(:time)
|
11
|
+
attributes << :unlock_token if klass.unlock_strategy_enabled?(:email)
|
12
|
+
attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def lock_access!
|
16
|
+
self.locked_at = Time.now.utc
|
17
|
+
save(validate: false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock_access!
|
21
|
+
self.locked_at = nil
|
22
|
+
self.failed_attempts = 0 if respond_to?(:failed_attempts=)
|
23
|
+
self.unlock_token = nil if respond_to?(:unlock_token=)
|
24
|
+
save(validate: false)
|
25
|
+
end
|
26
|
+
|
27
|
+
def access_locked?
|
28
|
+
!!locked_at && !lock_expired?
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def attempts_exceeded?
|
33
|
+
self.failed_attempts >= self.class.maximum_attempts
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_attempt?
|
37
|
+
self.failed_attempts == (self.class.maximum_attempts - 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
def lock_expired?
|
41
|
+
if unlock_strategy_enabled?(:time)
|
42
|
+
locked_at && locked_at < self.class.unlock_in.ago
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
def unlock_access_by_token(unlock_token)
|
50
|
+
original_token = unlock_token
|
51
|
+
unlock_token = Devision.token_generator.digest(self, :unlock_token, unlock_token)
|
52
|
+
|
53
|
+
lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
|
54
|
+
lockable.unlock_access! if lockable.persisted?
|
55
|
+
lockable.unlock_token = original_token
|
56
|
+
lockable
|
57
|
+
end
|
58
|
+
|
59
|
+
def unlock_strategy_enabled?(strategy)
|
60
|
+
self.lock_strategy == strategy
|
61
|
+
end
|
62
|
+
|
63
|
+
Devision::Models::Config.add_options(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Devision
|
2
|
+
module Models
|
3
|
+
|
4
|
+
# Recoverable takes care of resetting the user password and send reset instructions.
|
5
|
+
#
|
6
|
+
# ==Options
|
7
|
+
#
|
8
|
+
# Recoverable adds the following options to a model
|
9
|
+
#
|
10
|
+
# * +reset_password_keys+: the keys you want to use when recovering the password for an account
|
11
|
+
#
|
12
|
+
# == Examples
|
13
|
+
#
|
14
|
+
# # resets the user password and save the record, true if valid passwords are given, otherwise false
|
15
|
+
# User.find(1).reset_password!('password123', 'password123')
|
16
|
+
#
|
17
|
+
# # only resets the user password, without saving the record
|
18
|
+
# user = User.find(1)
|
19
|
+
# user.reset_password('password123', 'password123')
|
20
|
+
#
|
21
|
+
# # creates a new token and send it with instructions about how to reset the password
|
22
|
+
# User.find(1).send_reset_password_instructions
|
23
|
+
#
|
24
|
+
module Recoverable
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
def self.required_fields(klass)
|
28
|
+
[:reset_password_sent_at, :reset_password_token]
|
29
|
+
end
|
30
|
+
|
31
|
+
included do
|
32
|
+
attr_accessor :raw_reset_password_token
|
33
|
+
end
|
34
|
+
|
35
|
+
# Update password saving the record and clearing token. Returns true if
|
36
|
+
# the passwords are valid and the record was saved, false otherwise.
|
37
|
+
def reset_password!(new_password, new_password_confirmation)
|
38
|
+
self.password = new_password
|
39
|
+
self.password_confirmation = new_password_confirmation
|
40
|
+
|
41
|
+
if valid?
|
42
|
+
clear_reset_password_token
|
43
|
+
after_password_reset
|
44
|
+
end
|
45
|
+
|
46
|
+
save
|
47
|
+
end
|
48
|
+
|
49
|
+
# Resets reset password token and send reset password instructions by email.
|
50
|
+
# Returns the token which can then be sent via email
|
51
|
+
def generate_password_tokens
|
52
|
+
@raw_password_reset_token, enc = Devision.token_generator.generate(self.class, :reset_password_token)
|
53
|
+
|
54
|
+
self.reset_password_token = enc
|
55
|
+
self.reset_password_sent_at = Time.now.utc
|
56
|
+
@raw_password_reset_token
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_password_tokens!
|
60
|
+
generate_password_tokens && save(validate: false)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Checks if the reset password token sent is within the limit time.
|
64
|
+
# We do this by calculating if the difference between today and the
|
65
|
+
# sending date does not exceed the confirm in time configured.
|
66
|
+
# Returns true if the resource is not responding to reset_password_sent_at at all.
|
67
|
+
# reset_password_within is a model configuration, must always be an integer value.
|
68
|
+
#
|
69
|
+
# Example:
|
70
|
+
#
|
71
|
+
# # reset_password_within = 1.day and reset_password_sent_at = today
|
72
|
+
# reset_password_period_valid? # returns true
|
73
|
+
#
|
74
|
+
# # reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
|
75
|
+
# reset_password_period_valid? # returns true
|
76
|
+
#
|
77
|
+
# # reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
|
78
|
+
# reset_password_period_valid? # returns false
|
79
|
+
#
|
80
|
+
# # reset_password_within = 0.days
|
81
|
+
# reset_password_period_valid? # will always return false
|
82
|
+
#
|
83
|
+
def reset_password_period_valid?
|
84
|
+
reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
# Removes reset_password token
|
90
|
+
def clear_reset_password_token
|
91
|
+
self.raw_reset_password_token = nil
|
92
|
+
self.reset_password_token = nil
|
93
|
+
self.reset_password_sent_at = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def after_password_reset
|
97
|
+
end
|
98
|
+
|
99
|
+
module ClassMethods
|
100
|
+
# Attempt to find a user by its email. If a record is found, send new
|
101
|
+
# password instructions to it. If user is not found, returns a new user
|
102
|
+
# with an email not found error.
|
103
|
+
# Attributes must contain the user's email
|
104
|
+
def send_reset_password_instructions(attributes={})
|
105
|
+
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
|
106
|
+
recoverable.send_reset_password_instructions if recoverable.persisted?
|
107
|
+
recoverable
|
108
|
+
end
|
109
|
+
|
110
|
+
# Attempt to find a user by its reset_password_token to reset its
|
111
|
+
# password. If a user is found and token is still valid, reset its password and automatically
|
112
|
+
# try saving the record. If not user is found, returns a new user
|
113
|
+
# containing an error in reset_password_token attribute.
|
114
|
+
# Attributes must contain reset_password_token, password and confirmation
|
115
|
+
def reset_password_by_token(attributes={})
|
116
|
+
original_token = attributes[:reset_password_token]
|
117
|
+
reset_password_token = Devision.token_generator.digest(self, :reset_password_token, original_token)
|
118
|
+
|
119
|
+
recoverable = find_or_initialize_with_errors(:reset_password_token, reset_password_token)
|
120
|
+
|
121
|
+
if recoverable.persisted?
|
122
|
+
if recoverable.reset_password_period_valid?
|
123
|
+
recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
|
124
|
+
else
|
125
|
+
recoverable.errors.add(:reset_password_token, :expired)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
recoverable.reset_password_token = original_token
|
130
|
+
recoverable
|
131
|
+
end
|
132
|
+
|
133
|
+
Devision::Models::Config.add_options(self, :reset_password_keys, :reset_password_within)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Devision
|
2
|
+
module Models
|
3
|
+
# Rememberable manages generating and clearing token for remember the user
|
4
|
+
# from a saved cookie. Rememberable also has utility methods for dealing
|
5
|
+
# with serializing the user into the cookie and back from the cookie, trying
|
6
|
+
# to lookup the record based on the saved information.
|
7
|
+
# You probably wouldn't use rememberable methods directly, they are used
|
8
|
+
# mostly internally for handling the remember token.
|
9
|
+
#
|
10
|
+
# == Options
|
11
|
+
#
|
12
|
+
# Rememberable adds the following options in devise_for:
|
13
|
+
#
|
14
|
+
# * +remember_for+: the time you want the user will be remembered without
|
15
|
+
# asking for credentials. After this time the user will be blocked and
|
16
|
+
# will have to enter their credentials again. This configuration is also
|
17
|
+
# used to calculate the expires time for the cookie created to remember
|
18
|
+
# the user. By default remember_for is 2.weeks.
|
19
|
+
#
|
20
|
+
# * +extend_remember_period+: if true, extends the user's remember period
|
21
|
+
# when remembered via cookie. False by default.
|
22
|
+
#
|
23
|
+
# * +rememberable_options+: configuration options passed to the created cookie.
|
24
|
+
#
|
25
|
+
# == Examples
|
26
|
+
#
|
27
|
+
# User.find(1).remember_me! # regenerating the token
|
28
|
+
# User.find(1).forget_me! # clearing the token
|
29
|
+
#
|
30
|
+
# # generating info to put into cookies
|
31
|
+
# User.serialize_into_cookie(user)
|
32
|
+
#
|
33
|
+
# # lookup the user based on the incoming cookie information
|
34
|
+
# User.serialize_from_cookie(cookie_string)
|
35
|
+
module Rememberable
|
36
|
+
extend ActiveSupport::Concern
|
37
|
+
|
38
|
+
attr_accessor :remember_me, :extend_remember_period
|
39
|
+
|
40
|
+
def self.required_fields(klass)
|
41
|
+
[:remember_created_at]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generate a new remember token and save the record without validations
|
45
|
+
# unless remember_across_browsers is true and the user already has a valid token.
|
46
|
+
def remember_me!(extend_period=false)
|
47
|
+
self.remember_token = self.class.remember_token if generate_remember_token?
|
48
|
+
self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period)
|
49
|
+
save(validate: false) if self.changed?
|
50
|
+
end
|
51
|
+
|
52
|
+
# If the record is persisted, remove the remember token (but only if
|
53
|
+
# it exists), and save the record without validations.
|
54
|
+
def forget_me!
|
55
|
+
return unless persisted?
|
56
|
+
self.remember_token = nil if respond_to?(:remember_token=)
|
57
|
+
self.remember_created_at = nil
|
58
|
+
save(validate: false)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Remember token should be expired if expiration time not overpass now.
|
62
|
+
def remember_expired?
|
63
|
+
remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Remember token expires at created time + remember_for configuration
|
67
|
+
def remember_expires_at
|
68
|
+
remember_created_at + self.class.remember_for
|
69
|
+
end
|
70
|
+
|
71
|
+
def rememberable_value
|
72
|
+
if respond_to?(:remember_token)
|
73
|
+
remember_token
|
74
|
+
elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
|
75
|
+
salt
|
76
|
+
else
|
77
|
+
raise "authenticable_salt returned nil for the #{self.class.name} model. " \
|
78
|
+
"In order to use rememberable, you must ensure a password is always set " \
|
79
|
+
"or have a remember_token column in your model or implement your own " \
|
80
|
+
"rememberable_value in the model with custom logic."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def rememberable_options
|
85
|
+
self.class.rememberable_options
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def generate_remember_token? #:nodoc:
|
91
|
+
respond_to?(:remember_token) && remember_expired?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Generate a timestamp if extend_remember_period is true, if no remember_token
|
95
|
+
# exists, or if an existing remember token has expired.
|
96
|
+
def generate_remember_timestamp?(extend_period) #:nodoc:
|
97
|
+
extend_period || remember_created_at.nil? || remember_expired?
|
98
|
+
end
|
99
|
+
|
100
|
+
module ClassMethods
|
101
|
+
|
102
|
+
# Generate a token checking if one does not already exist in the database.
|
103
|
+
def remember_token #:nodoc:
|
104
|
+
loop do
|
105
|
+
token = Devision.nice_token
|
106
|
+
break token unless to_adapter.find_first({ remember_token: token })
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Devise::Models::Config.add_options(self, :remember_for, :extend_remember_period, :rememberable_options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Deprecate: Copied verbatim from Rails source, remove once we move to Rails 4 only.
|
2
|
+
require 'thread_safe'
|
3
|
+
require 'openssl'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module Devision
|
7
|
+
|
8
|
+
class TokenGenerator
|
9
|
+
def initialize(key_generator, digest="SHA256")
|
10
|
+
@key_generator = key_generator
|
11
|
+
@digest = digest
|
12
|
+
end
|
13
|
+
|
14
|
+
def digest(klass, column, value)
|
15
|
+
value.present? && OpenSSL::HMAC.hexdigest(@digest, key_for(column), value.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(klass, column)
|
19
|
+
key = key_for(column)
|
20
|
+
|
21
|
+
loop do
|
22
|
+
raw = Devision.nice_token
|
23
|
+
enc = OpenSSL::HMAC.hexdigest(@digest, key, raw)
|
24
|
+
break [raw, enc] unless klass.to_adapter.find_first({ column => enc })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def key_for(column)
|
31
|
+
@key_generator.generate_key("Devision #{column}")
|
32
|
+
end
|
33
|
+
end # TokenGenerator
|
34
|
+
|
35
|
+
# KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
|
36
|
+
# It can be used to derive a number of keys for various purposes from a given secret.
|
37
|
+
# This lets Rails applications have a single secure secret, but avoid reusing that
|
38
|
+
# key in multiple incompatible contexts.
|
39
|
+
class KeyGenerator
|
40
|
+
def initialize(secret, options = {})
|
41
|
+
@secret = secret
|
42
|
+
# The default iterations are higher than required for our key derivation uses
|
43
|
+
# on the off chance someone uses this for password storage
|
44
|
+
@iterations = options[:iterations] || 2**16
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a derived key suitable for use. The default key_size is chosen
|
48
|
+
# to be compatible with the default settings of ActiveSupport::MessageVerifier.
|
49
|
+
# i.e. OpenSSL::Digest::SHA1#block_length
|
50
|
+
def generate_key(salt, key_size=64)
|
51
|
+
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
|
52
|
+
end
|
53
|
+
end # KeyGenerator
|
54
|
+
|
55
|
+
# CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
|
56
|
+
# re-executing the key generation process when it's called using the same salt and
|
57
|
+
# key_size
|
58
|
+
class CachingKeyGenerator
|
59
|
+
def initialize(key_generator)
|
60
|
+
@key_generator = key_generator
|
61
|
+
@cache_keys = ThreadSafe::Cache.new
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a derived key suitable for use. The default key_size is chosen
|
65
|
+
# to be compatible with the default settings of ActiveSupport::MessageVerifier.
|
66
|
+
# i.e. OpenSSL::Digest::SHA1#block_length
|
67
|
+
def generate_key(salt, key_size=64)
|
68
|
+
@cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
|
69
|
+
end
|
70
|
+
end # CachingKeyGenerator
|
71
|
+
|
72
|
+
end # Devision
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devision
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Faucett
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: railties
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.2.6
|
62
|
+
- - "<"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '5'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 3.2.6
|
72
|
+
- - "<"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '5'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: orm_adapter
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.5'
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0.5'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: bcrypt
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.1'
|
96
|
+
type: :runtime
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.1'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: thread_safe
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.3'
|
110
|
+
type: :runtime
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0.3'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
- jwaterfaucett@gmail.com
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- ".gitignore"
|
125
|
+
- Gemfile
|
126
|
+
- LICENSE.txt
|
127
|
+
- README.md
|
128
|
+
- Rakefile
|
129
|
+
- devision.gemspec
|
130
|
+
- lib/devision.rb
|
131
|
+
- lib/devision/configuration_options.rb
|
132
|
+
- lib/devision/models/authenticatable.rb
|
133
|
+
- lib/devision/models/config.rb
|
134
|
+
- lib/devision/models/confirmable.rb
|
135
|
+
- lib/devision/models/database_authenticatable.rb
|
136
|
+
- lib/devision/models/lockable.rb
|
137
|
+
- lib/devision/models/recoverable.rb
|
138
|
+
- lib/devision/models/rememberable.rb
|
139
|
+
- lib/devision/token_generator.rb
|
140
|
+
- lib/devision/version.rb
|
141
|
+
homepage:
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.4.1
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Devise on a Diet
|
165
|
+
test_files: []
|