nacl_password 0.1.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/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/lib/nacl_password.rb +190 -0
- data/lib/nacl_password/concern.rb +190 -0
- data/lib/nacl_password/railtie.rb +10 -0
- data/lib/nacl_password/version.rb +3 -0
- data/lib/tasks/nacl_password_tasks.rake +4 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cab426388ecd1f11ff9f5832d6b5fe75a56e8e249eaec25c136c86bba89c6dc5
|
4
|
+
data.tar.gz: 9aa167457870e442f30550f1811979a8a78140979c7a01f1d86c50e3312219da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 544e6b5af9ca3af49950858ba5844b08ec64784794a8988ddbc5709827a6a54b5b86f67845ff1bf350c10aa41864355f71d59b41bb9b9532ab4146e09dbacb22
|
7
|
+
data.tar.gz: c2080b635da0588db6206fb4f5922c6f2f32ddb9c71d80e202640d0599aa92d4085d4f4a40e652b06e68501291fe9e5acda9cda15a6ca87aff907acece00c604
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Sampson Crowley
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# NaClPassword
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'nacl_password'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install nacl_password
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'coerce_boolean'
|
5
|
+
|
6
|
+
module NaClPassword
|
7
|
+
INVALID_OPTION_MESSAGE = "predefined options are :min, :interactive, :moderate, :sensitive, and :max"
|
8
|
+
UNCOERCEABLE_OPTION_MESSAGE = "value must be coercable to an integer or one of the predefined options (:min | :interactive | :moderate | :sensitive | :max)"
|
9
|
+
|
10
|
+
# Load rbnacl gem only when nacl_password is used. This is to avoid
|
11
|
+
# the entire app using this gem being dependent on a binary library.
|
12
|
+
def self.load_gem
|
13
|
+
require "rbnacl"
|
14
|
+
rescue LoadError, NameError
|
15
|
+
$stderr.puts <<-ERROR
|
16
|
+
You don't have rbnacl installed in your application.
|
17
|
+
Please add it to your Gemfile and run bundle install
|
18
|
+
ERROR
|
19
|
+
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
|
23
|
+
# The Argon2 hash function can handle maximum 2^32 bytes, but system available
|
24
|
+
# memory probably cannot.
|
25
|
+
# A password of length 1024 bytes is way more than any user would ever need.
|
26
|
+
# Put a restriction on password length that keeps memory usage in the available
|
27
|
+
# range, but is more than anyone would ever need.
|
28
|
+
# other defaults are tested ranges of functional values for libsodium that
|
29
|
+
# allow maximum possible security without crashing the system
|
30
|
+
def self.setup
|
31
|
+
load_gem
|
32
|
+
::NaClPassword::Argon2 ||= RbNaCl::PasswordHash::Argon2
|
33
|
+
::NaClPassword::MAX_PASSWORD_LENGTH ||= 1024
|
34
|
+
::NaClPassword::OPS_LIMIT_RANGE ||= 3..20
|
35
|
+
::NaClPassword::MEM_LIMIT_RANGE ||= (2**25)..(2**32) #32 MB - 4 GB
|
36
|
+
::NaClPassword::DIGEST_SIZE_RANGE ||= 64..512
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.const_missing(name)
|
41
|
+
NaClPassword.setup
|
42
|
+
if const_defined?(name)
|
43
|
+
const_get(name)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_reader :ops_limit # :nodoc:
|
51
|
+
attr_reader :mem_limit # :nodoc:
|
52
|
+
attr_reader :digest_size # :nodoc:
|
53
|
+
|
54
|
+
def ops_limit=(value)
|
55
|
+
@ops_limit = get_ops_limit(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def mem_limit=(value)
|
59
|
+
@mem_limit = get_mem_limit(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def digest_size=(value)
|
63
|
+
@digest_size = get_digest_size(value)
|
64
|
+
digest_size
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate(password)
|
68
|
+
salt = RbNaCl::Random.random_bytes(Argon2::SALTBYTES)
|
69
|
+
ops = get_ops_limit
|
70
|
+
mem = get_mem_limit
|
71
|
+
size = get_digest_size
|
72
|
+
"#{
|
73
|
+
Base64.strict_encode64(encrypt(password, salt, ops, mem, size))
|
74
|
+
}.#{
|
75
|
+
Base64.strict_encode64(salt)
|
76
|
+
}.#{
|
77
|
+
ops
|
78
|
+
}.#{
|
79
|
+
mem
|
80
|
+
}.#{
|
81
|
+
size
|
82
|
+
}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def authenticate(encoded, password)
|
86
|
+
digest, *metadata = decode(encoded)
|
87
|
+
RbNaCl::PasswordHash.argon2id(password, *metadata) == digest
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.const_missing(name)
|
91
|
+
NaClPassword.const_missing(name)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def encrypt(...)
|
96
|
+
RbNaCl::PasswordHash.argon2id(...)
|
97
|
+
end
|
98
|
+
|
99
|
+
def decode(encoded)
|
100
|
+
digest_64, salt_64, ops, mem, size = encoded.split(".")
|
101
|
+
digest = Base64.strict_decode64(digest_64)
|
102
|
+
salt = Base64.strict_decode64(salt_64)
|
103
|
+
ops = get_ops_limit(ops)
|
104
|
+
mem = get_mem_limit(mem)
|
105
|
+
size = get_digest_size(size)
|
106
|
+
[ digest, salt, ops, mem, size ]
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_ops_limit(ops = NaClPassword.ops_limit)
|
110
|
+
ops = :moderate unless CoerceBoolean.from(ops)
|
111
|
+
|
112
|
+
case ops
|
113
|
+
when :min then OPS_LIMIT_RANGE.min
|
114
|
+
when :interactive then 5
|
115
|
+
when :moderate then 10
|
116
|
+
when :sensitive then 15
|
117
|
+
when :max then OPS_LIMIT_RANGE.max
|
118
|
+
when Symbol
|
119
|
+
raise ArgumentError, INVALID_OPTION_MESSAGE
|
120
|
+
else
|
121
|
+
case ops = ops.to_i
|
122
|
+
when OPS_LIMIT_RANGE then ops
|
123
|
+
else
|
124
|
+
raise \
|
125
|
+
ArgumentError,
|
126
|
+
"ops_limit must be within the range #{OPS_LIMIT_RANGE}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
rescue NoMethodError
|
130
|
+
raise ArgumentError, UNCOERCEABLE_OPTION_MESSAGE
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_mem_limit(mem = NaClPassword.mem_limit)
|
134
|
+
mem = :moderate unless CoerceBoolean.from(mem)
|
135
|
+
|
136
|
+
case mem
|
137
|
+
when :min then MEM_LIMIT_RANGE.min # 32mb
|
138
|
+
when :interactive then (2**26) # 64mb
|
139
|
+
when :moderate then (2**28) # 256mb
|
140
|
+
when :sensitive then (2**30) # 1024mb
|
141
|
+
when :max then MEM_LIMIT_RANGE.max # 4096mb
|
142
|
+
when Symbol
|
143
|
+
raise ArgumentError, INVALID_OPTION_MESSAGE
|
144
|
+
else
|
145
|
+
case mem = mem.to_i
|
146
|
+
when MEM_LIMIT_RANGE then mem
|
147
|
+
else
|
148
|
+
raise \
|
149
|
+
ArgumentError,
|
150
|
+
"mem_limit must be within the range #{MEM_LIMIT_RANGE}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
rescue NoMethodError
|
154
|
+
raise ArgumentError, UNCOERCEABLE_OPTION_MESSAGE
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_digest_size(size = NaClPassword.digest_size)
|
158
|
+
size = :moderate unless CoerceBoolean.from(size)
|
159
|
+
|
160
|
+
case size
|
161
|
+
when :min then DIGEST_SIZE_RANGE.min
|
162
|
+
when :interactive then DIGEST_SIZE_RANGE.min * 2
|
163
|
+
when :moderate then DIGEST_SIZE_RANGE.min * 4
|
164
|
+
when :sensitive then DIGEST_SIZE_RANGE.min * 8
|
165
|
+
when :max then DIGEST_SIZE_RANGE.max
|
166
|
+
when Symbol
|
167
|
+
raise ArgumentError, INVALID_OPTION_MESSAGE
|
168
|
+
else
|
169
|
+
case size = size.to_i
|
170
|
+
when DIGEST_SIZE_RANGE
|
171
|
+
unless size % 64 == 0
|
172
|
+
raise ArgumentError, "digest_size must be a multiple of 64"
|
173
|
+
end
|
174
|
+
|
175
|
+
size
|
176
|
+
else
|
177
|
+
raise ArgumentError, "digest_size must be within the range #{DIGEST_SIZE_RANGE}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
rescue NoMethodError
|
181
|
+
raise ArgumentError, UNCOERCEABLE_OPTION_MESSAGE
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
self.ops_limit = :moderate
|
186
|
+
self.mem_limit = :moderate
|
187
|
+
self.digest_size = :moderate
|
188
|
+
end
|
189
|
+
|
190
|
+
require "nacl_password/railtie" if defined? Rails
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'nacl_password'
|
5
|
+
|
6
|
+
module NaClPassword
|
7
|
+
module Concern
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Adds methods to set and authenticate against a Argon2 password.
|
12
|
+
# This mechanism requires you to have a +XXX_digest+ attribute.
|
13
|
+
# Where +XXX+ is the attribute name of your desired password.
|
14
|
+
# the +digest+ attribute to use can be set by passing
|
15
|
+
# `digest_attribute: non_standard_attribute` to `nacl_password`
|
16
|
+
#
|
17
|
+
# The following validations are added automatically:
|
18
|
+
# * Password must be present on creation
|
19
|
+
# * Password length should be less than or equal to 1024 bytes
|
20
|
+
# * Confirmation of password (using a +XXX_confirmation+ attribute)
|
21
|
+
#
|
22
|
+
# If confirmation validation is not needed, simply leave out the
|
23
|
+
# value for +XXX_confirmation+ (i.e. don't provide a form field for
|
24
|
+
# it). When this attribute has a +nil+ value, the validation will not be
|
25
|
+
# triggered.
|
26
|
+
#
|
27
|
+
# It is also possible to suppress the default validations completely by
|
28
|
+
# passing skip_validations: true as an argument.
|
29
|
+
#
|
30
|
+
# Add rbnacl (~> 7.1) to Gemfile to use #nacl_password:
|
31
|
+
#
|
32
|
+
# gem "rbnacl", "~> 7.1"
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
#
|
36
|
+
# # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
|
37
|
+
# class User < ActiveRecord::Base
|
38
|
+
# include NaClPassword::Concern
|
39
|
+
# nacl_password
|
40
|
+
# nacl_password :recovery_password, validations: false
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
|
44
|
+
# user.save # => false, password required
|
45
|
+
# user.password = 'mUc3m00RsqyRe'
|
46
|
+
# user.save # => false, confirmation doesn't match
|
47
|
+
# user.password_confirmation = 'mUc3m00RsqyRe'
|
48
|
+
# user.save # => true
|
49
|
+
# user.recovery_password = "42password"
|
50
|
+
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
|
51
|
+
# user.save # => true
|
52
|
+
# user.authenticate('notright') # => false
|
53
|
+
# user.authenticate('mUc3m00RsqyRe') # => user
|
54
|
+
# user.authenticate_recovery_password('42password') # => user
|
55
|
+
# User.find_by(name: 'david')&.authenticate('notright') # => false
|
56
|
+
# User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
|
57
|
+
def nacl_password(attribute = :password, digest_attribute: nil, **opts)
|
58
|
+
NaClPassword.setup
|
59
|
+
|
60
|
+
digest_attribute ||= "#{attribute}_digest"
|
61
|
+
|
62
|
+
attribute = attribute.to_sym
|
63
|
+
digest_attribute = digest_attribute.to_sym
|
64
|
+
|
65
|
+
if digest_attribute.to_s == attribute.to_s
|
66
|
+
raise ArgumentError, "Digest Attribute Name can't be the same as Password Attribute Name"
|
67
|
+
end
|
68
|
+
|
69
|
+
skip_validations =
|
70
|
+
CoerceBoolean.from(opts[:skip_validations]) &&
|
71
|
+
(opts[:skip_validations] != :blank)
|
72
|
+
|
73
|
+
length_options =
|
74
|
+
skip_validations ? {} :
|
75
|
+
{ maximum: NaClPassword::MAX_PASSWORD_LENGTH }.
|
76
|
+
merge(
|
77
|
+
opts[:min_length] == :none \
|
78
|
+
? {} \
|
79
|
+
: { minimum: opts[:min_length].presence&.to_i || 8 }
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
include InstanceMethodsOnActivation.new(attribute.to_sym, digest_attribute, **length_options)
|
84
|
+
|
85
|
+
unless skip_validations
|
86
|
+
include ActiveModel::Validations
|
87
|
+
|
88
|
+
# This ensures the model has a password by checking whether the password_digest
|
89
|
+
# is present, so that this works with both new and existing records. However,
|
90
|
+
# when there is an error, the message is added to the password attribute instead
|
91
|
+
# so that the error message will make sense to the end-user.
|
92
|
+
unless opts[:skip_validations] == :blank
|
93
|
+
validate do |record|
|
94
|
+
unless record.__send__(digest_attribute).present?
|
95
|
+
record.errors.add(attribute, :blank)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
validates_length_of attribute, **length_options, allow_blank: true
|
101
|
+
|
102
|
+
validates_confirmation_of attribute, allow_blank: true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class InstanceMethodsOnActivation < Module
|
108
|
+
def initialize(attribute, digest_attribute, **attribute_opts)
|
109
|
+
# == Constants ============================================================
|
110
|
+
confirmation_var = "@#{attribute}_confirmation"
|
111
|
+
|
112
|
+
# == Attributes ===========================================================
|
113
|
+
attr_reader attribute
|
114
|
+
|
115
|
+
define_method("#{attribute}=") do |given_password|
|
116
|
+
instance_variable_set("@#{attribute}", given_password.presence)
|
117
|
+
|
118
|
+
if given_password.nil? || given_password.empty?
|
119
|
+
self.__send__("#{digest_attribute}=", nil)
|
120
|
+
else
|
121
|
+
self.__send__(
|
122
|
+
"#{digest_attribute}=",
|
123
|
+
NaClPassword.generate(given_password)
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
define_method("#{attribute}_confirmation=") do |given_password|
|
129
|
+
instance_variable_set(confirmation_var, given_password)
|
130
|
+
end
|
131
|
+
|
132
|
+
# == Boolean Methods ======================================================
|
133
|
+
define_method("#{attribute}_length_valid?") do
|
134
|
+
value = self.__send__(attribute)
|
135
|
+
invalid = false
|
136
|
+
|
137
|
+
if attribute_opts[:maximum].present?
|
138
|
+
invalid ||= (value.length > attribute_opts[:maximum])
|
139
|
+
end
|
140
|
+
|
141
|
+
if attribute_opts[:minimum].present?
|
142
|
+
invalid ||= (value.length < attribute_opts[:minimum])
|
143
|
+
end
|
144
|
+
|
145
|
+
!invalid
|
146
|
+
end
|
147
|
+
|
148
|
+
define_method("#{attribute}_confirmed?") do
|
149
|
+
self.__send__(attribute) == self.instance_variable_get(confirmation_var)
|
150
|
+
end
|
151
|
+
|
152
|
+
define_method("#{attribute}_ready?") do |require_confirmation = false|
|
153
|
+
value = self.__send__(attribute)
|
154
|
+
if value.nil? || value.empty?
|
155
|
+
self.__send__("#{digest_attribute}").present?
|
156
|
+
else
|
157
|
+
confirmation = self.instance_variable_get(confirmation_var)
|
158
|
+
if !require_confirmation && (confirmation.nil? || confirmation.empty?)
|
159
|
+
self.__send__("#{attribute}_length_valid?")
|
160
|
+
else
|
161
|
+
self.__send__("#{attribute}_confirmed?") \
|
162
|
+
&& self.__send__("#{attribute}_length_valid?")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# == Instance Methods =====================================================
|
168
|
+
|
169
|
+
# Returns +self+ if the password is correct, otherwise +nil+.
|
170
|
+
#
|
171
|
+
# class User < ActiveRecord::Base
|
172
|
+
# nacl_password validations: false
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
|
176
|
+
# user.save
|
177
|
+
# user.authenticate_password('mUc3m00RsqyRe') # => user
|
178
|
+
# user.authenticate_password('notright') # => nil
|
179
|
+
define_method("authenticate_#{attribute}") do |given_password|
|
180
|
+
return nil unless attribute_digest = __send__(digest_attribute)
|
181
|
+
if NaClPassword.authenticate(attribute_digest, given_password)
|
182
|
+
self
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
alias_method :authenticate, :authenticate_password if attribute == :password
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module NaClPassword
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer 'nacl_password.include_concern' do
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
require 'nacl_password/concern'
|
6
|
+
ActiveRecord::Base.send :include, NaClPassword::Concern
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nacl_password
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sampson Crowley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-04-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: coerce_boolean
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rbnacl
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '6.0'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 6.0.2.2
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '6.0'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 6.0.2.2
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: sqlite3
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
description: |2
|
76
|
+
Easier encryption and decryption with libsodium while remaining configurable
|
77
|
+
with validated options. Also includes a concern for a "has_secure_password"
|
78
|
+
style one-line setup
|
79
|
+
email:
|
80
|
+
- sampsonsprojects@gmail.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- MIT-LICENSE
|
86
|
+
- README.md
|
87
|
+
- lib/nacl_password.rb
|
88
|
+
- lib/nacl_password/concern.rb
|
89
|
+
- lib/nacl_password/railtie.rb
|
90
|
+
- lib/nacl_password/version.rb
|
91
|
+
- lib/tasks/nacl_password_tasks.rake
|
92
|
+
homepage: https://github.com/SampsonCrowley/nacl_password
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubygems_version: 3.1.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: RbNaCl on Rails
|
115
|
+
test_files: []
|