jwt 1.5.4 → 2.7.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.
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