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 +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +6 -1
- data/Gemfile +0 -2
- data/README.md +42 -2
- 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 +4 -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,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 [#
|
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
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
|
|
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
@@ -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: 0.
|
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/0.
|
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: []
|