postman-runtime
Version:
Underlying library of executing Postman Collections
259 lines (229 loc) • 8.05 kB
JavaScript
const _ = require('lodash'),
jose = require('jose'),
// jwt key constants
BEARER_AUTH_PREFIX = 'Bearer',
QUERY_KEY = 'token',
AUTH_KEYS = {
ALGORITHM: 'algorithm',
HEADER: 'header',
HEADER_ALGORITHM: 'alg',
PAYLOAD: 'payload',
SECRET: 'secret',
IS_SECRET_BASE_64_ENCODED: 'isSecretBase64Encoded',
PRIVATE_KEY: 'privateKey',
ADD_TOKEN_TO: 'addTokenTo',
HEADER_PREFIX: 'headerPrefix',
QUERY_PARAM_KEY: 'queryParamKey'
},
ADD_TOKEN_TO_TARGETS = {
HEADER: 'header',
QUERY_PARAM: 'queryParam'
},
AUTHORIZATION = 'Authorization',
BASE64 = 'base64',
UTF8 = 'utf8',
SPACE = ' ',
// HS Algorithms
HS_ALGORITHMS = {
HS256: 'HS256',
HS384: 'HS384',
HS512: 'HS512'
},
// algorithms supported
ALGORITHMS_SUPPORTED = {
...HS_ALGORITHMS,
RS256: 'RS256',
RS384: 'RS384',
RS512: 'RS512',
PS256: 'PS256',
PS384: 'PS384',
PS512: 'PS512',
ES256: 'ES256',
ES384: 'ES384',
ES512: 'ES512'
};
/**
* add the JWT Token to the request in auth header or query param
*
* @param {AuthInterface} auth - auth
* @param {Request} request - request
* @param {string} jwtToken - base64encoded jwt token
*/
function addTokenToRequest (auth, request, jwtToken) {
const addTokenTo = auth.get(AUTH_KEYS.ADD_TOKEN_TO) || ADD_TOKEN_TO_TARGETS.HEADER,
queryParamKey = auth.get(AUTH_KEYS.QUERY_PARAM_KEY) || QUERY_KEY;
if (addTokenTo === ADD_TOKEN_TO_TARGETS.HEADER) {
request.removeHeader(AUTHORIZATION, { ignoreCase: true });
let headerPrefix = auth.get(AUTH_KEYS.HEADER_PREFIX);
headerPrefix = _.isNil(headerPrefix) ? BEARER_AUTH_PREFIX : headerPrefix;
headerPrefix && (headerPrefix += SPACE);
request.addHeader({
key: AUTHORIZATION,
value: headerPrefix + jwtToken,
system: true
});
}
else if (addTokenTo === ADD_TOKEN_TO_TARGETS.QUERY_PARAM) {
request.url.query.remove(function (query) {
return query && query.key === queryParamKey;
});
request.url.query.add({
key: queryParamKey,
value: jwtToken,
system: true
});
}
}
/**
* Request auth payload structure
* request:{
* auth:{
* type:'jwt',
* jwt:{
* algorithm: <string> - ALGORITHMS_SUPPORTED,
* header: <JSON string> | JSON Object
* payload: <JSON string> | JSON Object
* secret: <string> - secret for HS algorithms
* isSecretBase64Encoded: <boolean> - optional property used when <secret> for HS algorithms
* is encoded in base64 format
* privateKey: <string> - PEM format private key for RS, PS, ES algorithms
* addTokenTo: <string> - possible values - header | queryParam,
* headerPrefix: <string> - prefix added before jwt token in header
* If headerPrefix is null | undefined - `Authorization:Bearer + SPACE + JWT_TOKEN`
* If headerPrefix is valid string - `Authorization:HEADER_PREFIX + SPACE + JWT_TOKEN`
* If headerPrefix is empty string - `Authorization:JWT_TOKEN`
* queryParamKey: <string> - optional property added when <addTokenTo> set to [queryParam],
* }
* }
* }
*/
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'jwt',
version: '1.0.0'
},
updates: [
{
property: 'Authorization',
type: 'header'
},
{
property: '*',
type: 'url.param'
}
]
},
/**
* Initializes an item (extracts parameters from intermediate requests if any, etc)
* before the actual authorization step
*
* @param {AuthInterface} auth -
* @param {Response} response -
* @param {AuthHandlerInterface~authInitHookCallback} done -
*/
init: function (auth, response, done) {
done();
},
/**
* Checks the item, and fetches any parameters that are not already provided.
*
* @param {AuthInterface} auth -
* @param {AuthHandlerInterface~authPreHookCallback} done -
*/
pre: function (auth, done) {
return done(null, true);
},
/**
* Verifies whether the auth succeeded
*
* @param {AuthInterface} auth -
* @param {Response} response -
* @param {AuthHandlerInterface~authPostHookCallback} done -
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs the request
*
* @param {AuthInterface} auth -
* @param {Request} request -
* @param {AuthHandlerInterface~authSignHookCallback} done -
*/
sign: function (auth, request, done) {
const algorithm = auth.get(AUTH_KEYS.ALGORITHM),
secret = auth.get(AUTH_KEYS.SECRET),
privateKey = auth.get(AUTH_KEYS.PRIVATE_KEY),
isHsAlgorithm = HS_ALGORITHMS[algorithm];
// bail out - invalid algorithm
if (!ALGORITHMS_SUPPORTED[algorithm]) {
return done(new Error('invalid algorithm'));
}
// bail out - secret not a valid string
if (isHsAlgorithm && (!secret || !_.isString(secret))) {
return done(new Error('Invalid secret key. Enter a valid key.'));
}
// bail out - private key not a valid string
if (!isHsAlgorithm && (!privateKey || !_.isString(privateKey))) {
return done(new Error('Invalid private key. Enter a valid key.'));
}
try {
const rawHeader = auth.get(AUTH_KEYS.HEADER),
rawPayload = auth.get(AUTH_KEYS.PAYLOAD);
let header = rawHeader,
payload = rawPayload;
if (typeof rawHeader === 'string') {
const trimmedHeader = rawHeader.trim();
header = trimmedHeader && JSON.parse(trimmedHeader);
}
// treat root level alg as source of truth.
// If header is not set, use empty object.
header = header ? { ...header, alg: algorithm } : { alg: algorithm };
if (typeof rawPayload === 'string') {
const trimmedPayload = rawPayload.trim();
payload = trimmedPayload ? JSON.parse(trimmedPayload) : {};
}
// HS Algorithms use secret for token generation
if (isHsAlgorithm) {
const isBase64 = auth.get(AUTH_KEYS.IS_SECRET_BASE_64_ENCODED),
secretBuffer = Buffer.from(secret, isBase64 ? BASE64 : UTF8);
new jose.SignJWT(payload)
.setProtectedHeader(header)
.sign(new Uint8Array(secretBuffer))
.then((token) => {
addTokenToRequest(auth, request, token);
return done();
})
.catch((err) => {
done(err);
});
}
// RS,PS,ES Algorithms use private key for token generation
else {
jose.importPKCS8(privateKey, algorithm)
.then((signKey) => {
return new jose.SignJWT(payload)
.setProtectedHeader(header)
.sign(signKey);
})
.then((token) => {
addTokenToRequest(auth, request, token);
return done();
})
.catch((err) => {
done(err);
});
}
}
catch (err) {
done(err);
}
}
};