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