jwk-loader 0.1.0 → 1.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/.rubocop.yml +1 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -2
- data/README.md +43 -3
- data/jwk-loader.gemspec +1 -0
- data/lib/jwk-loader.rb +30 -2
- data/lib/jwk_loader/config/config.rb +35 -0
- data/lib/jwk_loader/jwks.rb +5 -0
- data/lib/jwk_loader/jwks_uri_provider.rb +9 -8
- data/lib/jwk_loader/test.rb +32 -0
- data/lib/jwk_loader/version.rb +1 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc35d8bbe5c77c12e387ee48e54543073527d3851148d6c313cd062e7da5de5d
|
4
|
+
data.tar.gz: 9ee9af3ebd760f4608265ba99a845427d5a047c0a5fe1ded70297f6f408a8838
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c1da1b6d607b9006a3b8efe55bac39bf8d61db95d137db6705d9eb08ba5eb3340bb66fbad2b15945c4abdf8550143a75b8d34b80975c159baeb11728808c0e6
|
7
|
+
data.tar.gz: 2d14daadf7bf995b2324b3c9ccf46bc2cc67ecc4c00314e20dcc0d01a866dda2056d0b3f5e83a4df00d9f7c0ca02c9154714f2299481c41707a35f80b763cfc6
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
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
|
+
|
8
|
+
## [0.1.1] - 2022-08-26
|
9
|
+
|
10
|
+
- make sure 'net/http' is required [#2](https://github.com/anakinj/jwk-loader/pull/2) ([@lukad](https://github.com/lukad)).
|
11
|
+
|
3
12
|
## [0.1.0] - 2022-07-06
|
4
13
|
|
5
14
|
- Initial release
|
data/Gemfile
CHANGED
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
|
-
|
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
|
|
@@ -33,7 +73,7 @@ After checking out the repo, run `bundle install` to install dependencies. Then,
|
|
33
73
|
|
34
74
|
## Contributing
|
35
75
|
|
36
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
76
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/anakinj/jwk-loader. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/anakinj/jwk-loader/blob/main/CODE_OF_CONDUCT.md).
|
37
77
|
|
38
78
|
## License
|
39
79
|
|
data/jwk-loader.gemspec
CHANGED
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
|
-
|
11
|
-
|
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
|
data/lib/jwk_loader/jwks.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "uri"
|
4
4
|
require "json"
|
5
|
+
require "net/http"
|
5
6
|
|
6
7
|
module JwkLoader
|
7
8
|
module Jwks
|
@@ -13,6 +14,10 @@ module JwkLoader
|
|
13
14
|
from_json(response.body)
|
14
15
|
end
|
15
16
|
|
17
|
+
def from_memory(uri)
|
18
|
+
JwkLoader.memory_store.fetch(uri)
|
19
|
+
end
|
20
|
+
|
16
21
|
def from_json(jwks_json)
|
17
22
|
JSON.parse(jwks_json, symbolize_names: true)
|
18
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
|
7
|
+
def initialize(uri:, cache:, cache_grace_period:)
|
12
8
|
@uri = uri
|
13
|
-
@cache = cache
|
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)
|
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
|
data/lib/jwk_loader/version.rb
CHANGED
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:
|
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:
|
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/
|
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: []
|