Skip to content

Commit 9c41480

Browse files
authored
build: release
2 parents 2830021 + ff5b391 commit 9c41480

10 files changed

+172
-42
lines changed

CONTRIBUTING.md

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
- [Merging](#merging)
2525
- [Breaking Change](#breaking-change-1)
2626
- [Reverting](#reverting)
27+
- [Releasing](#releasing)
28+
- [General Considerations](#general-considerations)
2729
- [Major Release / Long-Term-Support](#major-release--long-term-support)
2830
- [Versioning](#versioning)
2931
- [Code of Conduct](#code-of-conduct)
@@ -379,6 +381,12 @@ If the commit reverts a previous commit, use the prefix `revert:`, followed by t
379381
This reverts commit 1234567890abcdef.
380382
```
381383
384+
## Releasing
385+
386+
### General Considerations
387+
388+
- The `package-lock.json` file has to be deleted and recreated by npm from scratch in regular intervals using the `npm i` command. It is not enough to only update the file via automated security pull requests (e.g. dependabot, snyk), that can create inconsistencies between sub-devependencies of a dependency and increase the chances of vulnerabilities. The file should be recreated once every release cycle which is usually monthly.
389+
382390
### Major Release / Long-Term-Support
383391
384392
Long-Term-Support (LTS) is provided for the previous Parse Server major version. For example, Parse Server 4.x will receive security updates until Parse Server 5.x is superseded by Parse Server 6.x and becomes the new LTS version. While the current major version is published on branch `release`, a LTS version is published on branch `release-#.x.x`, for example `release-4.x.x` for the Parse Server 4.x LTS branch.

changelogs/CHANGELOG_alpha.md

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
# [5.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.2...5.2.0-alpha.3) (2022-03-24)
2+
3+
4+
### Bug Fixes
5+
6+
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
7+
8+
# [5.2.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.1...5.2.0-alpha.2) (2022-03-24)
9+
10+
11+
### Bug Fixes
12+
13+
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
14+
15+
# [5.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-alpha.1) (2022-03-23)
16+
17+
18+
### Features
19+
20+
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
21+
122
# [5.0.0-alpha.29](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.28...5.0.0-alpha.29) (2022-03-12)
223

324

changelogs/CHANGELOG_beta.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
# [5.2.0-beta.2](https://github.com/parse-community/parse-server/compare/5.2.0-beta.1...5.2.0-beta.2) (2022-03-24)
2+
3+
4+
### Bug Fixes
5+
6+
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
7+
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
8+
9+
# [5.2.0-beta.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-beta.1) (2022-03-23)
10+
11+
12+
### Features
13+
14+
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
15+
116
# [5.0.0-beta.10](https://github.com/parse-community/parse-server/compare/5.0.0-beta.9...5.0.0-beta.10) (2022-03-15)
217

318

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "5.1.1",
3+
"version": "5.2.0-beta.2",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/ParseLiveQuery.spec.js

+90
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,41 @@ describe('ParseLiveQuery', function () {
319319
await object.save();
320320
});
321321

322+
it('can log on afterLiveQueryEvent throw', async () => {
323+
await reconfigureServer({
324+
liveQuery: {
325+
classNames: ['TestObject'],
326+
},
327+
startLiveQueryServer: true,
328+
verbose: false,
329+
silent: true,
330+
});
331+
332+
const object = new TestObject();
333+
await object.save();
334+
335+
const logger = require('../lib/logger').logger;
336+
spyOn(logger, 'error').and.callFake(() => {});
337+
338+
let session = undefined;
339+
Parse.Cloud.afterLiveQueryEvent('TestObject', ({ sessionToken }) => {
340+
session = sessionToken;
341+
/* eslint-disable no-undef */
342+
foo.bar();
343+
/* eslint-enable no-undef */
344+
});
345+
346+
const query = new Parse.Query(TestObject);
347+
query.equalTo('objectId', object.id);
348+
const subscription = await query.subscribe();
349+
object.set({ foo: 'bar' });
350+
await object.save();
351+
await new Promise(resolve => subscription.on('error', resolve));
352+
expect(logger.error).toHaveBeenCalledWith(
353+
`Failed running afterLiveQueryEvent on class TestObject for event update with session ${session} with:\n Error: {"message":"foo is not defined","code":141}`
354+
);
355+
});
356+
322357
it('can handle afterEvent sendEvent to false', async done => {
323358
await reconfigureServer({
324359
liveQuery: {
@@ -566,6 +601,33 @@ describe('ParseLiveQuery', function () {
566601
await query.subscribe();
567602
});
568603

604+
it('can log on beforeConnect throw', async () => {
605+
await reconfigureServer({
606+
liveQuery: {
607+
classNames: ['TestObject'],
608+
},
609+
startLiveQueryServer: true,
610+
verbose: false,
611+
silent: true,
612+
});
613+
614+
const logger = require('../lib/logger').logger;
615+
spyOn(logger, 'error').and.callFake(() => {});
616+
let token = undefined;
617+
Parse.Cloud.beforeConnect(({ sessionToken }) => {
618+
token = sessionToken;
619+
/* eslint-disable no-undef */
620+
foo.bar();
621+
/* eslint-enable no-undef */
622+
});
623+
new Parse.Query(TestObject).subscribe();
624+
await new Promise(resolve => Parse.LiveQuery.on('error', resolve));
625+
Parse.LiveQuery.removeAllListeners('error');
626+
expect(logger.error).toHaveBeenCalledWith(
627+
`Failed running beforeConnect for session ${token} with:\n Error: {"message":"foo is not defined","code":141}`
628+
);
629+
});
630+
569631
it('can handle beforeSubscribe error', async done => {
570632
await reconfigureServer({
571633
liveQuery: {
@@ -594,6 +656,34 @@ describe('ParseLiveQuery', function () {
594656
});
595657
});
596658

659+
it('can log on beforeSubscribe error', async () => {
660+
await reconfigureServer({
661+
liveQuery: {
662+
classNames: ['TestObject'],
663+
},
664+
startLiveQueryServer: true,
665+
verbose: false,
666+
silent: true,
667+
});
668+
669+
const logger = require('../lib/logger').logger;
670+
spyOn(logger, 'error').and.callFake(() => {});
671+
672+
Parse.Cloud.beforeSubscribe(TestObject, () => {
673+
/* eslint-disable no-undef */
674+
foo.bar();
675+
/* eslint-enable no-undef */
676+
});
677+
678+
const query = new Parse.Query(TestObject);
679+
const subscription = await query.subscribe();
680+
await new Promise(resolve => subscription.on('error', resolve));
681+
682+
expect(logger.error).toHaveBeenCalledWith(
683+
`Failed running beforeSubscribe on TestObject for session undefined with:\n Error: {"message":"foo is not defined","code":141}`
684+
);
685+
});
686+
597687
it('can handle mutate beforeSubscribe query', async done => {
598688
await reconfigureServer({
599689
liveQuery: {

spec/vulnerabilities.spec.js

+14
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,18 @@ describe('Vulnerabilities', () => {
280280
expect(text.error).toBe('Prohibited keyword in request data: {"value":"aValue[123]*"}.');
281281
});
282282
});
283+
284+
describe('Ignore non-matches', () => {
285+
it('ignores write request that contains only fraction of denied keyword', async () => {
286+
await reconfigureServer({
287+
requestKeywordDenylist: [{ key: 'abc' }],
288+
});
289+
// Initially saving an object executes the keyword detection in RestWrite.js
290+
const obj = new TestObject({ a: { b: { c: 0 } } });
291+
await expectAsync(obj.save()).toBeResolved();
292+
// Modifying a nested key executes the keyword detection in DatabaseController.js
293+
obj.increment('a.b.c');
294+
await expectAsync(obj.save()).toBeResolved();
295+
});
296+
});
283297
});

src/Controllers/DatabaseController.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import intersect from 'intersect';
1111
// @flow-disable-next
1212
import deepcopy from 'deepcopy';
1313
import logger from '../logger';
14+
import Utils from '../Utils';
1415
import * as SchemaController from './SchemaController';
1516
import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
1617
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
@@ -1763,8 +1764,8 @@ class DatabaseController {
17631764
if (this.options && this.options.requestKeywordDenylist) {
17641765
// Scan request data for denied keywords
17651766
for (const keyword of this.options.requestKeywordDenylist) {
1766-
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b;
1767-
if (isMatch(firstKey, keyword.key)) {
1767+
const match = Utils.objectContainsKeyValue({ firstKey: undefined }, keyword.key, undefined);
1768+
if (match) {
17681769
throw new Parse.Error(
17691770
Parse.Error.INVALID_KEY_NAME,
17701771
`Prohibited keyword in request data: ${JSON.stringify(keyword)}.`

src/LiveQuery/ParseLiveQueryServer.js

+13-32
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
1010
import SchemaController from '../Controllers/SchemaController';
1111
import _ from 'lodash';
1212
import { v4 as uuidv4 } from 'uuid';
13-
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
13+
import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers';
1414
import { getAuthForSessionToken, Auth } from '../Auth';
1515
import { getCacheController } from '../Controllers';
1616
import LRU from 'lru-cache';
@@ -194,14 +194,9 @@ class ParseLiveQueryServer {
194194
delete deletedParseObject.authData;
195195
}
196196
client.pushDelete(requestId, deletedParseObject);
197-
} catch (error) {
198-
Client.pushError(
199-
client.parseWebSocket,
200-
error.code || Parse.Error.SCRIPT_FAILED,
201-
error.message || error,
202-
false,
203-
requestId
204-
);
197+
} catch (e) {
198+
const error = resolveError(e);
199+
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
205200
logger.error(
206201
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
207202
JSON.stringify(error)
@@ -358,14 +353,9 @@ class ParseLiveQueryServer {
358353
if (client[functionName]) {
359354
client[functionName](requestId, currentParseObject, originalParseObject);
360355
}
361-
} catch (error) {
362-
Client.pushError(
363-
client.parseWebSocket,
364-
error.code || Parse.Error.SCRIPT_FAILED,
365-
error.message || error,
366-
false,
367-
requestId
368-
);
356+
} catch (e) {
357+
const error = resolveError(e);
358+
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
369359
logger.error(
370360
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
371361
JSON.stringify(error)
@@ -681,13 +671,9 @@ class ParseLiveQueryServer {
681671
logger.info(`Create new client: ${parseWebsocket.clientId}`);
682672
client.pushConnect();
683673
runLiveQueryEventHandlers(req);
684-
} catch (error) {
685-
Client.pushError(
686-
parseWebsocket,
687-
error.code || Parse.Error.SCRIPT_FAILED,
688-
error.message || error,
689-
false
690-
);
674+
} catch (e) {
675+
const error = resolveError(e);
676+
Client.pushError(parseWebsocket, error.code, error.message, false);
691677
logger.error(
692678
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
693679
JSON.stringify(error)
@@ -827,16 +813,11 @@ class ParseLiveQueryServer {
827813
installationId: client.installationId,
828814
});
829815
} catch (e) {
830-
Client.pushError(
831-
parseWebsocket,
832-
e.code || Parse.Error.SCRIPT_FAILED,
833-
e.message || e,
834-
false,
835-
request.requestId
836-
);
816+
const error = resolveError(e);
817+
Client.pushError(parseWebsocket, error.code, error.message, false, request.requestId);
837818
logger.error(
838819
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
839-
JSON.stringify(e)
820+
JSON.stringify(error)
840821
);
841822
}
842823
}

src/Utils.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,9 @@ class Utils {
341341
* @returns {Boolean} True if a match was found, false otherwise.
342342
*/
343343
static objectContainsKeyValue(obj, key, value) {
344-
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b;
345-
const isKeyMatch = k => isMatch(key, k);
346-
const isValueMatch = v => isMatch(value, v);
344+
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b;
345+
const isKeyMatch = k => isMatch(k, key);
346+
const isValueMatch = v => isMatch(v, value);
347347
for (const [k, v] of Object.entries(obj)) {
348348
if (key !== undefined && value === undefined && isKeyMatch(k)) {
349349
return true;

0 commit comments

Comments
 (0)