Skip to content

Commit

Permalink
fix: remove vulnerable dependency (#191)
Browse files Browse the repository at this point in the history
fix: remove vulnerable dependency

fixes #190
  • Loading branch information
mikejpeters committed Feb 19, 2024
1 parent 4ab6e60 commit 8cbf0ef
Show file tree
Hide file tree
Showing 4 changed files with 585 additions and 349 deletions.
52 changes: 25 additions & 27 deletions lib/validate.js
@@ -1,7 +1,6 @@
'use strict';

const fs = require('fs');
const is = require('is_js');
const path = require('path');

/**
Expand All @@ -12,7 +11,7 @@ const path = require('path');
function validateClient(serverless, options) {
const validationErrors = [];

if (!is.object(options)) {
if (!isObject(options)) {
validationErrors.push('Options must be an object defined under `custom.client`');
throw validationErrors;
}
Expand All @@ -25,27 +24,27 @@ function validateClient(serverless, options) {
}

// bucketName must be a string
if (!is.string(options.bucketName)) {
if (typeof options.bucketName !== 'string') {
validationErrors.push('Please specify a bucket name for the client in serverless.yml');
}

// check header options
if (options.objectHeaders) {
if (!is.object(options.objectHeaders)) {
if (!isObject(options.objectHeaders)) {
validationErrors.push('objectHeaders must be an object');
}

Object.keys(options.objectHeaders).forEach(p => {
if (!is.array(options.objectHeaders[p])) {
if (!Array.isArray(options.objectHeaders[p])) {
validationErrors.push('Each member of objectHeaders must be an array');
}

options.objectHeaders[p].forEach(h => {
if (!(is.existy(h.name) && is.string(h.name))) {
if (typeof h.name !== 'string') {
validationErrors.push(`Each object header must have a (string) 'name' attribute`);
}

if (!(is.existy(h.value) && is.string(h.value))) {
if (typeof h.value !== 'string') {
validationErrors.push(`Each object header must have a (string) 'value' attribute`);
}
});
Expand Down Expand Up @@ -80,48 +79,48 @@ function validateClient(serverless, options) {
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-websiteconfiguration.html
if (options.redirectAllRequestsTo) {
const clientConfigOptions = Object.keys(options);
if (is.inArray('indexDocument', clientConfigOptions)) {
if (clientConfigOptions.includes('indexDocument')) {
validationErrors.push('indexDocument cannot be specified with redirectAllRequestsTo');
}

if (is.inArray('errorDocument', clientConfigOptions)) {
if (clientConfigOptions.includes('errorDocument')) {
validationErrors.push('errorDocument cannot be specified with redirectAllRequestsTo');
}

if (is.inArray('routingRules', clientConfigOptions)) {
if (clientConfigOptions.includes('routingRules')) {
validationErrors.push('routingRules cannot be specified with redirectAllRequestsTo');
}

if (!is.existy(options.redirectAllRequestsTo.hostName)) {
if (!options.redirectAllRequestsTo.hostName) {
validationErrors.push(
'redirectAllRequestsTo.hostName is required if redirectAllRequestsTo is specified'
);
}
if (!is.string(options.redirectAllRequestsTo.hostName)) {
if (typeof options.redirectAllRequestsTo.hostName !== 'string') {
validationErrors.push('redirectAllRequestsTo.hostName must be a string');
}

if (options.redirectAllRequestsTo.protocol) {
if (!is.string(options.redirectAllRequestsTo.protocol)) {
if (typeof options.redirectAllRequestsTo.protocol !== 'string') {
validationErrors.push('redirectAllRequestsTo.protocol must be a string');
}
if (!is.inArray(options.redirectAllRequestsTo.protocol.toLowerCase(), ['http', 'https'])) {
if (!['http', 'https'].includes(options.redirectAllRequestsTo.protocol.toLowerCase())) {
validationErrors.push('redirectAllRequestsTo.protocol must be either http or https');
}
}
}

if (options.routingRules) {
if (!is.array(options.routingRules)) {
if (!Array.isArray(options.routingRules)) {
validationErrors.push('routingRules must be a list');
}

options.routingRules.forEach(r => {
if (!is.existy(r.redirect)) {
if (!r.redirect) {
validationErrors.push('redirect must be specified for each member of routingRules');
}

if (is.existy(r.redirect.replaceKeyPrefixWith) && is.existy(r.redirect.replaceKeyWith)) {
if (r.redirect.replaceKeyPrefixWith && r.redirect.replaceKeyWith) {
validationErrors.push(
'replaceKeyPrefixWith and replaceKeyWith cannot both be specified for a member of member of routingRules'
);
Expand All @@ -137,13 +136,13 @@ function validateClient(serverless, options) {
redirectProps.forEach(p => {
if (r.redirect[p]) {
if (p === 'httpRedirectCode') {
if (!is.integer(r.redirect[p])) {
if (!Number.isInteger(r.redirect[p])) {
validationErrors.push(
'redirect.httpRedirectCode must be an integer for each member of routingRules'
);
}
} else {
if (!is.string(r.redirect[p])) {
if (typeof r.redirect[p] !== 'string') {
validationErrors.push(
`redirect.${p} must be a string for each member of routingRules`
);
Expand All @@ -153,12 +152,7 @@ function validateClient(serverless, options) {
});

if (r.condition) {
if (
!(
is.existy(r.condition.httpErrorCodeReturnedEquals) ||
is.existy(r.condition.keyPrefixEquals)
)
) {
if (!r.condition.httpErrorCodeReturnedEquals && !r.condition.keyPrefixEquals) {
validationErrors.push(
'condition.httpErrorCodeReturnedEquals or condition.keyPrefixEquals must be defined for each member of routingRules'
);
Expand All @@ -168,11 +162,11 @@ function validateClient(serverless, options) {
conditionProps.forEach(p => {
if (r.condition[p]) {
if (p === 'httpErrorCodeReturnedEquals') {
if (!is.integer(r.condition[p])) {
if (!Number.isInteger(r.condition[p])) {
validationErrors.push('httpErrorCodeReturnedEquals must be an integer');
}
} else {
if (!is.string(r.condition[p])) {
if (typeof r.condition[p] !== 'string') {
validationErrors.push(`${p} must be a string`);
}
}
Expand All @@ -187,4 +181,8 @@ function validateClient(serverless, options) {
}
}

function isObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

module.exports = validateClient;
54 changes: 54 additions & 0 deletions lib/validate.test.js
Expand Up @@ -73,6 +73,60 @@ describe('validate', () => {
expect(() => validate(sls, { bucketName, objectHeaders })).not.to.throw();
});

it('throws if redirectAllRequestsTo is missing hostName', () => {
expect(() => validate(sls, { bucketName, redirectAllRequestsTo: {} })).to.throw();
expect(() =>
validate(sls, { bucketName, redirectAllRequestsTo: { hostName: 'example.com' } })
).not.to.throw();
});

it('throws if redirectAllRequestsTo has invalid protocol', () => {
expect(() =>
validate(sls, {
bucketName,
redirectAllRequestsTo: { hostName: 'example.com', protocol: 'ftp' }
})
).to.throw();
expect(() =>
validate(sls, {
bucketName,
redirectAllRequestsTo: { hostName: 'example.com', protocol: true }
})
).to.throw();
expect(() =>
validate(sls, {
bucketName,
redirectAllRequestsTo: { hostName: 'example.com', protocol: 'http' }
})
).not.to.throw();
expect(() =>
validate(sls, {
bucketName,
redirectAllRequestsTo: { hostName: 'example.com', protocol: 'https' }
})
).not.to.throw();
});

it('throws if redirectAllRequestsTo is specified along with other website options', () => {
const redirectAllRequestsTo = {
hostName: 'example.com',
protocol: 'https'
};
expect(() =>
validate(sls, { bucketName, redirectAllRequestsTo, indexDocument: 'index.html' })
).to.throw();
expect(() =>
validate(sls, { bucketName, redirectAllRequestsTo, errorDocument: 'error.html' })
).to.throw();
expect(() =>
validate(sls, {
bucketName,
redirectAllRequestsTo,
routingRules: { redirect: { replaceKeyWith: '' } }
})
).to.throw();
});

it('throws if reading `corsFile` fails or contains invalid json', () => {
readFileSyncStub.withArgs('nonexistent-file.json').throws();
readFileSyncStub.withArgs('invalid-rules.json').returns('not json');
Expand Down

0 comments on commit 8cbf0ef

Please sign in to comment.