Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't send header values that are 'undefined' #360

Merged
merged 1 commit into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions __tests__/lib/cleanHeaders.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { cleanHeaders } = require('../../src/lib/cleanHeaders');

describe('cleanHeaders', () => {
it('should b64-encode key in ReadMe-friendly format', () => {
expect(cleanHeaders('test')).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
});

it('should filter out undefined headers', () => {
expect(cleanHeaders('test', { 'x-readme-version': undefined })).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
});

it('should filter out null headers', () => {
expect(cleanHeaders('test', { 'x-readme-version': undefined, Accept: null })).toStrictEqual({
Authorization: 'Basic dGVzdDo=',
});
});

it('should pass in properly defined headers', () => {
expect(
cleanHeaders('test', { 'x-readme-version': undefined, Accept: null, 'Content-Type': 'application/json' })
).toStrictEqual({
Authorization: 'Basic dGVzdDo=',
'Content-Type': 'application/json',
});
});
});
12 changes: 5 additions & 7 deletions src/cmds/docs/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const fs = require('fs');
const editor = require('editor');
const { promisify } = require('util');
const APIError = require('../../lib/apiError');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const { getProjectVersion } = require('../../lib/versionSelect');
const { handleRes } = require('../../lib/handleRes');
const fetch = require('node-fetch');
Expand Down Expand Up @@ -52,15 +53,13 @@ exports.run = async function (opts) {
});

const filename = `${slug}.md`;
const encodedString = Buffer.from(`${key}:`).toString('base64');

const existingDoc = await fetch(`${config.host}/api/v1/docs/${slug}`, {
method: 'get',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': selectedVersion,
Authorization: `Basic ${encodedString}`,
Accept: 'application/json',
},
}),
}).then(res => handleRes(res));

await writeFile(filename, existingDoc.body);
Expand All @@ -72,11 +71,10 @@ exports.run = async function (opts) {

return fetch(`${config.host}/api/v1/docs/${slug}`, {
method: 'put',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': selectedVersion,
Authorization: `Basic ${encodedString}`,
'Content-Type': 'application/json',
},
}),
body: JSON.stringify(
Object.assign(existingDoc, {
body: updatedDoc,
Expand Down
18 changes: 7 additions & 11 deletions src/cmds/docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const config = require('config');
const crypto = require('crypto');
const frontMatter = require('gray-matter');
const { promisify } = require('util');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const { getProjectVersion } = require('../../lib/versionSelect');
const { handleRes } = require('../../lib/handleRes');
const fetch = require('node-fetch');
Expand Down Expand Up @@ -70,18 +71,15 @@ exports.run = async function (opts) {
return Promise.reject(new Error(`We were unable to locate Markdown files in ${folder}.`));
}

const encodedString = Buffer.from(`${key}:`).toString('base64');

function createDoc(slug, file, hash, err) {
if (err.error !== 'DOC_NOTFOUND') return Promise.reject(err);

return fetch(`${config.host}/api/v1/docs`, {
method: 'post',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': selectedVersion,
Authorization: `Basic ${encodedString}`,
'Content-Type': 'application/json',
},
}),
body: JSON.stringify({
slug,
body: file.content,
Expand All @@ -98,11 +96,10 @@ exports.run = async function (opts) {

return fetch(`${config.host}/api/v1/docs/${slug}`, {
method: 'put',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': selectedVersion,
Authorization: `Basic ${encodedString}`,
'Content-Type': 'application/json',
},
}),
body: JSON.stringify(
Object.assign(existingDoc, {
body: file.content,
Expand All @@ -124,11 +121,10 @@ exports.run = async function (opts) {

return fetch(`${config.host}/api/v1/docs/${slug}`, {
method: 'get',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': selectedVersion,
Authorization: `Basic ${encodedString}`,
Accept: 'application/json',
},
}),
})
.then(res => res.json())
.then(res => {
Expand Down
13 changes: 5 additions & 8 deletions src/cmds/openapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { prompt } = require('enquirer');
const OASNormalize = require('oas-normalize');
const promptOpts = require('../lib/prompts');
const APIError = require('../lib/apiError');
const { cleanHeaders } = require('../lib/cleanHeaders');
const { getProjectVersion } = require('../lib/versionSelect');
const fetch = require('node-fetch');
const FormData = require('form-data');
Expand Down Expand Up @@ -61,8 +62,6 @@ exports.run = async function (opts) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
}

const encodedString = Buffer.from(`${key}:`).toString('base64');

async function callApi(specPath, versionCleaned) {
// @todo Tailor messaging to what is actually being handled here. If the user is uploading a Swagger file, never mention that they uploaded/updated an OpenAPI file.

Expand Down Expand Up @@ -122,12 +121,11 @@ exports.run = async function (opts) {
formData.append('spec', bundledSpec);

const options = {
headers: {
headers: cleanHeaders(key, {
'x-readme-version': versionCleaned,
'x-readme-source': 'cli',
Authorization: `Basic ${encodedString}`,
Accept: 'application/json',
},
}),
body: formData,
};

Expand Down Expand Up @@ -163,10 +161,9 @@ exports.run = async function (opts) {
function getSpecs(url) {
return fetch(`${config.host}${url}`, {
method: 'get',
headers: {
headers: cleanHeaders(key, {
'x-readme-version': versionCleaned,
Authorization: `Basic ${encodedString}`,
},
}),
});
}

Expand Down
11 changes: 4 additions & 7 deletions src/cmds/versions/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const semver = require('semver');
const { prompt } = require('enquirer');
const promptOpts = require('../../lib/prompts');
const APIError = require('../../lib/apiError');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const { handleRes } = require('../../lib/handleRes');
const fetch = require('node-fetch');

Expand Down Expand Up @@ -54,7 +55,6 @@ exports.args = [
exports.run = async function (opts) {
let versionList;
const { key, version, codename, fork, main, beta, isPublic } = opts;
const encodedString = Buffer.from(`${key}:`).toString('base64');

if (!key) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
Expand All @@ -69,9 +69,7 @@ exports.run = async function (opts) {
if (!fork) {
versionList = await fetch(`${config.host}/api/v1/version`, {
method: 'get',
headers: {
Authorization: `Basic ${encodedString}`,
},
headers: cleanHeaders(key),
}).then(res => handleRes(res));
}

Expand All @@ -84,11 +82,10 @@ exports.run = async function (opts) {

return fetch(`${config.host}/api/v1/version`, {
method: 'post',
headers: {
headers: cleanHeaders(key, {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Basic ${encodedString}`,
},
}),
body: JSON.stringify({
version,
codename: codename || '',
Expand Down
6 changes: 2 additions & 4 deletions src/cmds/versions/delete.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const config = require('config');
const APIError = require('../../lib/apiError');
const { getProjectVersion } = require('../../lib/versionSelect');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const fetch = require('node-fetch');

exports.command = 'versions:delete';
Expand All @@ -25,7 +26,6 @@ exports.args = [

exports.run = async function (opts) {
const { key, version } = opts;
const encodedString = Buffer.from(`${key}:`).toString('base64');

if (!key) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
Expand All @@ -37,9 +37,7 @@ exports.run = async function (opts) {

return fetch(`${config.host}/api/v1/version/${selectedVersion}`, {
method: 'delete',
headers: {
Authorization: `Basic ${encodedString}`,
},
headers: cleanHeaders(key),
}).then(res => {
if (res.error) {
return Promise.reject(new APIError(res));
Expand Down
6 changes: 2 additions & 4 deletions src/cmds/versions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Table = require('cli-table');
const config = require('config');
const versionsCreate = require('./create');
const APIError = require('../../lib/apiError');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const fetch = require('node-fetch');

exports.command = 'versions';
Expand Down Expand Up @@ -82,7 +83,6 @@ const getVersionFormatted = version => {

exports.run = function (opts) {
const { key, version, raw } = opts;
const encodedString = Buffer.from(`${key}:`).toString('base64');

if (!key) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
Expand All @@ -92,9 +92,7 @@ exports.run = function (opts) {

return fetch(uri, {
method: 'get',
headers: {
Authorization: `Basic ${encodedString}`,
},
headers: cleanHeaders(key),
})
.then(res => res.json())
.then(data => {
Expand Down
11 changes: 4 additions & 7 deletions src/cmds/versions/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const config = require('config');
const { prompt } = require('enquirer');
const promptOpts = require('../../lib/prompts');
const APIError = require('../../lib/apiError');
const { cleanHeaders } = require('../../lib/cleanHeaders');
const { getProjectVersion } = require('../../lib/versionSelect');
const fetch = require('node-fetch');
const { handleRes } = require('../../lib/handleRes');
Expand Down Expand Up @@ -47,7 +48,6 @@ exports.args = [

exports.run = async function (opts) {
const { key, version, codename, newVersion, main, beta, isPublic, deprecated } = opts;
const encodedString = Buffer.from(`${key}:`).toString('base64');

if (!key) {
return Promise.reject(new Error('No project API key provided. Please use `--key`.'));
Expand All @@ -59,20 +59,17 @@ exports.run = async function (opts) {

const foundVersion = await fetch(`${config.host}/api/v1/version/${selectedVersion}`, {
method: 'get',
headers: {
Authorization: `Basic ${encodedString}`,
},
headers: cleanHeaders(key),
}).then(res => handleRes(res));

const promptResponse = await prompt(promptOpts.createVersionPrompt([{}], opts, foundVersion));

return fetch(`${config.host}/api/v1/version/${selectedVersion}`, {
method: 'put',
headers: {
headers: cleanHeaders(key, {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Basic ${encodedString}`,
},
}),
body: JSON.stringify({
codename: codename || '',
version: newVersion || promptResponse.newVersion,
Expand Down
23 changes: 23 additions & 0 deletions src/lib/cleanHeaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Returns the basic auth header and any other defined headers for use in node-fetch API calls.
* @param {string} key The ReadMe project API key
* @param {Object} inputHeaders Any additional headers to be cleaned
* @returns An object with cleaned request headers for usage in the node-fetch requests to the ReadMe API.
*/
function cleanHeaders(key, inputHeaders = {}) {
const encodedKey = Buffer.from(`${key}:`).toString('base64');
const headers = {
Authorization: `Basic ${encodedKey}`,
};

Object.keys(inputHeaders).forEach(header => {
// For some reason, node-fetch will send in the string 'undefined'
// if you pass in an undefined value for a header,
// so that's why headers are added incrementally.
if (typeof inputHeaders[header] === 'string') headers[header] = inputHeaders[header];
});

return headers;
}

module.exports = { cleanHeaders };
9 changes: 4 additions & 5 deletions src/lib/versionSelect.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
const { prompt } = require('enquirer');
const promptOpts = require('./prompts');
const { cleanHeaders } = require('./cleanHeaders');
const fetch = require('node-fetch');
const config = require('config');
const APIError = require('./apiError');

async function getProjectVersion(versionFlag, key, allowNewVersion) {
const encodedString = Buffer.from(`${key}:`).toString('base64');

try {
if (versionFlag) {
return await fetch(`${config.host}/api/v1/version/${versionFlag}`, {
method: 'get',
headers: { Authorization: `Basic ${encodedString}` },
headers: cleanHeaders(key),
})
.then(res => res.json())
.then(res => res.version);
}

const versionList = await fetch(`${config.host}/api/v1/version`, {
method: 'get',
headers: { Authorization: `Basic ${encodedString}` },
headers: cleanHeaders(key),
}).then(res => res.json());

if (allowNewVersion) {
Expand All @@ -29,7 +28,7 @@ async function getProjectVersion(versionFlag, key, allowNewVersion) {

await fetch(`${config.host}/api/v1/version`, {
method: 'post',
headers: { Authorization: `Basic ${encodedString}`, 'Content-Type': 'application/json' },
headers: cleanHeaders(key, { 'Content-Type': 'application/json' }),
body: JSON.stringify({
from: versionList[0].version,
version: newVersion,
Expand Down