Compare commits

...
20 Commits
Author SHA1 Message Date
Florent Vilmart 0faaec3224 Release notes for 2.2.23 (#2895) 2016-10-19 18:01:12 -07:00
Marian Ignev 021fe4d336 Add SashiDo Migration guides to README (#2896) 2016-10-19 20:56:43 -04:00
Florent Vilmart 5dc24334a5 Adds ability to run livequery server on different port (appengine) (#2892)
* Adds ability to run livequery server on different port (appengine)
2016-10-19 17:11:25 -07:00
Florent Vilmart 86f35014f4 Fixes #2780 (#2810)
* Adds regression test for #2780

* Fixes #2780

- Make sure we compare installationId from the data and not the auth when rejecting update
2016-10-19 19:54:19 -04:00
Yuki Takeichi 758975cf03 Revert " Saved filename fixed so that clients display it correctly #2415 (#2705)" (#2833)
This reverts commit 22c1a87d80.
2016-10-19 16:55:20 -04:00
Jeremy Louie a6a6f7ff60 Fix error when updating installation with useMasterKey (#2888)
* Add failing test for updating installations with masterKey

* Prevent auth.installationId from being used when using masterKey

This allows masterKey to update any installation object
Fixes ParsePlatform/parse-server##2887
2016-10-19 15:06:19 -04:00
Eugene Antropov 305b037176 Vkontakte Auth: Change users.get to secure.checkToken (#2880)
* Change users.get to secure.checkToken

You can't get user info by client token due vk restrictions. You must check token via secure.checkToken.

* Configuration checks for vk auth.

* Move config check to promise, remove debug log, add message to logger on error.
2016-10-19 10:38:01 -04:00
Jeremy Louie b88b0c578f Fix multiple use of notEqualTo (#2882)
* Add failing test for multiple .notEqualTo on relation with same class

* Fix multiple .notEqualTo on relations with the same class

Multiple  should use the union of all objectIds not the intersect
Fixes ParsePlatform/parse-server#1596
2016-10-18 16:44:47 -04:00
Florent Vilmart d8ba9e8b7d Adds validation for id_token and access_token (#2878)
* ADds validation for id_token and access_token

* nit
2016-10-17 12:44:24 -04:00
Greenkeeper 60d506615d chore(package): update cross-env to version 3.1.3 (#2870)
https://greenkeeper.io/
2016-10-17 10:28:35 -04:00
Kulshekhar Kabra de36d9640b Allow configuration options for Postgres (#2873)
* Allow configuration options for Postgres

* Fix the use of incorrect options object.

* Refactor and test the postgres config parser.

* Remove unnecessary try/catch

* Remove unnecessary try/catch

* Add blank line at the end of the test file

* Rename file for consistency purposes
2016-10-15 17:29:54 -04:00
Kulshekhar Kabra 7af320932a Use the Postgres Adapter with a Postgres URI (#2871)
* Use the Postgres Adapter with a Postgres URI

* Handle malformed databaseURI
2016-10-15 10:22:49 -04:00
Florent Vilmart c253daa9c9 Update bootstrap.sh (#2862)
* Update bootstrap.sh

Fix issue when generating config.json

* Update bootstrap.sh

Fix mongodb URI
2016-10-13 13:05:36 -04:00
Greenkeeper 09f871b786 chore(package): update cross-env to version 3.1.1 (#2824)
https://greenkeeper.io/
2016-10-07 09:36:48 -04:00
Greenkeeper c0c516271c chore(package): update lodash to version 4.16.4 (#2835)
https://greenkeeper.io/
2016-10-07 09:36:11 -04:00
Greenkeeper 2cea0030f3 chore(package): update nodemon to version 1.11.0 (#2839)
https://greenkeeper.io/
2016-10-07 09:36:04 -04:00
Florent Vilmart 6979bb430b Properly handle select/include pairs #2786 (#2809)
* Regression test for 2786

* Fix issue affecting selcting/including keys
2016-10-07 09:33:55 -04:00
Greenkeeper d8f0f9e158 chore(package): update lodash to version 4.16.2 (#2779)
https://greenkeeper.io/
2016-09-27 08:53:24 -04:00
Greenkeeper 27b135b1c2 chore(package): update parse to version 1.9.2 (#2788)
https://greenkeeper.io/
2016-09-27 08:53:17 -04:00
Florent Vilmart 954a8a4159 Better support for null values in arrays (#2777)
* Adds reproduction for #2752

* Make sure we support null values in arrays of pointers for inclusion
2016-09-26 13:26:06 -04:00
20 changed files with 441 additions and 54 deletions
+15
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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": {
+72
View File
@@ -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
+63
View File
@@ -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();
})
});
});
+16
View File
@@ -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) => {
+65
View File
@@ -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() {
+6 -11
View File
@@ -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;
}
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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');
+31 -5
View File
@@ -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();
}
+27 -8
View File
@@ -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
};
};
+5 -1
View File
@@ -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
+7 -1
View File
@@ -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.');