jwt 1.3.0 → 1.4.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.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/jwt.gemspec +3 -3
- data/lib/jwt.rb +34 -2
- data/spec/helper.rb +0 -3
- data/spec/jwt_spec.rb +160 -66
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22e5c1eafdf6a7e80397bbca142c3bff69524b21
|
4
|
+
data.tar.gz: e7acda6c7609412c393a5ebb29e575cb82618f9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f897824dd5f6ccc196f16c9944914c14ca0d476d9bc030bc8872ad7e2c5234a3db907ee35b780715689549fe397a4e76521b0e3d06d37f2b06d890cbcf855633
|
7
|
+
data.tar.gz: aec0e48a68cee3b5694d97f47aa0b3721ac2eee8a82b038c172c884d3ccfc1ade1728a4d4bf92d7cedd2d8bca9273cb180a14fb4ee306b01e981b2e752341515
|
data/Rakefile
CHANGED
data/jwt.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: jwt 1.
|
2
|
+
# stub: jwt 1.4.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "jwt"
|
6
|
-
s.version = "1.
|
6
|
+
s.version = "1.4.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = ["Jeff Lindsay"]
|
11
|
-
s.date = "2015-
|
11
|
+
s.date = "2015-03-10"
|
12
12
|
s.description = "JSON Web Token implementation in Ruby"
|
13
13
|
s.email = "progrium@gmail.com"
|
14
14
|
s.extra_rdoc_files = ["lib/jwt.rb", "lib/jwt/json.rb"]
|
data/lib/jwt.rb
CHANGED
@@ -11,8 +11,13 @@ require "jwt/json"
|
|
11
11
|
module JWT
|
12
12
|
class DecodeError < StandardError; end
|
13
13
|
class VerificationError < DecodeError; end
|
14
|
-
class ExpiredSignature <
|
15
|
-
class ImmatureSignature <
|
14
|
+
class ExpiredSignature < DecodeError; end
|
15
|
+
class ImmatureSignature < DecodeError; end
|
16
|
+
class InvalidIssuerError < DecodeError; end
|
17
|
+
class InvalidIatError < DecodeError; end
|
18
|
+
class InvalidAudError < DecodeError; end
|
19
|
+
class InvalidSubError < DecodeError; end
|
20
|
+
class InvalidJtiError < DecodeError; end
|
16
21
|
extend JWT::Json
|
17
22
|
|
18
23
|
module_function
|
@@ -105,8 +110,14 @@ module JWT
|
|
105
110
|
default_options = {
|
106
111
|
:verify_expiration => true,
|
107
112
|
:verify_not_before => true,
|
113
|
+
:verify_iss => true,
|
114
|
+
:verify_iat => true,
|
115
|
+
:verify_jti => true,
|
116
|
+
:verify_aud => true,
|
117
|
+
:verify_sub => true,
|
108
118
|
:leeway => 0
|
109
119
|
}
|
120
|
+
|
110
121
|
options = default_options.merge(options)
|
111
122
|
|
112
123
|
if verify
|
@@ -120,6 +131,27 @@ module JWT
|
|
120
131
|
if options[:verify_not_before] && payload.include?('nbf')
|
121
132
|
raise JWT::ImmatureSignature.new("Signature nbf has not been reached") unless payload['nbf'].to_i < (Time.now.to_i + options[:leeway])
|
122
133
|
end
|
134
|
+
if options[:verify_iss] && payload.include?('iss')
|
135
|
+
raise JWT::InvalidIssuerError.new("Invalid issuer") unless payload['iss'].to_s == options['iss'].to_s
|
136
|
+
end
|
137
|
+
if options[:verify_iat] && payload.include?('iat')
|
138
|
+
raise JWT::InvalidIatError.new("Invalid iat") unless (payload['iat'].is_a?(Integer) and payload['iat'].to_i <= Time.now.to_i)
|
139
|
+
end
|
140
|
+
if options[:verify_aud] && payload.include?('aud')
|
141
|
+
if payload['aud'].is_a?(Array)
|
142
|
+
raise JWT::InvalidAudError.new("Invalid audience") unless payload['aud'].include?(options['aud'])
|
143
|
+
else
|
144
|
+
raise JWT::InvalidAudError.new("Invalid audience") unless payload['aud'].to_s == options['aud'].to_s
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if options[:verify_sub] && payload.include?('sub')
|
148
|
+
raise JWT::InvalidSubError.new("Invalid subject") unless payload['sub'].to_s == options['sub'].to_s
|
149
|
+
end
|
150
|
+
if options[:verify_jti] && payload.include?('jti')
|
151
|
+
raise JWT::InvalidJtiError.new("need iat for verify jwt id") unless payload.include?('iat')
|
152
|
+
raise JWT::InvalidJtiError.new("Not a uniq jwt id") unless options['jti'].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
|
153
|
+
end
|
154
|
+
|
123
155
|
return payload,header
|
124
156
|
end
|
125
157
|
|
data/spec/helper.rb
CHANGED
data/spec/jwt_spec.rb
CHANGED
@@ -2,81 +2,175 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe JWT do
|
4
4
|
before do
|
5
|
-
@payload = {
|
5
|
+
@payload = {'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 }
|
6
6
|
end
|
7
7
|
|
8
|
-
it
|
9
|
-
secret =
|
8
|
+
it 'encodes and decodes JWTs' do
|
9
|
+
secret = 'secret'
|
10
10
|
jwt = JWT.encode(@payload, secret)
|
11
11
|
decoded_payload = JWT.decode(jwt, secret)
|
12
12
|
expect(decoded_payload).to include(@payload)
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
15
|
+
it 'encodes and decodes JWTs for RSA signatures' do
|
16
16
|
private_key = OpenSSL::PKey::RSA.generate(512)
|
17
|
-
jwt = JWT.encode(@payload, private_key,
|
17
|
+
jwt = JWT.encode(@payload, private_key, 'RS256')
|
18
18
|
decoded_payload = JWT.decode(jwt, private_key.public_key)
|
19
19
|
expect(decoded_payload).to include(@payload)
|
20
20
|
end
|
21
21
|
|
22
|
-
it
|
22
|
+
it 'encodes and decodes JWTs with custom header fields' do
|
23
23
|
private_key = OpenSSL::PKey::RSA.generate(512)
|
24
|
-
jwt = JWT.encode(@payload, private_key,
|
24
|
+
jwt = JWT.encode(@payload, private_key, 'RS256', {'kid' => 'default'})
|
25
25
|
decoded_payload = JWT.decode(jwt) do |header|
|
26
|
-
expect(header[
|
26
|
+
expect(header['kid']).to eq('default')
|
27
27
|
private_key.public_key
|
28
28
|
end
|
29
29
|
expect(decoded_payload).to include(@payload)
|
30
30
|
end
|
31
31
|
|
32
|
-
it
|
33
|
-
example_payload = {
|
32
|
+
it 'decodes valid JWTs' do
|
33
|
+
example_payload = {'hello' => 'world'}
|
34
34
|
example_secret = 'secret'
|
35
35
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
36
36
|
decoded_payload = JWT.decode(example_jwt, example_secret)
|
37
37
|
expect(decoded_payload).to include(example_payload)
|
38
38
|
end
|
39
39
|
|
40
|
-
it
|
40
|
+
it 'decodes valid JWTs with iss' do
|
41
|
+
example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
|
42
|
+
example_secret = 'secret'
|
43
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
44
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iss' => 'jwtiss'})
|
45
|
+
expect(decoded_payload).to include(example_payload)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises invalid issuer' do
|
49
|
+
# example_payload = {'hello' => 'world', 'iss' => 'jwtiss'}
|
50
|
+
example_payload2 = {'hello' => 'world'}
|
51
|
+
|
52
|
+
example_secret = 'secret'
|
53
|
+
|
54
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
55
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iss' => 'jwt_iss'}) }.to raise_error(JWT::InvalidIssuerError)
|
56
|
+
|
57
|
+
example_jwt2 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
58
|
+
decode_payload2 = JWT.decode(example_jwt2, example_secret, true, {'iss' => 'jwt_iss'})
|
59
|
+
expect(decode_payload2).to include(example_payload2)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'decodes valid JWTs with iat' do
|
63
|
+
example_payload = {'hello' => 'world', 'iat' => 1425917209}
|
64
|
+
example_secret = 'secret'
|
65
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU'
|
66
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'iat' => true})
|
67
|
+
expect(decoded_payload).to include(example_payload)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises decode exception when iat is invalid' do
|
71
|
+
# example_payload = {'hello' => 'world', 'iat' => 'abc'}
|
72
|
+
example_secret = 'secret'
|
73
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
|
74
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iat' => 1425917209}) }.to raise_error(JWT::InvalidIatError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'decodes valid JWTs with jti' do
|
78
|
+
example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
79
|
+
example_secret = 'secret'
|
80
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
81
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425917209')})
|
82
|
+
expect(decoded_payload).to include(example_payload)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'raises decode exception when jti is invalid' do
|
86
|
+
# example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
87
|
+
example_secret = 'secret'
|
88
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
89
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
|
90
|
+
expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'raises decode exception when jti without iat' do
|
94
|
+
# example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')}
|
95
|
+
example_secret = 'secret'
|
96
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE'
|
97
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'jti' => Digest::MD5.hexdigest('secret:1425922032')}) }.to raise_error(JWT::InvalidJtiError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'decodes valid JWTs with aud' do
|
101
|
+
example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
|
102
|
+
example_payload2 = {'hello' => 'world', 'aud' => ['url:pnd', 'aud:yes']}
|
103
|
+
example_secret = 'secret'
|
104
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
|
105
|
+
example_jwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbInVybDpwbmQiLCJhdWQ6eWVzIl19.qNPNcT4X9B5uI91rIwbW2bIPTsp8wbRYW3jkZkrmqbQ'
|
106
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'aud' => 'url:pnd'})
|
107
|
+
decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, {'aud' => 'url:pnd'})
|
108
|
+
expect(decoded_payload).to include(example_payload)
|
109
|
+
expect(decoded_payload2).to include(example_payload2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises deode exception when aud is invalid' do
|
113
|
+
# example_payload = {'hello' => 'world', 'aud' => 'url:pnd'}
|
114
|
+
example_secret = 'secret'
|
115
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8'
|
116
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'aud' => 'wrong:aud'}) }.to raise_error(JWT::InvalidAudError)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'decodes valid JWTs with sub' do
|
120
|
+
example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
121
|
+
example_secret = 'secret'
|
122
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
123
|
+
decoded_payload = JWT.decode(example_jwt, example_secret, true, {'sub' => 'subject'})
|
124
|
+
expect(decoded_payload).to include(example_payload)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'raise decode exception when the sub is invalid' do
|
128
|
+
# example_payload = {'hello' => 'world', 'sub' => 'subject'}
|
129
|
+
example_secret = 'secret'
|
130
|
+
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
131
|
+
expect{ JWT.decode(example_jwt, example_secret, true, {'iss' => 'subject'}) }.to raise_error(JWT::InvalidSubError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raises decode exception when the token is invalid' do
|
41
135
|
example_secret = 'secret'
|
42
136
|
# Same as above exmaple with some random bytes replaced
|
43
137
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
44
138
|
expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
|
45
139
|
end
|
46
140
|
|
47
|
-
it
|
141
|
+
it 'raises verification exception with wrong hmac key' do
|
48
142
|
right_secret = 'foo'
|
49
143
|
bad_secret = 'bar'
|
50
|
-
jwt_message = JWT.encode(@payload, right_secret,
|
144
|
+
jwt_message = JWT.encode(@payload, right_secret, 'HS256')
|
51
145
|
expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::VerificationError)
|
52
146
|
end
|
53
147
|
|
54
|
-
it
|
148
|
+
it 'raises verification exception with wrong rsa key' do
|
55
149
|
right_private_key = OpenSSL::PKey::RSA.generate(512)
|
56
150
|
bad_private_key = OpenSSL::PKey::RSA.generate(512)
|
57
|
-
jwt = JWT.encode(@payload, right_private_key,
|
151
|
+
jwt = JWT.encode(@payload, right_private_key, 'RS256')
|
58
152
|
expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError)
|
59
153
|
end
|
60
154
|
|
61
|
-
it
|
155
|
+
it 'raises decode exception with invalid signature' do
|
62
156
|
example_secret = 'secret'
|
63
157
|
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.'
|
64
158
|
expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
|
65
159
|
end
|
66
160
|
|
67
|
-
it
|
68
|
-
expect { JWT.decode(
|
161
|
+
it 'raises decode exception with nonexistent header' do
|
162
|
+
expect { JWT.decode('..stuff') }.to raise_error(JWT::DecodeError)
|
69
163
|
end
|
70
164
|
|
71
|
-
it
|
72
|
-
expect { JWT.decode(
|
165
|
+
it 'raises decode exception with nonexistent payload' do
|
166
|
+
expect { JWT.decode('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff') }.to raise_error(JWT::DecodeError)
|
73
167
|
end
|
74
168
|
|
75
|
-
it
|
169
|
+
it 'raises decode exception with nil jwt' do
|
76
170
|
expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError)
|
77
171
|
end
|
78
172
|
|
79
|
-
it
|
173
|
+
it 'allows decoding without key' do
|
80
174
|
right_secret = 'foo'
|
81
175
|
bad_secret = 'bar'
|
82
176
|
jwt = JWT.encode(@payload, right_secret)
|
@@ -84,35 +178,35 @@ describe JWT do
|
|
84
178
|
expect(decoded_payload).to include(@payload)
|
85
179
|
end
|
86
180
|
|
87
|
-
it
|
181
|
+
it 'checks the key when verify is truthy' do
|
88
182
|
right_secret = 'foo'
|
89
183
|
bad_secret = 'bar'
|
90
184
|
jwt = JWT.encode(@payload, right_secret)
|
91
|
-
verify =
|
185
|
+
verify = 'yes' =~ /^y/i
|
92
186
|
expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError)
|
93
187
|
end
|
94
188
|
|
95
|
-
it
|
96
|
-
expect { JWT.encode(@payload,
|
189
|
+
it 'raises exception on unsupported crypto algorithm' do
|
190
|
+
expect { JWT.encode(@payload, 'secret', 'HS1024') }.to raise_error(NotImplementedError)
|
97
191
|
end
|
98
192
|
|
99
|
-
it
|
193
|
+
it 'encodes and decodes plaintext JWTs' do
|
100
194
|
jwt = JWT.encode(@payload, nil, nil)
|
101
195
|
expect(jwt.split('.').length).to eq(2)
|
102
196
|
decoded_payload = JWT.decode(jwt, nil, nil)
|
103
197
|
expect(decoded_payload).to include(@payload)
|
104
198
|
end
|
105
199
|
|
106
|
-
it
|
200
|
+
it 'requires a signature segment when verify is truthy' do
|
107
201
|
jwt = JWT.encode(@payload, nil, nil)
|
108
202
|
expect(jwt.split('.').length).to eq(2)
|
109
203
|
expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError)
|
110
204
|
end
|
111
205
|
|
112
|
-
it
|
113
|
-
secret =
|
206
|
+
it 'does not use == to compare digests' do
|
207
|
+
secret = 'secret'
|
114
208
|
jwt = JWT.encode(@payload, secret)
|
115
|
-
crypto_segment = jwt.split(
|
209
|
+
crypto_segment = jwt.split('.').last
|
116
210
|
|
117
211
|
signature = JWT.base64url_decode(crypto_segment)
|
118
212
|
expect(signature).not_to receive('==')
|
@@ -122,96 +216,96 @@ describe JWT do
|
|
122
216
|
JWT.decode(jwt, secret)
|
123
217
|
end
|
124
218
|
|
125
|
-
it
|
219
|
+
it 'raises error when expired' do
|
126
220
|
expired_payload = @payload.clone
|
127
221
|
expired_payload['exp'] = Time.now.to_i - 1
|
128
|
-
secret =
|
222
|
+
secret = 'secret'
|
129
223
|
jwt = JWT.encode(expired_payload, secret)
|
130
224
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
131
225
|
end
|
132
226
|
|
133
|
-
it
|
227
|
+
it 'raise ExpiredSignature even when exp claims is a string' do
|
134
228
|
expired_payload = @payload.clone
|
135
229
|
expired_payload['exp'] = (Time.now.to_i).to_s
|
136
|
-
secret =
|
230
|
+
secret = 'secret'
|
137
231
|
jwt = JWT.encode(expired_payload, secret)
|
138
232
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
139
233
|
end
|
140
234
|
|
141
|
-
it
|
235
|
+
it 'performs normal decode with skipped expiration check' do
|
142
236
|
expired_payload = @payload.clone
|
143
237
|
expired_payload['exp'] = Time.now.to_i - 1
|
144
|
-
secret =
|
238
|
+
secret = 'secret'
|
145
239
|
jwt = JWT.encode(expired_payload, secret)
|
146
240
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
|
147
241
|
expect(decoded_payload).to include(expired_payload)
|
148
242
|
end
|
149
243
|
|
150
|
-
it
|
244
|
+
it 'performs normal decode using leeway' do
|
151
245
|
expired_payload = @payload.clone
|
152
246
|
expired_payload['exp'] = Time.now.to_i - 2
|
153
|
-
secret =
|
247
|
+
secret = 'secret'
|
154
248
|
jwt = JWT.encode(expired_payload, secret)
|
155
249
|
decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
|
156
250
|
expect(decoded_payload).to include(expired_payload)
|
157
251
|
end
|
158
252
|
|
159
|
-
it
|
253
|
+
it 'raises error when before nbf' do
|
160
254
|
immature_payload = @payload.clone
|
161
255
|
immature_payload['nbf'] = Time.now.to_i + 1
|
162
|
-
secret =
|
256
|
+
secret = 'secret'
|
163
257
|
jwt = JWT.encode(immature_payload, secret)
|
164
258
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
|
165
259
|
end
|
166
260
|
|
167
|
-
it
|
261
|
+
it 'doesnt raise error when after nbf' do
|
168
262
|
mature_payload = @payload.clone
|
169
|
-
secret =
|
263
|
+
secret = 'secret'
|
170
264
|
jwt = JWT.encode(mature_payload, secret)
|
171
265
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_expiration => false})
|
172
266
|
expect(decoded_payload).to include(mature_payload)
|
173
267
|
end
|
174
268
|
|
175
|
-
it
|
269
|
+
it 'raise ImmatureSignature even when nbf claim is a string' do
|
176
270
|
immature_payload = @payload.clone
|
177
271
|
immature_payload['nbf'] = (Time.now.to_i).to_s
|
178
|
-
secret =
|
272
|
+
secret = 'secret'
|
179
273
|
jwt = JWT.encode(immature_payload, secret)
|
180
274
|
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
|
181
275
|
end
|
182
276
|
|
183
|
-
it
|
277
|
+
it 'performs normal decode with skipped not before check' do
|
184
278
|
immature_payload = @payload.clone
|
185
279
|
immature_payload['nbf'] = Time.now.to_i + 2
|
186
|
-
secret =
|
280
|
+
secret = 'secret'
|
187
281
|
jwt = JWT.encode(immature_payload, secret)
|
188
282
|
decoded_payload = JWT.decode(jwt, secret, true, {:verify_not_before => false})
|
189
283
|
expect(decoded_payload).to include(immature_payload)
|
190
284
|
end
|
191
285
|
|
192
|
-
it
|
286
|
+
it 'performs normal decode using leeway' do
|
193
287
|
immature_payload = @payload.clone
|
194
288
|
immature_payload['nbf'] = Time.now.to_i - 2
|
195
|
-
secret =
|
289
|
+
secret = 'secret'
|
196
290
|
jwt = JWT.encode(immature_payload, secret)
|
197
291
|
decoded_payload = JWT.decode(jwt, secret, true, {:leeway => 3})
|
198
292
|
expect(decoded_payload).to include(immature_payload)
|
199
293
|
end
|
200
294
|
|
201
|
-
describe
|
202
|
-
it
|
203
|
-
expect(JWT.secure_compare(
|
295
|
+
describe 'secure comparison' do
|
296
|
+
it 'returns true if strings are equal' do
|
297
|
+
expect(JWT.secure_compare('Foo', 'Foo')).to be true
|
204
298
|
end
|
205
299
|
|
206
|
-
it
|
207
|
-
[nil,
|
208
|
-
expect(JWT.secure_compare(bad,
|
209
|
-
expect(JWT.secure_compare(
|
300
|
+
it 'returns false if either input is nil or empty' do
|
301
|
+
[nil, ''].each do |bad|
|
302
|
+
expect(JWT.secure_compare(bad, 'Foo')).to be false
|
303
|
+
expect(JWT.secure_compare('Foo', bad)).to be false
|
210
304
|
end
|
211
305
|
end
|
212
306
|
|
213
|
-
it
|
214
|
-
expect(JWT.secure_compare(
|
307
|
+
it 'retuns false if the strings are different' do
|
308
|
+
expect(JWT.secure_compare('Foo', 'Bar')).to be false
|
215
309
|
end
|
216
310
|
end
|
217
311
|
|
@@ -220,7 +314,7 @@ describe JWT do
|
|
220
314
|
expect(OpenSSL.errors).to be_empty
|
221
315
|
end
|
222
316
|
|
223
|
-
it
|
317
|
+
it 'raise exception on invalid signature' do
|
224
318
|
pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
|
225
319
|
-----BEGIN PUBLIC KEY-----
|
226
320
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
|
@@ -245,20 +339,20 @@ PUBKEY
|
|
245
339
|
expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
|
246
340
|
end
|
247
341
|
|
248
|
-
describe
|
249
|
-
it
|
250
|
-
allow(Base64).to receive(:encode64) {
|
251
|
-
expect(JWT.base64url_encode(
|
342
|
+
describe 'urlsafe base64 encoding' do
|
343
|
+
it 'replaces + and / with - and _' do
|
344
|
+
allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
|
345
|
+
expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
|
252
346
|
end
|
253
347
|
end
|
254
348
|
|
255
349
|
describe 'decoded_segments' do
|
256
|
-
it
|
257
|
-
secret =
|
350
|
+
it 'allows access to the decoded header and payload' do
|
351
|
+
secret = 'secret'
|
258
352
|
jwt = JWT.encode(@payload, secret)
|
259
353
|
decoded_segments = JWT.decoded_segments(jwt)
|
260
354
|
expect(decoded_segments.size).to eq(4)
|
261
|
-
expect(decoded_segments[0]).to eq({
|
355
|
+
expect(decoded_segments[0]).to eq({'typ' => 'JWT', 'alg' => 'HS256'})
|
262
356
|
expect(decoded_segments[1]).to eq(@payload)
|
263
357
|
end
|
264
358
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Lindsay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: echoe
|