firejwt 0.1.0 → 0.3.1
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/.github/workflows/test.yml +54 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +9 -2
- data/Gemfile +0 -4
- data/Gemfile.lock +70 -61
- data/LICENSE +198 -10
- data/Makefile +3 -3
- data/README.md +5 -5
- data/ext_test.go +6 -0
- data/firejwt.gemspec +2 -2
- data/firejwt.go +99 -21
- data/firejwt_test.go +138 -33
- data/go.mod +2 -5
- data/go.sum +4 -59
- data/lib/firejwt.rb +1 -1
- data/lib/firejwt/{key_set.rb → certificates.rb} +12 -6
- data/lib/firejwt/validator.rb +33 -31
- data/spec/firejwt/{key_set_spec.rb → certificates_spec.rb} +10 -10
- data/spec/firejwt/validator_spec.rb +59 -32
- data/spec/spec_helper.rb +31 -5
- metadata +9 -12
- data/.travis.yml +0 -20
- data/opt.go +0 -20
- data/testdata/cert.pem +0 -22
- data/testdata/priv.pem +0 -28
data/go.mod
CHANGED
@@ -3,10 +3,7 @@ module github.com/bsm/firejwt
|
|
3
3
|
go 1.13
|
4
4
|
|
5
5
|
require (
|
6
|
-
github.com/bsm/
|
7
|
-
github.com/bsm/
|
6
|
+
github.com/bsm/ginkgo v1.16.1
|
7
|
+
github.com/bsm/gomega v1.11.0
|
8
8
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
9
|
-
github.com/golang/protobuf v1.3.2
|
10
|
-
github.com/onsi/ginkgo v1.11.0
|
11
|
-
github.com/onsi/gomega v1.8.1
|
12
9
|
)
|
data/go.sum
CHANGED
@@ -1,61 +1,6 @@
|
|
1
|
-
github.com/
|
2
|
-
github.com/
|
3
|
-
github.com/
|
4
|
-
github.com/bsm/
|
5
|
-
github.com/bsm/bfs v0.9.0 h1:7sUB3a5ZzzhBlCELY+2pqCaI6MbO7F2a0jhIgHihhFs=
|
6
|
-
github.com/bsm/bfs v0.9.0/go.mod h1:N3md8kQvlteRDcfc8tqw759yW98dhj+6seWEVcg4CmM=
|
7
|
-
github.com/bsm/feedx v0.9.2 h1:9Af+bc6vvnPpli2D3Re4spwdKxox8kKjmnmE4qPICIc=
|
8
|
-
github.com/bsm/feedx v0.9.2/go.mod h1:63cqu0wUcW6RwIbhOnW27K8XluiOfdqnKFTVTroqNHI=
|
1
|
+
github.com/bsm/ginkgo v1.16.1 h1:jp1v1dbmbGZDWmnGXDTN+XK3U1fTTNja9xYa7VBI0l0=
|
2
|
+
github.com/bsm/ginkgo v1.16.1/go.mod h1:RabIZLzOCPghgHJKUqHZpqrQETA5AnF4aCSIYy5C1bk=
|
3
|
+
github.com/bsm/gomega v1.11.0 h1:wg9DVGPETNZLIbMsseneMV1a7uo/x+wsCyNXdEcifDI=
|
4
|
+
github.com/bsm/gomega v1.11.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
|
9
5
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
10
6
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
11
|
-
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
12
|
-
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
13
|
-
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
14
|
-
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
15
|
-
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
16
|
-
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
17
|
-
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
18
|
-
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
19
|
-
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
20
|
-
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
21
|
-
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
22
|
-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
23
|
-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
24
|
-
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
25
|
-
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
26
|
-
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
27
|
-
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
28
|
-
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
29
|
-
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
30
|
-
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
31
|
-
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
32
|
-
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
33
|
-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
34
|
-
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
35
|
-
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
36
|
-
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
|
37
|
-
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
38
|
-
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
39
|
-
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
40
|
-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
41
|
-
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
42
|
-
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
43
|
-
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
44
|
-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
45
|
-
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
46
|
-
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
47
|
-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
48
|
-
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
49
|
-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
50
|
-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
51
|
-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
52
|
-
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
53
|
-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
54
|
-
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
55
|
-
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
56
|
-
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
57
|
-
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
58
|
-
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
59
|
-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
60
|
-
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
61
|
-
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
data/lib/firejwt.rb
CHANGED
@@ -4,7 +4,7 @@ require 'uri'
|
|
4
4
|
require 'openssl'
|
5
5
|
|
6
6
|
module FireJWT
|
7
|
-
class
|
7
|
+
class Certificates
|
8
8
|
URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'.freeze
|
9
9
|
|
10
10
|
attr_reader :expires_at
|
@@ -12,14 +12,17 @@ module FireJWT
|
|
12
12
|
def initialize(url: URL)
|
13
13
|
super()
|
14
14
|
|
15
|
-
@url
|
15
|
+
@url = URI(url)
|
16
|
+
@keys = {}
|
17
|
+
|
16
18
|
expire!
|
17
19
|
refresh!
|
18
20
|
end
|
19
21
|
|
20
|
-
def get(
|
22
|
+
def get(kid)
|
21
23
|
refresh! if expired?
|
22
|
-
|
24
|
+
|
25
|
+
@keys[kid]
|
23
26
|
end
|
24
27
|
|
25
28
|
def refresh!(limit = 5)
|
@@ -33,8 +36,11 @@ module FireJWT
|
|
33
36
|
raise ArgumentError, 'Expires header not included in the response' unless resp['expires']
|
34
37
|
|
35
38
|
@expires_at = Time.httpdate(resp['expires'])
|
36
|
-
|
37
|
-
|
39
|
+
@keys.clear
|
40
|
+
|
41
|
+
JSON.parse(resp.body).each do |kid, pem|
|
42
|
+
cert = OpenSSL::X509::Certificate.new(pem)
|
43
|
+
@keys.store kid, cert.public_key
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
data/lib/firejwt/validator.rb
CHANGED
@@ -3,46 +3,48 @@ require 'jwt'
|
|
3
3
|
require 'net/http'
|
4
4
|
|
5
5
|
module FireJWT
|
6
|
+
class InvalidAuthTimeError < JWT::DecodeError; end
|
7
|
+
|
8
|
+
# Validator validates tokens applying guidelines outlined in
|
9
|
+
# https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library.
|
6
10
|
class Validator
|
7
|
-
# @param [
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
# @param [String] project_id the unique identifier for your Firebase project, which can be found in the URL of that project's console.
|
12
|
+
def initialize(project_id)
|
13
|
+
project_id = project_id.to_s
|
14
|
+
|
15
|
+
@certs = Certificates.new
|
16
|
+
@opts = {
|
17
|
+
algorithms: %w[RS256].freeze,
|
18
|
+
|
19
|
+
# exp must be in the future, iat must be in the past
|
20
|
+
verify_expiration: true,
|
21
|
+
verify_iat: true,
|
22
|
+
|
23
|
+
# aud must be your Firebase project ID
|
24
|
+
verify_aud: true, aud: project_id,
|
25
|
+
|
26
|
+
# iss must be "https://securetoken.google.com/<projectId>"
|
27
|
+
verify_iss: true, iss: "https://securetoken.google.com/#{project_id}",
|
28
|
+
}
|
17
29
|
end
|
18
30
|
|
19
31
|
# @param [String] token the token string
|
20
|
-
# @param [Hash] opts options
|
21
|
-
# @option opts [Boolean] :allow_expired allow expired tokens. Default: false.
|
22
|
-
# @option opts [String] :algorithm the expected algorithm. Default: RS256.
|
23
|
-
# @option opts [String] :aud verify the audience claim against the given value. Default: nil (= do not validate).
|
24
|
-
# @option opts [String] :iss verify the issuer claim against the given value. Default: nil (= do not verify).
|
25
|
-
# @option opts [String] :sub verify the subject claim against the given value. Default: nil (= do not verify).
|
26
|
-
# @option opts [Boolean] :verify_iat verify the issued at claim. Default: false.
|
27
|
-
# @option opts [Integer] :exp_leeway expiration leeway in seconds. Default: none.
|
28
32
|
# @return [FireJWT::Token] the token
|
29
33
|
# @raises [JWT::DecodeError] validation errors
|
30
|
-
def decode(token
|
31
|
-
|
32
|
-
|
33
|
-
@keys.get(header['kid'])
|
34
|
+
def decode(token)
|
35
|
+
payload, header = JWT.decode token, nil, true, **@opts do |header|
|
36
|
+
@certs.get(header['kid'])
|
34
37
|
end
|
35
|
-
Token.new(payload, header)
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
+
# sub must be a non-empty string
|
40
|
+
sub = payload['sub']
|
41
|
+
raise(JWT::InvalidSubError, 'Invalid subject. Expected non-empty string') unless sub.is_a?(String) && !sub.empty?
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
opts
|
43
|
+
# auth_time must be in the past
|
44
|
+
aut = payload['auth_time']
|
45
|
+
raise(InvalidAuthTimeError, 'Invalid auth_time') if !aut.is_a?(Numeric) || aut.to_f > Time.now.to_f
|
46
|
+
|
47
|
+
Token.new(payload, header)
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
@@ -1,28 +1,28 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
RSpec.describe FireJWT::
|
4
|
-
let
|
3
|
+
RSpec.describe FireJWT::Certificates do
|
4
|
+
let(:cert) { MockCert.new }
|
5
|
+
|
6
|
+
let! :http_request do
|
5
7
|
stub_request(:get, described_class::URL.to_s).to_return(
|
6
8
|
status: 200,
|
7
9
|
headers: { expires: (Time.now + 3600).httpdate },
|
8
|
-
body:
|
10
|
+
body: cert.to_json,
|
9
11
|
)
|
10
12
|
end
|
11
13
|
|
12
|
-
it '
|
13
|
-
expect(subject).to include(
|
14
|
-
MOCK_KID => instance_of(OpenSSL::PKey::RSA),
|
15
|
-
)
|
14
|
+
it 'inits' do
|
16
15
|
expect(subject.expires_at).to be_within(10).of(Time.now + 3600)
|
17
16
|
expect(subject).not_to be_expired
|
17
|
+
expect(http_request).to have_been_made
|
18
18
|
end
|
19
19
|
|
20
|
-
it '
|
20
|
+
it 'retrieves keys' do
|
21
21
|
expect(subject.get('BAD')).to be_nil
|
22
|
-
expect(subject.get(
|
22
|
+
expect(subject.get(cert.kid)).to be_instance_of(OpenSSL::PKey::RSA)
|
23
23
|
end
|
24
24
|
|
25
|
-
it '
|
25
|
+
it 'check/updates expiration status' do
|
26
26
|
expect(subject).not_to be_expired
|
27
27
|
subject.expire!
|
28
28
|
expect(subject).to be_expired
|
@@ -1,59 +1,86 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe FireJWT::Validator do
|
4
|
-
|
5
|
-
|
4
|
+
subject { described_class.new(project_id) }
|
5
|
+
|
6
|
+
let! :http_request do
|
7
|
+
stub_request(:get, FireJWT::Certificates::URL.to_s).to_return(
|
6
8
|
status: 200,
|
7
9
|
headers: { expires: (Time.now + 3600).httpdate },
|
8
|
-
body:
|
10
|
+
body: cert.to_json,
|
9
11
|
)
|
10
12
|
end
|
11
13
|
|
12
|
-
let :
|
13
|
-
Time.now.to_i
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
aud
|
20
|
-
iss
|
21
|
-
|
14
|
+
let :payload do
|
15
|
+
now = Time.now.to_i
|
16
|
+
{
|
17
|
+
'name' => 'Me',
|
18
|
+
'picture' => 'https://test.host/me.jpg',
|
19
|
+
'sub' => 'MDYwNDQwNjUtYWQ0ZC00ZDkwLThl',
|
20
|
+
'user_id' => 'MDYwNDQwNjUtYWQ0ZC00ZDkwLThl',
|
21
|
+
'aud' => project_id,
|
22
|
+
'iss' => 'https://securetoken.google.com/' << project_id,
|
23
|
+
'iat' => now - 1800,
|
24
|
+
'exp' => now + 3600,
|
25
|
+
'auth_time' => now,
|
26
|
+
'email' => 'me@example.com',
|
27
|
+
'email_verified' => true,
|
28
|
+
'firebase' => {
|
29
|
+
'sign_in_provider' => 'google.com',
|
30
|
+
'identities' => {
|
31
|
+
'google.com' => ['123123123123123123123'],
|
32
|
+
'email' => ['me@example.com'],
|
33
|
+
},
|
34
|
+
},
|
22
35
|
}
|
23
|
-
JWT.encode payload, MOCK_RSA, 'RS256', kid: MOCK_KID
|
24
36
|
end
|
25
37
|
|
26
|
-
|
38
|
+
let(:cert) { MockCert.new }
|
39
|
+
let(:project_id) { 'mock-project' }
|
40
|
+
let(:token) { JWT.encode payload, cert.pkey, 'RS256', kid: cert.kid }
|
41
|
+
|
42
|
+
it 'decodes' do
|
27
43
|
decoded = subject.decode(token)
|
28
44
|
expect(decoded).to be_instance_of(FireJWT::Token)
|
29
|
-
expect(decoded).to eq(
|
30
|
-
'sub' => 'me@example.com',
|
31
|
-
'aud' => 'you',
|
32
|
-
'iss' => 'me',
|
33
|
-
'exp' => exp_time,
|
34
|
-
)
|
45
|
+
expect(decoded).to eq(payload)
|
35
46
|
expect(decoded.header).to eq(
|
36
47
|
'alg' => 'RS256',
|
37
|
-
'kid' =>
|
48
|
+
'kid' => cert.kid,
|
38
49
|
)
|
50
|
+
expect(http_request).to have_been_made
|
39
51
|
end
|
40
52
|
|
41
|
-
it '
|
53
|
+
it 'rejects bad tokens' do
|
42
54
|
expect { subject.decode('BAD') }.to raise_error(JWT::DecodeError)
|
43
55
|
end
|
44
56
|
|
45
|
-
it '
|
46
|
-
|
47
|
-
expect { subject.decode(token
|
57
|
+
it 'verifies exp' do
|
58
|
+
payload['exp'] = Time.now.to_i - 1
|
59
|
+
expect { subject.decode(token) }.to raise_error(JWT::ExpiredSignature)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'verifies iat' do
|
63
|
+
payload['iat'] = Time.now.to_i + 10
|
64
|
+
expect { subject.decode(token) }.to raise_error(JWT::InvalidIatError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'verifies aud' do
|
68
|
+
payload['aud'] = 'other'
|
69
|
+
expect { subject.decode(token) }.to raise_error(JWT::InvalidAudError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'verifies iss' do
|
73
|
+
payload['iss'] = 'other'
|
74
|
+
expect { subject.decode(token) }.to raise_error(JWT::InvalidIssuerError)
|
48
75
|
end
|
49
76
|
|
50
|
-
it '
|
51
|
-
|
52
|
-
expect { subject.decode(token
|
77
|
+
it 'verifies sub' do
|
78
|
+
payload['sub'] = ''
|
79
|
+
expect { subject.decode(token) }.to raise_error(JWT::InvalidSubError)
|
53
80
|
end
|
54
81
|
|
55
|
-
it '
|
56
|
-
|
57
|
-
expect { subject.decode(token
|
82
|
+
it 'verifies auth_time' do
|
83
|
+
payload['auth_time'] = Time.now.to_i + 10
|
84
|
+
expect { subject.decode(token) }.to raise_error(FireJWT::InvalidAuthTimeError)
|
58
85
|
end
|
59
86
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,8 +4,34 @@ require 'webmock/rspec'
|
|
4
4
|
|
5
5
|
WebMock.disable_net_connect!
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
class MockCert
|
8
|
+
attr_reader :cert, :pkey
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@pkey = OpenSSL::PKey::RSA.new 2048
|
12
|
+
@cert = OpenSSL::X509::Certificate.new
|
13
|
+
@cert.version = 2
|
14
|
+
@cert.serial = 2605014480174073526
|
15
|
+
@cert.subject = OpenSSL::X509::Name.parse('/CN=securetoken.system.gserviceaccount.com')
|
16
|
+
@cert.issuer = @cert.subject
|
17
|
+
@cert.public_key = @pkey.public_key
|
18
|
+
@cert.not_before = Time.now
|
19
|
+
@cert.not_after = @cert.not_before + 3600
|
20
|
+
|
21
|
+
exts = OpenSSL::X509::ExtensionFactory.new
|
22
|
+
exts.subject_certificate = cert
|
23
|
+
exts.issuer_certificate = cert
|
24
|
+
@cert.add_extension(exts.create_extension('basicConstraints', 'CA:FALSE', true))
|
25
|
+
@cert.add_extension(exts.create_extension('keyUsage', 'Digital Signature', true))
|
26
|
+
@cert.add_extension(exts.create_extension('extendedKeyUsage', 'TLS Web Client Authentication', true))
|
27
|
+
@cert.sign(@pkey, OpenSSL::Digest.new('SHA256'))
|
28
|
+
end
|
29
|
+
|
30
|
+
def kid
|
31
|
+
@kid ||= Digest::SHA1.hexdigest(@cert.to_der)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json(*)
|
35
|
+
{ kid => @cert }.to_json
|
36
|
+
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firejwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Black Square Media Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubocop
|
56
|
+
name: rubocop-bsm
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -87,30 +87,27 @@ executables: []
|
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- ".github/workflows/test.yml"
|
90
91
|
- ".gitignore"
|
91
|
-
- ".rubocop-https---gitlab-com-bsm-misc-raw-master-rubocop-default-yml"
|
92
92
|
- ".rubocop.yml"
|
93
|
-
- ".travis.yml"
|
94
93
|
- Gemfile
|
95
94
|
- Gemfile.lock
|
96
95
|
- LICENSE
|
97
96
|
- Makefile
|
98
97
|
- README.md
|
99
98
|
- Rakefile
|
99
|
+
- ext_test.go
|
100
100
|
- firejwt.gemspec
|
101
101
|
- firejwt.go
|
102
102
|
- firejwt_test.go
|
103
103
|
- go.mod
|
104
104
|
- go.sum
|
105
105
|
- lib/firejwt.rb
|
106
|
-
- lib/firejwt/
|
106
|
+
- lib/firejwt/certificates.rb
|
107
107
|
- lib/firejwt/validator.rb
|
108
|
-
-
|
109
|
-
- spec/firejwt/key_set_spec.rb
|
108
|
+
- spec/firejwt/certificates_spec.rb
|
110
109
|
- spec/firejwt/validator_spec.rb
|
111
110
|
- spec/spec_helper.rb
|
112
|
-
- testdata/cert.pem
|
113
|
-
- testdata/priv.pem
|
114
111
|
homepage: https://github.com/bsm/firejwt
|
115
112
|
licenses:
|
116
113
|
- Apache-2.0
|
@@ -130,11 +127,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
127
|
- !ruby/object:Gem::Version
|
131
128
|
version: '0'
|
132
129
|
requirements: []
|
133
|
-
rubygems_version: 3.1.
|
130
|
+
rubygems_version: 3.1.4
|
134
131
|
signing_key:
|
135
132
|
specification_version: 4
|
136
133
|
summary: Firebase JWT validation
|
137
134
|
test_files:
|
138
|
-
- spec/firejwt/
|
135
|
+
- spec/firejwt/certificates_spec.rb
|
139
136
|
- spec/firejwt/validator_spec.rb
|
140
137
|
- spec/spec_helper.rb
|