auto_hash 0.2.0 → 0.3.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.
data/README.html CHANGED
@@ -9,21 +9,27 @@
9
9
  <h2>File: README.rdoc</h2>
10
10
  <table>
11
11
  <tr><td>Path:</td><td>README.rdoc</td></tr>
12
- <tr><td>Modified:</td><td>Thu Aug 05 04:26:11 -0400 2010</td></tr>
12
+ <tr><td>Modified:</td><td>Thu Aug 12 21:17:47 -0400 2010</td></tr>
13
13
  </table>
14
14
 
15
15
  <h1>auto_hash</h1>
16
16
  <p>
17
- A Ruby on Rails plugin to automate hashing an activerecord field and saving
18
- as a salt and digest in a single field.
17
+ A Ruby on Rails plugin to automate the cryptographic hashing of an
18
+ activerecord field.
19
+ </p>
20
+ <p>
21
+ UPDATE: auto_hash has quickly evolved to be a activerecord plugin that
22
+ merely wraps the excellent bcrypt-ruby library. <a
23
+ href="http://bcrypt-ruby.rubyforge.org">bcrypt-ruby.rubyforge.org</a>
19
24
  </p>
20
25
  <p>
21
26
  Works with both rails 2x and 3x
22
27
  </p>
23
28
  <p>
24
- WARNING: untested with ruby 1.9 at the moment.
29
+ WARNING: untested with ruby 1.9.2 at the moment. (Couldn&#8216;t get rails
30
+ 3x to work with ruby 1.9.2 - too early I guess)
25
31
  </p>
26
- <h2>Synopsis</h2>
32
+ <h3>Synopsis</h3>
27
33
  <pre>
28
34
  # In Model
29
35
  class User &lt; ActiveRecord::Base
@@ -31,19 +37,16 @@ WARNING: untested with ruby 1.9 at the moment.
31
37
  end
32
38
 
33
39
  # Elsewhere
34
- user = User.new(:username =&gt; &quot;kevin&quot;, :password =&gt; &quot;asdf&quot;)
35
- user.password #=&gt; &quot;6cc4ce889e770343f4b0d3708851f6624b5c1dda4bc4b6dd23ace50328fcd3e0-b99d218de031fad5df71&quot;
36
- user.password_hash_match?(&quot;asdf&quot;) # =&gt; true
40
+ user = User.create(:email =&gt; &quot;kevin@example.com&quot;, :password =&gt; &quot;asdf&quot;)
41
+ user.password #=&gt; &quot;$2a$10$0DvrQ.HMKRySMJrn0cGEM.AcesF82tkeBfNLTZTZ.VSWePihZD3mG&quot;
42
+ user.password == &quot;asdf&quot; # =&gt; true
37
43
 
38
44
  # works with updating fields also
39
- user = User.find_by_username(&quot;kevin&quot;)
45
+ user = User.find_by_email(&quot;kevin@example.com&quot;)
40
46
  user.password = &quot;better_password&quot;
41
- user.password_hash_match?(&quot;better_password&quot;) # =&gt; true
42
- </pre>
43
- <h2>Installing</h2>
44
- <pre>
45
- sudo gem install auto_hash
47
+ user.password == &quot;better_password&quot; # =&gt; true
46
48
  </pre>
49
+ <h3>Rails configuration (nothing unusual)</h3>
47
50
  <p>
48
51
  For rails 2x, in environment.rb
49
52
  </p>
@@ -58,25 +61,7 @@ For rails 3x, in Gemfile
58
61
  <pre>
59
62
  gem &quot;auto_hash&quot;
60
63
  </pre>
61
- <h2>Details</h2>
62
- <p>
63
- The hashing is pretty simple, you&#8216;ll find something like this in the
64
- source:
65
- </p>
66
- <pre>
67
- salt = ActiveSupport::SecureRandom.hex(10)
68
- hash = Digest::SHA2.new.update(value + salt).to_s
69
- </pre>
70
- <p>
71
- The value stored in the database field is a hash appended with its salt, as
72
- &quot;hash-salt&quot;
73
- </p>
74
- <p>
75
- In case you weren&#8216;t sure, a salt is not necessarily a secret - its
76
- simply a way to defeat dictionary attacks by adding huge variety to the
77
- hashed value.
78
- </p>
79
- <h2>Copyright</h2>
64
+ <h3>Copyright</h3>
80
65
  <p>
81
66
  Copyright (c) 2010 Kevin Swope. See LICENSE for details.
82
67
  </p>
@@ -94,4 +79,4 @@ Files: 1
94
79
  Classes: 0
95
80
  Modules: 0
96
81
  Methods: 0
97
- Elapsed: 0.030s
82
+ Elapsed: 0.044s
data/README.rdoc CHANGED
@@ -1,13 +1,18 @@
1
1
  = auto_hash
2
2
 
3
- A Ruby on Rails plugin to automate hashing an activerecord field and
4
- saving as a salt and digest in a single field.
3
+ A Ruby on Rails plugin to automate the cryptographic hashing of an
4
+ activerecord field.
5
+
6
+ UPDATE: auto_hash has quickly evolved to be a activerecord plugin that
7
+ merely wraps the excellent bcrypt-ruby
8
+ library. http://bcrypt-ruby.rubyforge.org
5
9
 
6
10
  Works with both rails 2x and 3x
7
11
 
8
- WARNING: untested with ruby 1.9 at the moment.
12
+ WARNING: untested with ruby 1.9.2 at the moment. (Couldn't get rails
13
+ 3x to work with ruby 1.9.2 - too early I guess)
9
14
 
10
- == Synopsis
15
+ === Synopsis
11
16
 
12
17
  # In Model
13
18
  class User < ActiveRecord::Base
@@ -15,18 +20,16 @@ WARNING: untested with ruby 1.9 at the moment.
15
20
  end
16
21
 
17
22
  # Elsewhere
18
- user = User.new(:username => "kevin", :password => "asdf")
19
- user.password #=> "6cc4ce889e770343f4b0d3708851f6624b5c1dda4bc4b6dd23ace50328fcd3e0-b99d218de031fad5df71"
20
- user.password_hash_match?("asdf") # => true
23
+ user = User.create(:email => "kevin@example.com", :password => "asdf")
24
+ user.password #=> "$2a$10$0DvrQ.HMKRySMJrn0cGEM.AcesF82tkeBfNLTZTZ.VSWePihZD3mG"
25
+ user.password == "asdf" # => true
21
26
 
22
27
  # works with updating fields also
23
- user = User.find_by_username("kevin")
28
+ user = User.find_by_email("kevin@example.com")
24
29
  user.password = "better_password"
25
- user.password_hash_match?("better_password") # => true
26
-
27
- == Installing
30
+ user.password == "better_password" # => true
28
31
 
29
- sudo gem install auto_hash
32
+ === Rails configuration (nothing unusual)
30
33
 
31
34
  For rails 2x, in environment.rb
32
35
  Rails::Initializer.run do |config| do
@@ -36,21 +39,6 @@ For rails 2x, in environment.rb
36
39
  For rails 3x, in Gemfile
37
40
  gem "auto_hash"
38
41
 
39
-
40
- == Details
41
-
42
- The hashing is pretty simple, you'll find something like this in the source:
43
-
44
- salt = ActiveSupport::SecureRandom.hex(10)
45
- hash = Digest::SHA2.new.update(value + salt).to_s
46
-
47
- The value stored in the database field is a hash appended with its salt, as
48
- "hash-salt"
49
-
50
- In case you weren't sure, a salt is not necessarily a secret - its
51
- simply a way to defeat dictionary attacks by adding huge variety to
52
- the hashed value.
53
-
54
- == Copyright
42
+ === Copyright
55
43
 
56
44
  Copyright (c) 2010 Kevin Swope. See LICENSE for details.
data/Rakefile CHANGED
@@ -12,6 +12,7 @@ begin
12
12
  gem.email = "gems-kevdev@snkmail.com"
13
13
  gem.homepage = "http://github.com/kswope/auto_hash"
14
14
  gem.authors = ["Kevin Swope"]
15
+ gem.add_dependency('bcrypt-ruby')
15
16
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
17
  end
17
18
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/auto_hash.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{auto_hash}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kevin Swope"]
12
- s.date = %q{2010-08-05}
12
+ s.date = %q{2010-08-14}
13
13
  s.description = %q{Ruby on Rails plugin to automate hashing an activerecord field and saving as a salt and digest in a single field.}
14
14
  s.email = %q{gems-kevdev@snkmail.com}
15
15
  s.extra_rdoc_files = [
@@ -147,9 +147,12 @@ Gem::Specification.new do |s|
147
147
  s.specification_version = 3
148
148
 
149
149
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
150
+ s.add_runtime_dependency(%q<bcrypt-ruby>, [">= 0"])
150
151
  else
152
+ s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
151
153
  end
152
154
  else
155
+ s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
153
156
  end
154
157
  end
155
158
 
data/created.rid CHANGED
@@ -1 +1 @@
1
- Thu, 05 Aug 2010 04:26:12 -0400
1
+ Thu, 12 Aug 2010 21:17:48 -0400
data/init.rb CHANGED
@@ -2,3 +2,4 @@
2
2
  # NOTE: this file is for rails 2x only
3
3
 
4
4
  require "auto_hash"
5
+
data/lib/auto_hash.rb CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ require 'bcrypt'
3
+
2
4
  module AutoHash
3
5
 
4
6
  def self.included(base)
@@ -16,7 +18,7 @@ module AutoHash
16
18
  # Dynamically define a new setter
17
19
  define_method "#{field_name}=" do |value|
18
20
 
19
- value = AutoHashBuilder.auto_hash_create(value)
21
+ value = BCrypt::Password.create(value)
20
22
 
21
23
  # write_attribute() is the documented way to write to a AR
22
24
  # field after you've overridden the setter,
@@ -26,49 +28,13 @@ module AutoHash
26
28
  end
27
29
 
28
30
  # Dynamically define the "comparer"
29
- define_method "#{field_name}_hash_match?" do |match|
30
- existing = send(field_name)
31
- value = AutoHashBuilder.auto_hash_compare(existing, match.to_s)
32
- end
31
+ define_method "#{field_name}" do
33
32
 
34
- end
35
-
36
- end
37
-
38
- # This nested class is here only to avoid exposing the autohash
39
- # inner api to the AR model. It has no other excuse to be a class.
40
- class AutoHashBuilder
41
-
42
- class << self
43
-
44
- def auto_hash_create(value)
45
- salt = ActiveSupport::SecureRandom.hex(10)
46
- hash = create_hash(value, salt)
47
- "#{hash}-#{salt}"
48
- end
33
+ BCrypt::Password.new(read_attribute(field_name))
49
34
 
50
- def auto_hash_compare(hash, value)
51
- salt = extract_salt_part(hash)
52
- hash_old = extract_hash_part(hash)
53
- hash_new = create_hash(value, salt) # try to recreate same hash
54
- hash_new == hash_old
55
35
  end
56
36
 
57
- private #~*~*~*~*~*~*~*~*~*~*
58
-
59
- def create_hash(value, salt)
60
- Digest::SHA2.new.update(value + salt).to_s
61
- end
62
-
63
- def extract_hash_part(hash)
64
- hash.split(/-/)[0]
65
- end
66
-
67
- def extract_salt_part(hash)
68
- hash.split(/-/)[1]
69
- end
70
-
71
- end # << class << self
37
+ end
72
38
 
73
39
  end
74
40
 
@@ -6,21 +6,17 @@ class UserTest < ActiveSupport::TestCase
6
6
 
7
7
  test "simple instantiation" do
8
8
 
9
- @user = User.new(:username => "kevin", :password => "asdf")
9
+ @user = User.new(:email => "me@example.com", :password => "asdf")
10
10
  assert @user
11
11
 
12
12
  end
13
13
 
14
- # We can't exactly test if the new password is a correctly hashed
15
- # password until running the comparer method, but we can check if
16
- # its nil or blank, or its not still the clear text password - which
17
- # would be quite a bug.
18
- test "password hashed" do
14
+ test "password touched" do
19
15
 
20
16
  original_password = "asdf"
21
- @user = User.new(:username => "kevin", :password => original_password)
17
+ @user = User.new(:email => "me@example.com", :password => original_password)
22
18
 
23
- assert_equal "kevin", @user.username # make sure other fields untouched
19
+ assert_equal "me@example.com", @user.email # make sure other fields untouched
24
20
  assert_not_equal nil, @user.password
25
21
  assert_not_equal "", @user.password
26
22
  assert_not_equal original_password, @user.password
@@ -31,12 +27,13 @@ class UserTest < ActiveSupport::TestCase
31
27
  test "matching" do
32
28
 
33
29
  original_password = "asdf"
34
- @user = User.new(:username => "kevin", :password => original_password)
35
30
 
36
- assert @user.password_hash_match?(original_password)
37
- assert ! @user.password_hash_match?("banana")
38
- assert ! @user.password_hash_match?("")
39
- assert ! @user.password_hash_match?(nil)
31
+ @user = User.new(:email => "me@example.com", :password => original_password)
32
+
33
+ assert @user.password == original_password
34
+ assert ! (@user.password == "banana")
35
+ assert ! (@user.password == "")
36
+ assert ! (@user.password == nil)
40
37
 
41
38
  end
42
39
 
@@ -44,15 +41,16 @@ class UserTest < ActiveSupport::TestCase
44
41
  test "write, find, match" do
45
42
 
46
43
  original_password = "asdf"
47
- @user = User.new(:username => "kevin", :password => original_password)
48
- @user.save
49
44
 
50
- user = User.find_by_username("kevin")
45
+ @user = User.create(:email => "me@example.com",
46
+ :password => original_password)
47
+
48
+ user = User.find_by_email("me@example.com")
51
49
 
52
- assert user.password_hash_match?(original_password)
53
- assert ! user.password_hash_match?("banana")
54
- assert ! user.password_hash_match?("")
55
- assert ! user.password_hash_match?(nil)
50
+ assert user.password == original_password
51
+ assert ! (user.password == "banana")
52
+ assert ! (user.password == "")
53
+ assert ! (user.password == nil)
56
54
 
57
55
  end
58
56
 
@@ -1,7 +1,7 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.string :username
4
+ t.string :email
5
5
  t.string :password
6
6
  t.timestamps
7
7
  end
@@ -12,7 +12,7 @@
12
12
  ActiveRecord::Schema.define(:version => 20100803040436) do
13
13
 
14
14
  create_table "users", :force => true do |t|
15
- t.string "username"
15
+ t.string "email"
16
16
  t.string "password"
17
17
  t.datetime "created_at"
18
18
  t.datetime "updated_at"
Binary file
@@ -6,21 +6,17 @@ class UserTest < ActiveSupport::TestCase
6
6
 
7
7
  test "simple instantiation" do
8
8
 
9
- @user = User.new(:username => "kevin", :password => "asdf")
9
+ @user = User.new(:email => "me@example.com", :password => "asdf")
10
10
  assert @user
11
11
 
12
12
  end
13
13
 
14
- # We can't exactly test if the new password is a correctly hashed
15
- # password until running the comparer method, but we can check if
16
- # its nil or blank, or its not still the clear text password - which
17
- # would be quite a bug.
18
- test "password hashed" do
14
+ test "password touched" do
19
15
 
20
16
  original_password = "asdf"
21
- @user = User.new(:username => "kevin", :password => original_password)
17
+ @user = User.new(:email => "me@example.com", :password => original_password)
22
18
 
23
- assert_equal "kevin", @user.username # make sure other fields untouched
19
+ assert_equal "me@example.com", @user.email # make sure other fields untouched
24
20
  assert_not_equal nil, @user.password
25
21
  assert_not_equal "", @user.password
26
22
  assert_not_equal original_password, @user.password
@@ -31,12 +27,13 @@ class UserTest < ActiveSupport::TestCase
31
27
  test "matching" do
32
28
 
33
29
  original_password = "asdf"
34
- @user = User.new(:username => "kevin", :password => original_password)
35
30
 
36
- assert @user.password_hash_match?(original_password)
37
- assert ! @user.password_hash_match?("banana")
38
- assert ! @user.password_hash_match?("")
39
- assert ! @user.password_hash_match?(nil)
31
+ @user = User.new(:email => "me@example.com", :password => original_password)
32
+
33
+ assert @user.password == original_password
34
+ assert ! (@user.password == "banana")
35
+ assert ! (@user.password == "")
36
+ assert ! (@user.password == nil)
40
37
 
41
38
  end
42
39
 
@@ -44,15 +41,16 @@ class UserTest < ActiveSupport::TestCase
44
41
  test "write, find, match" do
45
42
 
46
43
  original_password = "asdf"
47
- @user = User.new(:username => "kevin", :password => original_password)
48
- @user.save
49
44
 
50
- user = User.find_by_username("kevin")
45
+ @user = User.create(:email => "me@example.com",
46
+ :password => original_password)
47
+
48
+ user = User.find_by_email("me@example.com")
51
49
 
52
- assert user.password_hash_match?(original_password)
53
- assert ! user.password_hash_match?("banana")
54
- assert ! user.password_hash_match?("")
55
- assert ! user.password_hash_match?(nil)
50
+ assert user.password == original_password
51
+ assert ! (user.password == "banana")
52
+ assert ! (user.password == "")
53
+ assert ! (user.password == nil)
56
54
 
57
55
  end
58
56
 
@@ -30,4 +30,6 @@ gem 'sqlite3-ruby', :require => 'sqlite3'
30
30
  # end
31
31
 
32
32
  gem "auto_hash", :path => "../.."
33
- gem "redgreen"
33
+ gem "redgreen"
34
+
35
+ gem 'bcrypt-ruby', :require => "bcrypt"
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: /Users/kevin/Development/auto_hash
3
3
  specs:
4
- auto_hash (0.1)
4
+ auto_hash (0.2.0)
5
+ bcrypt-ruby
5
6
 
6
7
  GEM
7
8
  remote: http://rubygems.org/
@@ -35,6 +36,7 @@ GEM
35
36
  activesupport (3.0.0.rc)
36
37
  arel (0.4.0)
37
38
  activesupport (>= 3.0.0.beta)
39
+ bcrypt-ruby (2.1.2)
38
40
  builder (2.1.2)
39
41
  erubis (2.6.6)
40
42
  abstract (>= 1.0.0)
@@ -65,7 +67,7 @@ GEM
65
67
  thor (~> 0.14.0)
66
68
  rake (0.8.7)
67
69
  redgreen (1.2.2)
68
- sqlite3-ruby (1.3.0)
70
+ sqlite3-ruby (1.3.1)
69
71
  thor (0.14.0)
70
72
  treetop (1.4.8)
71
73
  polyglot (>= 0.3.1)
@@ -76,6 +78,7 @@ PLATFORMS
76
78
 
77
79
  DEPENDENCIES
78
80
  auto_hash!
81
+ bcrypt-ruby
79
82
  rails (= 3.0.0.rc)
80
83
  redgreen
81
84
  sqlite3-ruby
@@ -1,7 +1,7 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.string :username
4
+ t.string :email
5
5
  t.string :password
6
6
  t.timestamps
7
7
  end
@@ -13,7 +13,7 @@
13
13
  ActiveRecord::Schema.define(:version => 20100804020911) do
14
14
 
15
15
  create_table "users", :force => true do |t|
16
- t.string "username"
16
+ t.string "email"
17
17
  t.string "password"
18
18
  t.datetime "created_at"
19
19
  t.datetime "updated_at"
@@ -6,21 +6,17 @@ class UserTest < ActiveSupport::TestCase
6
6
 
7
7
  test "simple instantiation" do
8
8
 
9
- @user = User.new(:username => "kevin", :password => "asdf")
9
+ @user = User.new(:email => "me@example.com", :password => "asdf")
10
10
  assert @user
11
11
 
12
12
  end
13
13
 
14
- # We can't exactly test if the new password is a correctly hashed
15
- # password until running the comparer method, but we can check if
16
- # its nil or blank, or its not still the clear text password - which
17
- # would be quite a bug.
18
- test "password hashed" do
14
+ test "password touched" do
19
15
 
20
16
  original_password = "asdf"
21
- @user = User.new(:username => "kevin", :password => original_password)
17
+ @user = User.new(:email => "me@example.com", :password => original_password)
22
18
 
23
- assert_equal "kevin", @user.username # make sure other fields untouched
19
+ assert_equal "me@example.com", @user.email # make sure other fields untouched
24
20
  assert_not_equal nil, @user.password
25
21
  assert_not_equal "", @user.password
26
22
  assert_not_equal original_password, @user.password
@@ -31,12 +27,13 @@ class UserTest < ActiveSupport::TestCase
31
27
  test "matching" do
32
28
 
33
29
  original_password = "asdf"
34
- @user = User.new(:username => "kevin", :password => original_password)
35
30
 
36
- assert @user.password_hash_match?(original_password)
37
- assert ! @user.password_hash_match?("banana")
38
- assert ! @user.password_hash_match?("")
39
- assert ! @user.password_hash_match?(nil)
31
+ @user = User.new(:email => "me@example.com", :password => original_password)
32
+
33
+ assert @user.password == original_password
34
+ assert ! (@user.password == "banana")
35
+ assert ! (@user.password == "")
36
+ assert ! (@user.password == nil)
40
37
 
41
38
  end
42
39
 
@@ -44,15 +41,16 @@ class UserTest < ActiveSupport::TestCase
44
41
  test "write, find, match" do
45
42
 
46
43
  original_password = "asdf"
47
- @user = User.new(:username => "kevin", :password => original_password)
48
- @user.save
49
44
 
50
- user = User.find_by_username("kevin")
45
+ @user = User.create(:email => "me@example.com",
46
+ :password => original_password)
47
+
48
+ user = User.find_by_email("me@example.com")
51
49
 
52
- assert user.password_hash_match?(original_password)
53
- assert ! user.password_hash_match?("banana")
54
- assert ! user.password_hash_match?("")
55
- assert ! user.password_hash_match?(nil)
50
+ assert user.password == original_password
51
+ assert ! (user.password == "banana")
52
+ assert ! (user.password == "")
53
+ assert ! (user.password == nil)
56
54
 
57
55
  end
58
56
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auto_hash
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kevin Swope
@@ -15,10 +15,23 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-05 00:00:00 -04:00
18
+ date: 2010-08-14 00:00:00 -04:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bcrypt-ruby
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
22
35
  description: Ruby on Rails plugin to automate hashing an activerecord field and saving as a salt and digest in a single field.
23
36
  email: gems-kevdev@snkmail.com
24
37
  executables: []