firejwt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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