jwt 1.5.4 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +5 -13
  2. data/AUTHORS +119 -0
  3. data/CHANGELOG.md +812 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +400 -79
  7. data/lib/jwt/algos/algo_wrapper.rb +30 -0
  8. data/lib/jwt/algos/ecdsa.rb +62 -0
  9. data/lib/jwt/algos/eddsa.rb +33 -0
  10. data/lib/jwt/algos/hmac.rb +73 -0
  11. data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
  12. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
  13. data/lib/jwt/algos/none.rb +19 -0
  14. data/lib/jwt/algos/ps.rb +41 -0
  15. data/lib/jwt/algos/rsa.rb +21 -0
  16. data/lib/jwt/algos/unsupported.rb +19 -0
  17. data/lib/jwt/algos.rb +67 -0
  18. data/lib/jwt/base64.rb +19 -0
  19. data/lib/jwt/claims_validator.rb +37 -0
  20. data/lib/jwt/configuration/container.rb +21 -0
  21. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  23. data/lib/jwt/configuration.rb +15 -0
  24. data/lib/jwt/decode.rb +141 -29
  25. data/lib/jwt/encode.rb +79 -0
  26. data/lib/jwt/error.rb +10 -0
  27. data/lib/jwt/json.rb +11 -9
  28. data/lib/jwt/jwk/ec.rb +236 -0
  29. data/lib/jwt/jwk/hmac.rb +103 -0
  30. data/lib/jwt/jwk/key_base.rb +55 -0
  31. data/lib/jwt/jwk/key_finder.rb +46 -0
  32. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  33. data/lib/jwt/jwk/okp_rbnacl.rb +110 -0
  34. data/lib/jwt/jwk/rsa.rb +203 -0
  35. data/lib/jwt/jwk/set.rb +80 -0
  36. data/lib/jwt/jwk/thumbprint.rb +26 -0
  37. data/lib/jwt/jwk.rb +55 -0
  38. data/lib/jwt/security_utils.rb +32 -0
  39. data/lib/jwt/verify.rb +59 -44
  40. data/lib/jwt/version.rb +25 -4
  41. data/lib/jwt/x5c_key_finder.rb +55 -0
  42. data/lib/jwt.rb +16 -162
  43. data/ruby-jwt.gemspec +19 -9
  44. metadata +64 -97
  45. data/.codeclimate.yml +0 -20
  46. data/.gitignore +0 -6
  47. data/.rspec +0 -2
  48. data/.rubocop.yml +0 -2
  49. data/.travis.yml +0 -13
  50. data/Gemfile +0 -4
  51. data/Manifest +0 -8
  52. data/Rakefile +0 -1
  53. data/spec/fixtures/certs/ec256-private.pem +0 -8
  54. data/spec/fixtures/certs/ec256-public.pem +0 -4
  55. data/spec/fixtures/certs/ec256-wrong-private.pem +0 -8
  56. data/spec/fixtures/certs/ec256-wrong-public.pem +0 -4
  57. data/spec/fixtures/certs/ec384-private.pem +0 -9
  58. data/spec/fixtures/certs/ec384-public.pem +0 -5
  59. data/spec/fixtures/certs/ec384-wrong-private.pem +0 -9
  60. data/spec/fixtures/certs/ec384-wrong-public.pem +0 -5
  61. data/spec/fixtures/certs/ec512-private.pem +0 -10
  62. data/spec/fixtures/certs/ec512-public.pem +0 -6
  63. data/spec/fixtures/certs/ec512-wrong-private.pem +0 -10
  64. data/spec/fixtures/certs/ec512-wrong-public.pem +0 -6
  65. data/spec/fixtures/certs/rsa-1024-private.pem +0 -15
  66. data/spec/fixtures/certs/rsa-1024-public.pem +0 -6
  67. data/spec/fixtures/certs/rsa-2048-private.pem +0 -27
  68. data/spec/fixtures/certs/rsa-2048-public.pem +0 -9
  69. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +0 -27
  70. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +0 -9
  71. data/spec/fixtures/certs/rsa-4096-private.pem +0 -51
  72. data/spec/fixtures/certs/rsa-4096-public.pem +0 -14
  73. data/spec/jwt/verify_spec.rb +0 -175
  74. data/spec/jwt_spec.rb +0 -232
  75. data/spec/spec_helper.rb +0 -31
data/lib/jwt/verify.rb CHANGED
@@ -1,98 +1,113 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'jwt/error'
2
4
 
3
5
  module JWT
4
6
  # JWT verify methods
5
7
  class Verify
8
+ DEFAULTS = {
9
+ leeway: 0
10
+ }.freeze
11
+
6
12
  class << self
7
- %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
13
+ %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name|
8
14
  define_method method_name do |payload, options|
9
15
  new(payload, options).send(method_name)
10
16
  end
11
17
  end
18
+
19
+ def verify_claims(payload, options)
20
+ options.each do |key, val|
21
+ next unless key.to_s =~ /verify/
22
+
23
+ Verify.send(key, payload, options) if val
24
+ end
25
+ end
12
26
  end
13
27
 
14
28
  def initialize(payload, options)
15
29
  @payload = payload
16
- @options = options
30
+ @options = DEFAULTS.merge(options)
17
31
  end
18
32
 
19
33
  def verify_aud
20
- return unless (options_aud = extract_option(:aud))
34
+ return unless (options_aud = @options[:aud])
21
35
 
22
- if @payload['aud'].is_a?(Array)
23
- fail(
24
- JWT::InvalidAudError,
25
- 'Invalid audience'
26
- ) unless @payload['aud'].include?(options_aud.to_s)
27
- else
28
- fail(
29
- JWT::InvalidAudError,
30
- "Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}"
31
- ) unless @payload['aud'].to_s == options_aud.to_s
32
- end
36
+ aud = @payload['aud']
37
+ raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
33
38
  end
34
39
 
35
40
  def verify_expiration
36
41
  return unless @payload.include?('exp')
37
-
38
- if @payload['exp'].to_i < (Time.now.to_i - leeway)
39
- fail(JWT::ExpiredSignature, 'Signature has expired')
40
- end
42
+ raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
41
43
  end
42
44
 
43
45
  def verify_iat
44
46
  return unless @payload.include?('iat')
45
47
 
46
- if !(@payload['iat'].is_a?(Numeric)) || @payload['iat'].to_f > (Time.now.to_f + leeway)
47
- fail(JWT::InvalidIatError, 'Invalid iat')
48
- end
48
+ iat = @payload['iat']
49
+ raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
49
50
  end
50
51
 
51
52
  def verify_iss
52
- return unless (options_iss = extract_option(:iss))
53
+ return unless (options_iss = @options[:iss])
54
+
55
+ iss = @payload['iss']
53
56
 
54
- if @payload['iss'].to_s != options_iss.to_s
55
- fail(
56
- JWT::InvalidIssuerError,
57
- "Invalid issuer. Expected #{options_iss}, received #{@payload['iss'] || '<none>'}"
58
- )
57
+ options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
58
+
59
+ case iss
60
+ when *options_iss
61
+ nil
62
+ else
63
+ raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
59
64
  end
60
65
  end
61
66
 
62
67
  def verify_jti
63
- options_verify_jti = extract_option(:verify_jti)
68
+ options_verify_jti = @options[:verify_jti]
69
+ jti = @payload['jti']
70
+
64
71
  if options_verify_jti.respond_to?(:call)
65
- fail(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(@payload['jti'])
66
- else
67
- fail(JWT::InvalidJtiError, 'Missing jti') if @payload['jti'].to_s.strip.empty?
72
+ verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti)
73
+ raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
74
+ elsif jti.to_s.strip.empty?
75
+ raise(JWT::InvalidJtiError, 'Missing jti')
68
76
  end
69
77
  end
70
78
 
71
79
  def verify_not_before
72
80
  return unless @payload.include?('nbf')
73
-
74
- if @payload['nbf'].to_i > (Time.now.to_i + leeway)
75
- fail(JWT::ImmatureSignature, 'Signature nbf has not been reached')
76
- end
81
+ raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
77
82
  end
78
83
 
79
84
  def verify_sub
80
- return unless (options_sub = extract_option(:sub))
85
+ return unless (options_sub = @options[:sub])
81
86
 
82
- fail(
83
- JWT::InvalidSubError,
84
- "Invalid subject. Expected #{options_sub}, received #{@payload['sub'] || '<none>'}"
85
- ) unless @payload['sub'].to_s == options_sub.to_s
87
+ sub = @payload['sub']
88
+ raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
89
+ end
90
+
91
+ def verify_required_claims
92
+ return unless (options_required_claims = @options[:required_claims])
93
+
94
+ options_required_claims.each do |required_claim|
95
+ raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless @payload.include?(required_claim)
96
+ end
86
97
  end
87
98
 
88
99
  private
89
100
 
90
- def extract_option(key)
91
- @options.values_at(key.to_sym, key.to_s).compact.first
101
+ def global_leeway
102
+ @options[:leeway]
103
+ end
104
+
105
+ def exp_leeway
106
+ @options[:exp_leeway] || global_leeway
92
107
  end
93
108
 
94
- def leeway
95
- extract_option :leeway
109
+ def nbf_leeway
110
+ @options[:nbf_leeway] || global_leeway
96
111
  end
97
112
  end
98
113
  end
data/lib/jwt/version.rb CHANGED
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  # Moments version builder module
4
4
  module JWT
@@ -9,15 +9,36 @@ module JWT
9
9
  # Moments version builder module
10
10
  module VERSION
11
11
  # major version
12
- MAJOR = 1
12
+ MAJOR = 2
13
13
  # minor version
14
- MINOR = 5
14
+ MINOR = 7
15
15
  # tiny version
16
- TINY = 4
16
+ TINY = 0
17
17
  # alpha, beta, etc. tag
18
18
  PRE = nil
19
19
 
20
20
  # Build version string
21
21
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
22
22
  end
23
+
24
+ def self.openssl_3?
25
+ return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
26
+ return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
27
+ end
28
+
29
+ def self.rbnacl?
30
+ defined?(::RbNaCl)
31
+ end
32
+
33
+ def self.rbnacl_6_or_greater?
34
+ rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
35
+ end
36
+
37
+ def self.openssl_3_hmac_empty_key_regression?
38
+ openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
39
+ end
40
+
41
+ def self.openssl_version
42
+ @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
43
+ end
23
44
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'jwt/error'
5
+
6
+ module JWT
7
+ # If the x5c header certificate chain can be validated by trusted root
8
+ # certificates, and none of the certificates are revoked, returns the public
9
+ # key from the first certificate.
10
+ # See https://tools.ietf.org/html/rfc7515#section-4.1.6
11
+ class X5cKeyFinder
12
+ def initialize(root_certificates, crls = nil)
13
+ raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
14
+
15
+ @store = build_store(root_certificates, crls)
16
+ end
17
+
18
+ def from(x5c_header_or_certificates)
19
+ signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates)
20
+ store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain)
21
+
22
+ if store_context.verify
23
+ signing_certificate.public_key
24
+ else
25
+ error = "Certificate verification failed: #{store_context.error_string}."
26
+ if (current_cert = store_context.current_cert)
27
+ error = "#{error} Certificate subject: #{current_cert.subject}."
28
+ end
29
+
30
+ raise(JWT::VerificationError, error)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def build_store(root_certificates, crls)
37
+ store = OpenSSL::X509::Store.new
38
+ store.purpose = OpenSSL::X509::PURPOSE_ANY
39
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
40
+ root_certificates.each { |certificate| store.add_cert(certificate) }
41
+ crls&.each { |crl| store.add_crl(crl) }
42
+ store
43
+ end
44
+
45
+ def parse_certificates(x5c_header_or_certificates)
46
+ if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) }
47
+ x5c_header_or_certificates
48
+ else
49
+ x5c_header_or_certificates.map do |encoded|
50
+ OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/jwt.rb CHANGED
@@ -1,177 +1,31 @@
1
- require 'base64'
2
- require 'openssl'
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt/version'
4
+ require 'jwt/base64'
5
+ require 'jwt/json'
3
6
  require 'jwt/decode'
7
+ require 'jwt/configuration'
8
+ require 'jwt/encode'
4
9
  require 'jwt/error'
5
- require 'jwt/json'
10
+ require 'jwt/jwk'
6
11
 
7
12
  # JSON Web Token implementation
8
13
  #
9
14
  # Should be up to date with the latest spec:
10
- # https://tools.ietf.org/html/rfc7519#section-4.1.5
15
+ # https://tools.ietf.org/html/rfc7519
11
16
  module JWT
12
- extend JWT::Json
13
-
14
- NAMED_CURVES = {
15
- 'prime256v1' => 'ES256',
16
- 'secp384r1' => 'ES384',
17
- 'secp521r1' => 'ES512'
18
- }
17
+ extend ::JWT::Configuration
19
18
 
20
19
  module_function
21
20
 
22
- def sign(algorithm, msg, key)
23
- if %w(HS256 HS384 HS512).include?(algorithm)
24
- sign_hmac(algorithm, msg, key)
25
- elsif %w(RS256 RS384 RS512).include?(algorithm)
26
- sign_rsa(algorithm, msg, key)
27
- elsif %w(ES256 ES384 ES512).include?(algorithm)
28
- sign_ecdsa(algorithm, msg, key)
29
- else
30
- fail NotImplementedError, 'Unsupported signing method'
31
- end
32
- end
33
-
34
- def sign_rsa(algorithm, msg, private_key)
35
- private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
36
- end
37
-
38
- def sign_ecdsa(algorithm, msg, private_key)
39
- key_algorithm = NAMED_CURVES[private_key.group.curve_name]
40
- if algorithm != key_algorithm
41
- fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
42
- end
43
-
44
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
45
- asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
46
- end
47
-
48
- def verify_rsa(algorithm, public_key, signing_input, signature)
49
- public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
50
- end
51
-
52
- def verify_ecdsa(algorithm, public_key, signing_input, signature)
53
- key_algorithm = NAMED_CURVES[public_key.group.curve_name]
54
- if algorithm != key_algorithm
55
- fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
56
- end
57
-
58
- digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
59
- public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
60
- end
61
-
62
- def sign_hmac(algorithm, msg, key)
63
- OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
64
- end
65
-
66
- def base64url_encode(str)
67
- Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
68
- end
69
-
70
- def encoded_header(algorithm = 'HS256', header_fields = {})
71
- header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
72
- base64url_encode(encode_json(header))
73
- end
74
-
75
- def encoded_payload(payload)
76
- base64url_encode(encode_json(payload))
77
- end
78
-
79
- def encoded_signature(signing_input, key, algorithm)
80
- if algorithm == 'none'
81
- ''
82
- else
83
- signature = sign(algorithm, signing_input, key)
84
- base64url_encode(signature)
85
- end
86
- end
87
-
88
21
  def encode(payload, key, algorithm = 'HS256', header_fields = {})
89
- algorithm ||= 'none'
90
- segments = []
91
- segments << encoded_header(algorithm, header_fields)
92
- segments << encoded_payload(payload)
93
- segments << encoded_signature(segments.join('.'), key, algorithm)
94
- segments.join('.')
95
- end
96
-
97
- def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
98
- fail(JWT::DecodeError, 'Nil JSON web token') unless jwt
99
-
100
- options = {
101
- verify_expiration: true,
102
- verify_not_before: true,
103
- verify_iss: false,
104
- verify_iat: false,
105
- verify_jti: false,
106
- verify_aud: false,
107
- verify_sub: false,
108
- leeway: 0
109
- }
110
-
111
- merged_options = options.merge(custom_options)
112
-
113
- decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
114
- header, payload, signature, signing_input = decoder.decode_segments
115
- decoder.verify
116
-
117
- fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
118
-
119
- if verify
120
- algo, key = signature_algorithm_and_key(header, key, &keyfinder)
121
- if merged_options[:algorithm] && algo != merged_options[:algorithm]
122
- fail JWT::IncorrectAlgorithm, 'Expected a different algorithm'
123
- end
124
- verify_signature(algo, key, signing_input, signature)
125
- end
126
-
127
- [payload, header]
128
- end
129
-
130
- def signature_algorithm_and_key(header, key, &keyfinder)
131
- key = keyfinder.call(header) if keyfinder
132
- [header['alg'], key]
133
- end
134
-
135
- def verify_signature(algo, key, signing_input, signature)
136
- if %w(HS256 HS384 HS512).include?(algo)
137
- fail(JWT::VerificationError, 'Signature verification raised') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
138
- elsif %w(RS256 RS384 RS512).include?(algo)
139
- fail(JWT::VerificationError, 'Signature verification raised') unless verify_rsa(algo, key, signing_input, signature)
140
- elsif %w(ES256 ES384 ES512).include?(algo)
141
- fail(JWT::VerificationError, 'Signature verification raised') unless verify_ecdsa(algo, key, signing_input, signature)
142
- else
143
- fail JWT::VerificationError, 'Algorithm not supported'
144
- end
145
- rescue OpenSSL::PKey::PKeyError
146
- raise JWT::VerificationError, 'Signature verification raised'
147
- ensure
148
- OpenSSL.errors.clear
149
- end
150
-
151
- # From devise
152
- # constant-time comparison algorithm to prevent timing attacks
153
- def secure_compare(a, b)
154
- return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
155
- l = a.unpack "C#{a.bytesize}"
156
-
157
- res = 0
158
- b.each_byte { |byte| res |= byte ^ l.shift }
159
- res == 0
160
- end
161
-
162
- def raw_to_asn1(signature, private_key)
163
- byte_size = (private_key.group.degree + 7) / 8
164
- r = signature[0..(byte_size - 1)]
165
- s = signature[byte_size..-1]
166
- OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
167
- end
168
-
169
- def asn1_to_raw(signature, public_key)
170
- byte_size = (public_key.group.degree + 7) / 8
171
- OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
22
+ Encode.new(payload: payload,
23
+ key: key,
24
+ algorithm: algorithm,
25
+ headers: header_fields).segments
172
26
  end
173
27
 
174
- def base64url_decode(str)
175
- Decode.base64url_decode(str)
28
+ def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
29
+ Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
176
30
  end
177
31
  end
data/ruby-jwt.gemspec CHANGED
@@ -1,4 +1,6 @@
1
- lib = File.expand_path('../lib/', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'jwt/version'
4
6
 
@@ -6,24 +8,32 @@ Gem::Specification.new do |spec|
6
8
  spec.name = 'jwt'
7
9
  spec.version = JWT.gem_version
8
10
  spec.authors = [
9
- 'Jeff Lindsay',
10
11
  'Tim Rudat'
11
12
  ]
12
13
  spec.email = 'timrudat@gmail.com'
13
14
  spec.summary = 'JSON Web Token implementation in Ruby'
14
15
  spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
15
- spec.homepage = 'http://github.com/jwt/ruby-jwt'
16
+ spec.homepage = 'https://github.com/jwt/ruby-jwt'
16
17
  spec.license = 'MIT'
18
+ spec.required_ruby_version = '>= 2.5'
19
+ spec.metadata = {
20
+ 'bug_tracker_uri' => 'https://github.com/jwt/ruby-jwt/issues',
21
+ 'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md",
22
+ 'rubygems_mfa_required' => 'true'
23
+ }
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(spec|gemfiles|coverage|bin)/}) || # Irrelevant folders
27
+ f.match(/^\.+/) || # Files and folders starting with .
28
+ f.match(/^(Appraisals|Gemfile|Rakefile)$/) # Irrelevant files
29
+ end
17
30
 
18
- spec.files = `git ls-files -z`.split("\x0")
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
- spec.require_paths = %w(lib)
31
+ spec.executables = []
32
+ spec.require_paths = %w[lib]
22
33
 
34
+ spec.add_development_dependency 'appraisal'
23
35
  spec.add_development_dependency 'bundler'
24
36
  spec.add_development_dependency 'rake'
25
37
  spec.add_development_dependency 'rspec'
26
38
  spec.add_development_dependency 'simplecov'
27
- spec.add_development_dependency 'simplecov-json'
28
- spec.add_development_dependency 'codeclimate-test-reporter'
29
39
  end