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 +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
|
+
[](https://travis-ci.org/bsm/firejwt)
|
4
|
+
[](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
|