Skip to content

Commit

Permalink
96001 EPS JWT Token Wrapper (#19296)
Browse files Browse the repository at this point in the history
* 96001 add JWT wrapper class for EPS

* 96001 add settings

* 96001 update

* 96001 add specs
  • Loading branch information
randomsync authored Nov 13, 2024
1 parent 95b9926 commit 367d277
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
11 changes: 11 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1846,3 +1846,14 @@ schema_contract:

form_mock_ae_design_patterns:
prefill: true

vaos:
eps:
key_path: /fake/client/key/path
mock: false
client_id: "fake_client_id"
kid: "fake_kid"
audience_claim_url: "https://login.wellhive.com/oauth2/default/v1/token"
access_token_url: "https://login.wellhive.com/oauth2/default/v1/token"
api_url: "https://api.wellhive.com/care-navigation/v1"
scopes: "care-nav"
43 changes: 43 additions & 0 deletions modules/vaos/app/services/eps/jwt_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Eps
class JwtWrapper
SIGNING_ALGORITHM = 'RS512'

attr_reader :expiration, :settings

delegate :key_path, :client_id, :kid, :audience_claim_url, to: :settings
def initialize
@settings = Settings.vaos.eps
@expiration = 5
end

def sign_assertion
JWT.encode(claims, rsa_key, SIGNING_ALGORITHM, jwt_headers)
end

def rsa_key
@rsa_key ||= OpenSSL::PKey::RSA.new(File.read(key_path))
end

private

def claims
{
iss: client_id,
sub: client_id,
aud: audience_claim_url,
iat: Time.zone.now.to_i,
exp: expiration.minutes.from_now.to_i
}
end

def jwt_headers
{
kid: kid,
typ: 'JWT',
alg: SIGNING_ALGORITHM
}
end
end
end
88 changes: 88 additions & 0 deletions modules/vaos/spec/services/eps/jwt_wrapper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require 'rails_helper'

describe Eps::JwtWrapper do
subject { described_class.new }

let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) }
let(:settings) do
OpenStruct.new({
key_path: '/path/to/key.pem',
client_id: 'test_client',
kid: 'test_kid',
audience_claim_url: 'https://test.example.com'
})
end

before do
allow(Settings.vaos).to receive(:eps).and_return(settings)
allow(File).to receive(:read).with('/path/to/key.pem').and_return(rsa_key)
end

describe 'constants' do
it 'has a SIGNING_ALGORITHM' do
expect(described_class::SIGNING_ALGORITHM).to eq('RS512')
end
end

describe '#initialize' do
it 'sets default expiration to 5 minutes' do
expect(subject.expiration).to eq(5)
end

it 'initializes settings' do
expect(subject.settings).to eq(settings)
end
end

describe '#sign_assertion' do
let(:jwt_token) { subject.sign_assertion }
let(:decoded_token) do
JWT.decode(
jwt_token,
rsa_key.public_key,
true,
algorithm: described_class::SIGNING_ALGORITHM
)
end

it 'returns a valid JWT token' do
expect { decoded_token }.not_to raise_error
end

it 'includes the correct headers' do
headers = decoded_token.last
expect(headers['kid']).to eq('test_kid')
expect(headers['typ']).to eq('JWT')
expect(headers['alg']).to eq('RS512')
end

it 'includes the correct claims' do
claims = decoded_token.first
expect(claims['iss']).to eq('test_client')
expect(claims['sub']).to eq('test_client')
expect(claims['aud']).to eq('https://test.example.com')
expect(claims['iat']).to be_within(5).of(Time.zone.now.to_i)
expect(claims['exp']).to be_within(5).of(5.minutes.from_now.to_i)
end
end

describe '#rsa_key' do
it 'reads the key from the specified path' do
expect(File).to receive(:read).with('/path/to/key.pem').once.and_return(rsa_key)
2.times { subject.rsa_key } # Call twice to test memoization
end

it 'returns an RSA key instance' do
expect(subject.rsa_key).to be_a(OpenSSL::PKey::RSA)
end

it 'memoizes the RSA key' do
first_call = subject.rsa_key
second_call = subject.rsa_key
expect(first_call).to eq(second_call)
expect(File).to have_received(:read).once
end
end
end

0 comments on commit 367d277

Please sign in to comment.