added jsonwebtoken as inbuilt library (#5535)

* added jsonwebtoken as inbuilt library

* removed bundling

* handle callback in quickjs

* chore: tests folder restructure

* chore: lint fix

---------

Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
anusree-bruno
2025-10-22 14:57:19 +05:30
committed by GitHub
parent 986d5b0b2a
commit c997b91698
21 changed files with 1077 additions and 2 deletions

View File

@@ -27,6 +27,7 @@
"cheerio": "^1.0.0",
"crypto-js": "^4.2.0",
"json-query": "^2.2.2",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"nanoid": "3.3.8",

View File

@@ -33,6 +33,7 @@ const NodeVault = require('node-vault');
const xml2js = require('xml2js');
const cheerio = require('cheerio');
const tv4 = require('tv4');
const jsonwebtoken = require('jsonwebtoken');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
class ScriptRuntime {
@@ -185,6 +186,7 @@ class ScriptRuntime {
'node-fetch': fetch,
'crypto-js': CryptoJS,
xml2js: xml2js,
jsonwebtoken,
cheerio,
tv4,
...whitelistedModules,
@@ -354,6 +356,7 @@ class ScriptRuntime {
'node-fetch': fetch,
'crypto-js': CryptoJS,
'xml2js': xml2js,
jsonwebtoken,
cheerio,
tv4,
...whitelistedModules,

View File

@@ -35,6 +35,7 @@ const NodeVault = require('node-vault');
const xml2js = require('xml2js');
const cheerio = require('cheerio');
const tv4 = require('tv4');
const jsonwebtoken = require('jsonwebtoken');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
class TestRuntime {
@@ -103,7 +104,8 @@ class TestRuntime {
res,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults
__brunoTestResults: __brunoTestResults,
jwt: jsonwebtoken
};
if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -176,6 +178,7 @@ class TestRuntime {
'xml2js': xml2js,
cheerio,
tv4,
'jsonwebtoken': jsonwebtoken,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault

View File

@@ -2,12 +2,14 @@ const addAxiosShimToContext = require('./axios');
const addNanoidShimToContext = require('./nanoid');
const addPathShimToContext = require('./path');
const addUuidShimToContext = require('./uuid');
const addJwtShimToContext = require('./jwt');
const addLibraryShimsToContext = async (vm) => {
await addNanoidShimToContext(vm);
await addAxiosShimToContext(vm);
await addUuidShimToContext(vm);
await addPathShimToContext(vm);
await addJwtShimToContext(vm);
};
module.exports = addLibraryShimsToContext;

View File

@@ -0,0 +1,181 @@
const jwt = require('jsonwebtoken');
const { marshallToVm, invokeFunction } = require('../../utils');
const addJwtShimToContext = async (vm) => {
// --- sign ---
const _jwtSign = vm.newFunction('sign', function (payload, secret, options, callback) {
const nativePayload = vm.dump(payload);
const nativeSecret = vm.dump(secret);
let nativeOptions;
let callbackHandle = callback;
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
if (optionsType === 'function') {
callbackHandle = options;
nativeOptions = undefined;
} else if (optionsType === 'object' && options !== null) {
nativeOptions = vm.dump(options);
}
// If a callback is provided
if (callbackHandle && vm.typeof(callbackHandle) === 'function') {
let tokenResult;
let hostError;
try {
tokenResult = nativeOptions
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
: jwt.sign(nativePayload, nativeSecret);
} catch (err) {
hostError = err;
}
try {
if (hostError) {
const errVm = vm.newError(hostError.message || String(hostError));
invokeFunction(vm, callbackHandle, [errVm, vm.undefined])
.catch((e) => {
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
})
.finally(() => {
errVm.dispose();
callbackHandle.dispose();
});
} else {
const tokenVm = marshallToVm(String(tokenResult), vm);
invokeFunction(vm, callbackHandle, [vm.null, tokenVm])
.catch((e) => {
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
})
.finally(() => {
tokenVm.dispose();
callbackHandle.dispose();
});
}
} catch (e) {
console.warn('[JWT SHIM][sign.cb] unexpected error:', e);
callbackHandle.dispose();
}
return vm.undefined;
}
try {
const token = nativeOptions
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
: jwt.sign(nativePayload, nativeSecret);
return marshallToVm(token, vm);
} catch (err) {
throw vm.newError(err.message || String(err));
}
});
vm.setProp(vm.global, '__bruno__jwt__sign', _jwtSign);
_jwtSign.dispose();
// --- verify ---
const _jwtVerify = vm.newFunction('verify', function (token, secret, options, callback) {
const nativeToken = vm.dump(token);
const nativeSecret = vm.dump(secret);
let nativeOptions;
let actualCallback = callback;
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
if (optionsType === 'function') {
actualCallback = options;
nativeOptions = undefined;
} else if (optionsType === 'object' && options !== null) {
nativeOptions = vm.dump(options);
}
if (actualCallback && vm.typeof(actualCallback) === 'function') {
let decodedResult;
let hostError;
try {
decodedResult = nativeOptions
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
: jwt.verify(nativeToken, nativeSecret);
} catch (err) {
hostError = err;
}
try {
if (hostError) {
const vmErr = vm.newError(hostError.message || String(hostError));
invokeFunction(vm, actualCallback, [vmErr, vm.undefined])
.catch((e) => {
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
})
.finally(() => {
vmErr.dispose();
actualCallback.dispose();
});
} else {
const vmNull = vm.null;
const vmDecoded = marshallToVm(decodedResult, vm);
invokeFunction(vm, actualCallback, [vmNull, vmDecoded])
.catch((e) => {
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
})
.finally(() => {
vmDecoded.dispose();
actualCallback.dispose();
});
}
} catch (e) {
console.warn('[JWT SHIM][verify.cb] unexpected error:', e);
actualCallback.dispose();
}
return vm.undefined;
}
try {
const decoded = nativeOptions
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
: jwt.verify(nativeToken, nativeSecret);
return marshallToVm(decoded, vm);
} catch (err) {
throw vm.newError(err.message || String(err));
}
});
vm.setProp(vm.global, '__bruno__jwt__verify', _jwtVerify);
_jwtVerify.dispose();
// --- decode ---
const _jwtDecode = vm.newFunction('decode', function (token, options) {
const nativeToken = vm.dump(token);
let nativeOptions;
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
if (optionsType === 'object' && options !== null) {
nativeOptions = vm.dump(options);
}
try {
const decoded = nativeOptions
? jwt.decode(nativeToken, nativeOptions)
: jwt.decode(nativeToken);
return marshallToVm(decoded, vm);
} catch (err) {
throw vm.newError(err.message || String(err));
}
});
vm.setProp(vm.global, '__bruno__jwt__decode', _jwtDecode);
_jwtDecode.dispose();
vm.evalCode(`
globalThis.jwt = {};
globalThis.jwt.sign = globalThis.__bruno__jwt__sign;
globalThis.jwt.verify = globalThis.__bruno__jwt__verify;
globalThis.jwt.decode = globalThis.__bruno__jwt__decode;
globalThis.requireObject = {
...globalThis.requireObject,
'jsonwebtoken': globalThis.jwt,
};
`);
};
module.exports = addJwtShimToContext;

View File

@@ -30,6 +30,54 @@ const marshallToVm = (value, vm) => {
}
};
/**
* Invokes a QuickJS function handle.
* - Returns a Promise
*
* @param {Object} vm - QuickJS VM instance
* @param {QuickJSHandle} quickFn - A QuickJS function handle
* @param {Array} args - Arguments to pass to the function
* @returns {Promise<any>} - The result as a Promise
*/
async function invokeFunction(vm, quickFn, args = []) {
if (vm.typeof(quickFn) !== 'function') {
throw new TypeError('Target is not a QuickJS function');
}
const result = vm.callFunction(quickFn, vm.global, ...args);
if (result.error) {
const error = vm.dump(result.error);
result.error.dispose();
throw error;
}
// Check if the result is a QuickJS Promise handle (async functions)
if (vm.typeof(result.value) === 'object' && result.value.constructor && vm.typeof(result.value.constructor) === 'function') {
try {
const promiseHandle = vm.unwrapResult(result);
const resolvedResult = await vm.resolvePromise(promiseHandle);
promiseHandle.dispose();
const resolvedHandle = vm.unwrapResult(resolvedResult);
const value = vm.dump(resolvedHandle);
resolvedHandle.dispose();
return Promise.resolve(value);
} catch (promiseError) {
// If it's not a valid Promise, throw an error
result.value.dispose();
throw new Error(`Invalid Promise handle: ${promiseError.message}`);
}
}
const value = vm.dump(result.value);
result.value.dispose();
return (value && typeof value.then === 'function')
? value
: Promise.resolve(value);
}
module.exports = {
marshallToVm
marshallToVm,
invokeFunction
};

View File

@@ -0,0 +1,9 @@
{
"version": "1",
"name": "jsonwebtoken",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

View File

@@ -0,0 +1,66 @@
meta {
name: decode
type: http
seq: 1
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
script:pre-request {
const jwt = require('jsonwebtoken');
const testPayload = {
userId: 456,
username: 'decodeuser',
role: 'user',
iat: Math.floor(Date.now() / 1000)
};
const secret = bru.getEnvVar('secret') || 'test-secret-key';
const testToken = jwt.sign(testPayload, secret, { algorithm: 'HS256', expiresIn: '1h' });
try {
console.log('Testing JWT decoding...');
console.log('Test token:', testToken);
const decoded = jwt.decode(testToken);
bru.setEnvVar('decoded_payload', JSON.stringify(decoded));
} catch (error) {
console.error('JWT decoding failed:', error.message);
throw error;
}
}
tests {
test("Decoded payload should exist", function() {
const decodedPayload = bru.getEnvVar('decoded_payload');
expect(decodedPayload).to.exist;
});
test("Decoded payload should contain correct user data", function() {
const decodedPayload = JSON.parse(bru.getEnvVar('decoded_payload'));
expect(decodedPayload.userId).to.equal(456);
expect(decodedPayload.username).to.equal('decodeuser');
expect(decodedPayload.role).to.equal('user');
});
test("Decoded payload should have timestamp fields", function() {
const decodedPayload = JSON.parse(bru.getEnvVar('decoded_payload'));
expect(decodedPayload.iat).to.exist;
expect(decodedPayload.exp).to.exist;
expect(typeof decodedPayload.iat).to.equal('number');
expect(typeof decodedPayload.exp).to.equal('number');
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,8 @@
meta {
name: decode
seq: 3
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,4 @@
vars {
host: http://httpfaker.org
secret: my-secret-key
}

View File

@@ -0,0 +1,8 @@
meta {
name: sign
seq: 1
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,74 @@
meta {
name: sign with callback err
type: http
seq: 2
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
tests {
const jwt = require('jsonwebtoken');
const HS_SECRET = 'supersecret';
/**
* Helper that calls jwt.sign **with a callback** and resolves/rejects
* based on the callback's (err, token) — so tests can `await` it.
*/
function signViaCallback(payload, secret, options = {}) {
return new Promise((resolve, reject) => {
jwt.sign(payload, secret, options, (err, token) => {
if (err) return reject(err);
resolve(token);
});
});
}
/* ============================================================
ERROR TESTS — jwt.sign should call callback with `err`
============================================================ */
test('ERROR (callback) — missing secret for HS256', async function () {
try {
await signViaCallback({ sub: 'no_secret' }, undefined, { algorithm: 'HS256' });
throw new Error('Expected jwt.sign to error via callback');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/secret|private key must have a value/i);
}
});
test('ERROR (callback) — invalid expiresIn format', async function () {
try {
await signViaCallback({ sub: 'bad_exp' }, HS_SECRET, { expiresIn: 'not-a-time' });
throw new Error('Expected jwt.sign to error via callback');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/expiresIn/i);
}
});
test('ERROR (callback) — unsupported/invalid algorithm', async function () {
try {
await signViaCallback({ sub: 'bad_alg' }, HS_SECRET, { algorithm: 'FOO256' });
throw new Error('Expected jwt.sign to error via callback');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/algorithm/i);
}
});
test('CONTROL (callback) — succeeds when options are valid', async function () {
const token = await jwt.sign({ sub: 'ok' }, HS_SECRET, { algorithm: 'HS256', expiresIn: '10m' });
expect(token).to.be.a('string');
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,160 @@
meta {
name: sign with callback token
type: http
seq: 3
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
tests {
const jwt = require('jsonwebtoken');
const HS_SECRET = 'supersecret';
const payload = { sub: 'user123' };
function once(fn) {
let called = false;
return (...args) => {
if (!called) {
called = true;
fn(...args);
}
};
}
function signAsync(payload, secret, options = {}) {
return new Promise((resolve, reject) => {
jwt.sign(payload, secret, options, (err, token) => {
if (err) reject(err);
else resolve(token);
});
});
}
// ------------------------------------------------------------
// 1. Named Normal Callback
// ------------------------------------------------------------
test('sign — named normal callback', function () {
function signCallback(err, token) {
expect(err).to.be.null;
expect(token).to.be.a('string');
// Verify token to ensure correctness
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Named callback signed token:', token);
}
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256', expiresIn: '15m' }, signCallback);
});
// ------------------------------------------------------------
// 2. Anonymous Callback
// ------------------------------------------------------------
test('sign — anonymous callback', function () {
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, function (err, token) {
expect(err).to.be.null;
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Anonymous callback signed token:', token);
});
});
// ------------------------------------------------------------
// 3. Arrow Function Callback
// ------------------------------------------------------------
test('sign — arrow function callback', function () {
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, (err, token) => {
expect(err).to.be.null;
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Arrow callback signed token:', token);
});
});
// ------------------------------------------------------------
// 4. Bound Method Callback
// ------------------------------------------------------------
test('sign — bound method callback', function () {
const signer = {
prefix: '[SIGN]',
done(err, token) {
expect(err).to.be.null;
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log(this.prefix, 'Bound callback signed token:', token);
},
};
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, signer.done.bind(signer));
});
// ------------------------------------------------------------
// 5. Higher-Order Callback
// ------------------------------------------------------------
function makeSignCallback(label) {
return (err, token) => {
expect(err).to.be.null;
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log(label, 'Higher-order callback signed token:', token);
};
}
test('sign — higher-order callback', function () {
const cb = makeSignCallback('[CUSTOM LABEL]');
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, cb);
});
// ------------------------------------------------------------
// 6. Once-Wrapped Callback
// ------------------------------------------------------------
test('sign — once-wrapped callback', function () {
const cb = once((err, token) => {
expect(err).to.be.null;
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Once callback executed and signed token:', token);
});
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, cb);
});
// ------------------------------------------------------------
// 7. Promise / Async-Await
// ------------------------------------------------------------
test('sign — promise wrapper with async/await', async function () {
const token = await signAsync(payload, HS_SECRET, { algorithm: 'HS256', expiresIn: '15m' });
expect(token).to.be.a('string');
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Promise/async signed token:', token);
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,109 @@
meta {
name: sign
type: http
seq: 1
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
script:pre-request {
const jwt = require('jsonwebtoken');
const payload = {
userId: 123,
username: 'testuser',
role: 'admin',
iat: Math.floor(Date.now() / 1000)
};
const secret = bru.getEnvVar('secret');
const options = {
algorithm: 'HS256',
expiresIn: '1h'
};
try {
console.log('Testing JWT encoding...');
const token = jwt.sign(payload, secret, options);
console.log('JWT Token encoded successfully:', token);
bru.setEnvVar('jwt_token', token);
bru.setEnvVar('original_payload', JSON.stringify(payload));
console.log('JWT encoding test passed!');
} catch (error) {
console.error('JWT encoding failed:', error.message);
throw error;
}
}
tests {
const atob = require('atob')
test("JWT token should be generated", function() {
const jwtToken = bru.getEnvVar('jwt_token');
expect(jwtToken).to.exist;
});
test("JWT token should be a string", function() {
const jwtToken = bru.getEnvVar('jwt_token');
expect(typeof jwtToken).to.equal('string');
});
test("JWT token should have 3 parts (header.payload.signature)", function() {
const jwtToken = bru.getEnvVar('jwt_token');
const parts = jwtToken.split('.');
expect(parts.length).to.equal(3);
});
test("JWT token should be valid base64", function() {
const jwtToken = bru.getEnvVar('jwt_token');
const parts = jwtToken.split('.');
// Test that each part is valid base64
parts.forEach((part, index) => {
try {
atob(part);
} catch (e) {
throw new Error(`JWT part ${index + 1} is not valid base64`);
}
});
});
test("JWT token should contain expected payload data", function() {
const jwtToken = bru.getEnvVar('jwt_token');
const originalPayload = JSON.parse(bru.getEnvVar('original_payload'));
// Decode the payload part (second part of JWT)
const parts = jwtToken.split('.');
const payloadPart = parts[1];
const decodedPayload = JSON.parse(atob(payloadPart));
console.log(decodedPayload)
expect(decodedPayload.userId).to.equal(originalPayload.userId);
expect(decodedPayload.username).to.equal(originalPayload.username);
expect(decodedPayload.role).to.equal(originalPayload.role);
});
test("JWT token should have proper header", function() {
const jwtToken = bru.getEnvVar('jwt_token');
const parts = jwtToken.split('.');
const headerPart = parts[0];
const header = JSON.parse(atob(headerPart));
expect(header.alg).to.equal('HS256');
expect(header.typ).to.equal('JWT');
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,8 @@
meta {
name: verify
seq: 2
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,85 @@
meta {
name: verify with callback err
type: http
seq: 2
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
tests {
const jwt = require('jsonwebtoken');
const HS_SECRET = 'supersecret';
function verifyViaCallback(token, secret, options = {}) {
return new Promise((resolve, reject) => {
jwt.verify(token, secret, options, (err, decoded) => {
if (err) return reject(err);
resolve(decoded);
});
});
}
function createValidToken(payload = { sub: 'user123' }, secret = HS_SECRET) {
return jwt.sign(payload, secret, { algorithm: 'HS256', expiresIn: '1h' });
}
/* ============================================================
ERROR TESTS — jwt.verify should call callback with `err`
============================================================ */
test('ERROR (callback) — malformed token', async function () {
const malformedToken = 'abc.def'; // not a valid JWT
try {
await verifyViaCallback(malformedToken, HS_SECRET, { algorithms: ['HS256'] });
throw new Error('Expected jwt.verify to error via callback');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/jwt malformed|invalid token/i);
}
});
test('ERROR (callback) — invalid signature (wrong secret)', async function () {
const token = createValidToken(); // signed with HS_SECRET
try {
await verifyViaCallback(token, 'wrong_secret', { algorithms: ['HS256'] });
throw new Error('Expected jwt.verify to error via callback');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/invalid signature/i);
}
});
test('ERROR (callback) — invalid algorithm', async function () {
const token = createValidToken();
try {
// Pass unsupported algorithm intentionally
await verifyViaCallback(token, HS_SECRET, { algorithms: ['RS256'] });
throw new Error('Expected jwt.verify to error due to invalid algorithm');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/invalid algorithm/i);
}
});
test('ERROR (callback) — missing secret', async function () {
const token = createValidToken();
try {
await verifyViaCallback(token, undefined, { algorithms: ['HS256'] });
throw new Error('Expected jwt.verify to error due to missing secret');
} catch (err) {
expect(err).to.be.instanceOf(Error);
expect(String(err.message)).to.match(/secret|key must be provided/i);
}
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,114 @@
meta {
name: verify with callback token
type: http
seq: 3
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
tests {
const jwt = require('jsonwebtoken');
const HS_SECRET = 'supersecret';
const token = jwt.sign({ sub: 'user123' }, HS_SECRET, {
algorithm: 'HS256',
expiresIn: '15m',
});
function once(fn) {
let called = false;
return (...args) => {
if (!called) {
called = true;
fn(...args);
}
};
}
function verifyAsync(token, secret, options = {}) {
return new Promise((resolve, reject) => {
jwt.verify(token, secret, options, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
});
}
test('verify — named normal callback', function () {
function verifyCallback(err, decoded) {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log('Named callback verified user:', decoded.sub);
}
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, verifyCallback);
});
test('verify — anonymous callback', function () {
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, function (err, decoded) {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log('Anonymous callback verified user:', decoded.sub);
});
});
test('verify — arrow function callback', function () {
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log('Arrow callback verified user:', decoded.sub);
});
});
test('verify — bound method callback', function () {
const handler = {
prefix: '[VERIFY]',
done(err, decoded) {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log(this.prefix, 'Bound callback verified user:', decoded.sub);
},
};
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, handler.done.bind(handler));
});
function makeVerifyCallback(label) {
return (err, decoded) => {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log(label, 'Higher-order callback verified user:', decoded.sub);
};
}
test('verify — higher-order callback', function () {
const cb = makeVerifyCallback('[CUSTOM LABEL]');
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, cb);
});
test('verify — once-wrapped callback', function () {
const cb = once((err, decoded) => {
expect(err).to.be.null;
expect(decoded.sub).to.equal('user123');
console.log('Once callback executed and verified user:', decoded.sub);
});
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, cb);
});
test('verify — promise wrapper with async/await', async function () {
const decoded = await verifyAsync(token, HS_SECRET, { algorithms: ['HS256'] });
expect(decoded.sub).to.equal('user123');
console.log('Promise/async verified user:', decoded.sub);
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,104 @@
meta {
name: verify
type: http
seq: 1
}
post {
url: {{host}}/api/echo
body: none
auth: inherit
}
script:pre-request {
const jwt = require('jsonwebtoken');
const validPayload = {
userId: 789,
username: 'verifyuser',
role: 'admin',
iat: Math.floor(Date.now() / 1000)
};
const secret = bru.getEnvVar('secret') || 'test-secret-key';
const wrongSecret = 'wrong-secret-key';
const validToken = jwt.sign(validPayload, secret, { algorithm: 'HS256', expiresIn: '1h' });
const invalidToken = jwt.sign(validPayload, wrongSecret, { algorithm: 'HS256', expiresIn: '1h' });
bru.setEnvVar('valid_token', validToken);
bru.setEnvVar('invalid_token', invalidToken);
try {
console.log('Testing JWT verification...');
console.log('Valid token:', validToken);
const verified = jwt.verify(validToken, secret);
const verifiedWithOptions = jwt.verify(validToken, secret, {
algorithms: ['HS256'],
ignoreExpiration: false
});
if (!verifiedWithOptions) {
throw new Error('Verification with options should work');
}
console.log('JWT verification test passed!');
bru.setEnvVar('verified_payload', JSON.stringify(verified));
} catch (error) {
console.error('JWT verification failed:', error.message);
throw error;
}
}
tests {
test("Verified payload should exist", function() {
const verifiedPayload = bru.getEnvVar('verified_payload');
expect(verifiedPayload).to.exist;
});
test("Verified payload should be valid JSON", function() {
const verifiedPayload = bru.getEnvVar('verified_payload');
const parsed = JSON.parse(verifiedPayload);
expect(typeof parsed).to.equal('object');
});
test("Verified payload should contain correct user data", function() {
const verifiedPayload = JSON.parse(bru.getEnvVar('verified_payload'));
expect(verifiedPayload.userId).to.equal(789);
expect(verifiedPayload.username).to.equal('verifyuser');
expect(verifiedPayload.role).to.equal('admin');
});
test("Verified payload should have timestamp fields", function() {
const verifiedPayload = JSON.parse(bru.getEnvVar('verified_payload'));
expect(verifiedPayload.iat).to.exist;
expect(verifiedPayload.exp).to.exist;
expect(typeof verifiedPayload.iat).to.equal('number');
expect(typeof verifiedPayload.exp).to.equal('number');
});
test("Invalid token with wrong secret should throw error", function() {
const jwt = require('jsonwebtoken');
const invalidToken = bru.getEnvVar('invalid_token');
const secret = bru.getEnvVar('secret') || 'test-secret-key';
try {
jwt.verify(invalidToken, secret);
expect.fail('Expected JWT verification to throw an error for invalid token');
} catch (error) {
expect(error).to.exist;
expect(error.message).to.equal('invalid signature');
console.log('Invalid token correctly threw error:', error.message);
}
});
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,16 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/scripting/inbuilt-libraries/jsonwebtoken/fixtures/collection"],
"preferences": {
"request": {
"sslVerification": true,
"customCaCertificate": {
"enabled": false,
"filePath": ""
},
"keepDefaultCaCertificates": {
"enabled": true
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"collections": [
{
"pathname": "{{projectRoot}}/tests/scripting/inbuilt-libraries/jsonwebtoken/fixtures/collection",
"selectedEnvironment": "Prod"
}
]
}

View File

@@ -0,0 +1,64 @@
import { test, expect } from '../../../../playwright';
test.describe.serial('jwt collection success', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByTitle('jsonwebtoken').click();
await page.getByLabel('Developer Mode(use only if').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
// Run the collection
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
// Parse and validate test results
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(7);
await expect(parseInt(passed)).toBe(7);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByTitle('jsonwebtoken').click();
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
test.setTimeout(2 * 60 * 1000);
// Run the collection
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.getByText('Run', { exact: true }).click();
await page.getByRole('button', { name: 'Run Collection' }).click();
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
// Parse and validate test results
const result = await page.getByText('Total Requests: ').innerText();
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
if (!matches) {
throw new Error('Could not parse test results');
}
const [totalRequests, passed, failed, skipped] = matches.slice(1);
await expect(parseInt(totalRequests)).toBe(7);
await expect(parseInt(passed)).toBe(7);
await expect(parseInt(failed)).toBe(0);
await expect(parseInt(skipped)).toBe(0);
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
});
});