Unverified Commit 2a4330bc authored by Philipp Berger's avatar Philipp Berger
Browse files

chore: release v2.0.0

parent 56fd0b58
# Changelog
### 2.0.0 (2021-09-10)
* **backend:** fix: strict JWT schema check for signing app
* **backend:** fix: netmask of ipv6 addresses
* **backend:** feat: add v4 notification endpoint
* **backend:** feat: restrict location transfers endpoint
* **backend:** feat: add health department account based rate limits
* **backend:** feat: add rate limits for notifications and bloomfilter endpoints
* **backend:** feat: add phone number and email to health departments
* **backend:** feat: deny daily key rotation from unsigned health departments
* **backend:** perf: disable default etag generation
* **locations:** fix: redirection to login after successful share data request
* **locations:** fix: typo in authentication footer
* **locations:** fix: time display in completed share data requests
* **locations:** fix: values in csv download for qr codes
* **health-department:** fix: mismatch between list entries and counter
* **health-department:** fix: view update after venue owner completes share data request
* **health-department:** fix: misleading location name property in proccess details
* **health-department:** feat: improved usability in the timepicker when searching for locations
* **health-department:** feat: add note to tracing processes
* **health-department:** feat: possibility to trigger notifications for a specific location
* **health-department:** feat: possibility to trigger notifications for specific contacts
* **health-department:** feat: possibility to add public contact informations
* **health-department:** feat: possibility for admins to download the audit logfile
* **webapp:** fix: user transfer creation before approval
### 1.9.2 (2021-09-01)
* **locations:** fix: domain specific email validation
* **scanner:** feat: deny check ins of v3 badges
......@@ -8,7 +33,6 @@
* **health-department:** fix: handling of unregistered badges in tracing processes
### 1.9.0 (2021-08-18)
* **backend:** fix: openAPI JSON
* **backend:** feat: send email to operator after approved location transfer
* **backend:** feat: add note to tracing proccess
......
......@@ -20,3 +20,6 @@ tsconfig.json
cypress/fixtures
cypress/screenshots
# certificates
certs/*.pfx
\ No newline at end of file
......@@ -28,13 +28,14 @@ function generateCameraStream(path) {
}
function getConfigurationByFile(file) {
const pathToConfigFile = path.resolve('..', 'e2e/config', `${file}.json`)
const pathToConfigFile = path.resolve('..', 'e2e/config', `${file}.json`);
console.log('Path to config:' + pathToConfigFile);
console.log('Read config:' + pathToConfigFile);
return fse.readJson(pathToConfigFile)
return fse.readJson(pathToConfigFile);
}
module.exports = (on, config) => {
require('cypress-fail-fast/plugin')(on, config);
on('task', {
setCameraImage: async path => {
await generateCameraStream(path);
......@@ -52,7 +53,7 @@ module.exports = (on, config) => {
}
return false;
},
dbQuery: query => postgres(query.query, query.connection)
dbQuery: query => postgres(query.query, query.connection),
});
on('before:browser:launch', async (browser = {}, launchOptions) => {
......@@ -63,7 +64,7 @@ module.exports = (on, config) => {
launchOptions.args.push(
'--use-file-for-fake-video-capture=/tmp/luca/stream.mjpeg'
);
launchOptions.preferences.default["download"] = {
launchOptions.preferences.default['download'] = {
default_directory: path.join(__dirname, 'downloads'),
};
}
......@@ -71,6 +72,6 @@ module.exports = (on, config) => {
});
//switching between envs
const file = config.env.configFile || 'local'
return getConfigurationByFile(file)
const file = config.env.configFile || 'local';
return getConfigurationByFile(file);
};
{
"name": "e2e",
"version": "1.9.2",
"version": "2.0.0",
"main": "index.js",
"private": true,
"engines": {
......@@ -19,9 +19,12 @@
"faker": "5.5.3",
"fluent-ffmpeg": "2.1.2",
"fs-extra": "10.0.0",
"moment": "2.29.1"
"jsonwebtoken": "8.5.1",
"moment": "2.29.1",
"node-forge": "0.10.0"
},
"devDependencies": {
"cypress-fail-fast": "3.1.0",
"cypress-postgres": "1.1.1",
"eslint-plugin-sonarjs": "0.5.0",
"prettier": "2.2.1"
......
import forge, { pkcs12 } from 'node-forge';
import jwt from 'jsonwebtoken';
const JWT_ALGORITHM = 'RS512';
const PASSWORD = 'testing';
const decryptPkcs12 = p12file => {
const p12Asn1 = forge.asn1.fromDer(p12file);
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, PASSWORD);
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag })[
forge.pki.oids.certBag
];
if (!certBags || certBags.length === 0) {
throw new Error('Certificate not found');
}
const privateKeyBags = p12.getBags({
bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
})[forge.pki.oids.pkcs8ShroudedKeyBag];
if (!privateKeyBags || privateKeyBags.length === 0) {
throw new Error('Private Key not found');
}
const certBag = certBags[0];
const privateKeyBag = privateKeyBags[0];
if (!certBag.cert) {
throw new Error('Certificate not found');
}
if (!privateKeyBag.key) {
throw new Error('Private key not found');
}
const certDer = forge.asn1.toDer(forge.pki.certificateToAsn1(certBag.cert));
const md = forge.md.sha1.create();
md.update(certDer.data);
const fingerprint = md.digest().toHex();
const commonName = certBag.cert.subject.getField('CN')?.value;
const serialName = certBag.cert.subject.getField({ name: 'serialName' })
?.value;
const cert = forge.pki.certificateToPem(certBag.cert);
const publicKey = forge.pki.publicKeyToPem(certBag.cert.publicKey);
const privateKey = forge.pki.privateKeyToPem(privateKeyBag.key);
return {
cert: cert.replace(/\r\n/g, '\n'),
publicKey: publicKey.replace(/\r\n/g, '\n'),
privateKey: privateKey.replace(/\r\n/g, '\n'),
fingerprint,
commonName,
serialName,
};
};
const createSignedPublicKeys = (healthDepartment, p12file) => {
const pkcs12 = decryptPkcs12(p12file);
const signedPublicHDSKP = jwt.sign(
{
sub: healthDepartment.uuid, // health department uuid
iss: pkcs12.fingerprint, // sha1 fingerprint of cert (hex)
name: healthDepartment.name, // name of the health department
key: healthDepartment.publicHDSKP, // public key (base64)
type: 'publicHDSKP', // key type (base64)
},
pkcs12.privateKey,
{ algorithm: JWT_ALGORITHM }
);
const signedPublicHDEKP = jwt.sign(
{
sub: healthDepartment.uuid, // health department uuid
iss: pkcs12.fingerprint, // sha1 fingerprint of cert (hex)
name: healthDepartment.name, // name of the health department
key: healthDepartment.publicHDEKP, // public key (base64)
type: 'publicHDEKP', // key type (base64)
},
pkcs12.privateKey,
{ algorithm: JWT_ALGORITHM }
);
return {
publicCertificate: pkcs12.cert,
signedPublicHDSKP,
signedPublicHDEKP,
};
};
export const signHealthDepartment = async () => {
cy.request('GET', '/api/v3/auth/healthDepartmentEmployee/me')
.then(response => response.body.departmentId)
.then(departmentId =>
cy
.request('GET', `/api/v4/healthDepartments/${departmentId}`)
.then(response => response.body)
.then(healthDepartment =>
cy.readFile('./certs/health.pfx', 'binary').then(pkcs12 => {
const signedPublicKeys = createSignedPublicKeys(
healthDepartment,
pkcs12
);
cy.request(
'POST',
'/api/internal/end2end/signHealthDepartment',
signedPublicKeys
);
})
)
);
};
export const setDatePickerTime = () => {
cy.get('.ant-picker-dropdown').not('.ant-picker-dropdown-hidden').should('exist').within(() => {
cy.get('.ant-picker-time-panel-cell').eq(0).click().type('{enter}');
cy.get('.ant-picker-dropdown')
.not('.ant-picker-dropdown-hidden')
.should('exist')
.within(() => {
cy.get('.ant-picker-time-panel-cell').eq(0).click().type('{enter}');
});
};
export const setDatePickerStartDate = (startDate) => {
cy.get('#startDate').should('exist').should('be.visible').click().type(`${startDate}{enter}`);
cy.get('#startTime').should('exist').should('be.visible').click();
setDatePickerTime();
export const setDatePickerStartDate = startDate => {
cy.get('#startDate')
.should('exist')
.should('be.visible')
.click()
.type(`${startDate}{enter}`);
cy.get('#startTime').should('exist').should('be.visible').click();
setDatePickerTime();
};
export const setDatePickerEndDate = (endDate) => {
cy.get('#endDate').should('exist').click({ force: true }).type(`${endDate}{enter}`);
cy.get('#endTime').should('exist').click();
setDatePickerTime();
};
\ No newline at end of file
export const setDatePickerEndDate = endDate => {
cy.get('#endDate')
.should('exist')
.should('be.visible')
.click({ force: true })
.type(`${endDate}{enter}`);
cy.get('#endTime').should('exist').should('be.visible').click();
setDatePickerTime();
};
......@@ -10,7 +10,7 @@ import {
import { removeLocation } from '../location/location.helper';
describe('Download QR Codes PDF', {retries: 3}, () => {
describe('Download QR Codes PDF', { retries: 3 }, () => {
beforeEach(() => login());
after(() => removeLocation(NEW_RESTAURANT_LOCATION));
......@@ -18,7 +18,7 @@ describe('Download QR Codes PDF', {retries: 3}, () => {
it('downloads the Group PDF', () => {
cy.getByCy('createGroup').click();
cy.getByCy('restaurant').click();
cy.get('#groupName', {timeout: 20000}).type(RESTAURANT_NAME);
cy.get('#groupName', { timeout: 20000 }).type(RESTAURANT_NAME);
cy.get('form').submit();
cy.get('#locationSearch').type(RESTAURANT_ADDRESS);
cy.get('.pac-container > div:first-of-type').click({ force: true });
......@@ -33,10 +33,13 @@ describe('Download QR Codes PDF', {retries: 3}, () => {
cy.getByCy('download').click();
cy.get('.ant-message-notice').should('exist');
cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf', {timeout: 30000}
cy.readFile('./downloads/luca_QRCode_Test_Restaurant.pdf', {
timeout: 30000,
});
cy.task(
'deleteFileIfExists',
'./downloads/luca_QRCode_Test_Restaurant.pdf'
);
cy.task('deleteFileIfExists', './downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf');
});
});
......@@ -59,9 +62,12 @@ describe('Download QR Codes PDF', {retries: 3}, () => {
cy.getByCy('qrCodeDownload').click();
cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf'
'./downloads/luca_QRCode_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION.pdf'
);
cy.task(
'deleteFileIfExists',
'./downloads/luca_QRCode_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION.pdf'
);
cy.task('deleteFileIfExists', './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf');
});
});
......@@ -74,9 +80,12 @@ describe('Download QR Codes PDF', {retries: 3}, () => {
cy.getByCy('qrCodeDownload').click();
cy.get('.ant-message-notice', { timeout: 60000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCodes_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION_Tables.pdf'
'./downloads/luca_QRCodes_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION_Tables.pdf'
);
cy.task(
'deleteFileIfExists',
'./downloads/luca_QRCodes_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION_Tables.pdf'
);
cy.task('deleteFileIfExists', './downloads/luca_QRCodes_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION_Tables.pdf');
});
it('downloads the Location PDF if tables are configured', () => {
......@@ -92,10 +101,13 @@ describe('Download QR Codes PDF', {retries: 3}, () => {
cy.getByCy('qrCodeDownload').click();
cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf',
'./downloads/luca_QRCode_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION.pdf',
{ timeout: 20000 }
);
cy.task('deleteFileIfExists', './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf');
cy.task(
'deleteFileIfExists',
'./downloads/luca_QRCode_Nexenio_1_e2e_NEW_RESTAURANT_LOCATION.pdf'
);
});
});
});
......@@ -31,7 +31,6 @@ export const basicLocationLogin = (
origin: 'https://localhost',
},
});
cy.server();
cy.intercept({ method: 'GET', url: '**/me' }).as('me');
cy.visit(APP_ROUTE);
cy.window().then(window => {
......@@ -139,7 +138,10 @@ export const undoAccountDeletion = () => {
};
export const downloadLocationPrivateKeyFile = () => {
cy.getByCy('downloadPrivateKey', { timeout: 8000 }).click();
cy.getByCy('downloadPrivateKey', { timeout: 8000 })
.should('exist')
.should('be.visible')
.click();
cy.getByCy('checkPrivateKeyIsDownloaded').click();
cy.getByCy('next').should('exist').click();
};
......
......@@ -7,6 +7,7 @@ export const removeLocation = locationName => {
cy.getByCy(`location-${locationName}`).click();
cy.getByCy('openSettings').click();
cy.getByCy('deleteLocation').click();
cy.get('.ant-popover-buttons .ant-btn-primary').click();
cy.wait('@deleteLocation');
......
......@@ -11,16 +11,18 @@ import { registerDevice } from '../helpers/functions';
import { clean } from '../../workflow/helpers/functions';
import { addHealthDepartmentPrivateKeyFile } from '../../health-department/helper/ui/login.helper';
import { loginHealthDepartment } from '../../health-department/helper/api/auth.helper';
import { signHealthDepartment } from '../../health-department/helper/signHealthDepartment';
import { WEBAPP_ROUTE, LOCATIONS_ROUTE } from '../helpers/routes';
import {E2E_EMAIL, E2E_PASSWORD} from "../../locations/helpers/users";
import { E2E_EMAIL, E2E_PASSWORD } from '../../locations/helpers/users';
describe('WebApp / CheckIn', {retries: 3}, () => {
describe('WebApp / CheckIn', { retries: 3 }, () => {
before(() => {
clean();
basicLocationLogin();
createGroup(createGroupPayload, false);
logout();
loginHealthDepartment();
signHealthDepartment();
addHealthDepartmentPrivateKeyFile();
cy.wait(1000);
registerDevice();
......@@ -39,7 +41,7 @@ describe('WebApp / CheckIn', {retries: 3}, () => {
cy.get('@scannerId').then(scannerId => {
cy.visit(`${WEBAPP_ROUTE}/${scannerId}`);
});
cy.url({timeout: 4000}).should('contain', '/checkout');
cy.url({ timeout: 4000 }).should('contain', '/checkout');
cy.getByCy('locationName').should('contain', createGroupPayload.name);
// Simulate clock
......
......@@ -3,6 +3,7 @@ import moment from 'moment';
import { fillForm } from '../../contact-form/helpers/functions';
import { loginHealthDepartment } from '../../health-department/helper/api/auth.helper';
import { signHealthDepartment } from '../../health-department/helper/signHealthDepartment';
import { addHealthDepartmentPrivateKeyFile } from '../../health-department/helper/ui/login.helper';
......@@ -43,6 +44,7 @@ context('Workflow', () => {
// SETUP Health department
cy.log('Setup Health department');
loginHealthDepartment();
signHealthDepartment();
addHealthDepartmentPrivateKeyFile();
// We need to wait for the key rotation logic
cy.wait(1000);
......@@ -116,7 +118,6 @@ context('Workflow', () => {
WORKFLOW_LOCATION_PRIVATE_KEY_NAME
);
cy.getByCy('next').click();
cy.getByCy('finish').click();
// Check requested data in Health department
cy.log('Check requested data in Health department');
......
......@@ -17,6 +17,7 @@
import './commands';
import 'cypress-file-upload';
import "cypress-fail-fast";
beforeEach(() => {
cy.intercept('/', req => {
......
......@@ -255,6 +255,11 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-writer@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
......@@ -270,6 +275,14 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@4.1.1, chalk@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^1.0.0, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
......@@ -290,14 +303,6 @@ chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
check-more-types@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
......@@ -410,6 +415,13 @@ cross-spawn@^7.0.0:
shebang-command "^2.0.0"
which "^2.0.1"
cypress-fail-fast@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cypress-fail-fast/-/cypress-fail-fast-3.1.0.tgz#e81c5414d7246d97341a09fe3740f792063318bc"
integrity sha512-KvzFyBgwNWQZgrBAP+9fX7AA50mtCEkZi4kEpDbDE9sR7lD/q3L15MtL2R5WUbChAW46Et87dZx2iMMbhUEWIQ==
dependencies:
chalk "4.1.1"
cypress-file-upload@5.0.7:
version "5.0.7"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.7.tgz#acf24fe08a92b2d0c892a58b56811fb933d34ea9"
......@@ -511,6 +523,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
elegant-spinner@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
......@@ -934,6 +953,22 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jsonwebtoken@8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
......@@ -944,6 +979,23 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
lazy-ass@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
......@@ -993,7 +1045,37 @@ listr@^0.14.3:
p-map "^2.0.0"
rxjs "^6.3.3"
lodash.once@^4.1.1:
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.once@^4.0.0, lodash.once@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
......@@ -1382,6 +1464,11 @@ semver@4.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
shebang-command@^2.0.0: