firejwt 0.4.1 → 0.5.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 +4 -4
- data/.github/workflows/test.yml +2 -2
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +8 -8
- data/firejwt.gemspec +2 -2
- data/firejwt.go +37 -37
- data/firejwt_test.go +21 -19
- data/go.mod +1 -1
- data/go.sum +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab69a025ab89bc78cc7100320e86215b969e65b861321e8166ff0facf2aca0c7
|
4
|
+
data.tar.gz: 566c9ec8876efdde35f727a61c2b6ffc87203c241bd287ee5dbea302f35a5d5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 254b2653d48d41c64b4837e044a19f2412ffd93a019417c1ddf050b715643c0502b93a11439cefde9e13f9ab7416eb5b059f57a4ca9b8b080ad4ad4e7780be74
|
7
|
+
data.tar.gz: 44eb4c65193a62c0a3ae38d2c80898c623168f267caa74080260a9c37c59148a9918ec9e02371e6bb03358705873d5b8314f993cc2707692e5938a4de559f6bd
|
data/.github/workflows/test.yml
CHANGED
@@ -20,7 +20,7 @@ jobs:
|
|
20
20
|
runs-on: ubuntu-latest
|
21
21
|
strategy:
|
22
22
|
matrix:
|
23
|
-
go-version: [1.
|
23
|
+
go-version: [1.18.x, 1.17.x]
|
24
24
|
steps:
|
25
25
|
- name: Checkout
|
26
26
|
uses: actions/checkout@v2
|
@@ -41,7 +41,7 @@ jobs:
|
|
41
41
|
runs-on: ubuntu-latest
|
42
42
|
strategy:
|
43
43
|
matrix:
|
44
|
-
ruby-version: ["2.
|
44
|
+
ruby-version: ["2.7", "3.0", "3.1"]
|
45
45
|
steps:
|
46
46
|
- uses: actions/checkout@v2
|
47
47
|
- uses: ruby/setup-ruby@v1
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
firejwt (0.
|
4
|
+
firejwt (0.5.0)
|
5
5
|
jwt
|
6
6
|
|
7
7
|
GEM
|
@@ -16,7 +16,7 @@ GEM
|
|
16
16
|
hashdiff (1.0.1)
|
17
17
|
jwt (2.3.0)
|
18
18
|
parallel (1.21.0)
|
19
|
-
parser (3.1.
|
19
|
+
parser (3.1.1.0)
|
20
20
|
ast (~> 2.4.1)
|
21
21
|
public_suffix (4.0.6)
|
22
22
|
rainbow (3.1.1)
|
@@ -36,28 +36,28 @@ GEM
|
|
36
36
|
diff-lcs (>= 1.2.0, < 2.0)
|
37
37
|
rspec-support (~> 3.11.0)
|
38
38
|
rspec-support (3.11.0)
|
39
|
-
rubocop (1.
|
39
|
+
rubocop (1.26.0)
|
40
40
|
parallel (~> 1.10)
|
41
41
|
parser (>= 3.1.0.0)
|
42
42
|
rainbow (>= 2.2.2, < 4.0)
|
43
43
|
regexp_parser (>= 1.8, < 3.0)
|
44
44
|
rexml
|
45
|
-
rubocop-ast (>= 1.
|
45
|
+
rubocop-ast (>= 1.16.0, < 2.0)
|
46
46
|
ruby-progressbar (~> 1.7)
|
47
47
|
unicode-display_width (>= 1.4.0, < 3.0)
|
48
|
-
rubocop-ast (1.
|
49
|
-
parser (>= 3.
|
48
|
+
rubocop-ast (1.16.0)
|
49
|
+
parser (>= 3.1.1.0)
|
50
50
|
rubocop-bsm (0.6.0)
|
51
51
|
rubocop (~> 1.0)
|
52
52
|
rubocop-performance
|
53
53
|
rubocop-rake
|
54
54
|
rubocop-rspec
|
55
|
-
rubocop-performance (1.13.
|
55
|
+
rubocop-performance (1.13.3)
|
56
56
|
rubocop (>= 1.7.0, < 2.0)
|
57
57
|
rubocop-ast (>= 0.4.0)
|
58
58
|
rubocop-rake (0.6.0)
|
59
59
|
rubocop (~> 1.0)
|
60
|
-
rubocop-rspec (2.
|
60
|
+
rubocop-rspec (2.9.0)
|
61
61
|
rubocop (~> 1.19)
|
62
62
|
ruby-progressbar (1.11.0)
|
63
63
|
unicode-display_width (2.1.0)
|
data/firejwt.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'firejwt'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.5.0'
|
4
4
|
s.authors = ['Black Square Media Ltd']
|
5
5
|
s.email = ['info@blacksquaremedia.com']
|
6
6
|
s.summary = %(Firebase JWT validation)
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^spec/}) }
|
12
12
|
s.test_files = `git ls-files -z -- spec/*`.split("\x0")
|
13
13
|
s.require_paths = ['lib']
|
14
|
-
s.required_ruby_version = '>= 2.
|
14
|
+
s.required_ruby_version = '>= 2.7'
|
15
15
|
|
16
16
|
s.add_dependency 'jwt'
|
17
17
|
s.add_development_dependency 'rake'
|
data/firejwt.go
CHANGED
@@ -16,6 +16,10 @@ import (
|
|
16
16
|
"github.com/golang-jwt/jwt/v4"
|
17
17
|
)
|
18
18
|
|
19
|
+
func init() {
|
20
|
+
jwt.MarshalSingleStringAsArray = false
|
21
|
+
}
|
22
|
+
|
19
23
|
const defaultURL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
|
20
24
|
|
21
25
|
// Validator validates Firebase JWTs
|
@@ -99,11 +103,9 @@ func (v *Validator) Refresh() error {
|
|
99
103
|
}
|
100
104
|
|
101
105
|
var (
|
102
|
-
errKIDMissing = errors.New("missing kid header")
|
103
|
-
|
104
|
-
|
105
|
-
errNoSubject = errors.New("subject is missing")
|
106
|
-
errAuthFuture = errors.New("auth-time in the future")
|
106
|
+
errKIDMissing = errors.New("token is missing kid header")
|
107
|
+
errNoSubject = errors.New("token has no subject")
|
108
|
+
errAuthFuture = errors.New("token auth_time not valid")
|
107
109
|
errTokenInvalid = errors.New("token is invalid")
|
108
110
|
)
|
109
111
|
|
@@ -118,22 +120,10 @@ func (v *Validator) verify(token *jwt.Token) (interface{}, error) {
|
|
118
120
|
return nil, fmt.Errorf("invalid kid header %q", kid)
|
119
121
|
}
|
120
122
|
|
121
|
-
now := time.Now().Unix()
|
122
123
|
claims := token.Claims.(*Claims)
|
123
|
-
if claims.
|
124
|
-
return nil,
|
125
|
-
} else if claims.Issuer != v.issuer {
|
126
|
-
return nil, fmt.Errorf("invalid issuer claim %q", claims.Issuer)
|
127
|
-
} else if claims.Subject == "" {
|
128
|
-
return nil, errNoSubject
|
129
|
-
} else if claims.ExpiresAt <= now {
|
130
|
-
return nil, errExpired
|
131
|
-
} else if claims.IssuedAt > now {
|
132
|
-
return nil, errIssuedFuture
|
133
|
-
} else if claims.AuthAt > now {
|
134
|
-
return nil, errAuthFuture
|
124
|
+
if err := claims.validate(time.Now(), v.audience, v.issuer); err != nil {
|
125
|
+
return nil, err
|
135
126
|
}
|
136
|
-
|
137
127
|
return key.PublicKey, nil
|
138
128
|
}
|
139
129
|
|
@@ -188,19 +178,34 @@ func (k *publicKey) UnmarshalText(data []byte) error {
|
|
188
178
|
|
189
179
|
// Claims are included in the token.
|
190
180
|
type Claims struct {
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
181
|
+
Name string `json:"name,omitempty"`
|
182
|
+
Picture string `json:"picture,omitempty"`
|
183
|
+
UserID string `json:"user_id,omitempty"`
|
184
|
+
AuthAt *jwt.NumericDate `json:"auth_time,omitempty"`
|
185
|
+
Email string `json:"email,omitempty"`
|
186
|
+
EmailVerified bool `json:"email_verified"`
|
187
|
+
Firebase *FirebaseClaim `json:"firebase,omitempty"`
|
188
|
+
|
189
|
+
jwt.RegisteredClaims
|
190
|
+
}
|
191
|
+
|
192
|
+
func (c *Claims) validate(now time.Time, audience, issuer string) error {
|
193
|
+
if !c.VerifyExpiresAt(now, false) {
|
194
|
+
return jwt.ErrTokenExpired
|
195
|
+
} else if !c.VerifyIssuedAt(now, false) {
|
196
|
+
return jwt.ErrTokenUsedBeforeIssued
|
197
|
+
} else if !c.VerifyNotBefore(now, false) {
|
198
|
+
return jwt.ErrTokenNotValidYet
|
199
|
+
} else if !c.VerifyAudience(audience, true) {
|
200
|
+
return jwt.ErrTokenInvalidAudience
|
201
|
+
} else if !c.VerifyIssuer(issuer, true) {
|
202
|
+
return jwt.ErrTokenInvalidIssuer
|
203
|
+
} else if c.Subject == "" {
|
204
|
+
return errNoSubject
|
205
|
+
} else if c.AuthAt.After(now) {
|
206
|
+
return errAuthFuture
|
207
|
+
}
|
208
|
+
return nil
|
204
209
|
}
|
205
210
|
|
206
211
|
// FirebaseClaim represents firebase specific claim.
|
@@ -208,8 +213,3 @@ type FirebaseClaim struct {
|
|
208
213
|
SignInProvider string `json:"sign_in_provider,omitempty"`
|
209
214
|
Identities map[string][]string `json:"identities,omitempty"`
|
210
215
|
}
|
211
|
-
|
212
|
-
// Valid implements the jwt.Claims interface.
|
213
|
-
func (c *Claims) Valid() error {
|
214
|
-
return nil
|
215
|
-
}
|
data/firejwt_test.go
CHANGED
@@ -43,7 +43,7 @@ var _ = Describe("Validator", func() {
|
|
43
43
|
certKID: string(certPEM),
|
44
44
|
})
|
45
45
|
}))
|
46
|
-
seeds = mockClaims(time.Now()
|
46
|
+
seeds = mockClaims(time.Now())
|
47
47
|
|
48
48
|
var err error
|
49
49
|
subject, err = firejwt.Mocked(server.URL)
|
@@ -72,51 +72,51 @@ var _ = Describe("Validator", func() {
|
|
72
72
|
})
|
73
73
|
|
74
74
|
It("should verify exp", func() {
|
75
|
-
seeds.ExpiresAt = time.Now().
|
75
|
+
seeds.ExpiresAt = jwt.NewNumericDate(time.Now().Add(-time.Second))
|
76
76
|
_, err := subject.Decode(generate())
|
77
|
-
Expect(err).To(MatchError(`token
|
77
|
+
Expect(err).To(MatchError(`token is expired`))
|
78
78
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
79
79
|
})
|
80
80
|
|
81
81
|
It("should verify iat", func() {
|
82
|
-
seeds.IssuedAt = time.Now().
|
82
|
+
seeds.IssuedAt = jwt.NewNumericDate(time.Now().Add(time.Second))
|
83
83
|
_, err := subject.Decode(generate())
|
84
|
-
Expect(err).To(MatchError(`
|
84
|
+
Expect(err).To(MatchError(`token used before issued`))
|
85
85
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
86
86
|
})
|
87
87
|
|
88
88
|
It("should verify aud", func() {
|
89
|
-
seeds.Audience = "other"
|
89
|
+
seeds.Audience = jwt.ClaimStrings{"other"}
|
90
90
|
_, err := subject.Decode(generate())
|
91
|
-
Expect(err).To(MatchError(`invalid audience
|
91
|
+
Expect(err).To(MatchError(`token has invalid audience`))
|
92
92
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
93
93
|
})
|
94
94
|
|
95
95
|
It("should verify iss", func() {
|
96
96
|
seeds.Issuer = "other"
|
97
97
|
_, err := subject.Decode(generate())
|
98
|
-
Expect(err).To(MatchError(`invalid issuer
|
98
|
+
Expect(err).To(MatchError(`token has invalid issuer`))
|
99
99
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
100
100
|
})
|
101
101
|
|
102
102
|
It("should verify sub", func() {
|
103
103
|
seeds.Subject = ""
|
104
104
|
_, err := subject.Decode(generate())
|
105
|
-
Expect(err).To(MatchError(`
|
105
|
+
Expect(err).To(MatchError(`token has no subject`))
|
106
106
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
107
107
|
})
|
108
108
|
|
109
109
|
It("should verify auth time", func() {
|
110
|
-
seeds.AuthAt = time.Now().
|
110
|
+
seeds.AuthAt = jwt.NewNumericDate(time.Now().Add(time.Second))
|
111
111
|
_, err := subject.Decode(generate())
|
112
|
-
Expect(err).To(MatchError(`
|
112
|
+
Expect(err).To(MatchError(`token auth_time not valid`))
|
113
113
|
Expect(err).To(BeAssignableToTypeOf(&jwt.ValidationError{}))
|
114
114
|
})
|
115
115
|
})
|
116
116
|
|
117
117
|
var _ = Describe("Claims", func() {
|
118
118
|
It("should be JWT compatible", func() {
|
119
|
-
subject := mockClaims(1515151515)
|
119
|
+
subject := mockClaims(time.Unix(1515151515, 0))
|
120
120
|
Expect(json.Marshal(subject)).To(MatchJSON(`{
|
121
121
|
"name": "Me",
|
122
122
|
"picture": "https://test.host/me.jpg",
|
@@ -185,17 +185,12 @@ var _ = BeforeSuite(func() {
|
|
185
185
|
certPEM = buf.String()
|
186
186
|
})
|
187
187
|
|
188
|
-
func mockClaims(now
|
188
|
+
func mockClaims(now time.Time) *firejwt.Claims {
|
189
189
|
return &firejwt.Claims{
|
190
190
|
Name: "Me",
|
191
191
|
Picture: "https://test.host/me.jpg",
|
192
|
-
Subject: "MDYwNDQwNjUtYWQ0ZC00ZDkwLThl",
|
193
192
|
UserID: "MDYwNDQwNjUtYWQ0ZC00ZDkwLThl",
|
194
|
-
|
195
|
-
Issuer: "https://securetoken.google.com/mock-project",
|
196
|
-
IssuedAt: now - 1800,
|
197
|
-
ExpiresAt: now + 3600,
|
198
|
-
AuthAt: now,
|
193
|
+
AuthAt: jwt.NewNumericDate(now),
|
199
194
|
Email: "me@example.com",
|
200
195
|
EmailVerified: true,
|
201
196
|
Firebase: &firejwt.FirebaseClaim{
|
@@ -205,5 +200,12 @@ func mockClaims(now int64) *firejwt.Claims {
|
|
205
200
|
"email": {"me@example.com"},
|
206
201
|
},
|
207
202
|
},
|
203
|
+
RegisteredClaims: jwt.RegisteredClaims{
|
204
|
+
Subject: "MDYwNDQwNjUtYWQ0ZC00ZDkwLThl",
|
205
|
+
Audience: jwt.ClaimStrings{"mock-project"},
|
206
|
+
Issuer: "https://securetoken.google.com/mock-project",
|
207
|
+
IssuedAt: jwt.NewNumericDate(now.Add(-30 * time.Minute)),
|
208
|
+
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
|
209
|
+
},
|
208
210
|
}
|
209
211
|
}
|
data/go.mod
CHANGED
data/go.sum
CHANGED
@@ -2,5 +2,5 @@ github.com/bsm/ginkgo/v2 v2.0.0 h1:JZAs5u7SmJCrZlGFrC3v1stg/uC7OIj2q2FW9NxeIVE=
|
|
2
2
|
github.com/bsm/ginkgo/v2 v2.0.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
3
3
|
github.com/bsm/gomega v1.11.0 h1:wg9DVGPETNZLIbMsseneMV1a7uo/x+wsCyNXdEcifDI=
|
4
4
|
github.com/bsm/gomega v1.11.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
|
5
|
-
github.com/golang-jwt/jwt/v4 v4.
|
6
|
-
github.com/golang-jwt/jwt/v4 v4.
|
5
|
+
github.com/golang-jwt/jwt/v4 v4.4.0 h1:EmVIxB5jzbllGIjiCV5JG4VylbK3KE400tLGLI1cdfU=
|
6
|
+
github.com/golang-jwt/jwt/v4 v4.4.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firejwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Black Square Media Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -121,7 +121,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '2.
|
124
|
+
version: '2.7'
|
125
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
126
|
requirements:
|
127
127
|
- - ">="
|