diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2254e4cc9..6a7de82ff9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,6 +114,7 @@ Once you have babel running in watch mode, you can start making changes to parse * Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run: `npm run coverage` * Run the tests for the file you are working on with the following command: `npm test spec/MyFile.spec.js` * Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at */coverage/lcov-report/index.html*. +* Format your code by running `npm run clean`. * Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI. * **Do not** publish the *lib* folder. * Mocks belong in the `spec/support` folder. diff --git a/package.json b/package.json index 6cd3f3bb49..a8d606c387 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ }, "scripts": { "ci:check": "node ./resources/ci/ciCheck.js", + "clean": "npm run prettier && npm run lint-fix", "definitions": "node ./resources/buildConfigDefinitions.js && prettier --write 'src/Options/*.js'", "docs": "jsdoc -c ./jsdoc-conf.json", "lint": "flow && eslint --cache ./", diff --git a/spec/SecurityCheck.spec.js b/spec/SecurityCheck.spec.js index 5f79ca2bbd..647ed909c0 100644 --- a/spec/SecurityCheck.spec.js +++ b/spec/SecurityCheck.spec.js @@ -23,14 +23,20 @@ describe('Security Check', () => { await reconfigureServer(config); } - const securityRequest = (options) => request(Object.assign({ - url: securityUrl, - headers: { - 'X-Parse-Master-Key': Parse.masterKey, - 'X-Parse-Application-Id': Parse.applicationId, - }, - followRedirects: false, - }, options)).catch(e => e); + const securityRequest = options => + request( + Object.assign( + { + url: securityUrl, + headers: { + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Application-Id': Parse.applicationId, + }, + followRedirects: false, + }, + options + ) + ).catch(e => e); beforeEach(async () => { groupName = 'Example Group Name'; @@ -41,7 +47,7 @@ describe('Security Check', () => { solution: 'TestSolution', check: () => { return true; - } + }, }); checkFail = new Check({ group: 'TestGroup', @@ -50,14 +56,14 @@ describe('Security Check', () => { solution: 'TestSolution', check: () => { throw 'Fail'; - } + }, }); Group = class Group extends CheckGroup { setName() { return groupName; } setChecks() { - return [ checkSuccess, checkFail ]; + return [checkSuccess, checkFail]; } }; config = { @@ -154,7 +160,7 @@ describe('Security Check', () => { title: 'string', warning: 'string', solution: 'string', - check: () => {} + check: () => {}, }, { group: 'string', @@ -203,7 +209,9 @@ describe('Security Check', () => { title: 'string', warning: 'string', solution: 'string', - check: () => { throw 'error' }, + check: () => { + throw 'error'; + }, }); expect(check._checkState == CheckState.none); check.run(); @@ -277,7 +285,7 @@ describe('Security Check', () => { }); it('runs all checks of all groups', async () => { - const checkGroups = [ Group, Group ]; + const checkGroups = [Group, Group]; const runner = new CheckRunner({ checkGroups }); const report = await runner.run(); expect(report.report.groups[0].checks[0].state).toBe(CheckState.success); @@ -287,27 +295,27 @@ describe('Security Check', () => { }); it('reports correct default syntax version 1.0.0', async () => { - const checkGroups = [ Group ]; + const checkGroups = [Group]; const runner = new CheckRunner({ checkGroups, enableCheckLog: true }); const report = await runner.run(); expect(report).toEqual({ report: { - version: "1.0.0", - state: "fail", + version: '1.0.0', + state: 'fail', groups: [ { - name: "Example Group Name", - state: "fail", + name: 'Example Group Name', + state: 'fail', checks: [ { - title: "TestTitleSuccess", - state: "success", + title: 'TestTitleSuccess', + state: 'success', }, { - title: "TestTitleFail", - state: "fail", - warning: "TestWarning", - solution: "TestSolution", + title: 'TestTitleFail', + state: 'fail', + warning: 'TestWarning', + solution: 'TestSolution', }, ], }, @@ -319,7 +327,7 @@ describe('Security Check', () => { it('logs report', async () => { const logger = require('../lib/logger').logger; const logSpy = spyOn(logger, 'warn').and.callThrough(); - const checkGroups = [ Group ]; + const checkGroups = [Group]; const runner = new CheckRunner({ checkGroups, enableCheckLog: true }); const report = await runner.run(); const titles = report.report.groups.flatMap(group => group.checks.map(check => check.title)); diff --git a/src/Routers/SecurityRouter.js b/src/Routers/SecurityRouter.js index a9c50ecb8e..c7c217a048 100644 --- a/src/Routers/SecurityRouter.js +++ b/src/Routers/SecurityRouter.js @@ -4,10 +4,12 @@ import CheckRunner from '../Security/CheckRunner'; export class SecurityRouter extends PromiseRouter { mountRoutes() { - this.route('GET', '/security', + this.route( + 'GET', + '/security', middleware.promiseEnforceMasterKeyAccess, this._enforceSecurityCheckEnabled, - async (req) => { + async req => { const report = await new CheckRunner(req.config.security).run(); return { status: 200, diff --git a/src/Security/Check.js b/src/Security/Check.js index 7853fe7cce..dc57d63088 100644 --- a/src/Security/Check.js +++ b/src/Security/Check.js @@ -73,9 +73,9 @@ class Check { * The check state. */ const CheckState = Object.freeze({ - none: "none", - fail: "fail", - success: "success", + none: 'none', + fail: 'fail', + success: 'success', }); export default Check; diff --git a/src/Security/CheckGroups/CheckGroupDatabase.js b/src/Security/CheckGroups/CheckGroupDatabase.js index d0da79a4ba..f9b9340eb1 100644 --- a/src/Security/CheckGroups/CheckGroupDatabase.js +++ b/src/Security/CheckGroups/CheckGroupDatabase.js @@ -8,9 +8,9 @@ import Config from '../../Config'; import Parse from 'parse/node'; /** -* The security checks group for Parse Server configuration. -* Checks common Parse Server parameters such as access keys. -*/ + * The security checks group for Parse Server configuration. + * Checks common Parse Server parameters such as access keys. + */ class CheckGroupDatabase extends CheckGroup { setName() { return 'Database'; @@ -23,7 +23,8 @@ class CheckGroupDatabase extends CheckGroup { new Check({ title: 'Secure database password', warning: 'The database password is insecure and vulnerable to brute force attacks.', - solution: 'Choose a longer and/or more complex password with a combination of upper- and lowercase characters, numbers and special characters.', + solution: + 'Choose a longer and/or more complex password with a combination of upper- and lowercase characters, numbers and special characters.', check: () => { const password = databaseUrl.match(/\/\/\S+:(\S+)@/)[1]; const hasUpperCase = /[A-Z]/.test(password); diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index a0dc41ec47..729551ed7a 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -8,9 +8,9 @@ import Config from '../../Config'; import Parse from 'parse/node'; /** -* The security checks group for Parse Server configuration. -* Checks common Parse Server parameters such as access keys. -*/ + * The security checks group for Parse Server configuration. + * Checks common Parse Server parameters such as access keys. + */ class CheckGroupServerConfig extends CheckGroup { setName() { return 'Parse Server Configuration'; @@ -21,7 +21,8 @@ class CheckGroupServerConfig extends CheckGroup { new Check({ title: 'Secure master key', warning: 'The Parse Server master key is insecure and vulnerable to brute force attacks.', - solution: 'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.', + solution: + 'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.', check: () => { const masterKey = config.masterKey; const hasUpperCase = /[A-Z]/.test(masterKey); @@ -41,7 +42,7 @@ class CheckGroupServerConfig extends CheckGroup { new Check({ title: 'Security log disabled', warning: 'Security checks in logs may expose vulnerabilities to anyone access to logs.', - solution: 'Change Parse Server configuration to \'security.enableCheckLog: false\'.', + solution: "Change Parse Server configuration to 'security.enableCheckLog: false'.", check: () => { if (config.security && config.security.enableCheckLog) { throw 1; @@ -50,8 +51,9 @@ class CheckGroupServerConfig extends CheckGroup { }), new Check({ title: 'Client class creation disabled', - warning: 'Attackers are allowed to create new classes without restriction and flood the database.', - solution: 'Change Parse Server configuration to \'allowClientClassCreation: false\'.', + warning: + 'Attackers are allowed to create new classes without restriction and flood the database.', + solution: "Change Parse Server configuration to 'allowClientClassCreation: false'.", check: () => { if (config.allowClientClassCreation || config.allowClientClassCreation == null) { throw 1; diff --git a/src/Security/CheckRunner.js b/src/Security/CheckRunner.js index 2e522fefcb..a662ffbad4 100644 --- a/src/Security/CheckRunner.js +++ b/src/Security/CheckRunner.js @@ -46,7 +46,7 @@ class CheckRunner { // If report should be written to logs if (this.enableCheckLog) { - this._logReport(report) + this._logReport(report); } return report; } @@ -85,8 +85,8 @@ class CheckRunner { report: { version, state: CheckState.success, - groups: [] - } + groups: [], + }, }; // Identify report version @@ -95,13 +95,12 @@ class CheckRunner { default: // For each check group for (const group of groups) { - // Create group report const groupReport = { name: group.name(), state: CheckState.success, checks: [], - } + }; // Create check reports groupReport.checks = group.checks().map(check => { @@ -129,9 +128,9 @@ class CheckRunner { * @param {Object} report The report to log. */ _logReport(report) { - // Determine log level depending on whether any check failed - const log = report.report.state == CheckState.success ? (s) => logger.info(s) : (s) => logger.warn(s); + const log = + report.report.state == CheckState.success ? s => logger.info(s) : s => logger.warn(s); // Declare output const indent = ' '; @@ -142,7 +141,7 @@ class CheckRunner { // Traverse all groups and checks for compose output for (const group of report.report.groups) { - output += `\n- ${group.name}` + output += `\n- ${group.name}`; for (const check of group.checks) { checksCount++; @@ -166,7 +165,9 @@ class CheckRunner { `\n# #` + `\n###################################` + `\n` + - `\n${failedChecksCount > 0 ? 'Warning: ' : ''}${failedChecksCount} weak security setting(s) found${failedChecksCount > 0 ? '!' : ''}` + + `\n${ + failedChecksCount > 0 ? 'Warning: ' : '' + }${failedChecksCount} weak security setting(s) found${failedChecksCount > 0 ? '!' : ''}` + `\n${checksCount} check(s) executed` + `\n${skippedCheckCount} check(s) skipped` + `\n` + @@ -183,9 +184,12 @@ class CheckRunner { */ _getLogIconForState(state) { switch (state) { - case CheckState.success: return '✅'; - case CheckState.fail: return '❌'; - default: return 'ℹ️'; + case CheckState.success: + return '✅'; + case CheckState.fail: + return '❌'; + default: + return 'ℹ️'; } } diff --git a/src/Utils.js b/src/Utils.js index a7833f5727..6042da416c 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -178,7 +178,7 @@ class Utils { const type = types[key]; const isOptional = !!type.o; const param = params[key]; - if (!(isOptional && param == null) && (!type.v(param))) { + if (!(isOptional && param == null) && !type.v(param)) { throw `Invalid parameter ${key} must be of type ${type.t} but is ${typeof param}`; } }