/
verify_spec.rb
251 lines (205 loc) · 9.13 KB
/
verify_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# frozen_string_literal: true
RSpec.describe ::JWT::Verify do
let(:base_payload) { { 'user_id' => 'some@user.tld' } }
let(:options) { { leeway: 0 } }
context '.verify_aud(payload, options)' do
let(:scalar_aud) { 'ruby-jwt-aud' }
let(:array_aud) { %w[ruby-jwt-aud test-aud ruby-ruby-ruby] }
let(:scalar_payload) { base_payload.merge('aud' => scalar_aud) }
let(:array_payload) { base_payload.merge('aud' => array_aud) }
it 'must raise JWT::InvalidAudError when the singular audience does not match' do
expect do
described_class.verify_aud(scalar_payload, options.merge(aud: 'no-match'))
end.to raise_error JWT::InvalidAudError
end
it 'must raise JWT::InvalidAudError when the payload has an array and none match the supplied value' do
expect do
described_class.verify_aud(array_payload, options.merge(aud: 'no-match'))
end.to raise_error JWT::InvalidAudError
end
it 'must allow a matching singular audience to pass' do
described_class.verify_aud(scalar_payload, options.merge(aud: scalar_aud))
end
it 'must allow an array with any value matching the one in the options' do
described_class.verify_aud(array_payload, options.merge(aud: array_aud.first))
end
it 'must allow an array with any value matching any value in the options array' do
described_class.verify_aud(array_payload, options.merge(aud: array_aud))
end
it 'must allow a singular audience payload matching any value in the options array' do
described_class.verify_aud(scalar_payload, options.merge(aud: array_aud))
end
end
context '.verify_expiration(payload, options)' do
let(:payload) { base_payload.merge('exp' => (Time.now.to_i - 5)) }
it 'must raise JWT::ExpiredSignature when the token has expired' do
expect do
described_class.verify_expiration(payload, options)
end.to raise_error JWT::ExpiredSignature
end
it 'must allow some leeway in the expiration when global leeway is configured' do
described_class.verify_expiration(payload, options.merge(leeway: 10))
end
it 'must allow some leeway in the expiration when exp_leeway is configured' do
described_class.verify_expiration(payload, options.merge(exp_leeway: 10))
end
it 'must be expired if the exp claim equals the current time' do
payload['exp'] = Time.now.to_i
expect do
described_class.verify_expiration(payload, options)
end.to raise_error JWT::ExpiredSignature
end
context 'when leeway is not specified' do
let(:options) { {} }
it 'used a default leeway of 0' do
expect do
described_class.verify_expiration(payload, options)
end.to raise_error JWT::ExpiredSignature
end
end
end
context '.verify_iat(payload, options)' do
let(:iat) { Time.now.to_f }
let(:payload) { base_payload.merge('iat' => iat) }
it 'must allow a valid iat' do
described_class.verify_iat(payload, options)
end
it 'must ignore configured leeway' do
expect{described_class.verify_iat(payload.merge('iat' => (iat + 60)), options.merge(leeway: 70)) }
.to raise_error(JWT::InvalidIatError)
end
it 'must properly handle integer times' do
described_class.verify_iat(payload.merge('iat' => Time.now.to_i), options)
end
it 'must raise JWT::InvalidIatError when the iat value is not Numeric' do
expect do
described_class.verify_iat(payload.merge('iat' => 'not a number'), options)
end.to raise_error JWT::InvalidIatError
end
it 'must raise JWT::InvalidIatError when the iat value is in the future' do
expect do
described_class.verify_iat(payload.merge('iat' => (iat + 120)), options)
end.to raise_error JWT::InvalidIatError
end
end
context '.verify_iss(payload, options)' do
let(:iss) { 'ruby-jwt-gem' }
let(:payload) { base_payload.merge('iss' => iss) }
let(:invalid_token) { JWT.encode base_payload, payload[:secret] }
context 'when iss is a String' do
it 'must raise JWT::InvalidIssuerError when the configured issuer does not match the payload issuer' do
expect do
described_class.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
end.to raise_error JWT::InvalidIssuerError
end
it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
expect do
described_class.verify_iss(base_payload, options.merge(iss: iss))
end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
end
it 'must allow a matching issuer to pass' do
described_class.verify_iss(payload, options.merge(iss: iss))
end
end
context 'when iss is an Array' do
it 'must raise JWT::InvalidIssuerError when no matching issuers in array' do
expect do
described_class.verify_iss(payload, options.merge(iss: %w[first second]))
end.to raise_error JWT::InvalidIssuerError
end
it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
expect do
described_class.verify_iss(base_payload, options.merge(iss: %w[first second]))
end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
end
it 'must allow an array with matching issuer to pass' do
described_class.verify_iss(payload, options.merge(iss: ['first', iss, 'third']))
end
end
end
context '.verify_jti(payload, options)' do
let(:payload) { base_payload.merge('jti' => 'some-random-uuid-or-whatever') }
it 'must allow any jti when the verfy_jti key in the options is truthy but not a proc' do
described_class.verify_jti(payload, options.merge(verify_jti: true))
end
it 'must raise JWT::InvalidJtiError when the jti is missing' do
expect do
described_class.verify_jti(base_payload, options)
end.to raise_error JWT::InvalidJtiError, /missing/i
end
it 'must raise JWT::InvalidJtiError when the jti is an empty string' do
expect do
described_class.verify_jti(base_payload.merge('jti' => ' '), options)
end.to raise_error JWT::InvalidJtiError, /missing/i
end
it 'must raise JWT::InvalidJtiError when verify_jti proc returns false' do
expect do
described_class.verify_jti(payload, options.merge(verify_jti: ->(_jti) { false }))
end.to raise_error JWT::InvalidJtiError, /invalid/i
end
it 'true proc should not raise JWT::InvalidJtiError' do
described_class.verify_jti(payload, options.merge(verify_jti: ->(_jti) { true }))
end
it 'it should not throw arguement error with 2 args' do
expect do
described_class.verify_jti(payload, options.merge(verify_jti: ->(_jti, pl) {
true
}))
end.to_not raise_error
end
it 'should have payload as second param in proc' do
described_class.verify_jti(payload, options.merge(verify_jti: ->(_jti, pl) {
expect(pl).to eq(payload)
}))
end
end
context '.verify_not_before(payload, options)' do
let(:payload) { base_payload.merge('nbf' => (Time.now.to_i + 5)) }
it 'must raise JWT::ImmatureSignature when the nbf in the payload is in the future' do
expect do
described_class.verify_not_before(payload, options)
end.to raise_error JWT::ImmatureSignature
end
it 'must allow some leeway in the token age when global leeway is configured' do
described_class.verify_not_before(payload, options.merge(leeway: 10))
end
it 'must allow some leeway in the token age when nbf_leeway is configured' do
described_class.verify_not_before(payload, options.merge(nbf_leeway: 10))
end
end
context '.verify_sub(payload, options)' do
let(:sub) { 'ruby jwt subject' }
it 'must raise JWT::InvalidSubError when the subjects do not match' do
expect do
described_class.verify_sub(base_payload.merge('sub' => 'not-a-match'), options.merge(sub: sub))
end.to raise_error JWT::InvalidSubError
end
it 'must allow a matching sub' do
described_class.verify_sub(base_payload.merge('sub' => sub), options.merge(sub: sub))
end
context '.verify_claims' do
let(:fail_verifications_options) { { iss: 'mismatched-issuer', aud: 'no-match', sub: 'some subject' } }
let(:fail_verifications_payload) {
{
'exp' => (Time.now.to_i - 50),
'jti' => ' ',
'iss' => 'some-issuer',
'nbf' => (Time.now.to_i + 50),
'iat' => 'not a number',
'sub' => 'not-a-match'
}
}
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method|
let(:payload) { base_payload.merge(fail_verifications_payload) }
it "must skip verification when #{method} option is set to false" do
described_class.verify_claims(payload, options.merge(method => false))
end
it "must raise error when #{method} option is set to true" do
expect do
described_class.verify_claims(payload, options.merge(method => true).merge(fail_verifications_options))
end.to raise_error JWT::DecodeError
end
end
end
end
end