Fix: postman collection fails when auth object missing auth values (#4794)

* refactor: streamline authentication handling in postman-to-bruno.js by using a switch statement and introducing AUTH_TYPES constant for better readability and maintainability

* feat: enhance authentication handling in postman-to-bruno.js to manage missing auth values across collection, folder, and request levels, ensuring a default mode of 'none'

* fix: update authentication handling in postman-to-bruno.js to correctly set auth mode based on provided auth type

* fix: update authentication tests to ensure default values are set for various auth types in postman-to-bruno
This commit is contained in:
sanish chirayath
2025-06-24 16:32:32 +05:30
committed by GitHub
parent 2bbfb28090
commit 9fe13f1868
5 changed files with 727 additions and 94 deletions

View File

@@ -4,6 +4,17 @@ import each from 'lodash/each';
import postmanTranslation from './postman-translations';
import { invalidVariableCharacterRegex } from '../constants/index';
const AUTH_TYPES = Object.freeze({
BASIC: 'basic',
BEARER: 'bearer',
AWSV4: 'awsv4',
APIKEY: 'apikey',
DIGEST: 'digest',
OAUTH2: 'oauth2',
NOAUTH: 'noauth',
NONE: 'none'
});
const parseGraphQLRequest = (graphqlSource) => {
try {
let queryResultObject = {
@@ -129,107 +140,122 @@ const importCollectionLevelVariables = (variables, requestObject) => {
requestObject.vars.req = vars;
};
const processAuth = (auth, requestObject) => {
if (!auth || !auth.type || auth.type === 'noauth') {
export const processAuth = (auth, requestObject) => {
if (!auth || !auth.type || auth.type === AUTH_TYPES.NOAUTH) {
return;
}
let authValues = auth[auth.type];
if(!authValues) {
console.warn('Unexpected auth.type, auth object doesn\'t have the key', auth.type);
requestObject.auth.mode = auth.type;
authValues = {};
}
if (Array.isArray(authValues)) {
authValues = convertV21Auth(authValues);
}
if (auth.type === 'basic') {
requestObject.auth.mode = 'basic';
requestObject.auth.basic = {
username: authValues.username || '',
password: authValues.password || ''
};
} else if (auth.type === 'bearer') {
requestObject.auth.mode = 'bearer';
requestObject.auth.bearer = {
token: authValues.token || ''
};
} else if (auth.type === 'awsv4') {
requestObject.auth.mode = 'awsv4';
requestObject.auth.awsv4 = {
accessKeyId: authValues.accessKey || '',
secretAccessKey: authValues.secretKey || '',
sessionToken: authValues.sessionToken || '',
service: authValues.service || '',
region: authValues.region || '',
profileName: ''
};
} else if (auth.type === 'apikey') {
requestObject.auth.mode = 'apikey';
requestObject.auth.apikey = {
key: authValues.key || '',
value: authValues.value?.toString() || '', // Convert the value to a string as Postman's schema does not rigidly define the type of it,
placement: 'header' //By default we are placing the apikey values in headers!
};
} else if (auth.type === 'digest') {
requestObject.auth.mode = 'digest';
requestObject.auth.digest = {
username: authValues.username || '',
password: authValues.password || ''
};
} else if (auth.type === 'oauth2') {
const findValueUsingKey = (key) => {
return authValues[key] || '';
};
const oauth2GrantTypeMaps = {
authorization_code_with_pkce: 'authorization_code',
authorization_code: 'authorization_code',
client_credentials: 'client_credentials',
password_credentials: 'password_credentials'
};
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
switch (auth.type) {
case AUTH_TYPES.BASIC:
requestObject.auth.mode = AUTH_TYPES.BASIC;
requestObject.auth.basic = {
username: authValues.username || '',
password: authValues.password || ''
};
break;
case AUTH_TYPES.BEARER:
requestObject.auth.mode = AUTH_TYPES.BEARER;
requestObject.auth.bearer = {
token: authValues.token || ''
};
break;
case AUTH_TYPES.AWSV4:
requestObject.auth.mode = AUTH_TYPES.AWSV4;
requestObject.auth.awsv4 = {
accessKeyId: authValues.accessKey || '',
secretAccessKey: authValues.secretKey || '',
sessionToken: authValues.sessionToken || '',
service: authValues.service || '',
region: authValues.region || '',
profileName: ''
};
break;
case AUTH_TYPES.APIKEY:
requestObject.auth.mode = AUTH_TYPES.APIKEY;
requestObject.auth.apikey = {
key: authValues.key || '',
value: authValues.value?.toString() || '', // Convert the value to a string as Postman's schema does not rigidly define the type of it,
placement: 'header' //By default we are placing the apikey values in headers!
};
break;
case AUTH_TYPES.DIGEST:
requestObject.auth.mode = AUTH_TYPES.DIGEST;
requestObject.auth.digest = {
username: authValues.username || '',
password: authValues.password || ''
};
break;
case AUTH_TYPES.OAUTH2:
const findValueUsingKey = (key) => {
return authValues[key] || '';
};
const oauth2GrantTypeMaps = {
authorization_code_with_pkce: 'authorization_code',
authorization_code: 'authorization_code',
client_credentials: 'client_credentials',
password_credentials: 'password_credentials'
};
const grantType = oauth2GrantTypeMaps[findValueUsingKey('grant_type')] || 'authorization_code';
requestObject.auth.mode = 'oauth2';
if (grantType === 'authorization_code') {
requestObject.auth.oauth2 = {
grantType: 'authorization_code',
authorizationUrl: findValueUsingKey('authUrl'),
callbackUrl: findValueUsingKey('redirect_uri'),
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'password_credentials') {
requestObject.auth.oauth2 = {
grantType: 'password',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
username: findValueUsingKey('username'),
password: findValueUsingKey('password'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'client_credentials') {
requestObject.auth.oauth2 = {
grantType: 'client_credentials',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
}
} else {
console.warn('Unexpected auth.type', auth.type);
requestObject.auth.mode = AUTH_TYPES.OAUTH2;
if (grantType === 'authorization_code') {
requestObject.auth.oauth2 = {
grantType: 'authorization_code',
authorizationUrl: findValueUsingKey('authUrl'),
callbackUrl: findValueUsingKey('redirect_uri'),
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
pkce: Boolean(findValueUsingKey('grant_type') == 'authorization_code_with_pkce'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'password_credentials') {
requestObject.auth.oauth2 = {
grantType: 'password',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
username: findValueUsingKey('username'),
password: findValueUsingKey('password'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
} else if (grantType === 'client_credentials') {
requestObject.auth.oauth2 = {
grantType: 'client_credentials',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
state: findValueUsingKey('state'),
tokenPlacement: findValueUsingKey('addTokenTo') == 'header' ? 'header' : 'url',
credentialsPlacement: findValueUsingKey('client_authentication') == 'body' ? 'body' : 'basic_auth_header'
};
}
break;
default:
requestObject.auth.mode = AUTH_TYPES.NONE;
console.warn('Unexpected auth.type', auth.type);
}
};
@@ -663,5 +689,4 @@ const postmanToBruno = async (postmanCollection, { useWorkers = false } = {}) =>
}
};
export default postmanToBruno;

View File

@@ -235,4 +235,97 @@ describe('Collection Authentication', () => {
}
});
});
it('should handle missing auth values when auth.type exists', async() => {
const postmanCollection = {
info: {
name: 'Collection with missing auth values',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'basic'
// Missing basic auth values
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = await postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'basic',
basic: {
username: '',
password: ''
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
it('should handle missing auth values for different auth types', async() => {
const postmanCollection = {
info: {
name: 'Collection with missing auth values for different types',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'bearer'
// Missing bearer token
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
};
const result = await postmanToBruno(postmanCollection);
expect(result.root.request.auth).toEqual({
mode: 'bearer',
basic: null,
bearer: {
token: ''
},
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
});

View File

@@ -244,4 +244,56 @@ describe('Folder Authentication', () => {
digest: { username: 'digest user', password: 'digest pass' }
});
});
it('should handle missing auth values in folder level auth', async() => {
const postmanCollection = {
info: {
name: 'Folder with missing auth values',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'folder',
item: [],
auth: {
type: 'basic'
// Missing basic values
},
event: [
{
listen: 'prerequest',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
},
{
listen: 'test',
script: {
type: 'text/javascript',
packages: {},
exec: ['']
}
}
]
}
]
};
const result = await postmanToBruno(postmanCollection);
expect(result.items[0].root.request.auth).toEqual({
mode: 'basic',
basic: {
username: '',
password: ''
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
});

View File

@@ -0,0 +1,428 @@
const { processAuth } = require("../../../src/postman/postman-to-bruno");
describe('processAuth', () => {
let requestObject;
beforeEach(() => {
requestObject = {
auth: {
mode: 'none',
basic: null,
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
}
};
});
it('should handle no auth', () => {
processAuth(null, requestObject);
expect(requestObject.auth.mode).toBe('none');
});
it('should handle noauth type', () => {
processAuth({ type: 'noauth' }, requestObject);
expect(requestObject.auth.mode).toBe('none');
});
it('should handle basic auth', () => {
const auth = {
type: 'basic',
basic: [
{ key: 'username', value: 'testuser', type: 'string' },
{ key: 'password', value: 'testpass', type: 'string' }
]
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('basic');
expect(requestObject.auth.basic).toEqual({
username: 'testuser',
password: 'testpass'
});
});
it('should handle basic auth with missing values', () => {
const auth = {
type: 'basic',
basic: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('basic');
expect(requestObject.auth.basic).toEqual({
username: '',
password: ''
});
});
it('should handle basic auth with missing basic key', () => {
const auth = {
type: 'basic'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('basic');
expect(requestObject.auth.basic).toEqual({
username: '',
password: ''
});
});
it('should handle bearer auth', () => {
const auth = {
type: 'bearer',
bearer: {
token: 'test-token'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('bearer');
expect(requestObject.auth.bearer).toEqual({
token: 'test-token'
});
});
it('should handle bearer auth with missing values', () => {
const auth = {
type: 'bearer',
bearer: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('bearer');
expect(requestObject.auth.bearer).toEqual({
token: ''
});
});
it('should handle bearer auth with missing bearer key', () => {
const auth = {
type: 'bearer'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('bearer');
expect(requestObject.auth.bearer).toEqual({
token: ''
});
});
it('should handle awsv4 auth', () => {
const auth = {
type: 'awsv4',
awsv4: {
accessKey: 'test-access-key',
secretKey: 'test-secret-key',
sessionToken: 'test-session-token',
service: 'test-service',
region: 'test-region'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('awsv4');
expect(requestObject.auth.awsv4).toEqual({
accessKeyId: 'test-access-key',
secretAccessKey: 'test-secret-key',
sessionToken: 'test-session-token',
service: 'test-service',
region: 'test-region',
profileName: ''
});
});
it('should handle awsv4 auth with missing values', () => {
const auth = {
type: 'awsv4',
awsv4: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('awsv4');
expect(requestObject.auth.awsv4).toEqual({
accessKeyId: '',
secretAccessKey: '',
sessionToken: '',
service: '',
region: '',
profileName: ''
});
});
it('should handle awsv4 auth with missing awsv4 key', () => {
const auth = {
type: 'awsv4'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('awsv4');
expect(requestObject.auth.awsv4).toEqual({
accessKeyId: '',
secretAccessKey: '',
sessionToken: '',
service: '',
region: '',
profileName: ''
});
});
it('should handle apikey auth', () => {
const auth = {
type: 'apikey',
apikey: {
key: 'test-key',
value: 'test-value'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('apikey');
expect(requestObject.auth.apikey).toEqual({
key: 'test-key',
value: 'test-value',
placement: 'header'
});
});
it('should handle apikey auth with missing values', () => {
const auth = {
type: 'apikey',
apikey: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('apikey');
expect(requestObject.auth.apikey).toEqual({
key: '',
value: '',
placement: 'header'
});
});
it('should handle apikey auth with missing apikey key', () => {
const auth = {
type: 'apikey'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('apikey');
expect(requestObject.auth.apikey).toEqual({
key: '',
value: '',
placement: 'header'
});
});
it('should handle digest auth', () => {
const auth = {
type: 'digest',
digest: {
username: 'testuser',
password: 'testpass'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('digest');
expect(requestObject.auth.digest).toEqual({
username: 'testuser',
password: 'testpass'
});
});
it('should handle digest auth with missing values', () => {
const auth = {
type: 'digest',
digest: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('digest');
expect(requestObject.auth.digest).toEqual({
username: '',
password: ''
});
});
it('should handle digest auth with missing digest key', () => {
const auth = {
type: 'digest'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('digest');
expect(requestObject.auth.digest).toEqual({
username: '',
password: ''
});
});
it('should handle oauth2 auth with authorization_code grant type', () => {
const auth = {
type: 'oauth2',
oauth2: {
grant_type: 'authorization_code',
authUrl: 'https://auth.example.com',
redirect_uri: 'https://callback.example.com',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
addTokenTo: 'header',
client_authentication: 'body'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'authorization_code',
authorizationUrl: 'https://auth.example.com',
callbackUrl: 'https://callback.example.com',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
pkce: false,
tokenPlacement: 'header',
credentialsPlacement: 'body'
});
});
it('should handle oauth2 auth with password_credentials grant type', () => {
const auth = {
type: 'oauth2',
oauth2: {
grant_type: 'password_credentials',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
username: 'testuser',
password: 'testpass',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
addTokenTo: 'header',
client_authentication: 'body'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'password',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
username: 'testuser',
password: 'testpass',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
tokenPlacement: 'header',
credentialsPlacement: 'body'
});
});
it('should handle oauth2 auth with client_credentials grant type', () => {
const auth = {
type: 'oauth2',
oauth2: {
grant_type: 'client_credentials',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
addTokenTo: 'header',
client_authentication: 'body'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'client_credentials',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
tokenPlacement: 'header',
credentialsPlacement: 'body'
});
});
it('should handle oauth2 auth with missing values', () => {
const auth = {
type: 'oauth2',
oauth2: {}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'authorization_code',
authorizationUrl: '',
callbackUrl: '',
accessTokenUrl: '',
refreshTokenUrl: '',
clientId: '',
clientSecret: '',
scope: '',
state: '',
pkce: false,
tokenPlacement: 'url',
credentialsPlacement: 'basic_auth_header'
});
});
it('should handle oauth2 auth with missing oauth2 key', () => {
const auth = {
type: 'oauth2'
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'authorization_code',
authorizationUrl: '',
callbackUrl: '',
accessTokenUrl: '',
refreshTokenUrl: '',
clientId: '',
clientSecret: '',
scope: '',
state: '',
pkce: false,
tokenPlacement: 'url',
credentialsPlacement: 'basic_auth_header'
});
});
it('should handle oauth2 auth with authorization_code_with_pkce grant type', () => {
const auth = {
type: 'oauth2',
oauth2: {
grant_type: 'authorization_code_with_pkce',
authUrl: 'https://auth.example.com',
redirect_uri: 'https://callback.example.com',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
addTokenTo: 'header',
client_authentication: 'body'
}
};
processAuth(auth, requestObject);
expect(requestObject.auth.mode).toBe('oauth2');
expect(requestObject.auth.oauth2).toEqual({
grantType: 'authorization_code',
authorizationUrl: 'https://auth.example.com',
callbackUrl: 'https://callback.example.com',
accessTokenUrl: 'https://token.example.com',
refreshTokenUrl: 'https://refresh.example.com',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
scope: 'test-scope',
state: 'test-state',
pkce: true,
tokenPlacement: 'header',
credentialsPlacement: 'body'
});
});
});

View File

@@ -130,5 +130,40 @@ describe('Request Authentication', () => {
digest: null
});
});
it('should handle missing basic auth values in request level', async() => {
const postmanCollection = {
info: {
name: 'Missing Auth Request Collection',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [
{
name: 'Missing Auth Request',
request: {
method: 'GET',
url: 'https://api.example.com/test',
auth: {
type: 'basic'
}
}
}
]
};
const result = await postmanToBruno(postmanCollection);
expect(result.items[0].request.auth).toEqual({
mode: 'basic',
basic: {
username: '',
password: ''
},
bearer: null,
awsv4: null,
apikey: null,
oauth2: null,
digest: null
});
});
});