devision 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|