Hyperwallet requires requests and responses to be:
This setup ensures:
// Load jsrsasign (offline)
(function () {
if (typeof KJUR !== 'undefined') return;
const lib = pm.collectionVariables.get('JSRSASIGN_LIB');
if (!lib) throw new Error('JSRSASIGN_LIB missing');
eval(lib);
})();
// Sign + Encrypt Request
(function signAndEncrypt() {
if (!pm.request.body || !pm.request.body.raw) return;
const now = Math.floor(Date.now() / 1000);
const privateJWK = JSON.parse(pm.environment.get('PRIVATE_JWK'));
const publicJWK = JSON.parse(pm.environment.get('PUBLIC_JWK'));
const payload = JSON.parse(pm.request.body.raw);
const jwtPayload = {
...payload,
iss: pm.environment.get('EXPECTED_ISS'),
aud: pm.environment.get('EXPECTED_AUD'),
iat: now,
exp: now + 3600,
jti: KJUR.crypto.Util.getRandomHexOfNbytes(16)
};
const signedJWT = KJUR.jws.JWS.sign(
'RS256',
JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: privateJWK.kid }),
JSON.stringify(jwtPayload),
KEYUTIL.getKey(privateJWK)
);
const encryptedJWT = KJUR.crypto.Cipher.encrypt(
signedJWT,
KEYUTIL.getKey(publicJWK),
'RSAOAEP256'
);
pm.request.body.raw = JSON.stringify({ request: encryptedJWT });
})();
// Decrypt + Verify Response
pm.test("Encrypted response present", () => {
pm.expect(pm.response.text()).to.be.a('string').and.not.empty;
});
const privateJWK = JSON.parse(pm.environment.get('PRIVATE_JWK'));
const serverJWKS = JSON.parse(pm.environment.get('SERVER_JWKS'));
const encrypted = pm.response.text();
let decryptedJWS;
pm.test('JWE decrypted successfully', () => {
decryptedJWS = KJUR.crypto.Cipher.decrypt(encrypted, KEYUTIL.getKey(privateJWK), 'RSAOAEP256');
pm.expect(decryptedJWS).to.be.a('string');
});
const header = JSON.parse(KJUR.crypto.Util.b64utoutf8(decryptedJWS.split('.')[0]));
pm.test('JWS header contains kid', () => { pm.expect(header.kid).to.exist; });
const serverKey = serverJWKS.keys.find(k => k.use === 'sig' && k.kid === header.kid);
pm.test('Hyperwallet signing key found', () => { pm.expect(serverKey).to.exist; });
const signatureValid = KJUR.jws.JWS.verify(decryptedJWS, KEYUTIL.getKey(serverKey), ['RS256']);
pm.test('Response signature valid', () => { pm.expect(signatureValid).to.be.true; });
const payload = JSON.parse(KJUR.crypto.Util.b64utoutf8(decryptedJWS.split('.')[1]));
const now = Math.floor(Date.now() / 1000);
pm.test('iss valid', () => { pm.expect(payload.iss).to.eql(pm.environment.get('EXPECTED_ISS')); });
pm.test('aud valid', () => {
const expectedAud = pm.environment.get('EXPECTED_AUD');
if (Array.isArray(payload.aud)) { pm.expect(payload.aud).to.include(expectedAud); }
else { pm.expect(payload.aud).to.eql(expectedAud); }
});
pm.test('token not expired', () => { pm.expect(payload.exp).to.be.above(now); });
pm.environment.set('DECRYPTED_RESPONSE', JSON.stringify(payload));
if (payload && payload.links && Array.isArray(payload.links)) {
payload.links.forEach(link => {
if (!link.rel || !link.href) return;
if (link.rel === 'self') {
const token = link.href.split('/').pop();
pm.environment.set('LAST_RESOURCE_TOKEN', token);
}
pm.environment.set(`HATEOAS_${link.rel.toUpperCase()}`, link.href);
});
}
npm install -g newman
newman run postman/hyperwallet.postman_collection.json \
-e postman/production.postman_environment.json \
--env-var PRIVATE_JWK="$PRIVATE_JWK" \
--env-var PUBLIC_JWK="$PUBLIC_JWK" \
--env-var SERVER_JWKS="$SERVER_JWKS" \
--env-var EXPECTED_ISS="$EXPECTED_ISS" \
--env-var EXPECTED_AUD="$EXPECTED_AUD" \
--timeout-request 60000 \
--reporters cli,json \
--reporter-json-export newman-report.json