firejwt 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2ce9afa07e3b55c8dd86005870feb273078005bf10ab81a319203710e09ad80
4
+ data.tar.gz: 2dbeeb89fa9fec159adf5955b5179180be97a5ca01fb3893699ac77e1c6e92e3
5
+ SHA512:
6
+ metadata.gz: 6b1a070b01a9ed1a22b27f014aafb82cd2e0633021f3988c42a23f37068648e4296c30d2f66e17cb002f2b6d7af9210441e2e3b1621c562d8a24f0aa7f077165
7
+ data.tar.gz: 5cb2523c0648ee29f77a27fdd1bf67c3142c572256dd1a47989fe3969173718262447e3ae314e06698421d2299eb5fc993e79fb97d25a882a37b184e37423372
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *~
2
+ .rubocop-*
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from:
2
+ - https://gitlab.com/bsm/misc/raw/master/rubocop/default.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: "2.5"
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ matrix:
2
+ include:
3
+ - language: ruby
4
+ rvm:
5
+ - 2.7
6
+ before_install:
7
+ - gem install bundler
8
+ - language: ruby
9
+ rvm:
10
+ - 2.6
11
+ before_install:
12
+ - gem install bundler
13
+ - language: ruby
14
+ rvm:
15
+ - 2.5
16
+ before_install:
17
+ - gem install bundler
18
+ - language: go
19
+ go:
20
+ - 1.13.x
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development do
5
+ gem 'solargraph'
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ firejwt (0.1.0)
5
+ jwt
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.7.0)
11
+ public_suffix (>= 2.0.2, < 5.0)
12
+ ast (2.4.0)
13
+ backport (1.1.2)
14
+ benchmark (0.1.0)
15
+ crack (0.4.3)
16
+ safe_yaml (~> 1.0.0)
17
+ diff-lcs (1.3)
18
+ e2mmap (0.1.0)
19
+ hashdiff (1.0.0)
20
+ jaro_winkler (1.5.4)
21
+ jwt (2.2.1)
22
+ maruku (0.7.3)
23
+ mini_portile2 (2.4.0)
24
+ nokogiri (1.10.7)
25
+ mini_portile2 (~> 2.4.0)
26
+ parallel (1.19.1)
27
+ parser (2.7.0.2)
28
+ ast (~> 2.4.0)
29
+ public_suffix (4.0.3)
30
+ rainbow (3.0.0)
31
+ rake (13.0.1)
32
+ reverse_markdown (1.4.0)
33
+ nokogiri
34
+ rspec (3.9.0)
35
+ rspec-core (~> 3.9.0)
36
+ rspec-expectations (~> 3.9.0)
37
+ rspec-mocks (~> 3.9.0)
38
+ rspec-core (3.9.1)
39
+ rspec-support (~> 3.9.1)
40
+ rspec-expectations (3.9.0)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.9.0)
43
+ rspec-mocks (3.9.1)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.9.0)
46
+ rspec-support (3.9.2)
47
+ rubocop (0.79.0)
48
+ jaro_winkler (~> 1.5.1)
49
+ parallel (~> 1.10)
50
+ parser (>= 2.7.0.1)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ ruby-progressbar (~> 1.7)
53
+ unicode-display_width (>= 1.4.0, < 1.7)
54
+ ruby-progressbar (1.10.1)
55
+ safe_yaml (1.0.5)
56
+ solargraph (0.38.4)
57
+ backport (~> 1.1)
58
+ benchmark
59
+ bundler (>= 1.17.2)
60
+ e2mmap
61
+ jaro_winkler (~> 1.5)
62
+ maruku (~> 0.7, >= 0.7.3)
63
+ nokogiri (~> 1.9, >= 1.9.1)
64
+ parser (~> 2.3)
65
+ reverse_markdown (~> 1.0, >= 1.0.5)
66
+ rubocop (~> 0.52)
67
+ thor (~> 1.0)
68
+ tilt (~> 2.0)
69
+ yard (~> 0.9)
70
+ thor (1.0.1)
71
+ tilt (2.0.10)
72
+ unicode-display_width (1.6.1)
73
+ webmock (3.8.0)
74
+ addressable (>= 2.3.6)
75
+ crack (>= 0.3.2)
76
+ hashdiff (>= 0.4.0, < 2.0.0)
77
+ yard (0.9.24)
78
+
79
+ PLATFORMS
80
+ ruby
81
+
82
+ DEPENDENCIES
83
+ firejwt!
84
+ rake
85
+ rspec
86
+ rubocop
87
+ solargraph
88
+ webmock
89
+
90
+ BUNDLED WITH
91
+ 2.1.2
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2020 Black Square Media Ltd
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/Makefile ADDED
@@ -0,0 +1,7 @@
1
+ default: vet test
2
+
3
+ test:
4
+ go test ./...
5
+
6
+ vet:
7
+ go vet ./...
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # FireJWT
2
+
3
+ [![Build Status](https://travis-ci.org/bsm/firejwt.png?branch=master)](https://travis-ci.org/bsm/firejwt)
4
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ Decode and validate [Google Firebase](https://firebase.google.com/) JWT tokens with [Ruby](https://www.ruby-lang.org/) and [Go](https://golang.org/).
7
+
8
+ ## Usage
9
+
10
+ **Ruby**:
11
+
12
+ ```ruby
13
+ require 'firejwt'
14
+
15
+ # Init a validator
16
+ validator = FireJWT::Validator.new
17
+
18
+ # Decode a token
19
+ token = begin
20
+ validator.decode('eyJh...YbQ') # => {'sub' => 'me@example.com', 'aud' => 'my-audience'}
21
+ rescue JWT::DecodeError
22
+ nil
23
+ end
24
+ ```
25
+
26
+ **Go**:
27
+
28
+ ```go
29
+ package main
30
+
31
+ import (
32
+ "log"
33
+
34
+ "github.com/bsm/firejwt"
35
+ )
36
+
37
+ func main() {
38
+ vr, err := firejwt.New(nil)
39
+ if err != nil {
40
+ log.Fatalln(err)
41
+ }
42
+ defer vr.Stop()
43
+
44
+ tk, err := vr.Decode("eyJh...YbQ")
45
+ if err != nil {
46
+ log.Fatalln(err)
47
+ }
48
+
49
+ log.Println(tk.Claims) // => {"sub": "me@example.com", "aud": "my-audience"}
50
+ }
51
+ ```
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ RuboCop::RakeTask.new(:rubocop)
8
+
9
+ task default: %i[spec rubocop]
data/firejwt.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'firejwt'
3
+ s.version = '0.1.0'
4
+ s.authors = ['Black Square Media Ltd']
5
+ s.email = ['info@blacksquaremedia.com']
6
+ s.summary = %(Firebase JWT validation)
7
+ s.description = %()
8
+ s.homepage = 'https://github.com/bsm/firejwt'
9
+ s.license = 'Apache-2.0'
10
+
11
+ s.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^spec/}) }
12
+ s.test_files = `git ls-files -z -- spec/*`.split("\x0")
13
+ s.require_paths = ['lib']
14
+ s.required_ruby_version = '>= 2.5'
15
+
16
+ s.add_dependency 'jwt'
17
+ s.add_development_dependency 'rake'
18
+ s.add_development_dependency 'rspec'
19
+ s.add_development_dependency 'rubocop'
20
+ s.add_development_dependency 'webmock'
21
+ end
data/firejwt.go ADDED
@@ -0,0 +1,137 @@
1
+ package firejwt
2
+
3
+ import (
4
+ "context"
5
+ "crypto/rsa"
6
+ "crypto/x509"
7
+ "encoding/json"
8
+ "encoding/pem"
9
+ "fmt"
10
+ "log"
11
+ "net/http"
12
+ "sync/atomic"
13
+ "time"
14
+
15
+ "github.com/dgrijalva/jwt-go"
16
+ )
17
+
18
+ // Validator validates Firebase JWTs
19
+ type Validator struct {
20
+ opt *Options
21
+ htc http.Client
22
+
23
+ cancel context.CancelFunc
24
+ keyset atomic.Value
25
+ expires int64
26
+ }
27
+
28
+ // New issues a new Validator.
29
+ func New(opt *Options) (*Validator, error) {
30
+ v := &Validator{opt: opt.norm()}
31
+ if err := v.Refresh(); err != nil {
32
+ return nil, err
33
+ }
34
+
35
+ ctx, cancel := context.WithCancel(context.Background())
36
+ v.cancel = cancel
37
+ go v.loop(ctx)
38
+
39
+ return v, nil
40
+ }
41
+
42
+ // Stop stops the validator updates.
43
+ func (v *Validator) Stop() {
44
+ v.cancel()
45
+ }
46
+
47
+ // Decode decodes the token
48
+ func (v *Validator) Decode(tokenString string) (*jwt.Token, error) {
49
+ return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
50
+ kid, ok := token.Header["kid"].(string)
51
+ if !ok {
52
+ return nil, fmt.Errorf("missing kid header")
53
+ }
54
+
55
+ key, ok := v.keyset.Load().(map[string]publicKey)[kid]
56
+ if !ok {
57
+ return nil, fmt.Errorf("unknown kid header %s", kid)
58
+ }
59
+
60
+ return key.PublicKey, nil
61
+ })
62
+ }
63
+
64
+ // ExpTime returns the expiration time.
65
+ func (v *Validator) ExpTime() time.Time {
66
+ return time.Unix(atomic.LoadInt64(&v.expires), 0)
67
+ }
68
+
69
+ // Refresh retrieves the latest keys.
70
+ func (v *Validator) Refresh() error {
71
+ resp, err := v.htc.Get(v.opt.URL)
72
+ if err != nil {
73
+ return err
74
+ }
75
+ defer resp.Body.Close()
76
+
77
+ exp, err := time.Parse(time.RFC1123, resp.Header.Get("Expires"))
78
+ if err != nil {
79
+ return err
80
+ }
81
+
82
+ var keyset map[string]publicKey
83
+ if err := json.NewDecoder(resp.Body).Decode(&keyset); err != nil {
84
+ return err
85
+ }
86
+
87
+ v.keyset.Store(keyset)
88
+ atomic.StoreInt64(&v.expires, exp.Unix())
89
+ return nil
90
+ }
91
+
92
+ func (v *Validator) loop(ctx context.Context) {
93
+ t := time.NewTimer(time.Minute)
94
+ defer t.Stop()
95
+
96
+ for {
97
+ d := v.ExpTime().Sub(time.Now()) - time.Hour
98
+ if d < time.Minute {
99
+ d = time.Minute
100
+ }
101
+ t.Reset(d)
102
+
103
+ select {
104
+ case <-ctx.Done():
105
+ return
106
+ case <-t.C:
107
+ if err := v.Refresh(); err != nil {
108
+ log.Printf("[firejwt] failed to refresh keyset: %v", err)
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ // --------------------------------------------------------------------
115
+
116
+ type publicKey struct {
117
+ *rsa.PublicKey
118
+ }
119
+
120
+ func (k *publicKey) UnmarshalText(data []byte) error {
121
+ block, _ := pem.Decode(data)
122
+ if block == nil {
123
+ return fmt.Errorf("invalid certificate")
124
+ }
125
+
126
+ cert, err := x509.ParseCertificate(block.Bytes)
127
+ if err != nil {
128
+ return err
129
+ }
130
+
131
+ if cert.PublicKeyAlgorithm != x509.RSA {
132
+ return fmt.Errorf("unexpected public key algorithm: %s", cert.PublicKeyAlgorithm)
133
+ }
134
+
135
+ *k = publicKey{PublicKey: cert.PublicKey.(*rsa.PublicKey)}
136
+ return nil
137
+ }
data/firejwt_test.go ADDED
@@ -0,0 +1,104 @@
1
+ package firejwt_test
2
+
3
+ import (
4
+ "crypto/rsa"
5
+ "encoding/json"
6
+ "io/ioutil"
7
+ "net/http"
8
+ "net/http/httptest"
9
+ "testing"
10
+ "time"
11
+
12
+ "github.com/bsm/firejwt"
13
+ "github.com/dgrijalva/jwt-go"
14
+ . "github.com/onsi/ginkgo"
15
+ . "github.com/onsi/gomega"
16
+ )
17
+
18
+ var _ = Describe("Validator", func() {
19
+ var subject *firejwt.Validator
20
+ var server *httptest.Server
21
+
22
+ const kid = "e5a91d9f39fa4de254a1e89df00f05b7e248b985"
23
+
24
+ decode := func(method jwt.SigningMethod, claims *jwt.StandardClaims) (*jwt.Token, error) {
25
+ src := jwt.NewWithClaims(method, claims)
26
+ src.Header["kid"] = kid
27
+
28
+ str, err := src.SignedString(privKey)
29
+ Expect(err).NotTo(HaveOccurred())
30
+
31
+ return subject.Decode(str)
32
+ }
33
+
34
+ BeforeEach(func() {
35
+ server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
36
+ w.Header().Set("expires", "Mon, 20 Jan 2020 23:40:59 GMT")
37
+ json.NewEncoder(w).Encode(map[string]string{
38
+ kid: string(certPEM),
39
+ })
40
+ }))
41
+
42
+ var err error
43
+ subject, err = firejwt.New(&firejwt.Options{URL: server.URL})
44
+ Expect(err).NotTo(HaveOccurred())
45
+ })
46
+
47
+ AfterEach(func() {
48
+ subject.Stop()
49
+ })
50
+
51
+ It("should refresh on init", func() {
52
+ Expect(subject.ExpTime()).To(BeTemporally("==", time.Date(2020, 1, 20, 23, 40, 59, 0, time.UTC)))
53
+ })
54
+
55
+ It("should decode tokens", func() {
56
+ token, err := decode(jwt.SigningMethodRS256, &jwt.StandardClaims{
57
+ Subject: "me@example.com",
58
+ Audience: "you",
59
+ Issuer: "me",
60
+ ExpiresAt: time.Now().Add(time.Hour).Unix(),
61
+ })
62
+ Expect(err).NotTo(HaveOccurred())
63
+ Expect(token.Valid).To(BeTrue())
64
+ Expect(token.Claims).To(HaveKeyWithValue("sub", "me@example.com"))
65
+ })
66
+
67
+ It("should reject bad tokens", func() {
68
+ _, err := subject.Decode("BADTOKEN")
69
+ Expect(err).To(MatchError(`token contains an invalid number of segments`))
70
+ })
71
+
72
+ It("should reject expired tokens", func() {
73
+ _, err := decode(jwt.SigningMethodRS256, &jwt.StandardClaims{
74
+ Subject: "me@example.com",
75
+ ExpiresAt: time.Now().Add(-time.Minute).Unix(),
76
+ })
77
+ Expect(err).To(MatchError(`Token is expired`))
78
+ })
79
+ })
80
+
81
+ func TestSuite(t *testing.T) {
82
+ RegisterFailHandler(Fail)
83
+ RunSpecs(t, "firejwt")
84
+ }
85
+
86
+ // --------------------------------------------------------------------
87
+
88
+ var (
89
+ certPEM []byte
90
+ privKey *rsa.PrivateKey
91
+ )
92
+
93
+ var _ = BeforeSuite(func() {
94
+ var err error
95
+
96
+ certPEM, err = ioutil.ReadFile("testdata/cert.pem")
97
+ Expect(err).NotTo(HaveOccurred())
98
+
99
+ privPEM, err := ioutil.ReadFile("testdata/priv.pem")
100
+ Expect(err).NotTo(HaveOccurred())
101
+
102
+ privKey, err = jwt.ParseRSAPrivateKeyFromPEM(privPEM)
103
+ Expect(err).NotTo(HaveOccurred())
104
+ })
data/go.mod ADDED
@@ -0,0 +1,12 @@
1
+ module github.com/bsm/firejwt
2
+
3
+ go 1.13
4
+
5
+ require (
6
+ github.com/bsm/bfs v0.9.0
7
+ github.com/bsm/feedx v0.9.2
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
+ )
data/go.sum ADDED
@@ -0,0 +1,61 @@
1
+ github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
2
+ github.com/bmatcuk/doublestar v1.2.2 h1:oC24CykoSAB8zd7XgruHo33E0cHJf/WhQA/7BeXj+x0=
3
+ github.com/bmatcuk/doublestar v1.2.2/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
4
+ github.com/bsm/bfs v0.8.1/go.mod h1:cVv0jyqUY/jbHoG/WYPuWvOaOhW/HZ4jl7/JMlypvAE=
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=
9
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
10
+ 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 ADDED
@@ -0,0 +1,14 @@
1
+ module FireJWT
2
+ autoload :KeySet, 'firejwt/key_set'
3
+ autoload :Validator, 'firejwt/validator'
4
+
5
+ class Token < Hash
6
+ attr_reader :header
7
+
8
+ def initialize(payload, header)
9
+ super()
10
+ update(payload)
11
+ @header = header
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ require 'time'
2
+ require 'json'
3
+ require 'uri'
4
+ require 'openssl'
5
+
6
+ module FireJWT
7
+ class KeySet < Hash
8
+ URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'.freeze
9
+
10
+ attr_reader :expires_at
11
+
12
+ def initialize(url: URL)
13
+ super()
14
+
15
+ @url = URI(url)
16
+ expire!
17
+ refresh!
18
+ end
19
+
20
+ def get(key)
21
+ refresh! if expired?
22
+ self[key]
23
+ end
24
+
25
+ def refresh!(limit = 5)
26
+ resp = Net::HTTP.get_response(@url)
27
+ unless resp.is_a?(Net::HTTPOK)
28
+ raise "Server responded with #{resp.code}" if limit < 1
29
+
30
+ refresh!(limit - 1)
31
+ end
32
+
33
+ raise ArgumentError, 'Expires header not included in the response' unless resp['expires']
34
+
35
+ @expires_at = Time.httpdate(resp['expires'])
36
+ JSON.parse(resp.body).each do |kid, cert|
37
+ store kid, OpenSSL::X509::Certificate.new(cert).public_key
38
+ end
39
+ end
40
+
41
+ def expire!
42
+ @expires_at = Time.at(0)
43
+ end
44
+
45
+ def expired?
46
+ @expires_at < Time.now
47
+ end
48
+
49
+ def expires_soon?
50
+ @expires_at < (Time.now + 600)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ require 'json'
2
+ require 'jwt'
3
+ require 'net/http'
4
+
5
+ module FireJWT
6
+ class Validator
7
+ # @param [Hash] opts
8
+ # @option opts [String] :algorithm the expected algorithm. Default: RS256.
9
+ # @option opts [String] :aud verify the audience claim against the given value. Default: nil (= do not validate).
10
+ # @option opts [String] :iss verify the issuer claim against the given value. Default: nil (= do not verify).
11
+ # @option opts [String] :sub verify the subject claim against the given value. Default: nil (= do not verify).
12
+ # @option opts [Boolean] :verify_iat verify the issued at claim. Default: false.
13
+ # @option opts [Integer] :exp_leeway expiration leeway in seconds. Default: none.
14
+ def initialize(**opts)
15
+ @defaults = norm_opts(opts.dup)
16
+ @keys = KeySet.new
17
+ end
18
+
19
+ # @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
+ # @return [FireJWT::Token] the token
29
+ # @raises [JWT::DecodeError] validation errors
30
+ def decode(token, allow_expired: false, **opts)
31
+ opts = norm_opts(@defaults.merge(opts))
32
+ payload, header = JWT.decode token, nil, !allow_expired, opts do |header|
33
+ @keys.get(header['kid'])
34
+ end
35
+ Token.new(payload, header)
36
+ end
37
+
38
+ private
39
+
40
+ def norm_opts(opts)
41
+ opts[:verify_aud] = !opts.key?(:aud) unless opts.key?(:verify_aud)
42
+ opts[:verify_iss] = !opts.key?(:iss) unless opts.key?(:verify_iss)
43
+ opts[:verify_sub] = !opts.key?(:sub) unless opts.key?(:verify_sub)
44
+ opts[:algorithm] ||= 'RS256'
45
+ opts
46
+ end
47
+ end
48
+ end
data/opt.go ADDED
@@ -0,0 +1,20 @@
1
+ package firejwt
2
+
3
+ // Options contains optional configuration for the Validator.
4
+ type Options struct {
5
+ // Custom KID URL
6
+ URL string
7
+ }
8
+
9
+ func (o *Options) norm() *Options {
10
+ var o2 Options
11
+ if o != nil {
12
+ o2 = *o
13
+ }
14
+
15
+ if o2.URL == "" {
16
+ o2.URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
17
+ }
18
+
19
+ return &o2
20
+ }
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FireJWT::KeySet do
4
+ let! :keys_request do
5
+ stub_request(:get, described_class::URL.to_s).to_return(
6
+ status: 200,
7
+ headers: { expires: (Time.now + 3600).httpdate },
8
+ body: MOCK_RESPONSE.to_json,
9
+ )
10
+ end
11
+
12
+ it 'should init' do
13
+ expect(subject).to include(
14
+ MOCK_KID => instance_of(OpenSSL::PKey::RSA),
15
+ )
16
+ expect(subject.expires_at).to be_within(10).of(Time.now + 3600)
17
+ expect(subject).not_to be_expired
18
+ end
19
+
20
+ it 'should retrieve keys' do
21
+ expect(subject.get('BAD')).to be_nil
22
+ expect(subject.get(MOCK_KID)).to be_instance_of(OpenSSL::PKey::RSA)
23
+ end
24
+
25
+ it 'should check/update expiration status' do
26
+ expect(subject).not_to be_expired
27
+ subject.expire!
28
+ expect(subject).to be_expired
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FireJWT::Validator do
4
+ let! :keys_request do
5
+ stub_request(:get, FireJWT::KeySet::URL.to_s).to_return(
6
+ status: 200,
7
+ headers: { expires: (Time.now + 3600).httpdate },
8
+ body: MOCK_RESPONSE.to_json,
9
+ )
10
+ end
11
+
12
+ let :exp_time do
13
+ Time.now.to_i + 3600
14
+ end
15
+
16
+ let :token do
17
+ payload = {
18
+ sub: 'me@example.com',
19
+ aud: 'you',
20
+ iss: 'me',
21
+ exp: exp_time,
22
+ }
23
+ JWT.encode payload, MOCK_RSA, 'RS256', kid: MOCK_KID
24
+ end
25
+
26
+ it 'should decode' do
27
+ decoded = subject.decode(token)
28
+ 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
+ )
35
+ expect(decoded.header).to eq(
36
+ 'alg' => 'RS256',
37
+ 'kid' => 'e5a91d9f39fa4de254a1e89df00f05b7e248b985',
38
+ )
39
+ end
40
+
41
+ it 'should reject bad tokens' do
42
+ expect { subject.decode('BAD') }.to raise_error(JWT::DecodeError)
43
+ end
44
+
45
+ it 'should verify audiences' do
46
+ expect(subject.decode(token, aud: 'you')).to be_instance_of(FireJWT::Token)
47
+ expect { subject.decode(token, aud: 'other') }.to raise_error(JWT::InvalidAudError)
48
+ end
49
+
50
+ it 'should verify issuers' do
51
+ expect(subject.decode(token, iss: 'me')).to be_instance_of(FireJWT::Token)
52
+ expect { subject.decode(token, iss: 'other') }.to raise_error(JWT::InvalidIssuerError)
53
+ end
54
+
55
+ it 'should verify subjects' do
56
+ expect(subject.decode(token, sub: 'me@example.com')).to be_instance_of(FireJWT::Token)
57
+ expect { subject.decode(token, sub: 'other') }.to raise_error(JWT::InvalidSubError)
58
+ end
59
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec'
2
+ require 'firejwt'
3
+ require 'webmock/rspec'
4
+
5
+ WebMock.disable_net_connect!
6
+
7
+ MOCK_KID = 'e5a91d9f39fa4de254a1e89df00f05b7e248b985'.freeze
8
+ MOCK_RSA = OpenSSL::PKey::RSA.new File.read(File.expand_path('../testdata/priv.pem', __dir__))
9
+ MOCK_RESPONSE = {
10
+ MOCK_KID => File.read(File.expand_path('../testdata/cert.pem', __dir__)),
11
+ }.freeze
data/testdata/cert.pem ADDED
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDlzCCAn+gAwIBAgIUO5f7OhG0b1i6ped8/d0hRruQ7ccwDQYJKoZIhvcNAQEL
3
+ BQAwWjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEbMBkGA1UECgwSQmxh
4
+ Y2sgU3F1YXJlIE1lZGlhMR0wGwYDVQQDDBRibGFja3NxdWFyZW1lZGlhLmNvbTAg
5
+ Fw0yMDAxMjIxNTI5NDJaGA8yMTE5MTIyOTE1Mjk0MlowWjELMAkGA1UEBhMCR0Ix
6
+ DzANBgNVBAgMBkxvbmRvbjEbMBkGA1UECgwSQmxhY2sgU3F1YXJlIE1lZGlhMR0w
7
+ GwYDVQQDDBRibGFja3NxdWFyZW1lZGlhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
8
+ ggEPADCCAQoCggEBAMY6tyEp1lExQFxMa3znxjbIHYsSwl4t9NVQzeOibbMbI3Go
9
+ eiyuGhnJVhPNycvsDpkJVF7Q/FB4i2OesxcerRo5VYopOA77XDmyeCZ9E6Q5ELXc
10
+ i2QWJcRIXZk6EujwGTEQouYoaosNYLMz5NQ7rVdTpa5wgLlys5RatfcNR9nOL1Mr
11
+ it5/HrEbgQB8JCKPQA41DIhymI6MqtkPop2spLU2i809sh+Lk5vRCmkhH7A0SNaN
12
+ YPKLfI731dnTRqQHBFP5N3UBzSS/tCfeCwezmk+rkaV9zZpUkZ8XmuvOCG9PUe2u
13
+ C5Zq3ziA0QD+fn6bOlLFsZJx3aKEtSEavU/YRjsCAwEAAaNTMFEwHQYDVR0OBBYE
14
+ FIrrQ2gBVTfCJUeUNdxazBCQvdnJMB8GA1UdIwQYMBaAFIrrQ2gBVTfCJUeUNdxa
15
+ zBCQvdnJMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMW3up1k
16
+ ENUlySp/XYhwsa4mJEsba0LIyJO0/X6LKAPl0qnVmlHY5fC3j/kCx5MyjQVsHZCQ
17
+ l2vZPAJDF/XE8uC4CHc4ig7RSRCDeiK0M766z9agZwUaQOgR2XqwkM2wLAtvnUpF
18
+ c4KgolHedFZRobk+pwlvpoyl9k3W8nDsMdI3I6EgBJvFhVGXiAL2kFILIyiBX5TN
19
+ rTiMEER+Fd5WEfQpsXukBWsibWwqE7ZP940z2oOVfggakZIPrgaTRWJhAIKUEt4n
20
+ QvWjvRVEdr5L7PKxoOiPnz592Cojb/DXtS0Bu71lraVHHsMsIu1QFlclAOI5bBI1
21
+ 4TfOw5ufs75LxDE=
22
+ -----END CERTIFICATE-----
data/testdata/priv.pem ADDED
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDGOrchKdZRMUBc
3
+ TGt858Y2yB2LEsJeLfTVUM3jom2zGyNxqHosrhoZyVYTzcnL7A6ZCVRe0PxQeItj
4
+ nrMXHq0aOVWKKTgO+1w5sngmfROkORC13ItkFiXESF2ZOhLo8BkxEKLmKGqLDWCz
5
+ M+TUO61XU6WucIC5crOUWrX3DUfZzi9TK4refx6xG4EAfCQij0AONQyIcpiOjKrZ
6
+ D6KdrKS1NovNPbIfi5Ob0QppIR+wNEjWjWDyi3yO99XZ00akBwRT+Td1Ac0kv7Qn
7
+ 3gsHs5pPq5Glfc2aVJGfF5rrzghvT1HtrguWat84gNEA/n5+mzpSxbGScd2ihLUh
8
+ Gr1P2EY7AgMBAAECggEAaXiYM6cFB1JDQljO4DiZ+E/lmDe0/1NIb698vN+RqriH
9
+ 1VOlHdzMumerywG1mzDQW5DhOUnM1iwtTiYEeAq0Y72Zy9c+oooPeguBbkkiiEBs
10
+ qbbc27YFBjjSxFJn+VS2sqp9YiSi+7V0fCTiXiIaitpQz03Az+s9rXPOWdLRJgtj
11
+ 4XEUrsMu+Ejz3YhOWH6kCZfWqa9nAy3mUi8/x+cFaEBMFfcZZJYcfvjMmwoKaAR9
12
+ 24u7JGEnpLfV/wEwLtSS+ABynpT+HJtU9s7K3Jbe+Bd1g0qYtcbhIaNhg1rhV6sc
13
+ AglO5UO4p9Egr9UgcG8tbvYAcZtaaDADjND2f8wIsQKBgQD2qlWdF9HW9F2klNZY
14
+ WdkgtdDh0MAZW5VAfXvN+60tdE2tqOAnj7oWjxWhGL5GK0wqhApn/tADQF5rrbqq
15
+ eoQJcmxbI8fNBXstirLlUS8FZVijkBr9LdAJDY/5xM2BgsDaauSIikmNWnZImILN
16
+ EtGF5UoE3TfIOUl988ylwejQxwKBgQDNuyNwpdTtNIMoJwR9ANZzY8HFEeUmAnYi
17
+ ZSsvMJRm1Bhk/WNB+Ib2uIAQu2RScuYMjjOTauXzsRFqHz1eJ759+3hfhJsJOoYL
18
+ hj//8Bei4vbttgDzg4d2ovg2DGDyJph0J5wtpW6VON9Ym4mnmezQJfsCzEBh8Vr2
19
+ XBElv1cS7QKBgHHq6s05Yf0PMGxBHNkC7ccwkP6pRP6xEDYPfez8jddPPky0kIlU
20
+ 1JF0lX2oCsAnYO7FunSa9wB5auH6AxqWqIIgaTCSTsU+AcxfoQ1NOBUa4BvyArTo
21
+ wopbzCGDJZHpjB2TfmYcz6lLnRMb9FS3mzJmWY/zhr6eznUv8lSfQGGjAoGAd980
22
+ dSyK9nOEgF7LpLJaQf28J8GXjSAeCUh9cw+RSKEIXb+ul//hU9yI8jbd65R7KpGo
23
+ x5qfxfBEP1tYfIYX3nwp1S4Ez8nD1O8yV0Rj4UrxqexEfZ8DzUKD8aogyrdmWTfD
24
+ Lm2YE2aB7LUj7f4oF9gpe6XbVbY11Bos+5uTdrkCgYB331Fptm1CPC3DnudvcVUB
25
+ HwDZ7xMENdTQyx6WZX4SvjiFa8vWS1bq7TZhwIotLLerP0tQS7zP58wVDmyVWqz0
26
+ 2hvnZrJDZYlB9qD/rCfhHjM5FO7GEXhxh8nKHFUHivoh1n2DG9oxjUEDbg61jjwz
27
+ erRo6a3govRlEGXVFkC4dA==
28
+ -----END PRIVATE KEY-----
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firejwt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Black Square Media Ltd
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ''
84
+ email:
85
+ - info@blacksquaremedia.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rubocop-https---gitlab-com-bsm-misc-raw-master-rubocop-default-yml"
92
+ - ".rubocop.yml"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - LICENSE
97
+ - Makefile
98
+ - README.md
99
+ - Rakefile
100
+ - firejwt.gemspec
101
+ - firejwt.go
102
+ - firejwt_test.go
103
+ - go.mod
104
+ - go.sum
105
+ - lib/firejwt.rb
106
+ - lib/firejwt/key_set.rb
107
+ - lib/firejwt/validator.rb
108
+ - opt.go
109
+ - spec/firejwt/key_set_spec.rb
110
+ - spec/firejwt/validator_spec.rb
111
+ - spec/spec_helper.rb
112
+ - testdata/cert.pem
113
+ - testdata/priv.pem
114
+ homepage: https://github.com/bsm/firejwt
115
+ licenses:
116
+ - Apache-2.0
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '2.5'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubygems_version: 3.1.2
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: Firebase JWT validation
137
+ test_files:
138
+ - spec/firejwt/key_set_spec.rb
139
+ - spec/firejwt/validator_spec.rb
140
+ - spec/spec_helper.rb