web_authn 0.0.0 → 0.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0f760c97d1bf71827f8f5fc1ae2e0da5ada27e221e7ab85ae001f1f97be6210
4
- data.tar.gz: 0b4ec0fa7befdccfe91666f0f550318513222aee0600b1263c44743c1211d590
3
+ metadata.gz: fd0d84bc14a9f6773426c3c509b379268f26b36ee5555fd8c3603a45cc677221
4
+ data.tar.gz: 124bfe151246e6380a59984f95784f6f39671038ddf98aed99dd8d4b8b59233f
5
5
  SHA512:
6
- metadata.gz: 6048298d255edac0b1d0bcf5689118c11087bc514ef60a6972dcaf6316fde3c3ec09b58ebda43b49e3d95e6e736cb37dfa8998382419689eeb28c29064ee0c07
7
- data.tar.gz: 10ed1a6298c6b8a22f9840c7f967a369501bc7429ac123f2baf7e69f1155efd157eefacbd8de11bf7aff9a29b41f80b7a5b6fe81977cef2810480c789b2e1f7b
6
+ metadata.gz: 811e1addfca6f94a181bb59dc02fd1bde4ac6c5692e5a1ed5903eb8b7063db5409061ab2880e3425be6f940aac2394d90d1223dea6d4c0a1262560388ba2fa38
7
+ data.tar.gz: faf55e02e28045d0cd1a767170bf20f606a9329bd6c351f8d81ea974e6c78d0e08d4d02bd2e714431cddc3ff3fd0651106325487e8c257da3fecc2072ebcf356
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.1
@@ -0,0 +1,33 @@
1
+ module WebAuthn
2
+ class AttestationObject
3
+ attr_accessor :format, :attestation_statement, :authenticator_data
4
+ alias_method :fmt, :format
5
+ alias_method :att_stmt, :attestation_statement
6
+ alias_method :auth_data, :authenticator_data
7
+
8
+ %i(credential_id rp_id_hash flags public_key sign_count).each do |method|
9
+ delegate method, to: :authenticator_data
10
+ end
11
+
12
+ def initialize(attrs)
13
+ self.format = attrs[:fmt]
14
+ self.attestation_statement = case format
15
+ when 'none'
16
+ nil
17
+ when 'packed', 'tpm', 'android-key', 'android-safetynet', 'fido-u2f'
18
+ raise "Unsupported Attestation Format: #{attestation_object[:fmt]}"
19
+ else
20
+ raise 'Unknown Attestation Format'
21
+ end
22
+ self.authenticator_data = AuthenticatorData.decode attrs[:authData]
23
+ end
24
+
25
+ class << self
26
+ def decode(encoded_attestation_object)
27
+ new CBOR.decode(
28
+ Base64.urlsafe_decode64 encoded_attestation_object
29
+ ).with_indifferent_access
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ module WebAuthn
2
+ class AttestedCredentialData
3
+ attr_accessor :aaguid, :credential_id, :public_key
4
+
5
+ def initialize(aaguid:, credential_id:, public_key:)
6
+ self.aaguid = aaguid
7
+ self.credential_id = credential_id
8
+ self.public_key = public_key
9
+ end
10
+
11
+ class << self
12
+ def decode(attested_credential_data)
13
+ length = (
14
+ ((attested_credential_data.getbyte(16) << 8) & 0xFF) +
15
+ (attested_credential_data.getbyte(17) & 0xFF)
16
+ )
17
+ aaguid,
18
+ credential_id,
19
+ _encoded_cose_key_ = [
20
+ attested_credential_data.byteslice(0...16),
21
+ attested_credential_data.byteslice(18...(18 + length)),
22
+ attested_credential_data.byteslice((18 + length)..-1),
23
+ ]
24
+ cose_key = COSE::Key::EC2.from_cbor(_encoded_cose_key_)
25
+ crv = case cose_key.curve
26
+ when 1
27
+ :'P-256'
28
+ when 2
29
+ :'P-384'
30
+ when 3
31
+ :'P-521'
32
+ else
33
+ raise 'Non-supported EC curve'
34
+ end
35
+ jwk = JSON::JWK.new(
36
+ kty: :EC,
37
+ crv: crv,
38
+ x: Base64.urlsafe_encode64(cose_key.x_coordinate, padding: false),
39
+ y: Base64.urlsafe_encode64(cose_key.y_coordinate, padding: false),
40
+ )
41
+ new(
42
+ aaguid: Base64.urlsafe_encode64(aaguid, padding: false),
43
+ credential_id: Base64.urlsafe_encode64(credential_id, padding: false),
44
+ public_key: jwk.to_key
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ module WebAuthn
2
+ class AuthenticatorData
3
+ class Flags
4
+ _flags_ = [:up, :uv, :at, :ex]
5
+ attr_accessor *_flags_
6
+ _flags_.each do |flag|
7
+ alias_method :"#{flag}?", flag
8
+ end
9
+
10
+ def initialize(up:, uv:, at:, ex:)
11
+ self.up = up
12
+ self.uv = uv
13
+ self.at = at
14
+ self.ex = ex
15
+ end
16
+
17
+ class << self
18
+ def decode(input)
19
+ bit_array = input.getbyte(0)
20
+ new(
21
+ up: bit_array[0] == 1,
22
+ uv: bit_array[2] == 1,
23
+ at: bit_array[6] == 1,
24
+ ex: bit_array[7] == 1
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,4 +1,47 @@
1
1
  module WebAuthn
2
- class AuthData
2
+ class AuthenticatorData
3
+ attr_accessor :rp_id_hash, :flags, :sign_count, :attested_credential_data
4
+
5
+ %i(credential_id public_key).each do |method|
6
+ delegate method, to: :attested_credential_data, allow_nil: true
7
+ end
8
+
9
+ def initialize(rp_id_hash:, flags:, sign_count:, attested_credential_data: nil)
10
+ self.rp_id_hash = rp_id_hash
11
+ self.flags = flags
12
+ self.sign_count = sign_count
13
+ self.attested_credential_data = attested_credential_data
14
+ end
15
+
16
+ class << self
17
+ def decode(auth_data)
18
+ rp_id_hash,
19
+ _flags_,
20
+ sign_count = [
21
+ auth_data.byteslice(0...32),
22
+ auth_data.byteslice(32),
23
+ auth_data.byteslice(33...37)
24
+ ]
25
+ flags = Flags.decode(_flags_)
26
+ attested_credential_data = if flags.at?
27
+ if flags.ex?
28
+ raise 'Extension Data Not Supported Yet'
29
+ else
30
+ AttestedCredentialData.decode auth_data.byteslice(37..-1)
31
+ end
32
+ else
33
+ nil
34
+ end
35
+
36
+ new(
37
+ rp_id_hash: Base64.urlsafe_encode64(rp_id_hash, padding: false),
38
+ flags: flags,
39
+ sign_count: sign_count.unpack('N1').first,
40
+ attested_credential_data: attested_credential_data
41
+ )
42
+ end
43
+ end
3
44
  end
4
45
  end
46
+
47
+ require 'web_authn/authenticator_data/flags'
@@ -1,4 +1,23 @@
1
1
  module WebAuthn
2
2
  class ClientDataJSON
3
+ attr_accessor :type, :origin, :challenge, :raw
4
+
5
+ def initialize(attrs = {})
6
+ self.type = attrs[:type]
7
+ self.origin = attrs[:origin]
8
+ self.challenge = attrs[:challenge]
9
+ self.raw = attrs[:raw]
10
+ end
11
+
12
+ class << self
13
+ def decode(encoded_client_data_json)
14
+ raw_client_data_json = Base64.urlsafe_decode64 encoded_client_data_json
15
+ new JSON.parse(
16
+ raw_client_data_json
17
+ ).merge(
18
+ raw: raw_client_data_json
19
+ ).with_indifferent_access
20
+ end
21
+ end
3
22
  end
4
23
  end
@@ -0,0 +1,54 @@
1
+ module WebAuthn
2
+ class Context
3
+ class Authentication < Context
4
+ attr_accessor :authenticator_data
5
+
6
+ # TODO: will need more methods, or let developers access deep methods by themselves.
7
+ %i(rp_id_hash flags sign_count).each do |method|
8
+ delegate method, to: :authenticator_data
9
+ end
10
+
11
+ def authentication?
12
+ true
13
+ end
14
+
15
+ def verify!(encoded_authenticator_data, public_key:, sign_count:, signature:)
16
+ raw_authenticator_data = Base64.urlsafe_decode64 encoded_authenticator_data
17
+ self.authenticator_data = AuthenticatorData.decode(
18
+ raw_authenticator_data
19
+ )
20
+ verify_sign_count!(sign_count, authenticator_data.sign_count)
21
+ verify_signature!(raw_authenticator_data, client_data_json.raw, public_key, signature)
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ def verify_sign_count!(before, current)
28
+ if before == 0 && current == 0
29
+ self # NOTE: no counter supported on the authenticator
30
+ elsif before < current
31
+ self
32
+ else
33
+ raise 'Invalid Sign Count'
34
+ end
35
+ end
36
+
37
+ def verify_signature!(raw_authenticator_data, raw_client_data_json, public_key, signature)
38
+ signature_base_string = [
39
+ raw_authenticator_data,
40
+ OpenSSL::Digest::SHA256.digest(raw_client_data_json)
41
+ ].join
42
+ result = public_key.dsa_verify_asn1(
43
+ OpenSSL::Digest::SHA256.digest(signature_base_string),
44
+ Base64.urlsafe_decode64(signature)
45
+ )
46
+ if result
47
+ self
48
+ else
49
+ raise 'Invalid Signature'
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ module WebAuthn
2
+ class Context
3
+ class Registration < Context
4
+ attr_accessor :attestation_object
5
+
6
+ # TODO: will need more methods, or let developers access deep methods by themselves.
7
+ %i(credential_id rp_id_hash flags public_key sign_count).each do |method|
8
+ delegate method, to: :attestation_object
9
+ end
10
+
11
+ def registration?
12
+ true
13
+ end
14
+
15
+ def verify!(encoded_attestation_object)
16
+ self.attestation_object = AttestationObject.decode(
17
+ encoded_attestation_object
18
+ )
19
+ self
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ module WebAuthn
2
+ class Context
3
+ attr_accessor :client_data_json
4
+
5
+ def initialize(client_data_json)
6
+ self.client_data_json = client_data_json
7
+ end
8
+
9
+ def verify_session!(origin:, challenge:)
10
+ raise 'Invalid Client Data JSON Origin' unless client_data_json.origin == origin
11
+ raise 'Invalid Client Data JSON Session' unless client_data_json.challenge == challenge
12
+ self
13
+ end
14
+
15
+ def registration?
16
+ false
17
+ end
18
+
19
+ def authentication?
20
+ false
21
+ end
22
+
23
+ class << self
24
+ def for(encoded_client_data_json, origin:, challenge:)
25
+ client_data_json = ClientDataJSON.decode encoded_client_data_json
26
+
27
+ context = case client_data_json.type
28
+ when 'webauthn.create'
29
+ Registration.new(client_data_json)
30
+ when 'webauthn.get'
31
+ Authentication.new(client_data_json)
32
+ else
33
+ raise 'Unknown Client Data JSON Type'
34
+ end
35
+
36
+ context.verify_session!(origin: origin, challenge: challenge)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ require 'web_authn/context/authentication'
43
+ require 'web_authn/context/registration'
data/lib/web_authn.rb CHANGED
@@ -1,7 +1,24 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'cbor'
4
+ require 'cose'
5
+ require 'cose/key/ec2'
6
+ require 'json/jwt'
7
+
1
8
  module WebAuthn
2
- # TODO:
9
+ module_function
10
+
11
+ def context_for(encoded_client_data_json, origin:, challenge:)
12
+ Context.for(
13
+ encoded_client_data_json,
14
+ origin: origin,
15
+ challenge: challenge
16
+ )
17
+ end
3
18
  end
4
19
 
5
20
  require 'web_authn/attestation_object'
21
+ require 'web_authn/attested_credential_data'
6
22
  require 'web_authn/authenticator_data'
7
23
  require 'web_authn/client_data_json'
24
+ require 'web_authn/context'
@@ -0,0 +1,46 @@
1
+ require 'web_authn'
2
+
3
+ authenticator_data = 'MsuA3KzDw1JGLLAfO_4wLebzcS8w_SDs0Zw7pbhYlJUBAAAAOw'
4
+
5
+ signature = 'MEUCIQDXp8Wqzz3ZYV7avKvH3R3XQhW7xPYb5Cq2nx3gpflDGwIgPN0tSy2mmgpI06IIKmjrIUxCvL4Rfc53mFXfVd_yL58'
6
+ sign_count = 0
7
+
8
+ client_data_json = 'eyJjaGFsbGVuZ2UiOiJjbUZ1Wkc5dExYTjBjbWx1WnkxblpXNWxjbUYwWldRdFlua3RjbkF0YzJWeWRtVnkiLCJvcmlnaW4iOiJodHRwczovL3dlYi1hdXRobi5zZWxmLWlzc3VlZC5hcHAiLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0'
9
+
10
+ origin = 'https://web-authn.self-issued.app'
11
+ challenge = 'cmFuZG9tLXN0cmluZy1nZW5lcmF0ZWQtYnktcnAtc2VydmVy'
12
+
13
+ public_key = OpenSSL::PKey::EC.new <<-PEM
14
+ -----BEGIN PUBLIC KEY-----
15
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpNU/8TjYoyN8FlhZ+YsOMAvyfQ4
16
+ i6/JN0/DPXuZMoxLvdb1vjh7vPUt2Osw3Bq+0NZsx3U/8kmpFuwsZhTi9A==
17
+ -----END PUBLIC KEY-----
18
+ PEM
19
+
20
+ context = WebAuthn.context_for(
21
+ client_data_json,
22
+ origin: origin,
23
+ challenge: challenge,
24
+ )
25
+ raise unless context.authentication?
26
+
27
+ context.verify!(
28
+ authenticator_data,
29
+ public_key: public_key,
30
+ sign_count: sign_count,
31
+ signature: signature
32
+ )
33
+
34
+ puts <<-OUT
35
+ # RP ID Hash
36
+ #{context.rp_id_hash}
37
+
38
+ # Flags
39
+ up: #{context.flags.up}
40
+ uv: #{context.flags.uv}
41
+ at: #{context.flags.at}
42
+ ex: #{context.flags.ex}
43
+
44
+ # Sign Count
45
+ #{context.sign_count}
46
+ OUT
@@ -0,0 +1,34 @@
1
+ # Common
2
+ context = WebAuthn.context_for(
3
+ client_data_json,
4
+ origin: request.base_url,
5
+ challenge: session[:challenge],
6
+ )
7
+
8
+ # Registration
9
+ raise unless context.registration?
10
+
11
+ context.verify!(params[:attestation_object])
12
+ current_account.fido_authenticators.create(
13
+ credential_id: context.credential_id,
14
+ public_key: context.public_key.to_pem,
15
+ sign_count: context.sign_count
16
+ )
17
+
18
+ # Authentication
19
+ raise unless context.authentication?
20
+
21
+ fido_authentiator = FIDO::Authenticatior.find_by(credential_id: params[:credential_id])
22
+ raise unless fido_authentiator.present?
23
+
24
+ context.verify!(
25
+ authenticator_data,
26
+ public_key: fido_authentiator.public_key,
27
+ sign_count: fido_authentiator.sign_count,
28
+ signature: params[:signature]
29
+ )
30
+
31
+ fido_authentiator.update!(
32
+ sign_count: context.sign_count
33
+ )
34
+ authenticate authenticator.user
@@ -0,0 +1,37 @@
1
+ require 'web_authn'
2
+
3
+ attestation_object = 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEMsuA3KzDw1JGLLAfO_4wLebzcS8w_SDs0Zw7pbhYlJVBAAAAMAAAAAAAAAAAAAAAAAAAAAAAQM1zXqvmYeVH9o2q1YcBZDSlkhvVs_2RjnKESVUktkQwQnYcU8jdo-duNLKrIOZNg0g4RCm0UMDZxtdXhR2bCu2lAQIDJiABIVggDMGhDLXoZit2uSMLyL-_emlFrGzlH7b2KpKpgYNzPRYiWCAl795OxcS2QimEnC9Jl_pNG3Gy_9O6m3_GbZdGsk90aw'
4
+
5
+ client_data_json = 'eyJjaGFsbGVuZ2UiOiJjbUZ1Wkc5dExYTjBjbWx1WnkxblpXNWxjbUYwWldRdFlua3RjbkF0YzJWeWRtVnkiLCJuZXdfa2V5c19tYXlfYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViLWF1dGhuLnNlbGYtaXNzdWVkLmFwcCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ'
6
+
7
+ origin = 'https://web-authn.self-issued.app'
8
+ challenge = 'cmFuZG9tLXN0cmluZy1nZW5lcmF0ZWQtYnktcnAtc2VydmVy'
9
+
10
+ context = WebAuthn.context_for(
11
+ client_data_json,
12
+ origin: origin,
13
+ challenge: challenge
14
+ )
15
+ raise unless context.registration?
16
+
17
+ context.verify! attestation_object
18
+
19
+ puts <<-OUT
20
+ # RP ID Hash
21
+ #{context.rp_id_hash}
22
+
23
+ # Flags
24
+ up: #{context.flags.up}
25
+ uv: #{context.flags.uv}
26
+ at: #{context.flags.at}
27
+ ex: #{context.flags.ex}
28
+
29
+ # Credential ID
30
+ #{context.credential_id}
31
+
32
+ # Public Key
33
+ #{context.public_key.to_pem}
34
+
35
+ # Sign Count
36
+ #{context.sign_count}
37
+ OUT
data/web_authn.gemspec CHANGED
@@ -12,8 +12,10 @@ Gem::Specification.new do |gem|
12
12
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
13
  gem.require_paths = ['lib']
14
14
  gem.required_ruby_version = '>= 2.3'
15
- gem.add_runtime_dependency 'json-jwt', '>= 1.9.4'
16
- gem.add_runtime_dependency 'cbor', '>= 0.5.9.3'
15
+ gem.add_runtime_dependency 'activesupport'
16
+ gem.add_runtime_dependency 'cbor'
17
+ gem.add_runtime_dependency 'cose'
18
+ gem.add_runtime_dependency 'json-jwt'
17
19
  gem.add_development_dependency 'rake', '~> 10.0'
18
20
  gem.add_development_dependency 'simplecov'
19
21
  gem.add_development_dependency 'rspec'
metadata CHANGED
@@ -1,43 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web_authn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nov matake
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-29 00:00:00.000000000 Z
11
+ date: 2018-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: json-jwt
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.9.4
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.9.4
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: cbor
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.5.9.3
33
+ version: '0'
34
+ type: :runtime
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: cose
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json-jwt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
34
62
  type: :runtime
35
63
  prerelease: false
36
64
  version_requirements: !ruby/object:Gem::Requirement
37
65
  requirements:
38
66
  - - ">="
39
67
  - !ruby/object:Gem::Version
40
- version: 0.5.9.3
68
+ version: '0'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: rake
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -97,9 +125,7 @@ dependencies:
97
125
  description: W3C WebAuthn (a.k.a. FIDO2) RP library in Ruby
98
126
  email:
99
127
  - nov@matake.jp
100
- executables:
101
- - console
102
- - setup
128
+ executables: []
103
129
  extensions: []
104
130
  extra_rdoc_files: []
105
131
  files:
@@ -111,12 +137,18 @@ files:
111
137
  - README.md
112
138
  - Rakefile
113
139
  - VERSION
114
- - bin/console
115
- - bin/setup
116
140
  - lib/web_authn.rb
117
141
  - lib/web_authn/attestation_object.rb
142
+ - lib/web_authn/attested_credential_data.rb
118
143
  - lib/web_authn/authenticator_data.rb
144
+ - lib/web_authn/authenticator_data/flags.rb
119
145
  - lib/web_authn/client_data_json.rb
146
+ - lib/web_authn/context.rb
147
+ - lib/web_authn/context/authentication.rb
148
+ - lib/web_authn/context/registration.rb
149
+ - samples/authentication_response.rb
150
+ - samples/concept.rb
151
+ - samples/registration_response.rb
120
152
  - spec/spec_helper.rb
121
153
  - spec/web_authn_spec.rb
122
154
  - web_authn.gemspec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "web_authn"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here