mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0faaec3224 | ||
|
|
021fe4d336 | ||
|
|
5dc24334a5 | ||
|
|
86f35014f4 | ||
|
|
758975cf03 | ||
|
|
a6a6f7ff60 | ||
|
|
305b037176 | ||
|
|
b88b0c578f | ||
|
|
d8ba9e8b7d | ||
|
|
60d506615d | ||
|
|
de36d9640b | ||
|
|
7af320932a | ||
|
|
c253daa9c9 | ||
|
|
09f871b786 | ||
|
|
c0c516271c | ||
|
|
2cea0030f3 | ||
|
|
6979bb430b | ||
|
|
d8f0f9e158 | ||
|
|
27b135b1c2 | ||
|
|
954a8a4159 |
@@ -1,5 +1,20 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.2.23
|
||||
|
||||
* Run liveQuery server from CLI with a different port, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Support for Postgres databaseURI, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Support for Postgres options, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Improved support for google login (id_token and access_token), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Improvements with VKontakte login, thanks to [Eugene Antropov](https://github.com/antigp)
|
||||
* Improved support for `select` and `include`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
* Fix error when updating installation with useMasterKey (#2888), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Fix bug affecting usage of multiple `notEqualTo`, thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Improved support for null values in arrays, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.2.22
|
||||
|
||||
* Minimum nodejs engine is now 4.5
|
||||
|
||||
@@ -110,10 +110,11 @@ We have provided a basic [Node.js application](https://github.com/ParsePlatform/
|
||||
|
||||
* [Heroku and mLab](https://devcenter.heroku.com/articles/deploying-a-parse-server-to-heroku)
|
||||
* [AWS and Elastic Beanstalk](http://mobile.awsblog.com/post/TxCD57GZLM2JR/How-to-set-up-Parse-Server-on-AWS-using-AWS-Elastic-Beanstalk)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
|
||||
* [Google App Engine](https://medium.com/@justinbeckwith/deploying-parse-server-to-google-app-engine-6bc0b7451d50)
|
||||
* [Microsoft Azure](https://azure.microsoft.com/en-us/blog/azure-welcomes-parse-developers/)
|
||||
* [SashiDo](https://blog.sashido.io/tag/migration/)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
|
||||
* [Pivotal Web Services](https://github.com/cf-platform-eng/pws-parse-server)
|
||||
* [Back4app](http://blog.back4app.com/2016/03/01/quick-wizard-migration/)
|
||||
* [HyperDev](https://hyperdev.com/blog/use-parse-server-apps-backend-hyperdev/)
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
BOLD='\033[1m'
|
||||
CHECK="${GREEN}\xE2\x9C\x93${NC}"
|
||||
DEFAULT_MONGODB_URI='mongodb://localhost:127.0.0.1:27017/parse'
|
||||
DEFAULT_MONGODB_URI='mongodb://127.0.0.1:27017/parse'
|
||||
|
||||
confirm() {
|
||||
DEFAULT=$1;
|
||||
@@ -144,7 +144,7 @@ read -r MONGODB_URI
|
||||
|
||||
cat > ./config.json << EOF
|
||||
{
|
||||
"applicationId": "${APP_ID}",
|
||||
"appId": "${APP_ID}",
|
||||
"masterKey": "${MASTER_KEY}",
|
||||
"appName": "${APP_NAME}",
|
||||
"cloud": "./cloud/main",
|
||||
|
||||
+5
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.22",
|
||||
"version": "2.2.23",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -24,13 +24,12 @@
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.14.0",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.16.0",
|
||||
"lodash": "4.16.4",
|
||||
"lru-cache": "4.0.1",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.10",
|
||||
"multer": "1.2.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"parse": "1.9.1",
|
||||
"parse": "1.9.2",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "1.1.0",
|
||||
"parse-server-s3-adapter": "1.0.5",
|
||||
@@ -53,13 +52,13 @@
|
||||
"babel-preset-stage-0": "6.5.0",
|
||||
"babel-register": "6.14.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "3.0.0",
|
||||
"cross-env": "3.1.3",
|
||||
"deep-diff": "0.3.4",
|
||||
"gaze": "1.1.1",
|
||||
"istanbul": "1.0.0-alpha.1",
|
||||
"jasmine": "2.5.2",
|
||||
"mongodb-runner": "3.4.0",
|
||||
"nodemon": "1.10.0",
|
||||
"nodemon": "1.11.0",
|
||||
"request-promise": "4.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -906,6 +906,78 @@ describe('Installations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows you to update installation with masterKey', done => {
|
||||
let installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
let device = 'android';
|
||||
let input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(createResult => {
|
||||
let installationObj = Parse.Installation.createWithoutData(createResult.response.objectId);
|
||||
installationObj.set('customField', 'custom value');
|
||||
return installationObj.save(null, {useMasterKey: true});
|
||||
}).then(updateResult => {
|
||||
expect(updateResult).not.toBeUndefined();
|
||||
expect(updateResult.get('customField')).toEqual('custom value');
|
||||
done();
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
fail('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly handle installation save #2780', done => {
|
||||
let installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
let device = 'android';
|
||||
let input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input).then(() => {
|
||||
let query = new Parse.Query(Parse.Installation);
|
||||
query.equalTo('installationId', installId);
|
||||
query.first({useMasterKey: true}).then((installation) => {
|
||||
return installation.save({
|
||||
key: 'value'
|
||||
}, {useMasterKey: true});
|
||||
}).then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
jfail(err)
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly reject updating installationId', done => {
|
||||
let installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
let device = 'android';
|
||||
let input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input).then(() => {
|
||||
let query = new Parse.Query(Parse.Installation);
|
||||
query.equalTo('installationId', installId);
|
||||
query.first({useMasterKey: true}).then((installation) => {
|
||||
return installation.save({
|
||||
key: 'value',
|
||||
installationId: '22222222-abcd-abcd-abcd-123456789abc'
|
||||
}, {useMasterKey: true});
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(136);
|
||||
expect(err.message).toBe('installationId may not be changed in this operation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Look at additional tests from installation_collection_test.go:882
|
||||
// TODO: Do we need to support _tombstone disabling of installations?
|
||||
// TODO: Test deletion, badge increments
|
||||
|
||||
@@ -1922,4 +1922,67 @@ describe('Parse.Object testing', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle includes on null arrays #2752', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnotherObject");
|
||||
let obj3 = new Parse.Object("NestedObject");
|
||||
obj3.set({
|
||||
"foo": "bar"
|
||||
})
|
||||
obj2.set({
|
||||
"key": obj3
|
||||
})
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("objects", [null, null, obj2]);
|
||||
return obj1.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
query.include("objects.key");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
let obj = res[0];
|
||||
expect(obj.get("objects")).not.toBe(undefined);
|
||||
let array = obj.get("objects");
|
||||
expect(Array.isArray(array)).toBe(true);
|
||||
expect(array[0]).toBe(null);
|
||||
expect(array[1]).toBe(null);
|
||||
expect(array[2].get("key").get("foo")).toEqual("bar");
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle select and include #2786', (done) => {
|
||||
let score = new Parse.Object("GameScore");
|
||||
let player = new Parse.Object("Player");
|
||||
score.set({
|
||||
"score": 1234
|
||||
});
|
||||
|
||||
score.save().then(() => {
|
||||
player.set("gameScore", score);
|
||||
player.set("other", "value");
|
||||
return player.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("Player");
|
||||
query.include("gameScore");
|
||||
query.select("gameScore");
|
||||
return query.find();
|
||||
}).then((res) => {
|
||||
let obj = res[0];
|
||||
let gameScore = obj.get("gameScore");
|
||||
let other = obj.get("other");
|
||||
expect(other).toBeUndefined();
|
||||
expect(gameScore).not.toBeUndefined();
|
||||
expect(gameScore.get("score")).toBe(1234);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,6 +127,22 @@ describe('Parse.Query testing', () => {
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 0);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
query.notEqualTo("hater", user2);
|
||||
query.notEqualTo("liker", user2);
|
||||
// user2 doesn't like any cake so this should be 0
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 0);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
query.equalTo("hater", user);
|
||||
query.equalTo("liker", user);
|
||||
// user doesn't hate any cake so this should be 0
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 0);
|
||||
});
|
||||
}).then(function(){
|
||||
done();
|
||||
}).catch((err) => {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
const parser = require('../src/Adapters/Storage/Postgres/PostgresConfigParser');
|
||||
|
||||
const queryParamTests = {
|
||||
'a=1&b=2': { a: '1', b: '2' },
|
||||
'a=abcd%20efgh&b=abcd%3Defgh': { a: 'abcd efgh', b: 'abcd=efgh' },
|
||||
'a=1&b&c=true': { a: '1', b: '', c: 'true' }
|
||||
}
|
||||
|
||||
describe('PostgresConfigParser.parseQueryParams', () => {
|
||||
it('creates a map from a query string', () => {
|
||||
|
||||
for (const key in queryParamTests) {
|
||||
const result = parser.parseQueryParams(key);
|
||||
|
||||
const testObj = queryParamTests[key];
|
||||
|
||||
expect(Object.keys(result).length)
|
||||
.toEqual(Object.keys(testObj).length);
|
||||
|
||||
for (const k in result) {
|
||||
expect(result[k]).toEqual(testObj[k]);
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
const baseURI = 'postgres://username:password@localhost:5432/db-name'
|
||||
|
||||
const dbOptionsTest = {};
|
||||
dbOptionsTest[`${baseURI}?ssl=true&binary=true&application_name=app_name&fallback_application_name=f_app_name&poolSize=10`] = {
|
||||
ssl: true,
|
||||
binary: true,
|
||||
application_name: 'app_name',
|
||||
fallback_application_name: 'f_app_name',
|
||||
poolSize: 10
|
||||
};
|
||||
dbOptionsTest[`${baseURI}?ssl=&binary=aa`] = {
|
||||
ssl: false,
|
||||
binary: false
|
||||
}
|
||||
|
||||
describe('PostgresConfigParser.getDatabaseOptionsFromURI', () => {
|
||||
it('creates a db options map from a query string', () => {
|
||||
|
||||
for (const key in dbOptionsTest) {
|
||||
const result = parser.getDatabaseOptionsFromURI(key);
|
||||
|
||||
const testObj = dbOptionsTest[key];
|
||||
|
||||
for (const k in testObj) {
|
||||
expect(result[k]).toEqual(testObj[k]);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('sets the poolSize to 10 if the it is not a number', () => {
|
||||
|
||||
const result = parser.getDatabaseOptionsFromURI(`${baseURI}?poolSize=sdf`);
|
||||
|
||||
expect(result.poolSize).toEqual(10);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
const pgp = require('pg-promise')();
|
||||
const parser = require('./PostgresConfigParser');
|
||||
|
||||
export function createClient(uri, databaseOptions) {
|
||||
let client;
|
||||
let dbOptions = {};
|
||||
databaseOptions = databaseOptions || {};
|
||||
|
||||
if (uri) {
|
||||
dbOptions = parser.getDatabaseOptionsFromURI(uri);
|
||||
}
|
||||
|
||||
for (const key in databaseOptions) {
|
||||
dbOptions[key] = databaseOptions[key];
|
||||
}
|
||||
|
||||
client = pgp(dbOptions);
|
||||
|
||||
if (dbOptions.pgOptions) {
|
||||
for (const key in dbOptions.pgOptions) {
|
||||
client.pg.defaults[key] = dbOptions.pgOptions[key];
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
const url = require('url');
|
||||
|
||||
function getDatabaseOptionsFromURI(uri) {
|
||||
const databaseOptions = {};
|
||||
|
||||
const parsedURI = url.parse(uri);
|
||||
const queryParams = parseQueryParams(parsedURI.query);
|
||||
const authParts = parsedURI.auth ? parsedURI.auth.split(':') : [];
|
||||
|
||||
databaseOptions.host = parsedURI.hostname || 'localhost';
|
||||
databaseOptions.port = parsedURI.port ? parseInt(parsedURI.port) : 5432;
|
||||
databaseOptions.database = parsedURI.pathname
|
||||
? parsedURI.pathname.substr(1)
|
||||
: undefined;
|
||||
|
||||
databaseOptions.user = authParts.length > 0 ? authParts[0] : '';
|
||||
databaseOptions.password = authParts.length > 1 ? authParts[1] : '';
|
||||
|
||||
databaseOptions.ssl =
|
||||
queryParams.ssl && queryParams.ssl.toLowerCase() === 'true' ? true : false;
|
||||
databaseOptions.binary =
|
||||
queryParams.binary && queryParams.binary.toLowerCase() === 'true' ? true : false;
|
||||
|
||||
databaseOptions.client_encoding = queryParams.client_encoding;
|
||||
databaseOptions.application_name = queryParams.application_name;
|
||||
databaseOptions.fallback_application_name = queryParams.fallback_application_name;
|
||||
|
||||
if (queryParams.poolSize) {
|
||||
databaseOptions.poolSize = parseInt(queryParams.poolSize) || 10;
|
||||
}
|
||||
|
||||
return databaseOptions;
|
||||
}
|
||||
|
||||
function parseQueryParams(queryString) {
|
||||
queryString = queryString || '';
|
||||
|
||||
return queryString
|
||||
.split('&')
|
||||
.reduce((p, c) => {
|
||||
const parts = c.split('=');
|
||||
p[decodeURIComponent(parts[0])] =
|
||||
parts.length > 1
|
||||
? decodeURIComponent(parts.slice(1).join('='))
|
||||
: '';
|
||||
return p;
|
||||
}, {});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseQueryParams: parseQueryParams,
|
||||
getDatabaseOptionsFromURI: getDatabaseOptionsFromURI
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
const pgp = require('pg-promise')();
|
||||
import { createClient } from './PostgresClient';
|
||||
|
||||
const PostgresRelationDoesNotExistError = '42P01';
|
||||
const PostgresDuplicateRelationError = '42P07';
|
||||
@@ -379,9 +379,10 @@ export class PostgresStorageAdapter {
|
||||
constructor({
|
||||
uri,
|
||||
collectionPrefix = '',
|
||||
databaseOptions
|
||||
}) {
|
||||
this._collectionPrefix = collectionPrefix;
|
||||
this._client = pgp(uri);
|
||||
this._client = createClient(uri, databaseOptions);
|
||||
}
|
||||
|
||||
_ensureSchemaCollectionExists() {
|
||||
|
||||
@@ -676,17 +676,12 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
|
||||
return query;
|
||||
}
|
||||
|
||||
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query) {
|
||||
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null;
|
||||
let allIds = [idsFromNin, ids].filter(list => list !== null);
|
||||
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
||||
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = [], query) {
|
||||
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : [];
|
||||
let allIds = [...idsFromNin,...ids].filter(list => list !== null);
|
||||
|
||||
let idsIntersection = [];
|
||||
if (totalLength > 125) {
|
||||
idsIntersection = intersect.big(allIds);
|
||||
} else {
|
||||
idsIntersection = intersect(allIds);
|
||||
}
|
||||
// make a set and spread to remove duplicates
|
||||
allIds = [...new Set(allIds)];
|
||||
|
||||
// Need to make sure we don't clobber existing shorthand $eq constraints on objectId.
|
||||
if (!('objectId' in query)) {
|
||||
@@ -696,8 +691,8 @@ DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query)
|
||||
$eq: query.objectId
|
||||
};
|
||||
}
|
||||
query.objectId['$nin'] = idsIntersection;
|
||||
|
||||
query.objectId['$nin'] = allIds;
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import AdaptableController from './AdaptableController';
|
||||
import { FilesAdapter } from '../Adapters/Files/FilesAdapter';
|
||||
import path from 'path';
|
||||
import mime from 'mime';
|
||||
import uuid from 'node-uuid';
|
||||
|
||||
const legacyFilesRegex = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*");
|
||||
|
||||
@@ -27,7 +26,7 @@ export class FilesController extends AdaptableController {
|
||||
contentType = mime.lookup(filename);
|
||||
}
|
||||
|
||||
filename = uuid.v4() + '_' + filename;
|
||||
filename = randomHexString(32) + '_' + filename;
|
||||
|
||||
var location = this.adapter.getFileLocation(config, filename);
|
||||
return this.adapter.createFile(filename, data, contentType).then(() => {
|
||||
|
||||
+25
-5
@@ -7,6 +7,7 @@ var batch = require('./batch'),
|
||||
multer = require('multer'),
|
||||
Parse = require('parse/node').Parse,
|
||||
path = require('path'),
|
||||
url = require('url'),
|
||||
authDataManager = require('./authDataManager');
|
||||
|
||||
import defaults from './defaults';
|
||||
@@ -54,6 +55,7 @@ import DatabaseController from './Controllers/DatabaseController';
|
||||
import SchemaCache from './Controllers/SchemaCache';
|
||||
import ParsePushAdapter from 'parse-server-push-adapter';
|
||||
import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||
|
||||
import { ParseServerRESTController } from './ParseServerRESTController';
|
||||
// Mutate the Parse object to add the Cloud Code handlers
|
||||
@@ -145,11 +147,7 @@ class ParseServer {
|
||||
if ((databaseOptions || (databaseURI && databaseURI != defaults.DefaultMongoURI) || collectionPrefix !== '') && databaseAdapter) {
|
||||
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/collectionPrefix.';
|
||||
} else if (!databaseAdapter) {
|
||||
databaseAdapter = new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
mongoOptions: databaseOptions,
|
||||
});
|
||||
databaseAdapter = this.getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions)
|
||||
} else {
|
||||
databaseAdapter = loadAdapter(databaseAdapter)
|
||||
}
|
||||
@@ -252,6 +250,28 @@ class ParseServer {
|
||||
}
|
||||
}
|
||||
|
||||
getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) {
|
||||
let protocol;
|
||||
try{
|
||||
const parsedURI = url.parse(databaseURI);
|
||||
protocol = parsedURI.protocol ? parsedURI.protocol.toLowerCase() : null;
|
||||
}catch(e){}
|
||||
switch (protocol) {
|
||||
case 'postgres:':
|
||||
return new PostgresStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
});
|
||||
default:
|
||||
return new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
mongoOptions: databaseOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get app() {
|
||||
return ParseServer.app(this.config);
|
||||
}
|
||||
|
||||
+8
-5
@@ -488,7 +488,6 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
pointersHash[className].add(pointer.objectId);
|
||||
}
|
||||
}
|
||||
|
||||
let includeRestOptions = {};
|
||||
if (restOptions.keys) {
|
||||
let keys = new Set(restOptions.keys.split(','));
|
||||
@@ -500,10 +499,14 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
set.add(keyPath[i]);
|
||||
if (i < keyPath.length) {
|
||||
set.add(keyPath[i]);
|
||||
}
|
||||
return set;
|
||||
}, new Set());
|
||||
includeRestOptions.keys = Array.from(keySet).join(',');
|
||||
if (keySet.size > 0) {
|
||||
includeRestOptions.keys = Array.from(keySet).join(',');
|
||||
}
|
||||
}
|
||||
|
||||
let queryPromises = Object.keys(pointersHash).map((className) => {
|
||||
@@ -555,7 +558,7 @@ function findPointers(object, path) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
if (typeof object !== 'object') {
|
||||
if (typeof object !== 'object' || !object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -585,7 +588,7 @@ function replacePointers(object, path, replace) {
|
||||
.filter((obj) => typeof obj !== 'undefined');
|
||||
}
|
||||
|
||||
if (typeof object !== 'object') {
|
||||
if (typeof object !== 'object' || !object) {
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -578,8 +578,12 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
this.data.installationId = this.data.installationId.toLowerCase();
|
||||
}
|
||||
|
||||
// If data.installationId is not set, we can lookup in the auth
|
||||
let installationId = this.data.installationId || this.auth.installationId;
|
||||
let installationId = this.data.installationId;
|
||||
|
||||
// If data.installationId is not set and we're not master, we can lookup in auth
|
||||
if (!installationId && !this.auth.isMaster) {
|
||||
installationId = this.auth.installationId;
|
||||
}
|
||||
|
||||
if (installationId) {
|
||||
installationId = installationId.toLowerCase();
|
||||
@@ -635,8 +639,8 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for update.');
|
||||
}
|
||||
if (installationId && objectIdMatch.installationId &&
|
||||
installationId !== objectIdMatch.installationId) {
|
||||
if (this.data.installationId && objectIdMatch.installationId &&
|
||||
this.data.installationId !== objectIdMatch.installationId) {
|
||||
throw new Parse.Error(136,
|
||||
'installationId may not be changed in this ' +
|
||||
'operation');
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
var https = require('https');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
// Returns a promise that fulfills iff this user id is valid.
|
||||
function validateAuthData(authData) {
|
||||
return request("tokeninfo?id_token="+authData.access_token)
|
||||
function validateIdToken(id, token) {
|
||||
return request("tokeninfo?id_token="+token)
|
||||
.then((response) => {
|
||||
if (response && response.sub == authData.id) {
|
||||
if (response && response.sub == id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
@@ -15,7 +14,34 @@ function validateAuthData(authData) {
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills iff this app id is valid.
|
||||
function validateAuthToken(id, token) {
|
||||
return request("tokeninfo?access_token="+token)
|
||||
.then((response) => {
|
||||
if (response && response.user_id == id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Google auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills if this user id is valid.
|
||||
function validateAuthData(authData) {
|
||||
if (authData.id_token) {
|
||||
return validateIdToken(authData.id, authData.id_token);
|
||||
} else {
|
||||
return validateAuthToken(authData.id, authData.access_token).then(() => {
|
||||
// Validation with auth token worked
|
||||
return;
|
||||
}, () => {
|
||||
// Try with the id_token param
|
||||
return validateIdToken(authData.id, authData.access_token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills if this app id is valid.
|
||||
function validateAppId() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -1,28 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
// Helper functions for accessing the vkontakte API.
|
||||
|
||||
var https = require('https');
|
||||
var Parse = require('parse/node').Parse;
|
||||
var logger = require('../logger').default;
|
||||
|
||||
// Returns a promise that fulfills iff this user id is valid.
|
||||
function validateAuthData(authData) {
|
||||
return request("users.get?v=V&access_token=" + authData.access_token).then(function (response) {
|
||||
if (response && response.response && response.response[0].uid == authData.id) {
|
||||
return;
|
||||
function validateAuthData(authData, params) {
|
||||
return vkOAuth2Request(params).then(function (response) {
|
||||
if (response && response && response.access_token) {
|
||||
return request("api.vk.com", "method/secure.checkToken?token=" + authData.access_token + "&client_secret=" + params.appSecret + "&access_token=" + response.access_token).then(function (response) {
|
||||
if (response && response.response && response.response.user_id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.');
|
||||
logger.error('Vk Auth', 'Vk appIds or appSecret is incorrect.');
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk appIds or appSecret is incorrect.');
|
||||
});
|
||||
}
|
||||
|
||||
function vkOAuth2Request(params) {
|
||||
var promise = new Parse.Promise();
|
||||
return promise.then(function(){
|
||||
if (!params || !params.appIds || !params.appIds.length || !params.appSecret || !params.appSecret.length ) {
|
||||
logger.error('Vk Auth', 'Vk auth is not configured. Missing appIds or appSecret.');
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is not configured. Missing appIds or appSecret.');
|
||||
}
|
||||
return request("oauth.vk.com", "access_token?client_id=" + params.appIds + "&client_secret=" + params.appSecret + "&v=5.59&grant_type=client_credentials")
|
||||
})
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills iff this app id is valid.
|
||||
function validateAppId() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// A promisey wrapper for api requests
|
||||
function request(path) {
|
||||
function request(host, path) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
https.get("https://api.vk.com/method/" + path, function (res) {
|
||||
https.get("https://" + host + "/" + path, function (res) {
|
||||
var data = '';
|
||||
res.on('data', function (chunk) {
|
||||
data += chunk;
|
||||
@@ -40,4 +59,4 @@ function request(path) {
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
};
|
||||
@@ -199,7 +199,7 @@ export default {
|
||||
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
|
||||
action: numberOrBoolParser("cluster")
|
||||
},
|
||||
"liveQuery.classNames": {
|
||||
"liveQuery": {
|
||||
help: "parse-server's LiveQuery configuration object",
|
||||
action: objectParser
|
||||
},
|
||||
@@ -214,6 +214,10 @@ export default {
|
||||
help: "Starts the liveQuery server",
|
||||
action: booleanParser
|
||||
},
|
||||
"liveQueryPort": {
|
||||
help: 'Specific port to start the live query server',
|
||||
action: numberParser("liveQueryPort")
|
||||
},
|
||||
"liveQueryServerOptions": {
|
||||
help: "Live query server configuration options (will start the liveQuery server)",
|
||||
action: objectParser
|
||||
|
||||
@@ -35,7 +35,13 @@ function startServer(options, callback) {
|
||||
|
||||
var server = app.listen(options.port, callback);
|
||||
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
|
||||
ParseServer.createLiveQueryServer(server, options.liveQueryServerOptions);
|
||||
let liveQueryServer = server;
|
||||
if (options.liveQueryPort) {
|
||||
liveQueryServer = express().listen(options.liveQueryPort, () => {
|
||||
console.log('ParseLiveQuery listening on ' + options.liveQueryPort);
|
||||
});
|
||||
}
|
||||
ParseServer.createLiveQueryServer(liveQueryServer, options.liveQueryServerOptions);
|
||||
}
|
||||
var handleShutdown = function() {
|
||||
console.log('Termination signal received. Shutting down.');
|
||||
|
||||
Reference in New Issue
Block a user