jwk-loader 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95cc2cfa71f70b65862e3be731568a8f935934ed814f45d282eed64c713e5e71
4
- data.tar.gz: 3d9d2ba513abc82c1a8f356287622829b784b3e4fa89746cf517c036301c905c
3
+ metadata.gz: cc35d8bbe5c77c12e387ee48e54543073527d3851148d6c313cd062e7da5de5d
4
+ data.tar.gz: 9ee9af3ebd760f4608265ba99a845427d5a047c0a5fe1ded70297f6f408a8838
5
5
  SHA512:
6
- metadata.gz: d8cb52c884a168816011173d453f0567c023f17e78f10badd5da583fbf4af6445723d434d7a77ee6d0f51bd645a6f2eaf4a9019646a0f568403a7286f4d03819
7
- data.tar.gz: 76b90c92b4af19b21aa1ddd07d9e7b300042f3521301094b00576aee491fe602e7e70e9a6dd2ed166a1b2f50b5f0ceb0f21bfee3bb8433ee965d4e4e08067f64
6
+ metadata.gz: 7c1da1b6d607b9006a3b8efe55bac39bf8d61db95d137db6705d9eb08ba5eb3340bb66fbad2b15945c4abdf8550143a75b8d34b80975c159baeb11728808c0e6
7
+ data.tar.gz: 2d14daadf7bf995b2324b3c9ccf46bc2cc67ecc4c00314e20dcc0d01a866dda2056d0b3f5e83a4df00d9f7c0ca02c9154714f2299481c41707a35f80b763cfc6
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,8 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.0] - 2023-12-28
4
+
5
+ - `jwk_loader/test` for convenience for testing without external dependencies. [#6](https://github.com/anakinj/jwk-loader/pull/6) ([@anakinj](https://github.com/anakinj))
6
+ - 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))
7
+
3
8
  ## [0.1.1] - 2022-08-26
4
9
 
5
- - make sure 'net/http' is required [#1](https://github.com/anakinj/jwk-loader/pull/2) ([@lukad](https://github.com/lukad)).
10
+ - make sure 'net/http' is required [#2](https://github.com/anakinj/jwk-loader/pull/2) ([@lukad](https://github.com/lukad)).
6
11
 
7
12
  ## [0.1.0] - 2022-07-06
8
13
 
data/Gemfile CHANGED
@@ -11,5 +11,3 @@ gem "rubocop", "~> 1.32.0"
11
11
  gem "simplecov"
12
12
  gem "vcr"
13
13
  gem "webmock"
14
-
15
- gem "jwt"
data/README.md CHANGED
@@ -16,16 +16,56 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
16
 
17
17
  ### Using as a jwks loader when decoding JWT tokens
18
18
 
19
- ```
19
+ ```ruby
20
20
  require "jwt"
21
21
  require "jwk-loader"
22
22
 
23
23
  JWT.decode(token, nil, true, algorithm: "RS512", jwks: JwkLoader.for_uri(uri: "https://url/to/public/jwks") )
24
24
  ```
25
25
 
26
+ ### Testing endpoints protected by JWT tokens
27
+
28
+ When testing HTTP endpoints protected by asymmetric JWT keys the mechanism in `jwk_loader/test` can be used to simplify the process.
29
+
30
+ ```ruby
31
+ require 'jwk_loader/test'
32
+
33
+ RSpec.describe 'GET /protected' do
34
+ include JwkLoader::Test
35
+
36
+ context 'when called with a valid token' do
37
+ let(:token) { sign_test_token(token_payload: { user_id: 'user' }, jwk_endpoint: 'https://url/to/public/jwks') }
38
+ subject(:response) { get('/protected', { 'HTTP_AUTHORIZATION' => "Bearer #{token}" }) }
39
+
40
+ it 'is a success' do
41
+ expect(response.status).to eq(200)
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
26
47
  ### Configuring the gem
27
48
 
28
- TODO: Will probably be implemented at some point
49
+ ```ruby
50
+ require "jwt-loader"
51
+
52
+ JwkLoader.configure do |config|
53
+ config[:cache] = YetAnotherCache.new
54
+ config[:cache_grace_period] = 999
55
+ end
56
+ ```
57
+
58
+ or in alternative
59
+
60
+ ```ruby
61
+ require "jwt-loader"
62
+
63
+ JwkLoader.configure do |config|
64
+ config.cache = YetAnotherCache.new
65
+ config.cache_grace_period = 999
66
+ end
67
+ ```
68
+
29
69
 
30
70
  ## Development
31
71
 
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.0.0"
5
5
  end
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.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joakim Antman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-26 00:00:00.000000000 Z
11
+ date: 2023-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
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'
27
41
  description:
28
42
  email:
29
43
  - antmanj@gmail.com
@@ -41,10 +55,12 @@ files:
41
55
  - Rakefile
42
56
  - jwk-loader.gemspec
43
57
  - lib/jwk-loader.rb
58
+ - lib/jwk_loader/config/config.rb
44
59
  - lib/jwk_loader/error.rb
45
60
  - lib/jwk_loader/jwks.rb
46
61
  - lib/jwk_loader/jwks_uri_provider.rb
47
62
  - lib/jwk_loader/memory_cache.rb
63
+ - lib/jwk_loader/test.rb
48
64
  - lib/jwk_loader/version.rb
49
65
  homepage: https://github.com/anakinj/jwk-loader
50
66
  licenses:
@@ -53,7 +69,7 @@ metadata:
53
69
  allowed_push_host: https://rubygems.org
54
70
  homepage_uri: https://github.com/anakinj/jwk-loader
55
71
  source_code_uri: https://github.com/anakinj/jwk-loader
56
- changelog_uri: https://github.com/anakinj/jwk-loader/blob/0.1.1/CHANGELOG.md
72
+ changelog_uri: https://github.com/anakinj/jwk-loader/blob/1.0.0/CHANGELOG.md
57
73
  rubygems_mfa_required: 'true'
58
74
  post_install_message:
59
75
  rdoc_options: []