firejwt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +20 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +91 -0
- data/LICENSE +13 -0
- data/Makefile +7 -0
- data/README.md +51 -0
- data/Rakefile +9 -0
- data/firejwt.gemspec +21 -0
- data/firejwt.go +137 -0
- data/firejwt_test.go +104 -0
- data/go.mod +12 -0
- data/go.sum +61 -0
- data/lib/firejwt.rb +14 -0
- data/lib/firejwt/key_set.rb +53 -0
- data/lib/firejwt/validator.rb +48 -0
- data/opt.go +20 -0
- data/spec/firejwt/key_set_spec.rb +30 -0
- data/spec/firejwt/validator_spec.rb +59 -0
- data/spec/spec_helper.rb +11 -0
- data/testdata/cert.pem +22 -0
- data/testdata/priv.pem +28 -0
- metadata +140 -0
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
data/.rubocop.yml
ADDED
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
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
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
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|