jwk-loader 0.1.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95cc2cfa71f70b65862e3be731568a8f935934ed814f45d282eed64c713e5e71
4
- data.tar.gz: 3d9d2ba513abc82c1a8f356287622829b784b3e4fa89746cf517c036301c905c
3
+ metadata.gz: e77eb1ce66789b0d37f9d200ae80d5af43b7de7745f00bfe0548b87301120c79
4
+ data.tar.gz: 5d71e2e2cc5f4caa96b076c7528e03e6e257e306176f935e2683d3781cb1f8db
5
5
  SHA512:
6
- metadata.gz: d8cb52c884a168816011173d453f0567c023f17e78f10badd5da583fbf4af6445723d434d7a77ee6d0f51bd645a6f2eaf4a9019646a0f568403a7286f4d03819
7
- data.tar.gz: 76b90c92b4af19b21aa1ddd07d9e7b300042f3521301094b00576aee491fe602e7e70e9a6dd2ed166a1b2f50b5f0ceb0f21bfee3bb8433ee965d4e4e08067f64
6
+ metadata.gz: c3d808d24e6c331db89d5cd8cad4bce5ce94e36e9d8ad99c2a18588bcb8043416b04239da6a0b899d4c193591e6736cee0198358bbb5abc163b007251e445397
7
+ data.tar.gz: 025ce184c452c2d8dcf632ee823d98e215a80c011db6d75bd253d44ad4a66e8a4f95dd1eb013fd2fdb51a6dbc1540ea59fbaef931e8239a8b18951da327b86f1
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.1.0"
3
+ }
data/.rubocop.yml CHANGED
@@ -17,6 +17,7 @@ Layout/LineLength:
17
17
  Naming/FileName:
18
18
  Exclude:
19
19
  - 'lib/jwk-loader.rb'
20
+ - 'spec/jwk-loader_spec.rb'
20
21
 
21
22
  Metrics/BlockLength:
22
23
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,9 +1,21 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.1.1] - 2022-08-26
3
+ ## [1.1.0](https://github.com/anakinj/jwk-loader/compare/v1.0.0...v1.1.0) (2024-08-10)
4
4
 
5
- - make sure 'net/http' is required [#1](https://github.com/anakinj/jwk-loader/pull/2) ([@lukad](https://github.com/lukad)).
6
5
 
7
- ## [0.1.0] - 2022-07-06
6
+ ### Features
8
7
 
9
- - Initial release
8
+ * Official support for Ruby 3.2 and 3.3 ([2f6079f](https://github.com/anakinj/jwk-loader/commit/2f6079fd490a4918524974ffb1d897abbf875787))
9
+
10
+ ## [1.0.0](https://github.com/anakinj/jwk-loader/compare/v0.1.1...v1.0.0) (2023-12-28)
11
+
12
+ ### Features
13
+
14
+ - `jwk_loader/test` for convenience for testing without external dependencies. [#6](https://github.com/anakinj/jwk-loader/pull/6) ([@anakinj](https://github.com/anakinj))
15
+ - Serialize the cached key sets into `JWT::JWK:Set` to avoid generating OpenSSL PKeys for each time the keys are used. [#6](https://github.com/anakinj/jwk-loader/pull/6) ([@anakinj](https://github.com/anakinj))
16
+
17
+ ## [1.0.0](https://github.com/anakinj/jwk-loader/compare/v0.1.0...v0.1.1) (2022-08-26)
18
+
19
+ ### Fixes
20
+
21
+ - make sure 'net/http' is required [#2](https://github.com/anakinj/jwk-loader/pull/2) ([@lukad](https://github.com/lukad)).
data/Gemfile CHANGED
@@ -2,14 +2,11 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in jwk-loader.gemspec
6
5
  gemspec
7
6
 
8
- gem "rake", "~> 13.0"
9
- gem "rspec", "~> 3.0"
10
- gem "rubocop", "~> 1.32.0"
7
+ gem "rake"
8
+ gem "rspec"
9
+ gem "rubocop"
11
10
  gem "simplecov"
12
11
  gem "vcr"
13
12
  gem "webmock"
14
-
15
- gem "jwt"
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- # JwkLoader
1
+ # jwk-loader
2
2
 
3
- This gem can be used in combination with the [jwt](https://rubygems.org/gems/jwt) gem as the mechanism to load and cache the JWKs in the application.
3
+ [![Gem Version](https://badge.fury.io/rb/jwk-loader.svg)](https://badge.fury.io/rb/jwk-loader)
4
+ [![Build status](https://github.com/anakinj/jwk-loader/actions/workflows/test.yml/badge.svg)](https://github.com/anakinj/jwk-loader/actions/workflows/test.yml)
5
+
6
+ This gem can be used in combination with the [ruby-jwt](https://rubygems.org/gems/jwt) gem as the mechanism to load and cache the JWKs.
4
7
 
5
8
  ## Installation
6
9
 
@@ -16,16 +19,56 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
19
 
17
20
  ### Using as a jwks loader when decoding JWT tokens
18
21
 
19
- ```
22
+ ```ruby
20
23
  require "jwt"
21
24
  require "jwk-loader"
22
25
 
23
26
  JWT.decode(token, nil, true, algorithm: "RS512", jwks: JwkLoader.for_uri(uri: "https://url/to/public/jwks") )
24
27
  ```
25
28
 
29
+ ### Testing endpoints protected by JWT tokens
30
+
31
+ When testing HTTP endpoints protected by asymmetric JWT keys the mechanism in `jwk_loader/test` can be used to simplify the process.
32
+
33
+ ```ruby
34
+ require 'jwk_loader/test'
35
+
36
+ RSpec.describe 'GET /protected' do
37
+ include JwkLoader::Test
38
+
39
+ context 'when called with a valid token' do
40
+ let(:token) { sign_test_token(token_payload: { user_id: "user" }, jwk_endpoint: "https://url/to/public/jwks") }
41
+ subject(:response) { get('/protected', { 'HTTP_AUTHORIZATION' => "Bearer #{token}" }) }
42
+
43
+ it 'is a success' do
44
+ expect(response.status).to eq(200)
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
26
50
  ### Configuring the gem
27
51
 
28
- TODO: Will probably be implemented at some point
52
+ ```ruby
53
+ require "jwt-loader"
54
+
55
+ JwkLoader.configure do |config|
56
+ config[:cache] = YetAnotherCache.new
57
+ config[:cache_grace_period] = 999
58
+ end
59
+ ```
60
+
61
+ or in alternative
62
+
63
+ ```ruby
64
+ require "jwt-loader"
65
+
66
+ JwkLoader.configure do |config|
67
+ config.cache = YetAnotherCache.new
68
+ config.cache_grace_period = 999
69
+ end
70
+ ```
71
+
29
72
 
30
73
  ## Development
31
74
 
data/jwk-loader.gemspec CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ["lib"]
34
34
  spec.add_dependency "concurrent-ruby"
35
+ spec.add_dependency "jwt", "~> 2.6"
35
36
  end
data/lib/jwk-loader.rb CHANGED
@@ -5,9 +5,37 @@ require_relative "jwk_loader/jwks"
5
5
  require_relative "jwk_loader/jwks_uri_provider"
6
6
  require_relative "jwk_loader/memory_cache"
7
7
  require_relative "jwk_loader/error"
8
+ require_relative "jwk_loader/config/config"
8
9
 
9
10
  module JwkLoader
10
- def self.for_uri(**options)
11
- JwksUriProvider.new(**options)
11
+ class << self
12
+ def for_uri(**options)
13
+ options[:cache] ||= config[:cache]
14
+ options[:cache_grace_period] ||= config[:cache_grace_period]
15
+ JwksUriProvider.new(**options)
16
+ end
17
+
18
+ def cache
19
+ config[:cache]
20
+ end
21
+
22
+ def configure
23
+ yield config
24
+ end
25
+
26
+ def config
27
+ @config ||= JwkLoader::Config.new.tap do |cfg|
28
+ cfg[:cache] = MemoryCache.new
29
+ cfg[:cache_grace_period] = 900
30
+ end
31
+ end
32
+
33
+ def reset!
34
+ @config = nil
35
+ end
36
+
37
+ def memory_store
38
+ @memory_store ||= MemoryCache.new
39
+ end
12
40
  end
13
41
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JwkLoader
4
+ class Config
5
+ class ConfigurationNotFound < JwkLoader::Error
6
+ def initialize(key)
7
+ super("Configuration for #{key} not available")
8
+ end
9
+ end
10
+
11
+ def []=(key, value)
12
+ registry[key] = value
13
+ end
14
+
15
+ def [](key)
16
+ registry[key] || (raise ConfigurationNotFound, key)
17
+ end
18
+
19
+ def method_missing(name, *args)
20
+ return send(:[]=, name.to_s[0..-2].to_sym, *args) if name.to_s.end_with?("=")
21
+
22
+ send(:[], name, *args)
23
+ end
24
+
25
+ def respond_to_missing?(_name, _include_private)
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ def registry
32
+ @registry ||= {}
33
+ end
34
+ end
35
+ end
@@ -14,6 +14,10 @@ module JwkLoader
14
14
  from_json(response.body)
15
15
  end
16
16
 
17
+ def from_memory(uri)
18
+ JwkLoader.memory_store.fetch(uri)
19
+ end
20
+
17
21
  def from_json(jwks_json)
18
22
  JSON.parse(jwks_json, symbolize_names: true)
19
23
  end
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JwkLoader
4
- def self.cache
5
- @cache ||= MemoryCache.new
6
- end
7
-
8
4
  class JwksUriProvider
9
5
  attr_reader :uri, :cache, :cache_grace_period
10
6
 
11
- def initialize(uri:, cache: JwkLoader.cache, cache_grace_period: 900)
7
+ def initialize(uri:, cache:, cache_grace_period:)
12
8
  @uri = uri
13
- @cache = cache || MemoryCache.new
9
+ @cache = cache
14
10
  @cache_grace_period = cache_grace_period
15
11
  end
16
12
 
@@ -22,7 +18,7 @@ module JwkLoader
22
18
  private
23
19
 
24
20
  def jwks
25
- from_cache || from_uri
21
+ from_cache || from_memory || from_uri
26
22
  end
27
23
 
28
24
  def invalidate_cache!
@@ -35,12 +31,17 @@ module JwkLoader
35
31
  cache_entry&.fetch(:jwks)
36
32
  end
37
33
 
34
+ def from_memory
35
+ JwkLoader::Jwks.from_memory(uri)
36
+ end
37
+
38
38
  def cache_entry
39
39
  cache.fetch(uri)
40
40
  end
41
41
 
42
42
  def from_uri
43
- JwkLoader::Jwks.from_uri(uri).tap do |jwks|
43
+ data = JwkLoader::Jwks.from_uri(uri)
44
+ JWT::JWK::Set.new(data).tap do |jwks|
44
45
  cache.store(uri, jwks: jwks, fetched_at: Time.now)
45
46
  end
46
47
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JwkLoader
4
+ module Test
5
+ def generate_signing_key(algorithm:)
6
+ case algorithm
7
+ when "RS256", "RS384", "RS512"
8
+ OpenSSL::PKey::RSA.new(2048)
9
+ when "ES256"
10
+ OpenSSL::PKey::EC.generate("prime256v1")
11
+ else
12
+ raise "Unsupported algorithm: #{algorithm}"
13
+ end
14
+ end
15
+
16
+ def test_signing_key_for(jwk_endpoint:, algorithm: "RS512")
17
+ key_set = JwkLoader.memory_store.fetch(jwk_endpoint)
18
+
19
+ if key_set.nil?
20
+ key_set = JWT::JWK::Set.new([generate_signing_key(algorithm: algorithm)])
21
+ JwkLoader.memory_store.store(jwk_endpoint, key_set)
22
+ end
23
+
24
+ key_set.first
25
+ end
26
+
27
+ def sign_test_token(token_payload:, jwk_endpoint:, algorithm: "RS512")
28
+ key = test_signing_key_for(jwk_endpoint: jwk_endpoint, algorithm: algorithm)
29
+ JWT.encode(token_payload, key.signing_key, algorithm, kid: key[:kid])
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JwkLoader
4
- VERSION = "0.1.1"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -0,0 +1,9 @@
1
+ {
2
+ "release-type": "ruby",
3
+ "include-v-in-tag": true,
4
+ "packages": {
5
+ ".": {
6
+ "version-file": "lib/jwk_loader/version.rb"
7
+ }
8
+ }
9
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwk-loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joakim Antman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-26 00:00:00.000000000 Z
11
+ date: 2024-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -24,13 +24,28 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description:
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
41
+ description:
28
42
  email:
29
43
  - antmanj@gmail.com
30
44
  executables: []
31
45
  extensions: []
32
46
  extra_rdoc_files: []
33
47
  files:
48
+ - ".release-please-manifest.json"
34
49
  - ".rspec"
35
50
  - ".rubocop.yml"
36
51
  - CHANGELOG.md
@@ -41,11 +56,14 @@ files:
41
56
  - Rakefile
42
57
  - jwk-loader.gemspec
43
58
  - lib/jwk-loader.rb
59
+ - lib/jwk_loader/config/config.rb
44
60
  - lib/jwk_loader/error.rb
45
61
  - lib/jwk_loader/jwks.rb
46
62
  - lib/jwk_loader/jwks_uri_provider.rb
47
63
  - lib/jwk_loader/memory_cache.rb
64
+ - lib/jwk_loader/test.rb
48
65
  - lib/jwk_loader/version.rb
66
+ - release-please-config.json
49
67
  homepage: https://github.com/anakinj/jwk-loader
50
68
  licenses:
51
69
  - MIT
@@ -53,9 +71,9 @@ metadata:
53
71
  allowed_push_host: https://rubygems.org
54
72
  homepage_uri: https://github.com/anakinj/jwk-loader
55
73
  source_code_uri: https://github.com/anakinj/jwk-loader
56
- changelog_uri: https://github.com/anakinj/jwk-loader/blob/0.1.1/CHANGELOG.md
74
+ changelog_uri: https://github.com/anakinj/jwk-loader/blob/1.1.0/CHANGELOG.md
57
75
  rubygems_mfa_required: 'true'
58
- post_install_message:
76
+ post_install_message:
59
77
  rdoc_options: []
60
78
  require_paths:
61
79
  - lib
@@ -70,8 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
88
  - !ruby/object:Gem::Version
71
89
  version: '0'
72
90
  requirements: []
73
- rubygems_version: 3.3.7
74
- signing_key:
91
+ rubygems_version: 3.5.11
92
+ signing_key:
75
93
  specification_version: 4
76
94
  summary: Tooling for handling JWK loading, parsing and caching
77
95
  test_files: []