jwtb 2.0.0.beta2.bsk1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +20 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +13 -0
  7. data/CHANGELOG.md +411 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +7 -0
  10. data/Manifest +8 -0
  11. data/README.md +443 -0
  12. data/Rakefile +11 -0
  13. data/jwtb.gemspec +31 -0
  14. data/lib/jwtb.rb +67 -0
  15. data/lib/jwtb/decode.rb +45 -0
  16. data/lib/jwtb/default_options.rb +14 -0
  17. data/lib/jwtb/encode.rb +51 -0
  18. data/lib/jwtb/error.rb +15 -0
  19. data/lib/jwtb/signature.rb +146 -0
  20. data/lib/jwtb/verify.rb +84 -0
  21. data/lib/jwtb/version.rb +24 -0
  22. data/spec/fixtures/certs/ec256-private.pem +8 -0
  23. data/spec/fixtures/certs/ec256-public.pem +4 -0
  24. data/spec/fixtures/certs/ec256-wrong-private.pem +8 -0
  25. data/spec/fixtures/certs/ec256-wrong-public.pem +4 -0
  26. data/spec/fixtures/certs/ec384-private.pem +9 -0
  27. data/spec/fixtures/certs/ec384-public.pem +5 -0
  28. data/spec/fixtures/certs/ec384-wrong-private.pem +9 -0
  29. data/spec/fixtures/certs/ec384-wrong-public.pem +5 -0
  30. data/spec/fixtures/certs/ec512-private.pem +10 -0
  31. data/spec/fixtures/certs/ec512-public.pem +6 -0
  32. data/spec/fixtures/certs/ec512-wrong-private.pem +10 -0
  33. data/spec/fixtures/certs/ec512-wrong-public.pem +6 -0
  34. data/spec/fixtures/certs/rsa-1024-private.pem +15 -0
  35. data/spec/fixtures/certs/rsa-1024-public.pem +6 -0
  36. data/spec/fixtures/certs/rsa-2048-private.pem +27 -0
  37. data/spec/fixtures/certs/rsa-2048-public.pem +9 -0
  38. data/spec/fixtures/certs/rsa-2048-wrong-private.pem +27 -0
  39. data/spec/fixtures/certs/rsa-2048-wrong-public.pem +9 -0
  40. data/spec/fixtures/certs/rsa-4096-private.pem +51 -0
  41. data/spec/fixtures/certs/rsa-4096-public.pem +14 -0
  42. data/spec/integration/readme_examples_spec.rb +216 -0
  43. data/spec/jwtb/verify_spec.rb +190 -0
  44. data/spec/jwtb_spec.rb +233 -0
  45. data/spec/spec_helper.rb +28 -0
  46. metadata +225 -0
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'jwtb/verify'
4
+
5
+ module JWTB
6
+ RSpec.describe Verify do
7
+ let(:base_payload) { { 'user_id' => 'some@user.tld' } }
8
+ let(:options) { { leeway: 0 } }
9
+
10
+ context '.verify_aud(payload, options)' do
11
+ let(:scalar_aud) { 'ruby-jwt-aud' }
12
+ let(:array_aud) { %w(ruby-jwt-aud test-aud ruby-ruby-ruby) }
13
+ let(:scalar_payload) { base_payload.merge('aud' => scalar_aud) }
14
+ let(:array_payload) { base_payload.merge('aud' => array_aud) }
15
+
16
+ it 'must raise JWTB::InvalidAudError when the singular audience does not match' do
17
+ expect do
18
+ Verify.verify_aud(scalar_payload, options.merge(aud: 'no-match'))
19
+ end.to raise_error JWTB::InvalidAudError
20
+ end
21
+
22
+ it 'must raise JWTB::InvalidAudError when the payload has an array and none match the supplied value' do
23
+ expect do
24
+ Verify.verify_aud(array_payload, options.merge(aud: 'no-match'))
25
+ end.to raise_error JWTB::InvalidAudError
26
+ end
27
+
28
+ it 'must allow a matching singular audience to pass' do
29
+ Verify.verify_aud(scalar_payload, options.merge(aud: scalar_aud))
30
+ end
31
+
32
+ it 'must allow an array with any value matching the one in the options' do
33
+ Verify.verify_aud(array_payload, options.merge(aud: array_aud.first))
34
+ end
35
+
36
+ it 'must allow an array with any value matching any value in the options array' do
37
+ Verify.verify_aud(array_payload, options.merge(aud: array_aud))
38
+ end
39
+
40
+ it 'must allow a singular audience payload matching any value in the options array' do
41
+ Verify.verify_aud(scalar_payload, options.merge(aud: array_aud))
42
+ end
43
+ end
44
+
45
+ context '.verify_expiration(payload, options)' do
46
+ let(:leeway) { 10 }
47
+ let(:payload) { base_payload.merge('exp' => (Time.now.to_i - 5)) }
48
+
49
+ it 'must raise JWTB::ExpiredSignature when the token has expired' do
50
+ expect do
51
+ Verify.verify_expiration(payload, options)
52
+ end.to raise_error JWTB::ExpiredSignature
53
+ end
54
+
55
+ it 'must allow some leeway in the expiration when global leeway is configured' do
56
+ Verify.verify_expiration(payload, options.merge(leeway: 10))
57
+ end
58
+
59
+ it 'must allow some leeway in the expiration when exp_leeway is configured' do
60
+ Verify.verify_expiration(payload, options.merge(exp_leeway: 10))
61
+ end
62
+
63
+ it 'must be expired if the exp claim equals the current time' do
64
+ payload['exp'] = Time.now.to_i
65
+
66
+ expect do
67
+ Verify.verify_expiration(payload, options)
68
+ end.to raise_error JWTB::ExpiredSignature
69
+ end
70
+ end
71
+
72
+ context '.verify_iat(payload, options)' do
73
+ let(:iat) { Time.now.to_f }
74
+ let(:payload) { base_payload.merge('iat' => iat) }
75
+
76
+ it 'must allow a valid iat' do
77
+ Verify.verify_iat(payload, options)
78
+ end
79
+
80
+ it 'must allow configured leeway' do
81
+ Verify.verify_iat(payload.merge('iat' => (iat + 60)), options.merge(leeway: 70))
82
+ end
83
+
84
+ it 'must allow configured iat_leeway' do
85
+ Verify.verify_iat(payload.merge('iat' => (iat + 60)), options.merge(iat_leeway: 70))
86
+ end
87
+
88
+ it 'must properly handle integer times' do
89
+ Verify.verify_iat(payload.merge('iat' => Time.now.to_i), options)
90
+ end
91
+
92
+ it 'must raise JWTB::InvalidIatError when the iat value is not Numeric' do
93
+ expect do
94
+ Verify.verify_iat(payload.merge('iat' => 'not a number'), options)
95
+ end.to raise_error JWTB::InvalidIatError
96
+ end
97
+
98
+ it 'must raise JWTB::InvalidIatError when the iat value is in the future' do
99
+ expect do
100
+ Verify.verify_iat(payload.merge('iat' => (iat + 120)), options)
101
+ end.to raise_error JWTB::InvalidIatError
102
+ end
103
+ end
104
+
105
+ context '.verify_iss(payload, options)' do
106
+ let(:iss) { 'ruby-jwt-gem' }
107
+ let(:payload) { base_payload.merge('iss' => iss) }
108
+
109
+ let(:invalid_token) { JWTB.encode base_payload, payload[:secret] }
110
+
111
+ it 'must raise JWTB::InvalidIssuerError when the configured issuer does not match the payload issuer' do
112
+ expect do
113
+ Verify.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
114
+ end.to raise_error JWTB::InvalidIssuerError
115
+ end
116
+
117
+ it 'must raise JWTB::InvalidIssuerError when the payload does not include an issuer' do
118
+ expect do
119
+ Verify.verify_iss(base_payload, options.merge(iss: iss))
120
+ end.to raise_error(JWTB::InvalidIssuerError, /received <none>/)
121
+ end
122
+
123
+ it 'must allow a matching issuer to pass' do
124
+ Verify.verify_iss(payload, options.merge(iss: iss))
125
+ end
126
+ end
127
+
128
+ context '.verify_jti(payload, options)' do
129
+ let(:payload) { base_payload.merge('jti' => 'some-random-uuid-or-whatever') }
130
+
131
+ it 'must allow any jti when the verfy_jti key in the options is truthy but not a proc' do
132
+ Verify.verify_jti(payload, options.merge(verify_jti: true))
133
+ end
134
+
135
+ it 'must raise JWTB::InvalidJtiError when the jti is missing' do
136
+ expect do
137
+ Verify.verify_jti(base_payload, options)
138
+ end.to raise_error JWTB::InvalidJtiError, /missing/i
139
+ end
140
+
141
+ it 'must raise JWTB::InvalidJtiError when the jti is an empty string' do
142
+ expect do
143
+ Verify.verify_jti(base_payload.merge('jti' => ' '), options)
144
+ end.to raise_error JWTB::InvalidJtiError, /missing/i
145
+ end
146
+
147
+ it 'must raise JWTB::InvalidJtiError when verify_jti proc returns false' do
148
+ expect do
149
+ Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti) { false }))
150
+ end.to raise_error JWTB::InvalidJtiError, /invalid/i
151
+ end
152
+
153
+ it 'true proc should not raise JWTB::InvalidJtiError' do
154
+ Verify.verify_jti(payload, options.merge(verify_jti: ->(_jti) { true }))
155
+ end
156
+ end
157
+
158
+ context '.verify_not_before(payload, options)' do
159
+ let(:payload) { base_payload.merge('nbf' => (Time.now.to_i + 5)) }
160
+
161
+ it 'must raise JWTB::ImmatureSignature when the nbf in the payload is in the future' do
162
+ expect do
163
+ Verify.verify_not_before(payload, options)
164
+ end.to raise_error JWTB::ImmatureSignature
165
+ end
166
+
167
+ it 'must allow some leeway in the token age when global leeway is configured' do
168
+ Verify.verify_not_before(payload, options.merge(leeway: 10))
169
+ end
170
+
171
+ it 'must allow some leeway in the token age when nbf_leeway is configured' do
172
+ Verify.verify_not_before(payload, options.merge(nbf_leeway: 10))
173
+ end
174
+ end
175
+
176
+ context '.verify_sub(payload, options)' do
177
+ let(:sub) { 'ruby jwt subject' }
178
+
179
+ it 'must raise JWTB::InvalidSubError when the subjects do not match' do
180
+ expect do
181
+ Verify.verify_sub(base_payload.merge('sub' => 'not-a-match'), options.merge(sub: sub))
182
+ end.to raise_error JWTB::InvalidSubError
183
+ end
184
+
185
+ it 'must allow a matching sub' do
186
+ Verify.verify_sub(base_payload.merge('sub' => sub), options.merge(sub: sub))
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,233 @@
1
+ require 'spec_helper'
2
+ require 'jwtb'
3
+ require 'jwtb/encode'
4
+ require 'jwtb/decode'
5
+
6
+ describe JWTB do
7
+ let(:payload) { { 'user_id' => 'some@user.tld' } }
8
+
9
+ let :data do
10
+ {
11
+ :secret => 'My$ecretK3y',
12
+ :rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-private.pem'))),
13
+ :rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-public.pem'))),
14
+ :wrong_rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
15
+ :wrong_rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
16
+ 'ES256_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-private.pem'))),
17
+ 'ES256_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-public.pem'))),
18
+ 'ES384_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-private.pem'))),
19
+ 'ES384_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-public.pem'))),
20
+ 'ES512_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-private.pem'))),
21
+ 'ES512_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-public.pem'))),
22
+ 'NONE' => 'eyJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.',
23
+ 'HS256' => 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.kWOVtIOpWcG7JnyJG0qOkTDbOy636XrrQhMm_8JrRQ8',
24
+ 'HS512256' => 'eyJhbGciOiJIUzUxMjI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.Ds_4ibvf7z4QOBoKntEjDfthy3WJ-3rKMspTEcHE2bA',
25
+ 'HS384' => 'eyJhbGciOiJIUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.VuV4j4A1HKhWxCNzEcwc9qVF3frrEu-BRLzvYPkbWO0LENRGy5dOiBQ34remM3XH',
26
+ 'HS512' => 'eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.8zNtCBTJIZTHpZ-BkhR-6sZY1K85Nm5YCKqV3AxRdsBJDt_RR-REH2db4T3Y0uQwNknhrCnZGvhNHrvhDwV1kA',
27
+ 'RS256' => 'eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.eSXvWP4GViiwUALj_-qTxU68I1oM0XjgDsCZBBUri2Ghh9d75QkVDoZ_v872GaqunN5A5xcnBK0-cOq-CR6OwibgJWfOt69GNzw5RrOfQ2mz3QI3NYEq080nF69h8BeqkiaXhI24Q51joEgfa9aj5Y-oitLAmtDPYTm7vTcdGufd6AwD3_3jajKBwkh0LPSeMtbe_5EyS94nFoEF9OQuhJYjUmp7agsBVa8FFEjVw5jEgVqkvERSj5hSY4nEiCAomdVxIKBfykyi0d12cgjhI7mBFwWkPku8XIPGZ7N8vpiSLdM68BnUqIK5qR7NAhtvT7iyLFgOqhZNUQ6Ret5VpQ',
28
+ 'RS384' => 'eyJhbGciOiJSUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.Sfgk56moPghtsjaP4so6tOy3I553mgwX-5gByMC6dX8lpeWgsxSeAd_K8IyO7u4lwYOL0DSftnqO1HEOuN1AKyBbDvaTXz3u2xNA2x4NYLdW4AZA6ritbYcKLO5BHTXw5ueMbtA1jjGXP0zI_aK2iJTMBmB8SCF88RYBUH01Tyf4PlLj98pGL-v3prZd6kZkIeRJ3326h04hslcB5HQKmgeBk24QNLIoIC-CD329HPjJ7TtGx01lj-ehTBnwVbBGzYFAyoalV5KgvL_MDOfWPr1OYHnR5s_Fm6_3Vg4u6lBljvHOrmv4Nfx7d8HLgbo8CwH4qn1wm6VQCtuDd-uhRg',
29
+ 'RS512' => 'eyJhbGciOiJSUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.LIIAUEuCkGNdpYguOO5LoW4rZ7ED2POJrB0pmEAAchyTdIK4HKh1jcLxc6KyGwZv40njCgub3y72q6vcQTn7oD0zWFCVQRIDW1911Ii2hRNHuigiPUnrnZh1OQ6z65VZRU6GKs8omoBGU9vrClBU0ODqYE16KxYmE_0n4Xw2h3D_L1LF0IAOtDWKBRDa3QHwZRM9sHsHNsBuD5ye9KzDYN1YALXj64LBfA-DoCKfpVAm9NkRPOyzjR2X2C3TomOSJgqWIVHJucudKDDAZyEbO4RA5pI-UFYy1370p9bRajvtDyoBuLDCzoSkMyQ4L2DnLhx5CbWcnD7Cd3GUmnjjTA',
30
+ 'ES256' => '',
31
+ 'ES384' => '',
32
+ 'ES512' => ''
33
+ }
34
+ end
35
+
36
+ after(:each) do
37
+ expect(OpenSSL.errors).to be_empty
38
+ end
39
+
40
+ context 'alg: NONE' do
41
+ let(:alg) { 'none' }
42
+
43
+ it 'should generate a valid token' do
44
+ token = JWTB.encode payload, nil, alg
45
+
46
+ expect(token).to eq data['NONE']
47
+ end
48
+
49
+ it 'should decode a valid token' do
50
+ jwt_payload, header = JWTB.decode data['NONE'], nil, false
51
+
52
+ expect(header['alg']).to eq alg
53
+ expect(jwt_payload).to eq payload
54
+ end
55
+
56
+ it 'should display a better error message if payload exp is_a?(Time)' do
57
+ payload['exp'] = Time.now
58
+
59
+ expect do
60
+ JWTB.encode payload, nil, alg
61
+ end.to raise_error JWTB::InvalidPayload
62
+ end
63
+ end
64
+
65
+ %w(HS256 HS512256 HS384 HS512).each do |alg|
66
+ context "alg: #{alg}" do
67
+ it 'should generate a valid token' do
68
+ token = JWTB.encode payload, data[:secret], alg
69
+
70
+ expect(token).to eq data[alg]
71
+ end
72
+
73
+ it 'should decode a valid token' do
74
+ jwt_payload, header = JWTB.decode data[alg], data[:secret], true, algorithm: alg
75
+
76
+ expect(header['alg']).to eq alg
77
+ expect(jwt_payload).to eq payload
78
+ end
79
+
80
+ it 'wrong secret should raise JWTB::DecodeError' do
81
+ expect do
82
+ JWTB.decode data[alg], 'wrong_secret', true, algorithm: alg
83
+ end.to raise_error JWTB::VerificationError
84
+ end
85
+
86
+ it 'wrong secret and verify = false should not raise JWTB::DecodeError' do
87
+ expect do
88
+ JWTB.decode data[alg], 'wrong_secret', false
89
+ end.not_to raise_error
90
+ end
91
+ end
92
+ end
93
+
94
+ %w(RS256 RS384 RS512).each do |alg|
95
+ context "alg: #{alg}" do
96
+ it 'should generate a valid token' do
97
+ token = JWTB.encode payload, data[:rsa_private], alg
98
+
99
+ expect(token).to eq data[alg]
100
+ end
101
+
102
+ it 'should decode a valid token' do
103
+ jwt_payload, header = JWTB.decode data[alg], data[:rsa_public], true, algorithm: alg
104
+
105
+ expect(header['alg']).to eq alg
106
+ expect(jwt_payload).to eq payload
107
+ end
108
+
109
+ it 'wrong key should raise JWTB::DecodeError' do
110
+ key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
111
+
112
+ expect do
113
+ JWTB.decode data[alg], key, true, algorithm: alg
114
+ end.to raise_error JWTB::DecodeError
115
+ end
116
+
117
+ it 'wrong key and verify = false should not raise JWTB::DecodeError' do
118
+ key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
119
+
120
+ expect do
121
+ JWTB.decode data[alg], key, false
122
+ end.not_to raise_error
123
+ end
124
+ end
125
+ end
126
+
127
+ %w(ES256 ES384 ES512).each do |alg|
128
+ context "alg: #{alg}" do
129
+ before(:each) do
130
+ data[alg] = JWTB.encode payload, data["#{alg}_private"], alg
131
+ end
132
+
133
+ let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) }
134
+
135
+ it 'should generate a valid token' do
136
+ jwt_payload, header = JWTB.decode data[alg], data["#{alg}_public"], true, algorithm: alg
137
+
138
+ expect(header['alg']).to eq alg
139
+ expect(jwt_payload).to eq payload
140
+ end
141
+
142
+ it 'should decode a valid token' do
143
+ jwt_payload, header = JWTB.decode data[alg], data["#{alg}_public"], true, algorithm: alg
144
+
145
+ expect(header['alg']).to eq alg
146
+ expect(jwt_payload).to eq payload
147
+ end
148
+
149
+ it 'wrong key should raise JWTB::DecodeError' do
150
+ expect do
151
+ JWTB.decode data[alg], wrong_key
152
+ end.to raise_error JWTB::DecodeError
153
+ end
154
+
155
+ it 'wrong key and verify = false should not raise JWTB::DecodeError' do
156
+ expect do
157
+ JWTB.decode data[alg], wrong_key, false
158
+ end.not_to raise_error
159
+ end
160
+ end
161
+ end
162
+
163
+ context 'Invalid' do
164
+ it 'algorithm should raise NotImplementedError' do
165
+ expect do
166
+ JWTB.encode payload, 'secret', 'HS255'
167
+ end.to raise_error NotImplementedError
168
+ end
169
+
170
+ it 'ECDSA curve_name should raise JWTB::IncorrectAlgorithm' do
171
+ key = OpenSSL::PKey::EC.new 'secp256k1'
172
+ key.generate_key
173
+
174
+ expect do
175
+ JWTB.encode payload, key, 'ES256'
176
+ end.to raise_error JWTB::IncorrectAlgorithm
177
+
178
+ token = JWTB.encode payload, data['ES256_private'], 'ES256'
179
+ key.private_key = nil
180
+
181
+ expect do
182
+ JWTB.decode token, key
183
+ end.to raise_error JWTB::IncorrectAlgorithm
184
+ end
185
+ end
186
+
187
+ context 'Verify' do
188
+ context 'algorithm' do
189
+ it 'should raise JWTB::IncorrectAlgorithm on missmatch' do
190
+ token = JWTB.encode payload, data[:secret], 'HS512'
191
+
192
+ expect do
193
+ JWTB.decode token, data[:secret], true, algorithm: 'HS384'
194
+ end.to raise_error JWTB::IncorrectAlgorithm
195
+
196
+ expect do
197
+ JWTB.decode token, data[:secret], true, algorithm: 'HS512'
198
+ end.not_to raise_error
199
+ end
200
+
201
+ it 'should raise JWTB::IncorrectAlgorithm if no algorithm is provided' do
202
+ token = JWTB.encode payload, data[:rsa_public].to_s, 'HS256'
203
+
204
+ expect do
205
+ JWTB.decode token, data[:rsa_public], true
206
+ end.to raise_error JWTB::IncorrectAlgorithm
207
+ end
208
+ end
209
+
210
+ context 'issuer claim' do
211
+ let(:iss) { 'ruby-jwt-gem' }
212
+ let(:invalid_token) { JWTB.encode payload, data[:secret] }
213
+
214
+ let :token do
215
+ iss_payload = payload.merge(iss: iss)
216
+ JWTB.encode iss_payload, data[:secret]
217
+ end
218
+
219
+ it 'if verify_iss is set to false (default option) should not raise JWTB::InvalidIssuerError' do
220
+ expect do
221
+ JWTB.decode token, data[:secret], true, iss: iss, algorithm: 'HS256'
222
+ end.not_to raise_error
223
+ end
224
+ end
225
+ end
226
+
227
+ context 'Base64' do
228
+ it 'urlsafe replace + / with - _' do
229
+ allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
230
+ expect(JWTB::Encode.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,28 @@
1
+ require 'rspec'
2
+ require 'simplecov'
3
+ require 'simplecov-json'
4
+ require 'codeclimate-test-reporter'
5
+ require 'codacy-coverage'
6
+
7
+ Codacy::Reporter.start
8
+
9
+ SimpleCov.configure do
10
+ root File.join(File.dirname(__FILE__), '..')
11
+ project_name 'Ruby JWT - Ruby JSON Web Token implementation'
12
+ add_filter 'spec'
13
+ end
14
+
15
+ SimpleCov.start if ENV['COVERAGE']
16
+
17
+ CERT_PATH = File.join(File.dirname(__FILE__), 'fixtures', 'certs')
18
+
19
+ RSpec.configure do |config|
20
+ config.expect_with :rspec do |c|
21
+ c.syntax = [:should, :expect]
22
+ end
23
+
24
+ config.run_all_when_everything_filtered = true
25
+ config.filter_run :focus
26
+
27
+ config.order = 'random'
28
+ end