build: release
This commit is contained in:
@@ -24,6 +24,8 @@
|
|||||||
- [Merging](#merging)
|
- [Merging](#merging)
|
||||||
- [Breaking Change](#breaking-change-1)
|
- [Breaking Change](#breaking-change-1)
|
||||||
- [Reverting](#reverting)
|
- [Reverting](#reverting)
|
||||||
|
- [Releasing](#releasing)
|
||||||
|
- [General Considerations](#general-considerations)
|
||||||
- [Major Release / Long-Term-Support](#major-release--long-term-support)
|
- [Major Release / Long-Term-Support](#major-release--long-term-support)
|
||||||
- [Versioning](#versioning)
|
- [Versioning](#versioning)
|
||||||
- [Code of Conduct](#code-of-conduct)
|
- [Code of Conduct](#code-of-conduct)
|
||||||
@@ -379,6 +381,12 @@ If the commit reverts a previous commit, use the prefix `revert:`, followed by t
|
|||||||
This reverts commit 1234567890abcdef.
|
This reverts commit 1234567890abcdef.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Releasing
|
||||||
|
|
||||||
|
### General Considerations
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
### Major Release / Long-Term-Support
|
### Major Release / Long-Term-Support
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 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))
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 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))
|
||||||
|
|
||||||
|
# [5.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-alpha.1) (2022-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 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))
|
||||||
|
|
||||||
# [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)
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 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))
|
||||||
|
* 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))
|
||||||
|
|
||||||
|
# [5.2.0-beta.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-beta.1) (2022-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 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))
|
||||||
|
|
||||||
# [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)
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "5.1.1",
|
"version": "5.2.0-beta.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10329,9 +10329,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimist-options": {
|
"minimist-options": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "5.1.1",
|
"version": "5.2.0-beta.2",
|
||||||
"description": "An express module providing a Parse-compatible API server",
|
"description": "An express module providing a Parse-compatible API server",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -319,6 +319,41 @@ describe('ParseLiveQuery', function () {
|
|||||||
await object.save();
|
await object.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can log on afterLiveQueryEvent throw', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
liveQuery: {
|
||||||
|
classNames: ['TestObject'],
|
||||||
|
},
|
||||||
|
startLiveQueryServer: true,
|
||||||
|
verbose: false,
|
||||||
|
silent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const object = new TestObject();
|
||||||
|
await object.save();
|
||||||
|
|
||||||
|
const logger = require('../lib/logger').logger;
|
||||||
|
spyOn(logger, 'error').and.callFake(() => {});
|
||||||
|
|
||||||
|
let session = undefined;
|
||||||
|
Parse.Cloud.afterLiveQueryEvent('TestObject', ({ sessionToken }) => {
|
||||||
|
session = sessionToken;
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
foo.bar();
|
||||||
|
/* eslint-enable no-undef */
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = new Parse.Query(TestObject);
|
||||||
|
query.equalTo('objectId', object.id);
|
||||||
|
const subscription = await query.subscribe();
|
||||||
|
object.set({ foo: 'bar' });
|
||||||
|
await object.save();
|
||||||
|
await new Promise(resolve => subscription.on('error', resolve));
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
|
`Failed running afterLiveQueryEvent on class TestObject for event update with session ${session} with:\n Error: {"message":"foo is not defined","code":141}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can handle afterEvent sendEvent to false', async done => {
|
it('can handle afterEvent sendEvent to false', async done => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
liveQuery: {
|
liveQuery: {
|
||||||
@@ -566,6 +601,33 @@ describe('ParseLiveQuery', function () {
|
|||||||
await query.subscribe();
|
await query.subscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can log on beforeConnect throw', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
liveQuery: {
|
||||||
|
classNames: ['TestObject'],
|
||||||
|
},
|
||||||
|
startLiveQueryServer: true,
|
||||||
|
verbose: false,
|
||||||
|
silent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = require('../lib/logger').logger;
|
||||||
|
spyOn(logger, 'error').and.callFake(() => {});
|
||||||
|
let token = undefined;
|
||||||
|
Parse.Cloud.beforeConnect(({ sessionToken }) => {
|
||||||
|
token = sessionToken;
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
foo.bar();
|
||||||
|
/* eslint-enable no-undef */
|
||||||
|
});
|
||||||
|
new Parse.Query(TestObject).subscribe();
|
||||||
|
await new Promise(resolve => Parse.LiveQuery.on('error', resolve));
|
||||||
|
Parse.LiveQuery.removeAllListeners('error');
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
|
`Failed running beforeConnect for session ${token} with:\n Error: {"message":"foo is not defined","code":141}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can handle beforeSubscribe error', async done => {
|
it('can handle beforeSubscribe error', async done => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
liveQuery: {
|
liveQuery: {
|
||||||
@@ -594,6 +656,34 @@ describe('ParseLiveQuery', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can log on beforeSubscribe error', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
liveQuery: {
|
||||||
|
classNames: ['TestObject'],
|
||||||
|
},
|
||||||
|
startLiveQueryServer: true,
|
||||||
|
verbose: false,
|
||||||
|
silent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = require('../lib/logger').logger;
|
||||||
|
spyOn(logger, 'error').and.callFake(() => {});
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSubscribe(TestObject, () => {
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
foo.bar();
|
||||||
|
/* eslint-enable no-undef */
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = new Parse.Query(TestObject);
|
||||||
|
const subscription = await query.subscribe();
|
||||||
|
await new Promise(resolve => subscription.on('error', resolve));
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
|
`Failed running beforeSubscribe on TestObject for session undefined with:\n Error: {"message":"foo is not defined","code":141}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can handle mutate beforeSubscribe query', async done => {
|
it('can handle mutate beforeSubscribe query', async done => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
liveQuery: {
|
liveQuery: {
|
||||||
|
|||||||
@@ -280,4 +280,18 @@ describe('Vulnerabilities', () => {
|
|||||||
expect(text.error).toBe('Prohibited keyword in request data: {"value":"aValue[123]*"}.');
|
expect(text.error).toBe('Prohibited keyword in request data: {"value":"aValue[123]*"}.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Ignore non-matches', () => {
|
||||||
|
it('ignores write request that contains only fraction of denied keyword', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
requestKeywordDenylist: [{ key: 'abc' }],
|
||||||
|
});
|
||||||
|
// Initially saving an object executes the keyword detection in RestWrite.js
|
||||||
|
const obj = new TestObject({ a: { b: { c: 0 } } });
|
||||||
|
await expectAsync(obj.save()).toBeResolved();
|
||||||
|
// Modifying a nested key executes the keyword detection in DatabaseController.js
|
||||||
|
obj.increment('a.b.c');
|
||||||
|
await expectAsync(obj.save()).toBeResolved();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import intersect from 'intersect';
|
|||||||
// @flow-disable-next
|
// @flow-disable-next
|
||||||
import deepcopy from 'deepcopy';
|
import deepcopy from 'deepcopy';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import Utils from '../Utils';
|
||||||
import * as SchemaController from './SchemaController';
|
import * as SchemaController from './SchemaController';
|
||||||
import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
|
import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
|
||||||
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||||
@@ -1763,8 +1764,8 @@ class DatabaseController {
|
|||||||
if (this.options && this.options.requestKeywordDenylist) {
|
if (this.options && this.options.requestKeywordDenylist) {
|
||||||
// Scan request data for denied keywords
|
// Scan request data for denied keywords
|
||||||
for (const keyword of this.options.requestKeywordDenylist) {
|
for (const keyword of this.options.requestKeywordDenylist) {
|
||||||
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b;
|
const match = Utils.objectContainsKeyValue({ firstKey: undefined }, keyword.key, undefined);
|
||||||
if (isMatch(firstKey, keyword.key)) {
|
if (match) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_KEY_NAME,
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
`Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
|
`Prohibited keyword in request data: ${JSON.stringify(keyword)}.`
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
|
|||||||
import SchemaController from '../Controllers/SchemaController';
|
import SchemaController from '../Controllers/SchemaController';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
|
import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers';
|
||||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||||
import { getCacheController } from '../Controllers';
|
import { getCacheController } from '../Controllers';
|
||||||
import LRU from 'lru-cache';
|
import LRU from 'lru-cache';
|
||||||
@@ -194,14 +194,9 @@ class ParseLiveQueryServer {
|
|||||||
delete deletedParseObject.authData;
|
delete deletedParseObject.authData;
|
||||||
}
|
}
|
||||||
client.pushDelete(requestId, deletedParseObject);
|
client.pushDelete(requestId, deletedParseObject);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
Client.pushError(
|
const error = resolveError(e);
|
||||||
client.parseWebSocket,
|
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
|
||||||
error.code || Parse.Error.SCRIPT_FAILED,
|
|
||||||
error.message || error,
|
|
||||||
false,
|
|
||||||
requestId
|
|
||||||
);
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||||
JSON.stringify(error)
|
JSON.stringify(error)
|
||||||
@@ -358,14 +353,9 @@ class ParseLiveQueryServer {
|
|||||||
if (client[functionName]) {
|
if (client[functionName]) {
|
||||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
Client.pushError(
|
const error = resolveError(e);
|
||||||
client.parseWebSocket,
|
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
|
||||||
error.code || Parse.Error.SCRIPT_FAILED,
|
|
||||||
error.message || error,
|
|
||||||
false,
|
|
||||||
requestId
|
|
||||||
);
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
|
||||||
JSON.stringify(error)
|
JSON.stringify(error)
|
||||||
@@ -681,13 +671,9 @@ class ParseLiveQueryServer {
|
|||||||
logger.info(`Create new client: ${parseWebsocket.clientId}`);
|
logger.info(`Create new client: ${parseWebsocket.clientId}`);
|
||||||
client.pushConnect();
|
client.pushConnect();
|
||||||
runLiveQueryEventHandlers(req);
|
runLiveQueryEventHandlers(req);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
Client.pushError(
|
const error = resolveError(e);
|
||||||
parseWebsocket,
|
Client.pushError(parseWebsocket, error.code, error.message, false);
|
||||||
error.code || Parse.Error.SCRIPT_FAILED,
|
|
||||||
error.message || error,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
|
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
|
||||||
JSON.stringify(error)
|
JSON.stringify(error)
|
||||||
@@ -827,16 +813,11 @@ class ParseLiveQueryServer {
|
|||||||
installationId: client.installationId,
|
installationId: client.installationId,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Client.pushError(
|
const error = resolveError(e);
|
||||||
parseWebsocket,
|
Client.pushError(parseWebsocket, error.code, error.message, false, request.requestId);
|
||||||
e.code || Parse.Error.SCRIPT_FAILED,
|
|
||||||
e.message || e,
|
|
||||||
false,
|
|
||||||
request.requestId
|
|
||||||
);
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
|
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
|
||||||
JSON.stringify(e)
|
JSON.stringify(error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,9 +341,9 @@ class Utils {
|
|||||||
* @returns {Boolean} True if a match was found, false otherwise.
|
* @returns {Boolean} True if a match was found, false otherwise.
|
||||||
*/
|
*/
|
||||||
static objectContainsKeyValue(obj, key, value) {
|
static objectContainsKeyValue(obj, key, value) {
|
||||||
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(a).test(b)) || a === b;
|
const isMatch = (a, b) => (typeof a === 'string' && new RegExp(b).test(a)) || a === b;
|
||||||
const isKeyMatch = k => isMatch(key, k);
|
const isKeyMatch = k => isMatch(k, key);
|
||||||
const isValueMatch = v => isMatch(value, v);
|
const isValueMatch = v => isMatch(v, value);
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
if (key !== undefined && value === undefined && isKeyMatch(k)) {
|
if (key !== undefined && value === undefined && isKeyMatch(k)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user