firebase_id_token 3.0.0 → 4.0.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 +1 -7
- data/CHANGELOG.md +36 -1
- data/Gemfile +7 -0
- data/README.md +57 -28
- data/firebase_id_token.gemspec +4 -4
- data/lib/firebase_id_token/certificates/active_support.rb +92 -0
- data/lib/firebase_id_token/certificates/redis.rb +46 -0
- data/lib/firebase_id_token/certificates.rb +69 -60
- data/lib/firebase_id_token/configuration.rb +10 -2
- data/lib/firebase_id_token/exceptions/unsupported_cache_operation_error.rb +11 -0
- data/lib/firebase_id_token/signature.rb +30 -13
- data/lib/firebase_id_token/testing/certificates.rb +1 -1
- data/lib/firebase_id_token/version.rb +1 -1
- data/lib/firebase_id_token.rb +6 -6
- data/spec/firebase_id_token/certificates/active_support_spec.rb +43 -0
- data/spec/firebase_id_token/certificates/redis_spec.rb +39 -0
- data/spec/firebase_id_token/certificates_session_cookie_spec.rb +49 -0
- data/spec/firebase_id_token/signature_session_cookie_spec.rb +65 -0
- data/spec/firebase_id_token/signature_spec.rb +15 -1
- data/spec/firebase_id_token/signature_test_spec.rb +4 -0
- data/spec/firebase_id_token_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/certificates_shared_examples.rb +196 -0
- metadata +24 -15
- data/CODE_OF_CONDUCT.md +0 -74
- data/spec/firebase_id_token/certificates_spec.rb +0 -164
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7737e97a521a870ff07e83c63f2dbf0d72e89ba85858f42ae9443762518a15a
|
|
4
|
+
data.tar.gz: 20f27c0f72411d19d6f0efb29fb48528a31c5b6a9ccee7d42300ff61f08833c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e120f07c5e5017932ab02dcd7955bc3ee292ed360f6da45f56f739394488aa8ae41bab42ab30dbd1f4dd7f51e3dfeed6c3d3a1acd00925656e87be8253a3cda
|
|
7
|
+
data.tar.gz: a84b333fc8a2a9ad2cec07300ba4ad46a72b9685cebb9cfd0dcb7e2651a123cfed5732487851b76e0a0f8a2ef897989e417418eecf0d38375b287e582a0dd0c9
|
data/.github/workflows/test.yml
CHANGED
|
@@ -3,6 +3,7 @@ on:
|
|
|
3
3
|
push:
|
|
4
4
|
branches:
|
|
5
5
|
- master
|
|
6
|
+
pull_request:
|
|
6
7
|
|
|
7
8
|
jobs:
|
|
8
9
|
test:
|
|
@@ -25,10 +26,3 @@ jobs:
|
|
|
25
26
|
run: bundle install
|
|
26
27
|
- name: Run tests
|
|
27
28
|
run: bundle exec rspec
|
|
28
|
-
- name: Publish code coverage
|
|
29
|
-
uses: paambaati/codeclimate-action@v4.0.0
|
|
30
|
-
env:
|
|
31
|
-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
|
32
|
-
with:
|
|
33
|
-
debug: true
|
|
34
|
-
coverageLocations: ${{github.workspace}}/coverage/coverage.json:simplecov
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## [4.0.0] - 2026-07-28
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Support for verifying Firebase Session Cookies with
|
|
13
|
+
`FirebaseIdToken::Signature.verify(cookie, type: :session_cookie)`. Session
|
|
14
|
+
Cookie certificates are downloaded from their own Google API URL and cached
|
|
15
|
+
apart from the ID Token ones.
|
|
16
|
+
[PR #46](https://github.com/fschuindt/firebase_id_token/pull/46).
|
|
17
|
+
- Support for any `ActiveSupport::Cache` store through the new
|
|
18
|
+
`config.cache_store` configuration, with lazy certificate download on cache
|
|
19
|
+
miss, removing the need for Redis and scheduled certificate requests.
|
|
20
|
+
[PR #43](https://github.com/fschuindt/firebase_id_token/pull/43), closes
|
|
21
|
+
[issue #6](https://github.com/fschuindt/firebase_id_token/issues/6). The
|
|
22
|
+
legacy `config.redis` configuration keeps working as before.
|
|
23
|
+
- A warning on `README.md` that `FirebaseIdToken.test!` must never be called
|
|
24
|
+
outside of a test suite, as the fixture private key is public in this
|
|
25
|
+
repository.
|
|
26
|
+
- [Dev] `ostruct` as a development dependency, as it's no longer a default
|
|
27
|
+
gem in Ruby >= 3.5 and Pry requires it when running the specs.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- `Signature.verify` raised `NoMethodError` when a correctly signed token had
|
|
31
|
+
no `sub` claim, even with `raise_error: false`. It now returns `nil` as
|
|
32
|
+
documented.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Loosened the HTTParty dependency constraint to `>= 0.21, < 1.0`, allowing
|
|
36
|
+
newer releases. [Issue #44](https://github.com/fschuindt/firebase_id_token/issues/44).
|
|
37
|
+
- CI now also runs on pull requests.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
- The Code Climate coverage publishing step and badges, as the service and
|
|
41
|
+
its GitHub Action are defunct. SimpleCov still reports coverage on every
|
|
42
|
+
test run.
|
|
10
43
|
|
|
11
44
|
## [3.0.0] - 2023-04-11
|
|
12
45
|
|
|
@@ -20,6 +53,7 @@ Nothing.
|
|
|
20
53
|
- SimpleCov JSON formatter and `json` as dependency.
|
|
21
54
|
|
|
22
55
|
### Changed
|
|
56
|
+
- Redis is no longer required. Any ActiveSupport cache will now work.
|
|
23
57
|
- It won't default to `Redis.new` anymore. You must now provide Redis details during configuration. [Details here](https://github.com/fschuindt/firebase_id_token/issues/30).
|
|
24
58
|
- Upgraded Redis to 5.0.6.
|
|
25
59
|
- Upgraded Redis Namespace to 1.10.
|
|
@@ -152,6 +186,7 @@ Nothing.
|
|
|
152
186
|
## [0.1.0] - 2017-04-23
|
|
153
187
|
*Version removed.*
|
|
154
188
|
|
|
189
|
+
[4.0.0]: https://github.com/fschuindt/firebase_id_token/compare/3.0.0...4.0.0
|
|
155
190
|
[3.0.0]: https://github.com/fschuindt/firebase_id_token/compare/2.5.2...3.0.0
|
|
156
191
|
[2.5.2]: https://github.com/fschuindt/firebase_id_token/compare/2.5.1...2.5.2
|
|
157
192
|
[2.5.1]: https://github.com/fschuindt/firebase_id_token/compare/2.5.0...2.5.1
|
data/Gemfile
CHANGED
|
@@ -2,3 +2,10 @@ source 'https://rubygems.org'
|
|
|
2
2
|
|
|
3
3
|
# Specify your gem's dependencies in firebase_id_token.gemspec
|
|
4
4
|
gemspec
|
|
5
|
+
|
|
6
|
+
# connection_pool 3.x is incompatible with ActiveSupport 7.x's
|
|
7
|
+
# RedisCacheStore (positional options hash vs keyword arguments).
|
|
8
|
+
gem 'connection_pool', '< 3.0'
|
|
9
|
+
|
|
10
|
+
# No longer a default gem in Ruby >= 3.5; needed by pry in the specs.
|
|
11
|
+
gem 'ostruct'
|
data/README.md
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
# Ruby Firebase ID Token verifier
|
|
1
|
+
# Ruby Firebase ID Token verifier
|
|
2
2
|
|
|
3
|
-
[](https://codeclimate.com/github/fschuindt/firebase_id_token/coverage)
|
|
5
|
-
[](https://codeclimate.com/github/fschuindt/firebase_id_token)
|
|
6
|
-
[](https://codeclimate.com/github/fschuindt/firebase_id_token)
|
|
7
|
-
[](http://inch-ci.org/github/fschuindt/firebase_id_token)
|
|
3
|
+
[](https://github.com/fschuindt/firebase_id_token/actions/workflows/test.yml)
|
|
8
4
|
|
|
9
|
-
A Ruby gem to verify the signature of Firebase ID Tokens (JWT). It uses
|
|
5
|
+
A Ruby gem to verify the signature of Firebase ID Tokens (JWT). It uses ActiveSupport::Cache to store Google's x509 certificates and manage their expiration time, so you don't need to request Google's API in every execution and can access it as fast as reading from memory.
|
|
10
6
|
|
|
11
7
|
It also checks the JWT payload parameters as recommended [here](https://firebase.google.com/docs/auth/admin/verify-id-tokens) by Firebase official documentation.
|
|
12
8
|
|
|
13
|
-
Feel free to open any issue or to [contact me](https://fschuindt.github.io/blog/about/) directly.
|
|
9
|
+
Feel free to open any issue or to [contact me](https://fschuindt.github.io/blog/about/) directly.
|
|
14
10
|
Any contribution is welcome.
|
|
15
11
|
|
|
16
12
|
## Docs
|
|
17
13
|
|
|
18
14
|
+ http://www.rubydoc.info/gems/firebase_id_token
|
|
19
15
|
|
|
20
|
-
## Requirements
|
|
21
|
-
|
|
22
|
-
+ Redis
|
|
23
|
-
|
|
24
16
|
## Installing
|
|
25
17
|
|
|
26
18
|
```
|
|
@@ -29,7 +21,7 @@ gem install firebase_id_token
|
|
|
29
21
|
|
|
30
22
|
or in your Gemfile
|
|
31
23
|
```
|
|
32
|
-
gem 'firebase_id_token', '~>
|
|
24
|
+
gem 'firebase_id_token', '~> 4.0.0'
|
|
33
25
|
```
|
|
34
26
|
then
|
|
35
27
|
```
|
|
@@ -42,13 +34,27 @@ It's needed to set up your Firebase Project ID.
|
|
|
42
34
|
|
|
43
35
|
If you are using Rails, this should probably go into `config/initializers/firebase_id_token.rb`.
|
|
44
36
|
```ruby
|
|
37
|
+
FirebaseIdToken.configure do |config|
|
|
38
|
+
config.cache_store = ActiveSupport::Cache::RedisCacheStore.new
|
|
39
|
+
config.project_ids = ['your-firebase-project-id']
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You can use the old method of configuration as well. If you use this method, you'll have to proactively
|
|
44
|
+
download certificates (see [Downloading Certificates](#downloading-certificates) below)
|
|
45
|
+
```ruby
|
|
45
46
|
FirebaseIdToken.configure do |config|
|
|
46
47
|
config.redis = Redis.new
|
|
47
48
|
config.project_ids = ['your-firebase-project-id']
|
|
48
49
|
end
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
-
|
|
52
|
+
- A cache store instance inheriting from [ActiveSupport::Cache::Store](https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html) must be supplied.
|
|
53
|
+
- Examples:
|
|
54
|
+
- `ActiveSupport::Cache::RedisCacheStore.new(Redis.new(host: '10.0.1.1', port: 6380, db: 15))`
|
|
55
|
+
- `ActiveSupport::Cache::MemoryStore.new(namespace: "firebase_auth")`
|
|
56
|
+
- `ActiveSupport::Cache::FileStore.new("cache", namespace: "firebase_auth")`
|
|
57
|
+
- `Rails.cache`
|
|
52
58
|
- `project_ids` must be an Array.
|
|
53
59
|
|
|
54
60
|
*If you want to verify signatures from more than one Firebase project, just add more Project IDs to the list.*
|
|
@@ -67,22 +73,22 @@ To do it, simply:
|
|
|
67
73
|
FirebaseIdToken::Certificates.request
|
|
68
74
|
```
|
|
69
75
|
|
|
70
|
-
It will download the certificates and save it in
|
|
76
|
+
It will download the certificates and save it in cache, but only if the cache certificates database is empty. To force download and override of the cache entries, use:
|
|
71
77
|
```ruby
|
|
72
78
|
FirebaseIdToken::Certificates.request!
|
|
73
79
|
```
|
|
74
80
|
|
|
75
|
-
Google give us information about the certificates' expiration time, it's used to set a
|
|
81
|
+
Google give us information about the certificates' expiration time, it's used to set a cache TTL (Time-To-Live) when saving it. By doing so, the certificates will be automatically deleted after its expiration.
|
|
76
82
|
|
|
77
83
|
#### Certificates Info
|
|
78
84
|
|
|
79
|
-
Checks the presence of certificates in the
|
|
85
|
+
Checks the presence of certificates in the cache.
|
|
80
86
|
```ruby
|
|
81
87
|
FirebaseIdToken::Certificates.present?
|
|
82
88
|
=> true
|
|
83
89
|
```
|
|
84
90
|
|
|
85
|
-
How many seconds until the certificate's expiration.
|
|
91
|
+
How many seconds until the certificate's expiration. _NOTE_: Currently only functional when using Redis
|
|
86
92
|
```ruby
|
|
87
93
|
FirebaseIdToken::Certificates.ttl
|
|
88
94
|
=> 22352
|
|
@@ -101,6 +107,8 @@ FirebaseIdToken::Certificates.find('ec8f292sd30224afac5c55540df66d1f999d')
|
|
|
101
107
|
```
|
|
102
108
|
|
|
103
109
|
#### Downloading in Rails
|
|
110
|
+
If you pass in the `cache_store` configuration option (see [configuration](#configuration)), the certificates will be
|
|
111
|
+
requested at runtime when needed and you can ignore this section.
|
|
104
112
|
|
|
105
113
|
If you are using Rails, it's clever to download certificates in a cron task, you can use [whenever](https://github.com/javan/whenever).
|
|
106
114
|
|
|
@@ -112,12 +120,12 @@ Create your task in `lib/tasks/firebase.rake`:
|
|
|
112
120
|
```ruby
|
|
113
121
|
namespace :firebase do
|
|
114
122
|
namespace :certificates do
|
|
115
|
-
desc "Request Google's x509 certificates when
|
|
123
|
+
desc "Request Google's x509 certificates when the cache is empty"
|
|
116
124
|
task request: :environment do
|
|
117
125
|
FirebaseIdToken::Certificates.request
|
|
118
126
|
end
|
|
119
127
|
|
|
120
|
-
desc "Request Google's x509 certificates and override
|
|
128
|
+
desc "Request Google's x509 certificates and override the cache"
|
|
121
129
|
task force_request: :environment do
|
|
122
130
|
FirebaseIdToken::Certificates.request!
|
|
123
131
|
end
|
|
@@ -144,7 +152,7 @@ When developing, you should just run the task:
|
|
|
144
152
|
$ rake firebase:certificates:request
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
*You need Redis to be running
|
|
155
|
+
*You need Redis to be running if you're using RedisCacheStore*
|
|
148
156
|
|
|
149
157
|
### Verifying Tokens
|
|
150
158
|
|
|
@@ -164,6 +172,21 @@ FirebaseIdToken::Signature.verify('aaaaaa')
|
|
|
164
172
|
=> nil
|
|
165
173
|
```
|
|
166
174
|
|
|
175
|
+
#### Verifying Session Cookies
|
|
176
|
+
|
|
177
|
+
[Firebase Session Cookies](https://firebase.google.com/docs/auth/admin/manage-cookies) are issued by `https://session.firebase.google.com` and signed by a different certificate set than ID Tokens. Pass `type: :session_cookie` to verify one:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
FirebaseIdToken::Signature.verify(session_cookie, type: :session_cookie)
|
|
181
|
+
=> {"iss"=>"https://session.firebase.google.com/firebase-id-token", "name"=>"Bob Test", [...]}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
When using the `cache_store` configuration, the Session Cookie certificates are downloaded automatically on the first verification, just like the ID Token ones. If you are on the legacy Redis configuration and download certificates with a cron task, you also need to request the Session Cookie certificates:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
FirebaseIdToken::Certificates.request(source: :session_cookie)
|
|
188
|
+
```
|
|
189
|
+
|
|
167
190
|
#### WARNING!
|
|
168
191
|
|
|
169
192
|
##### Expired tokens can point to long gone certificates
|
|
@@ -180,7 +203,7 @@ More details [here](https://github.com/fschuindt/firebase_id_token/issues/29).
|
|
|
180
203
|
|
|
181
204
|
##### Trying to verify tokens without downloaded certificates will raise an error
|
|
182
205
|
|
|
183
|
-
If you try to verify a signature without any certificates in
|
|
206
|
+
If you try to verify a signature without any certificates in the cache, it will raise a `FirebaseIdToken::Exceptions::NoCertificatesError`.
|
|
184
207
|
|
|
185
208
|
##### "I keep on getting `nil` on `verify`"
|
|
186
209
|
|
|
@@ -190,7 +213,7 @@ Poorly synchronized clocks will sometimes make the server think the token's `iat
|
|
|
190
213
|
|
|
191
214
|
In case you need, here's a example of the payload structure from a Google login in JSON.
|
|
192
215
|
```json
|
|
193
|
-
{
|
|
216
|
+
{
|
|
194
217
|
"iss":"https://securetoken.google.com/{{YOUR_FIREBASE_APP_ID}}",
|
|
195
218
|
"name":"Ugly Bob",
|
|
196
219
|
"picture":"https://someurl.com/photo.jpg",
|
|
@@ -202,12 +225,12 @@ In case you need, here's a example of the payload structure from a Google login
|
|
|
202
225
|
"exp":33029000017, // needs to be in the future
|
|
203
226
|
"email":"uglybob@emailurl.com",
|
|
204
227
|
"email_verified":true,
|
|
205
|
-
"firebase":{
|
|
206
|
-
"identities":{
|
|
207
|
-
"google.com":[
|
|
228
|
+
"firebase":{
|
|
229
|
+
"identities":{
|
|
230
|
+
"google.com":[
|
|
208
231
|
"1010101010101010101"
|
|
209
232
|
],
|
|
210
|
-
"email":[
|
|
233
|
+
"email":[
|
|
211
234
|
"uglybob@emailurl.com"
|
|
212
235
|
]
|
|
213
236
|
},
|
|
@@ -232,6 +255,12 @@ Just run:
|
|
|
232
255
|
FirebaseIdToken.test!
|
|
233
256
|
```
|
|
234
257
|
|
|
258
|
+
> **Warning**
|
|
259
|
+
> `FirebaseIdToken.test!` replaces signature verification with a fixture
|
|
260
|
+
> certificate whose private key is publicly available in this repository.
|
|
261
|
+
> Never call it outside of your test suite. An application running in test
|
|
262
|
+
> mode will accept forged tokens crafted by anyone.
|
|
263
|
+
|
|
235
264
|
By using this mode, the following methods become available.
|
|
236
265
|
|
|
237
266
|
```ruby
|
|
@@ -268,7 +297,7 @@ module Api
|
|
|
268
297
|
@routes = Engine.routes
|
|
269
298
|
@user = users(:one)
|
|
270
299
|
end
|
|
271
|
-
|
|
300
|
+
|
|
272
301
|
def create_token(sub: nil)
|
|
273
302
|
_payload = payload.merge({sub: sub})
|
|
274
303
|
JWT.encode _payload, OpenSSL::PKey::RSA.new(FirebaseIdToken::Testing::Certificates.private_key), 'RS256'
|
data/firebase_id_token.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
|
|
12
12
|
spec.summary = 'A Firebase ID Token verifier.'
|
|
13
13
|
spec.description = "A Ruby gem to verify the signature of Firebase ID "\
|
|
14
|
-
"Tokens. It uses
|
|
14
|
+
"Tokens. It uses ActiveSupport::Cache to store Google's x509 certificates and manage "\
|
|
15
15
|
"their expiration time, so you don't need to request Google's API in "\
|
|
16
16
|
"every execution and can access it as fast as reading from memory."
|
|
17
17
|
spec.homepage = 'https://github.com/fschuindt/firebase_id_token'
|
|
@@ -29,10 +29,10 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.add_development_dependency 'simplecov', '~> 0.22.0'
|
|
30
30
|
spec.add_development_dependency 'simplecov_json_formatter', '~> 0.1.2'
|
|
31
31
|
spec.add_development_dependency 'pry', '~> 0.14.2'
|
|
32
|
+
spec.add_development_dependency 'redis', '~> 5.0', '>= 5.0.6'
|
|
33
|
+
spec.add_development_dependency 'redis-namespace', '~> 1.10'
|
|
32
34
|
|
|
33
|
-
spec.
|
|
34
|
-
spec.add_runtime_dependency 'redis-namespace', '~> 1.10'
|
|
35
|
-
spec.add_dependency 'httparty', '~> 0.21.0'
|
|
35
|
+
spec.add_dependency 'httparty', '>= 0.21', '< 1.0'
|
|
36
36
|
spec.add_runtime_dependency 'jwt', '~> 2.7'
|
|
37
37
|
spec.add_runtime_dependency 'activesupport', '~> 7.0', '>= 7.0.4.3'
|
|
38
38
|
spec.add_runtime_dependency 'json', '~> 2.6', '>= 2.6.3'
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module FirebaseIdToken
|
|
2
|
+
# ActiveSupport specific methods for Certificates class
|
|
3
|
+
class Certificates::ActiveSupport < Certificates
|
|
4
|
+
attr_reader :cache_store
|
|
5
|
+
RACE_CONDITION_TIME = 5.seconds
|
|
6
|
+
|
|
7
|
+
def initialize(source: :id_token)
|
|
8
|
+
@source = source
|
|
9
|
+
@cache_store = ::FirebaseIdToken.configuration.cache_store
|
|
10
|
+
@local_certs = read_certificates
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.ttl(source: :id_token)
|
|
14
|
+
current_time = Time.now.to_i
|
|
15
|
+
entry = new(source: source).cache_store.read CACHE_KEYS.fetch(source)
|
|
16
|
+
if entry
|
|
17
|
+
expires_at = JSON.parse(entry)["expires_at"]
|
|
18
|
+
return expires_at - current_time < 0 ? 0 : expires_at - current_time
|
|
19
|
+
end
|
|
20
|
+
return 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def read_certificates
|
|
26
|
+
entry = cache_store.read(cache_key)
|
|
27
|
+
if entry.nil?
|
|
28
|
+
lock do
|
|
29
|
+
request!
|
|
30
|
+
end
|
|
31
|
+
entry = cache_store.read(cache_key)
|
|
32
|
+
end
|
|
33
|
+
certs = {}
|
|
34
|
+
certs = JSON.parse(JSON.parse(entry)["data"]) if entry
|
|
35
|
+
certs
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
return {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# we can't use ActiveSupport's fetch, because we need to set the expiration time of
|
|
41
|
+
# the key based on a value we read from the request. This is a rudimentary mutex instead
|
|
42
|
+
def lock
|
|
43
|
+
acquire_lock
|
|
44
|
+
yield
|
|
45
|
+
ensure
|
|
46
|
+
release_lock
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def acquire_lock
|
|
50
|
+
maybe_sleep
|
|
51
|
+
cache_store.write(lock_key, true, expires_in: 5.seconds)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def maybe_sleep
|
|
55
|
+
iteration = 0
|
|
56
|
+
while cache_store.exist?(lock_key)
|
|
57
|
+
iteration += 1
|
|
58
|
+
sleep 1
|
|
59
|
+
break if iteration > 5
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def release_lock
|
|
64
|
+
cache_store.delete(lock_key)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def lock_key
|
|
68
|
+
"#{cache_key}_lock"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def save_certificates
|
|
72
|
+
expires_at = Time.now.to_i + ttl
|
|
73
|
+
# set the expiration of the key to the certification expiration - RACE_CONDITION_TIME, so that the entry
|
|
74
|
+
# will be expired before the certificate is
|
|
75
|
+
cache_store.write cache_key, { data: @request.body, expires_at: expires_at }.to_json,
|
|
76
|
+
expires_in: (ttl - RACE_CONDITION_TIME)
|
|
77
|
+
@local_certs = @request.body
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ttl
|
|
81
|
+
cache_control = @request.headers['cache-control']
|
|
82
|
+
ttl = cache_control.match(/max-age=([0-9]+)/).captures.first.to_i
|
|
83
|
+
|
|
84
|
+
if ttl > 3600
|
|
85
|
+
ttl
|
|
86
|
+
else
|
|
87
|
+
raise ::FirebaseIdToken::Exceptions::CertificatesTtlError
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module FirebaseIdToken
|
|
2
|
+
# Redis specific methods for Certificates class
|
|
3
|
+
class Certificates::Redis < Certificates
|
|
4
|
+
attr_reader :redis
|
|
5
|
+
|
|
6
|
+
def initialize(source: :id_token)
|
|
7
|
+
@source = source
|
|
8
|
+
@redis = ::Redis::Namespace.new('firebase_id_token',
|
|
9
|
+
redis: FirebaseIdToken.configuration.redis)
|
|
10
|
+
@local_certs = read_certificates
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns the current certificates TTL (Time-To-Live) in seconds. *Zero
|
|
14
|
+
# meaning no certificates.* It's the same as the certificates expiration
|
|
15
|
+
# time, use it to know when to request again.
|
|
16
|
+
# @return [Fixnum]
|
|
17
|
+
def self.ttl(source: :id_token)
|
|
18
|
+
ttl = new(source: source).redis.ttl(CACHE_KEYS.fetch(source))
|
|
19
|
+
ttl < 0 ? 0 : ttl
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def read_certificates
|
|
25
|
+
certs = @redis.get cache_key
|
|
26
|
+
certs ? JSON.parse(certs) : {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def save_certificates
|
|
30
|
+
@redis.setex cache_key, ttl, @request.body
|
|
31
|
+
@local_certs = read_certificates
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ttl
|
|
35
|
+
cache_control = @request.headers['cache-control']
|
|
36
|
+
ttl = cache_control.match(/max-age=([0-9]+)/).captures.first.to_i
|
|
37
|
+
|
|
38
|
+
if ttl > 3600
|
|
39
|
+
ttl
|
|
40
|
+
else
|
|
41
|
+
raise Exceptions::CertificatesTtlError
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|