firebase_id_token 2.5.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cb7c188a4b5026064a64c3a11963be1caae523a448ecc5d998597b7c6989d74
4
- data.tar.gz: b0d57512d245bc43ff295bc0987d8529b6881fdcb7c3508d227204c029821725
3
+ metadata.gz: a7737e97a521a870ff07e83c63f2dbf0d72e89ba85858f42ae9443762518a15a
4
+ data.tar.gz: 20f27c0f72411d19d6f0efb29fb48528a31c5b6a9ccee7d42300ff61f08833c9
5
5
  SHA512:
6
- metadata.gz: c17980aaa3e572ac10bf4208ae27ca2dc75781665c219422b23209eb9f7183532d50c2e2491d9036cfd2702cadaddeadb3cac926cf2e7f0d1f0baacc85e22c57
7
- data.tar.gz: d9db140915704be2cf596a136e60bc9a4afee5e99eb1eebd35adfb65eeb2beec535ed72188e7a7454413629591f4310f8ae6db050dcd23f4f474dff0737f501f
6
+ metadata.gz: 2e120f07c5e5017932ab02dcd7955bc3ee292ed360f6da45f56f739394488aa8ae41bab42ab30dbd1f4dd7f51e3dfeed6c3d3a1acd00925656e87be8253a3cda
7
+ data.tar.gz: a84b333fc8a2a9ad2cec07300ba4ad46a72b9685cebb9cfd0dcb7e2651a123cfed5732487851b76e0a0f8a2ef897989e417418eecf0d38375b287e582a0dd0c9
@@ -0,0 +1,28 @@
1
+ name: Test
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: "Run Tests"
11
+ runs-on: ubuntu-latest
12
+ services:
13
+ redis:
14
+ image: redis
15
+ ports:
16
+ - 6379:6379
17
+ options: --entrypoint redis-server
18
+ steps:
19
+ - uses: actions/checkout@v3
20
+ - uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '3.2.2'
23
+ - name: Install Bundler
24
+ run: gem install bundler:2.4.13
25
+ - name: Install Dependencies
26
+ run: bundle install
27
+ - name: Run tests
28
+ run: bundle exec rspec
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/CHANGELOG.md CHANGED
@@ -6,7 +6,74 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [2.5.2] - 2023-03-16
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.
43
+
44
+ ## [3.0.0] - 2023-04-11
45
+
46
+ ### Added
47
+ - CI testing using GitHub Actions.
48
+ - Explains on `README.md` about the behavior of `verify` on expired tokens. [Details here](https://github.com/fschuindt/firebase_id_token/issues/29).
49
+ - Warns about the poorly synchronized clocks issue with the token's `iat`. [Details here](https://github.com/fschuindt/firebase_id_token/issues/21#issuecomment-623133926).
50
+ - Gives better examples when testing. [Details here](https://github.com/fschuindt/firebase_id_token/pull/38).
51
+ - Created a `.ruby-version` file.
52
+ - Added ActiveSupport as dependency for `Time.current`.
53
+ - SimpleCov JSON formatter and `json` as dependency.
54
+
55
+ ### Changed
56
+ - Redis is no longer required. Any ActiveSupport cache will now work.
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).
58
+ - Upgraded Redis to 5.0.6.
59
+ - Upgraded Redis Namespace to 1.10.
60
+ - Upgraded HTTParty to 0.21.0.
61
+ - Upgraded JWT to 2.7.
62
+ - Upgraded [Dev] Ruby to 3.2.2.
63
+ - Upgraded [Dev] Bundler to 2.4.13.
64
+ - Upgraded [Dev] Rake to 13.0.6.
65
+ - Upgraded [Dev] RSpec to 3.12.
66
+ - Upgraded [Dev] Redcarpet to 3.6.
67
+ - Upgraded [Dev] Simplecov to 0.22.0.
68
+ - Upgraded [Dev] Pry to 0.14.2.
69
+
70
+ ### Fixed
71
+ - Code Climate test coverage report.
72
+
73
+ ### Removed
74
+ - Travis CI badge.
75
+
76
+ ## [2.5.2] - 2023-04-09
10
77
 
11
78
  ### Fixed
12
79
  - [CWE-472](https://github.com/fschuindt/firebase_id_token/pull/41).
@@ -119,6 +186,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
119
186
  ## [0.1.0] - 2017-04-23
120
187
  *Version removed.*
121
188
 
189
+ [4.0.0]: https://github.com/fschuindt/firebase_id_token/compare/3.0.0...4.0.0
190
+ [3.0.0]: https://github.com/fschuindt/firebase_id_token/compare/2.5.2...3.0.0
191
+ [2.5.2]: https://github.com/fschuindt/firebase_id_token/compare/2.5.1...2.5.2
122
192
  [2.5.1]: https://github.com/fschuindt/firebase_id_token/compare/2.5.0...2.5.1
123
193
  [2.5.0]: https://github.com/fschuindt/firebase_id_token/compare/2.4.0...2.5.0
124
194
  [2.4.0]: https://github.com/fschuindt/firebase_id_token/compare/2.3.2...2.4.0
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 (pre-release)
1
+ # Ruby Firebase ID Token verifier
2
2
 
3
- ![Alt text](https://api.travis-ci.org/fschuindt/firebase_id_token.svg?branch=master)
4
- [![Code Climate](https://codeclimate.com/github/fschuindt/firebase_id_token/badges/gpa.svg)](https://codeclimate.com/github/fschuindt/firebase_id_token)
5
- [![Issue Count](https://codeclimate.com/github/fschuindt/firebase_id_token/badges/issue_count.svg)](https://codeclimate.com/github/fschuindt/firebase_id_token)
6
- [![Test Coverage](https://codeclimate.com/github/fschuindt/firebase_id_token/badges/coverage.svg)](https://codeclimate.com/github/fschuindt/firebase_id_token/coverage)
7
- [![Inline docs](http://inch-ci.org/github/fschuindt/firebase_id_token.svg?branch=master)](http://inch-ci.org/github/fschuindt/firebase_id_token)
3
+ [![Actions Status](https://github.com/fschuindt/firebase_id_token/actions/workflows/test.yml/badge.svg?branch=master)](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. It uses Redis 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.
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', '~> 2.5.2'
24
+ gem 'firebase_id_token', '~> 4.0.0'
33
25
  ```
34
26
  then
35
27
  ```
@@ -43,24 +35,29 @@ It's needed to set up your Firebase Project ID.
43
35
  If you are using Rails, this should probably go into `config/initializers/firebase_id_token.rb`.
44
36
  ```ruby
45
37
  FirebaseIdToken.configure do |config|
38
+ config.cache_store = ActiveSupport::Cache::RedisCacheStore.new
46
39
  config.project_ids = ['your-firebase-project-id']
47
40
  end
48
41
  ```
49
42
 
50
- `project_ids` must be a Array.
51
-
52
- *If you want to verify signatures from more than one Firebase project, just add more Project IDs to the list.*
53
-
54
- You can also pass a Redis instance to `config` if you are not using Redis defaults.
55
- In this case, you must have the gem `redis` in your `Gemfile`.
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)
56
45
  ```ruby
57
46
  FirebaseIdToken.configure do |config|
47
+ config.redis = Redis.new
58
48
  config.project_ids = ['your-firebase-project-id']
59
- config.redis = Redis.new(host: '10.0.1.1', port: 6380, db: 15)
60
49
  end
61
50
  ```
62
51
 
63
- Otherwise, it will use just `Redis.new` as the instance.
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`
58
+ - `project_ids` must be an Array.
59
+
60
+ *If you want to verify signatures from more than one Firebase project, just add more Project IDs to the list.*
64
61
 
65
62
  ## Usage
66
63
 
@@ -71,45 +68,47 @@ really helpful. But here is a complete guide:
71
68
 
72
69
  Before verifying tokens, you need to download Google's x509 certificates.
73
70
 
74
- To do it simply:
71
+ To do it, simply:
75
72
  ```ruby
76
73
  FirebaseIdToken::Certificates.request
77
74
  ```
78
75
 
79
- It will download the certificates and save it in Redis, but only if Redis certificates database is empty. To force download and override Redis database, use:
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:
80
77
  ```ruby
81
78
  FirebaseIdToken::Certificates.request!
82
79
  ```
83
80
 
84
- Google give us information about the certificates expiration time, it's used to set a Redis TTL (Time-To-Live) when saving it. By doing so, the certificates will be automatically deleted after its expiration.
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.
85
82
 
86
83
  #### Certificates Info
87
84
 
88
- Checks the presence of certificates in Redis database.
85
+ Checks the presence of certificates in the cache.
89
86
  ```ruby
90
87
  FirebaseIdToken::Certificates.present?
91
88
  => true
92
89
  ```
93
90
 
94
- How many seconds until the certificate's expiration.
91
+ How many seconds until the certificate's expiration. _NOTE_: Currently only functional when using Redis
95
92
  ```ruby
96
93
  FirebaseIdToken::Certificates.ttl
97
94
  => 22352
98
95
  ```
99
96
 
100
- Lists all certificates in a database.
97
+ Lists all certificates in the database.
101
98
  ```ruby
102
99
  FirebaseIdToken::Certificates.all
103
100
  => [{"ec8f292sd30224afac5c55540df66d1f999d" => <OpenSSL::X509::Certificate: [...]]
104
101
  ```
105
102
 
106
- Finds the respective certificate of a given Key ID.
103
+ Finds the respective certificate of a given Key ID (`kid`).
107
104
  ```ruby
108
105
  FirebaseIdToken::Certificates.find('ec8f292sd30224afac5c55540df66d1f999d')
109
106
  => <OpenSSL::X509::Certificate: subject=<OpenSSL::X509 [...]>
110
107
  ```
111
108
 
112
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.
113
112
 
114
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).
115
114
 
@@ -121,12 +120,12 @@ Create your task in `lib/tasks/firebase.rake`:
121
120
  ```ruby
122
121
  namespace :firebase do
123
122
  namespace :certificates do
124
- desc "Request Google's x509 certificates when Redis is empty"
123
+ desc "Request Google's x509 certificates when the cache is empty"
125
124
  task request: :environment do
126
125
  FirebaseIdToken::Certificates.request
127
126
  end
128
127
 
129
- desc "Request Google's x509 certificates and override Redis"
128
+ desc "Request Google's x509 certificates and override the cache"
130
129
  task force_request: :environment do
131
130
  FirebaseIdToken::Certificates.request!
132
131
  end
@@ -153,7 +152,7 @@ When developing, you should just run the task:
153
152
  $ rake firebase:certificates:request
154
153
  ```
155
154
 
156
- *And remember, you need the Redis server to be running.*
155
+ *You need Redis to be running if you're using RedisCacheStore*
157
156
 
158
157
  ### Verifying Tokens
159
158
 
@@ -173,30 +172,65 @@ FirebaseIdToken::Signature.verify('aaaaaa')
173
172
  => nil
174
173
  ```
175
174
 
176
- **WARNING:** If you try to verify a signature without any certificates in Redis database it will raise a `FirebaseIdToken::Exceptions::NoCertificatesError`.
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
+
190
+ #### WARNING!
191
+
192
+ ##### Expired tokens can point to long gone certificates
193
+
194
+ Notice that often when the token have expired, the Firebase certificate can be already missing from the Firebase servers. In these cases, `verify` will return `nil`.
195
+
196
+ If you want to take specific actions in such cases, here's a solution suggested by the user [cfanpnk](https://github.com/fschuindt/firebase_id_token/issues/29#issuecomment-751137511):
197
+
198
+ 1. Use `verify!` to raise an exception.
199
+ 2. Rescue `FirebaseIdToken::Exceptions::CertificateNotFound` and return `401`.
200
+ 3. The client app will refresh the token if expired.
201
+
202
+ More details [here](https://github.com/fschuindt/firebase_id_token/issues/29).
203
+
204
+ ##### Trying to verify tokens without downloaded certificates will raise an error
205
+
206
+ If you try to verify a signature without any certificates in the cache, it will raise a `FirebaseIdToken::Exceptions::NoCertificatesError`.
207
+
208
+ ##### "I keep on getting `nil` on `verify`"
209
+
210
+ Poorly synchronized clocks will sometimes make the server think the token's `iat` is on the future, which will render the token as invalid. Make sure your server's or development system's clock is correctly set. On macOS, some people reported success by unchecking and checking the "Set date and time automatically" configuration checkbox. See [here](https://github.com/fschuindt/firebase_id_token/issues/21#issuecomment-623133926).
177
211
 
178
212
  #### Payload Structure
179
213
 
180
214
  In case you need, here's a example of the payload structure from a Google login in JSON.
181
215
  ```json
182
- {
183
- "iss":"https://securetoken.google.com/firebase-id-token",
216
+ {
217
+ "iss":"https://securetoken.google.com/{{YOUR_FIREBASE_APP_ID}}",
184
218
  "name":"Ugly Bob",
185
219
  "picture":"https://someurl.com/photo.jpg",
186
- "aud":"firebase-id-token",
220
+ "aud":"{{YOUR_FIREBASE_APP_ID}}",
187
221
  "auth_time":1492981192,
188
222
  "user_id":"theUserID",
189
223
  "sub":"theUserID",
190
- "iat":1492981200,
191
- "exp":33029000017,
224
+ "iat":1492981200, // needs to be in the past
225
+ "exp":33029000017, // needs to be in the future
192
226
  "email":"uglybob@emailurl.com",
193
227
  "email_verified":true,
194
- "firebase":{
195
- "identities":{
196
- "google.com":[
228
+ "firebase":{
229
+ "identities":{
230
+ "google.com":[
197
231
  "1010101010101010101"
198
232
  ],
199
- "email":[
233
+ "email":[
200
234
  "uglybob@emailurl.com"
201
235
  ]
202
236
  },
@@ -206,34 +240,42 @@ In case you need, here's a example of the payload structure from a Google login
206
240
 
207
241
  ```
208
242
 
243
+ If you're using this snippet for testing, make sure you check the comments in it.
209
244
 
210
- ## Development
211
- The test suite can be run with `bundle exec rake rspec`
245
+ ## Testing
212
246
 
247
+ ```
248
+ bundle exec rake rspec
249
+ ```
213
250
 
214
- The test mode is prepared as preparation for the test.
251
+ ### Testing Mode
215
252
 
216
- `FirebaseIdToken.test!`
253
+ Just run:
254
+ ```
255
+ FirebaseIdToken.test!
256
+ ```
217
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.
218
263
 
219
- By using test mode, the following methods become available.
264
+ By using this mode, the following methods become available.
220
265
 
221
266
  ```ruby
222
267
  # RSA PRIVATE KEY
223
268
  FirebaseIdToken::Testing::Certificates.private_key
269
+
224
270
  # CERTIFICATE
225
271
  FirebaseIdToken::Testing::Certificates.certificate
226
272
  ```
227
273
 
228
- CERTIFICATE will always return the same value and will not communicate to google.
274
+ `certificate` will always return the same value. No external HTTP call is performed.
229
275
 
276
+ #### Example: Testing in Rails
230
277
 
231
- ### Example
232
- #### Rails test
233
-
234
- Describe the following in test_helper.rb etc.
235
-
236
- * test_helper
278
+ Describes the following in `test_helper.rb`.
237
279
 
238
280
  ```ruby
239
281
  class ActiveSupport::TestCase
@@ -243,7 +285,7 @@ class ActiveSupport::TestCase
243
285
  end
244
286
  ```
245
287
 
246
- * controller_test
288
+ Test example:
247
289
 
248
290
  ```ruby
249
291
  require 'test_helper'
@@ -255,7 +297,7 @@ module Api
255
297
  @routes = Engine.routes
256
298
  @user = users(:one)
257
299
  end
258
-
300
+
259
301
  def create_token(sub: nil)
260
302
  _payload = payload.merge({sub: sub})
261
303
  JWT.encode _payload, OpenSSL::PKey::RSA.new(FirebaseIdToken::Testing::Certificates.private_key), 'RS256'
@@ -274,7 +316,6 @@ module Api
274
316
  end
275
317
  ```
276
318
 
277
-
278
319
  ## License
279
320
 
280
321
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -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 Redis to store Google's x509 certificates and manage "\
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'
@@ -22,16 +22,18 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_development_dependency 'bundler', '~> 2.3', '>= 2.3.11'
26
- spec.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3'
27
- spec.add_development_dependency 'rspec', '~> 3.0'
28
- spec.add_development_dependency 'redcarpet', '~> 3.4', '>= 3.4.0'
29
- spec.add_development_dependency 'simplecov', '~> 0.14.1'
30
- spec.add_development_dependency 'codeclimate-test-reporter', '~> 1.0', '>= 1.0.0'
31
- spec.add_development_dependency 'pry', '~> 0.12.2'
25
+ spec.add_development_dependency 'bundler', '~> 2.4', '>= 2.4.13'
26
+ spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6'
27
+ spec.add_development_dependency 'rspec', '~> 3.12'
28
+ spec.add_development_dependency 'redcarpet', '~> 3.6'
29
+ spec.add_development_dependency 'simplecov', '~> 0.22.0'
30
+ spec.add_development_dependency 'simplecov_json_formatter', '~> 0.1.2'
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.add_runtime_dependency 'redis', '~> 4.0', '>= 4.0.1'
34
- spec.add_runtime_dependency 'redis-namespace', '~> 1.6', '>= 1.6.0'
35
- spec.add_dependency 'httparty', '~> 0.21', '>= 0.16.2'
36
- spec.add_runtime_dependency 'jwt', '~> 2.1', '>= 2.1.0'
35
+ spec.add_dependency 'httparty', '>= 0.21', '< 1.0'
36
+ spec.add_runtime_dependency 'jwt', '~> 2.7'
37
+ spec.add_runtime_dependency 'activesupport', '~> 7.0', '>= 7.0.4.3'
38
+ spec.add_runtime_dependency 'json', '~> 2.6', '>= 2.6.3'
37
39
  end
@@ -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
+