mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a45d1848db | ||
|
|
6381e15e6d | ||
|
|
73195627c0 | ||
|
|
053bbefda9 | ||
|
|
032d5e55ca | ||
|
|
fe5947ce4c | ||
|
|
0e900cbefd | ||
|
|
41358d2226 | ||
|
|
73260897cd | ||
|
|
d3e6c0dea3 | ||
|
|
b4e27e1160 | ||
|
|
79b0c790f0 | ||
|
|
64f608ec9f | ||
|
|
67260b5fe0 | ||
|
|
411daf4d41 |
+1
-1
@@ -12,7 +12,7 @@ addons:
|
||||
apt_packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
before_script:
|
||||
- ls -al "$HOME/.mongodb/versions"
|
||||
- node -e 'require("./lib/index.js")'
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
|
||||
+19
-2
@@ -1,7 +1,24 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.3.4
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.4)
|
||||
### 2.3.6
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.5...2.3.6)
|
||||
|
||||
#### Improvements
|
||||
* Adds support for injecting a middleware for instumentation in the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Alleviate mongodb bug with $or queries [SERVER-13732](https://jira.mongodb.org/browse/SERVER-13732), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
|
||||
|
||||
#### Bug Fixes
|
||||
* Fix issue affecting password policy and empty passwords, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* Fix issue when logging url in non string objects, thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
|
||||
|
||||
#### Dependencies updates:
|
||||
* [ws@2.1.0](https://npmjs.com/package/ws)
|
||||
* [uws@0.13.0](https://npmjs.com/package/uws)
|
||||
* [pg-promise@5.6.2](https://npmjs.com/package/pg-promise)
|
||||
|
||||
|
||||
### 2.3.5
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.5)
|
||||
|
||||
#### Bug Fixes
|
||||
* Allow empty client key
|
||||
|
||||
@@ -217,6 +217,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
|
||||
* `passwordPolicy` - Optional password policy rules to enforce.
|
||||
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
|
||||
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
|
||||
|
||||
##### Logging
|
||||
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.3.4",
|
||||
"version": "2.3.6",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -34,14 +34,14 @@
|
||||
"parse-server-push-adapter": "1.2.0",
|
||||
"parse-server-s3-adapter": "1.0.6",
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "5.5.7",
|
||||
"pg-promise": "5.6.2",
|
||||
"redis": "2.6.5",
|
||||
"request": "2.79.0",
|
||||
"semver": "5.2.0",
|
||||
"tv4": "1.2.7",
|
||||
"winston": "2.3.1",
|
||||
"winston-daily-rotate-file": "1.4.4",
|
||||
"ws": "2.0.3"
|
||||
"ws": "2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "3.1.4",
|
||||
"deep-diff": "0.3.4",
|
||||
"eslint": "^3.10.2",
|
||||
"eslint": "^3.16.1",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"gaze": "1.1.1",
|
||||
"istanbul": "1.0.0-alpha.1",
|
||||
@@ -69,7 +69,7 @@
|
||||
"scripts": {
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"lint": "eslint --cache ./",
|
||||
"build": "babel src/ -d lib/",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"pretest": "npm run lint",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 $COVERAGE_OPTION jasmine",
|
||||
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 jasmine",
|
||||
@@ -86,6 +86,6 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.2",
|
||||
"uws": "^0.12.0"
|
||||
"uws": "^0.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
var FilesAdapter = require("parse-server-fs-adapter").default;
|
||||
var S3Adapter = require("parse-server-s3-adapter").default;
|
||||
var ParsePushAdapter = require("parse-server-push-adapter").default;
|
||||
const Config = require('../src/Config');
|
||||
|
||||
describe("AdapterLoader", ()=>{
|
||||
|
||||
@@ -118,6 +119,30 @@ describe("AdapterLoader", ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load custom push adapter from string (#3544)", (done) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
|
||||
var options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id'
|
||||
}
|
||||
}
|
||||
const pushAdapterOptions = {
|
||||
adapter: adapterPath,
|
||||
options
|
||||
};
|
||||
expect(() => {
|
||||
reconfigureServer({
|
||||
push: pushAdapterOptions,
|
||||
}).then(() => {
|
||||
const config = new Config(Parse.applicationId);
|
||||
const pushAdapter = config.pushWorker.adapter;
|
||||
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
|
||||
expect(pushAdapter.options).toEqual(pushAdapterOptions);
|
||||
done();
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should load S3Adapter from direct passing", (done) => {
|
||||
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||
expect(() => {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
var DatabaseController = require('../src/Controllers/DatabaseController.js');
|
||||
var validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
describe('DatabaseController', function() {
|
||||
|
||||
describe('validateQuery', function() {
|
||||
|
||||
it('should restructure simple cases of SERVER-13732', (done) => {
|
||||
var query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
|
||||
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reject invalid queries', (done) => {
|
||||
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should accept valid queries', (done) => {
|
||||
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = function(options) {
|
||||
return {
|
||||
options: options,
|
||||
send: function() {},
|
||||
getValidPushTypes: function() {
|
||||
return Object.keys(options.options);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -219,6 +219,28 @@ describe("Password Policy: ", () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should fail if password is empty', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
validatorPattern: "^.{8,}" // password should contain at least 8 char
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
user.setUsername("user1");
|
||||
user.setPassword("");
|
||||
user.set('email', 'user1@parse.com');
|
||||
user.signUp().then(() => {
|
||||
fail('Should have failed as password does not conform to the policy.');
|
||||
done();
|
||||
}).catch((error) => {
|
||||
expect(error.message).toEqual('Cannot sign up user with an empty password.');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
|
||||
@@ -18,14 +18,7 @@ function addWriteACL(query, acl) {
|
||||
function addReadACL(query, acl) {
|
||||
const newQuery = _.cloneDeep(query);
|
||||
//Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and
|
||||
if (newQuery.hasOwnProperty('$or')) {
|
||||
newQuery.$or = newQuery.$or.map(function(qobj) {
|
||||
qobj._rperm = {'$in' : [null, '*', ...acl]};
|
||||
return qobj;
|
||||
});
|
||||
} else {
|
||||
newQuery._rperm = { "$in" : [null, "*", ...acl]};
|
||||
}
|
||||
newQuery._rperm = {"$in": [null, "*", ...acl]};
|
||||
return newQuery;
|
||||
}
|
||||
|
||||
@@ -63,6 +56,30 @@ const validateQuery = query => {
|
||||
if (query.$or) {
|
||||
if (query.$or instanceof Array) {
|
||||
query.$or.forEach(validateQuery);
|
||||
|
||||
/* In MongoDB, $or queries which are not alone at the top level of the
|
||||
* query can not make efficient use of indexes due to a long standing
|
||||
* bug known as SERVER-13732.
|
||||
*
|
||||
* This block restructures queries in which $or is not the sole top
|
||||
* level element by moving all other top-level predicates inside every
|
||||
* subdocument of the $or predicate, allowing MongoDB's query planner
|
||||
* to make full use of the most relevant indexes.
|
||||
*
|
||||
* EG: {$or: [{a: 1}, {a: 2}], b: 2}
|
||||
* Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
|
||||
*
|
||||
* https://jira.mongodb.org/browse/SERVER-13732
|
||||
*/
|
||||
Object.keys(query).forEach(key => {
|
||||
const noCollisions = !query.$or.some(subq => subq.hasOwnProperty(key))
|
||||
if (key != '$or' && noCollisions) {
|
||||
query.$or.forEach(subquery => {
|
||||
subquery[key] = query[key];
|
||||
});
|
||||
delete query[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.');
|
||||
}
|
||||
@@ -919,4 +936,6 @@ function joinTableName(className, key) {
|
||||
return `_Join:${key}:${className}`;
|
||||
}
|
||||
|
||||
// Expose validateQuery for tests
|
||||
DatabaseController._validateQuery = validateQuery;
|
||||
module.exports = DatabaseController;
|
||||
|
||||
@@ -41,7 +41,18 @@ export class LoggerController extends AdaptableController {
|
||||
|
||||
// check the url
|
||||
if (e.url) {
|
||||
e.url = this.maskSensitiveUrl(e.url);
|
||||
// for strings
|
||||
if (typeof e.url === 'string') {
|
||||
e.url = this.maskSensitiveUrl(e.url);
|
||||
} else if (Array.isArray(e.url)) { // for strings in array
|
||||
e.url = e.url.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return this.maskSensitiveUrl(item);
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (e.body) {
|
||||
|
||||
+1
-1
@@ -366,7 +366,7 @@ RestWrite.prototype.transformUser = function() {
|
||||
|
||||
return promise.then(() => {
|
||||
// Transform the password
|
||||
if (!this.data.password) {
|
||||
if (this.data.password === undefined) { // ignore only if undefined. should proceed if empty ('')
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -249,4 +249,7 @@ export default {
|
||||
help: "Live query server configuration options (will start the liveQuery server)",
|
||||
action: objectParser
|
||||
},
|
||||
"middleware": {
|
||||
help: "middleware for express server, can be string or function"
|
||||
}
|
||||
};
|
||||
|
||||
+13
-1
@@ -5,6 +5,7 @@ import definitions from './definitions/parse-server';
|
||||
import cluster from 'cluster';
|
||||
import os from 'os';
|
||||
import runner from './utils/runner';
|
||||
const path = require("path");
|
||||
|
||||
const help = function(){
|
||||
console.log(' Get Started guide:');
|
||||
@@ -30,9 +31,20 @@ const help = function(){
|
||||
|
||||
function startServer(options, callback) {
|
||||
const app = express();
|
||||
if (options.middleware) {
|
||||
let middleware;
|
||||
if (typeof options.middleware == 'function') {
|
||||
middleware = options.middleware;
|
||||
} if (typeof options.middleware == 'string') {
|
||||
middleware = require(path.resolve(process.cwd(), options.middleware));
|
||||
} else {
|
||||
throw "middleware should be a string or a function";
|
||||
}
|
||||
app.use(middleware);
|
||||
}
|
||||
|
||||
const api = new ParseServer(options);
|
||||
const sockets = {};
|
||||
|
||||
app.use(options.mountPath, api);
|
||||
|
||||
const server = app.listen(options.port, options.host, callback);
|
||||
|
||||
+16
-7
@@ -14,6 +14,16 @@ var RestQuery = require('./RestQuery');
|
||||
var RestWrite = require('./RestWrite');
|
||||
var triggers = require('./triggers');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some((triggerType) => {
|
||||
return triggers.getTrigger(className, triggers.Types[triggerType], config.applicationId);
|
||||
});
|
||||
}
|
||||
|
||||
function checkLiveQuery(className, config) {
|
||||
return config.liveQueryController && config.liveQueryController.hasLiveQuery(className)
|
||||
}
|
||||
|
||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||
function find(config, auth, className, restWhere, restOptions, clientSDK) {
|
||||
enforceRoleSecurity('find', className, auth);
|
||||
@@ -49,10 +59,9 @@ function del(config, auth, className, objectId) {
|
||||
var inflatedObject;
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) ||
|
||||
triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) ||
|
||||
(config.liveQueryController && config.liveQueryController.hasLiveQuery(className)) ||
|
||||
className == '_Session') {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||
return find(config, Auth.master(config), className, {objectId: objectId})
|
||||
.then((response) => {
|
||||
if (response && response.results && response.results.length) {
|
||||
@@ -108,9 +117,9 @@ function update(config, auth, className, objectId, restObject, clientSDK) {
|
||||
enforceRoleSecurity('update', className, auth);
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
|
||||
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||
|
||||
(config.liveQueryController && config.liveQueryController.hasLiveQuery(className))) {
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery) {
|
||||
return find(config, Auth.master(config), className, {objectId: objectId});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
|
||||
Reference in New Issue
Block a user