mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01f4bcc3e3 | ||
|
|
8441dd2956 | ||
|
|
7db69ac8f2 | ||
|
|
3087b2121e | ||
|
|
2afb28564e | ||
|
|
39f8143326 | ||
|
|
5ed037f8a5 | ||
|
|
3f6445d035 | ||
|
|
02566f0906 | ||
|
|
4bdff73933 | ||
|
|
605f83c36a | ||
|
|
5f757b0741 | ||
|
|
9678b7afee | ||
|
|
a6dc76d52f | ||
|
|
5b8aa22730 | ||
|
|
c9431a8ff3 | ||
|
|
4e453925f7 | ||
|
|
ba68acfb0d | ||
|
|
2e6173fb99 | ||
|
|
d1476ecabd | ||
|
|
5de33ac226 | ||
|
|
d087ae9b35 | ||
|
|
19d17d8610 | ||
|
|
d42b08055b | ||
|
|
b1ab388c96 | ||
|
|
cc55bfb7ba | ||
|
|
67c7670437 | ||
|
|
f271ac9ac1 | ||
|
|
61b4468dac | ||
|
|
d668bd984c | ||
|
|
7cc4ef95c0 | ||
|
|
57e94dc82c | ||
|
|
a254759da8 | ||
|
|
b380cd2bd6 | ||
|
|
86a5ae40e3 | ||
|
|
ce1de0a5ef | ||
|
|
bd0d21af29 | ||
|
|
4ecf0ac10c | ||
|
|
176f1b9085 | ||
|
|
06b8157eea | ||
|
|
5a167a9a46 | ||
|
|
5b40a589c0 | ||
|
|
2c5b77f284 | ||
|
|
173c5d46e3 | ||
|
|
2234a104d4 | ||
|
|
83c0f92553 | ||
|
|
220a13392d | ||
|
|
d7d87a31f0 | ||
|
|
ec1bbc8d74 | ||
|
|
42aacdf62b | ||
|
|
2ccd039497 | ||
|
|
5420c38adf | ||
|
|
4822d2b668 | ||
|
|
6adffb09d0 | ||
|
|
035fcd2615 |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,5 +1,19 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.1.2 (2/19/2016)
|
||||
|
||||
* Change: The S3 file adapter constructor requires a bucket name
|
||||
* Fix: Parse Query should throw if improperly encoded
|
||||
* Fix: Issue where roles were not used in some requests
|
||||
* Fix: serverURL will no longer default to api.parse.com/1
|
||||
|
||||
### 2.1.1 (2/18/2016)
|
||||
|
||||
* Experimental: Schemas API support for DELETE operations
|
||||
* Fix: Session token issue fetching Users
|
||||
* Fix: Facebook auth validation
|
||||
* Fix: Invalid error when deleting missing session
|
||||
|
||||
### 2.1.0 (2/17/2016)
|
||||
|
||||
* Feature: Support for additional OAuth providers
|
||||
|
||||
+1
-4
@@ -12,7 +12,4 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
|
||||
|
||||
##### Code of Conduct
|
||||
|
||||
This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
|
||||
[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com
|
||||
|
||||
|
||||
This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com). By participating, you are expected to honor this code.
|
||||
|
||||
@@ -1,18 +1,56 @@
|
||||
## parse-server
|
||||

|
||||
|
||||
[](https://travis-ci.org/ParsePlatform/parse-server)
|
||||
[](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
|
||||
[](https://www.npmjs.com/package/parse-server)
|
||||
|
||||
A Parse.com API compatible router package for Express
|
||||
Parse Server is an open source version of the Parse backend that can be deployed to any infrastructure that can run Node.js.
|
||||
|
||||
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
|
||||
|
||||
Read the announcement blog post here: http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/
|
||||
|
||||
Read the migration guide here: https://parse.com/docs/server/guide#migrating
|
||||
## Getting Started
|
||||
|
||||
There is a development wiki here on GitHub: https://github.com/ParsePlatform/parse-server/wiki
|
||||
[](https://heroku.com/deploy?template=https://github.com/parseplatform/parse-server-example)
|
||||
[](https://azuredeploy.net/?repository=https://github.com/parseplatform/parse-server-example)
|
||||
<a title="Deploy to AWS" href="https://console.aws.amazon.com/elasticbeanstalk/home?region=us-west-2#/newApplication?applicationName=ParseServer&solutionStackName=Node.js&tierName=WebServer&sourceBundleUrl=https://s3.amazonaws.com/elasticbeanstalk-samples-us-east-1/eb-parse-server-sample/parse-server-example.zip" target="_blank"><img src="http://d0.awsstatic.com/product-marketing/Elastic%20Beanstalk/deploy-to-aws.png" height="40"></a>
|
||||
|
||||
We also have an [example project](https://github.com/ParsePlatform/parse-server-example) using the parse-server module on Express.
|
||||
You can create an instance of ParseServer, and mount it on a new or existing Express website:
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var ParseServer = require('parse-server').ParseServer;
|
||||
var app = express();
|
||||
|
||||
// Specify the connection string for your mongodb database
|
||||
// and the location to your Parse cloud code
|
||||
var api = new ParseServer({
|
||||
databaseURI: 'mongodb://localhost:27017/dev',
|
||||
cloud: '/home/myApp/cloud/main.js', // Provide an absolute path
|
||||
appId: 'myAppId',
|
||||
masterKey: '', //Add your master key here. Keep it secret!
|
||||
fileKey: 'optionalFileKey',
|
||||
serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
|
||||
});
|
||||
|
||||
// Serve the Parse API on the /parse URL prefix
|
||||
app.use('/parse', api);
|
||||
|
||||
app.listen(1337, function() {
|
||||
console.log('parse-server-example running on port 1337.');
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation for Parse Server is available in the [wiki](https://github.com/ParsePlatform/parse-server/wiki) for this repository. The [Parse Server guide](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide) is a good place to get started.
|
||||
|
||||
If you're interested in developing for Parse Server, the [Development guide](https://github.com/ParsePlatform/parse-server/wiki/Development-Guide) will help you get set up.
|
||||
|
||||
### Migration Guide
|
||||
|
||||
The hosted version of Parse will be fully retired on January 28th, 2017. If you are planning to migrate an app, you need to begin work as soon as possible. Learn more in the [Migration guide](https://github.com/ParsePlatform/parse-server/wiki/Migrating-an-Existing-Parse-App).
|
||||
|
||||
---
|
||||
|
||||
@@ -74,7 +112,7 @@ It is possible to leverage the OAuth support with any 3rd party authentication t
|
||||
|
||||
oauth: {
|
||||
my_custom_auth: {
|
||||
module: "PATH_TO_MODULE" // OR object,
|
||||
module: "PATH_TO_MODULE" // OR object,
|
||||
option1: "",
|
||||
option2: "",
|
||||
}
|
||||
@@ -97,56 +135,19 @@ For more informations about custom auth please see the examples:
|
||||
* databaseAdapter (unfinished) - The backing store can be changed by creating an adapter class (see `DatabaseAdapter.js`)
|
||||
* loggerAdapter - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js))
|
||||
* enableAnonymousUsers - Defaults to true. Set to false to disable anonymous users.
|
||||
|
||||
---
|
||||
|
||||
### Usage
|
||||
|
||||
You can create an instance of ParseServer, and mount it on a new or existing Express website:
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var ParseServer = require('parse-server').ParseServer;
|
||||
|
||||
var app = express();
|
||||
|
||||
var port = process.env.PORT || 1337;
|
||||
|
||||
// Specify the connection string for your mongodb database
|
||||
// and the location to your Parse cloud code
|
||||
var api = new ParseServer({
|
||||
databaseURI: 'mongodb://localhost:27017/dev',
|
||||
cloud: '/home/myApp/cloud/main.js', // Provide an absolute path
|
||||
appId: 'myAppId',
|
||||
masterKey: '', //Add your master key here. Keep it secret!
|
||||
fileKey: 'optionalFileKey',
|
||||
serverURL: 'http://localhost:' + port + '/parse' // Don't forget to change to https if needed
|
||||
});
|
||||
|
||||
// Serve the Parse API on the /parse URL prefix
|
||||
app.use('/parse', api);
|
||||
|
||||
// Hello world
|
||||
app.get('/', function(req, res) {
|
||||
res.status(200).send('Express is running here.');
|
||||
});
|
||||
|
||||
app.listen(port, function() {
|
||||
console.log('parse-server-example running on port ' + port + '.');
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Standalone usage
|
||||
|
||||
You can configure the Parse Server with environment variables:
|
||||
|
||||
```js
|
||||
```js
|
||||
PARSE_SERVER_DATABASE_URI
|
||||
PARSE_SERVER_CLOUD_CODE_MAIN
|
||||
PARSE_SERVER_COLLECTION_PREFIX
|
||||
PARSE_SERVER_APPLICATION_ID // required
|
||||
PARSE_SERVER_CLIENT_KEY
|
||||
PARSE_SERVER_CLIENT_KEY
|
||||
PARSE_SERVER_REST_API_KEY
|
||||
PARSE_SERVER_DOTNET_KEY
|
||||
PARSE_SERVER_JAVASCRIPT_KEY
|
||||
@@ -158,8 +159,7 @@ PARSE_SERVER_FACEBOOK_APP_IDS // string of comma separated list
|
||||
```
|
||||
|
||||
|
||||
|
||||
Alernatively, you can use the `PARSE_SERVER_OPTIONS` environment variable set to the JSON of your configuration (see Usage).
|
||||
Alternatively, you can use the `PARSE_SERVER_OPTIONS` environment variable set to the JSON of your configuration (see Usage).
|
||||
|
||||
To start the server, just run `npm start`.
|
||||
|
||||
@@ -192,3 +192,7 @@ You can also set up an app on Parse, providing the connection string for your mo
|
||||
### Not supported
|
||||
|
||||
* `Parse.User.current()` or `Parse.Cloud.useMasterKey()` in cloud code. Instead of `Parse.User.current()` use `request.user` and instead of `Parse.Cloud.useMasterKey()` pass `useMasterKey: true` to each query. To make queries and writes as a specific user within Cloud Code, you need the user's session token, which is available in `request.user.getSessionToken()`.
|
||||
|
||||
## Contributing
|
||||
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.2",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
|
||||
+16
-5
@@ -587,7 +587,7 @@ describe('miscellaneous', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('test cloud function query parameters', (done) => {
|
||||
Parse.Cloud.define('echoParams', (req, res) => {
|
||||
res.success(req.params);
|
||||
@@ -621,8 +621,8 @@ describe('miscellaneous', function() {
|
||||
// Register a function with validation
|
||||
Parse.Cloud.define('functionWithParameterValidation', (req, res) => {
|
||||
res.success('works');
|
||||
}, (params) => {
|
||||
return params.success === 100;
|
||||
}, (request) => {
|
||||
return request.params.success === 100;
|
||||
});
|
||||
|
||||
Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then((s) => {
|
||||
@@ -638,8 +638,8 @@ describe('miscellaneous', function() {
|
||||
// Register a function with validation
|
||||
Parse.Cloud.define('functionWithParameterValidationFailure', (req, res) => {
|
||||
res.success('noway');
|
||||
}, (params) => {
|
||||
return params.success === 100;
|
||||
}, (request) => {
|
||||
return request.params.success === 100;
|
||||
});
|
||||
|
||||
Parse.Cloud.run('functionWithParameterValidationFailure', {"success":500}).then((s) => {
|
||||
@@ -721,4 +721,15 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on invalid function', done => {
|
||||
Parse.Cloud.run('somethingThatDoesDefinitelyNotExist').then((s) => {
|
||||
fail('This should have never suceeded');
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.code).toEqual(Parse.Error.SCRIPT_FAILED);
|
||||
expect(e.message).toEqual('Invalid function.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('Parse Role testing', () => {
|
||||
}).then((x) => {
|
||||
x.set('foo', 'baz');
|
||||
// This should fail:
|
||||
return x.save();
|
||||
return x.save({},{sessionToken: ""});
|
||||
}).then((x) => {
|
||||
fail('Should not have been able to save.');
|
||||
}, (e) => {
|
||||
|
||||
@@ -4,6 +4,9 @@ var cache = require('../src/cache');
|
||||
var Config = require('../src/Config');
|
||||
var rest = require('../src/rest');
|
||||
|
||||
var querystring = require('querystring');
|
||||
var request = require('request');
|
||||
|
||||
var config = new Config('test');
|
||||
var nobody = auth.nobody(config);
|
||||
|
||||
@@ -92,4 +95,49 @@ describe('rest query', () => {
|
||||
}).catch((error) => { console.log(error); });
|
||||
});
|
||||
|
||||
it('query with wrongly encoded parameter', (done) => {
|
||||
rest.create(config, nobody, 'TestParameterEncode', {foo: 'bar'}
|
||||
).then(() => {
|
||||
return rest.create(config, nobody,
|
||||
'TestParameterEncode', {foo: 'baz'});
|
||||
}).then(() => {
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
where: '{"foo":{"$ne": "baz"}}',
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
expect(b.error).toEqual('Improper encode of parameter');
|
||||
done();
|
||||
});
|
||||
}).then(() => {
|
||||
var headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
};
|
||||
request.get({
|
||||
headers: headers,
|
||||
url: 'http://localhost:8378/1/classes/TestParameterEncode?'
|
||||
+ querystring.stringify({
|
||||
limit: 1
|
||||
}).replace('=', '%3D'),
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
expect(b.error).toEqual('Improper encode of parameter');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
var Parse = require('parse/node').Parse;
|
||||
var request = require('request');
|
||||
var dd = require('deep-diff');
|
||||
var Config = require('../src/Config');
|
||||
|
||||
var config = new Config('test');
|
||||
|
||||
var hasAllPODobject = () => {
|
||||
var obj = new Parse.Object('HasAllPOD');
|
||||
@@ -633,4 +636,102 @@ describe('schemas', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('requires the master key to delete schemas', done => {
|
||||
request.del({
|
||||
url: 'http://localhost:8378/1/schemas/DoesntMatter',
|
||||
headers: noAuthHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to delete non-empty collection', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.del({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(255);
|
||||
expect(body.error).toEqual('class HasAllPOD not empty, contains 1 objects, cannot drop schema');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when deleting collections with invalid class names', done => {
|
||||
request.del({
|
||||
url: 'http://localhost:8378/1/schemas/_GlobalConfig',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||
expect(body.error).toEqual('Invalid classname: _GlobalConfig, classnames can only have alphanumeric characters and _, and must start with an alpha character ');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('does not fail when deleting nonexistant collections', done => {
|
||||
request.del({
|
||||
url: 'http://localhost:8378/1/schemas/Missing',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body).toEqual({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes collections including join tables', done => {
|
||||
var obj = new Parse.Object('MyClass');
|
||||
obj.set('data', 'data');
|
||||
obj.save()
|
||||
.then(() => {
|
||||
var obj2 = new Parse.Object('MyOtherClass');
|
||||
var relation = obj2.relation('aRelation');
|
||||
relation.add(obj);
|
||||
return obj2.save();
|
||||
})
|
||||
.then(obj2 => obj2.destroy())
|
||||
.then(() => {
|
||||
request.del({
|
||||
url: 'http://localhost:8378/1/schemas/MyOtherClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toEqual({});
|
||||
config.database.db.collection('test__Join:aRelation:MyOtherClass', { strict: true }, (err, coll) => {
|
||||
//Expect Join table to be gone
|
||||
expect(err).not.toEqual(null);
|
||||
config.database.db.collection('test_MyOtherClass', { strict: true }, (err, coll) => {
|
||||
// Expect data table to be gone
|
||||
expect(err).not.toEqual(null);
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/schemas/MyOtherClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
//Expect _SCHEMA entry to be gone.
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||
expect(body.error).toEqual('class MyOtherClass does not exist');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}, error => {
|
||||
fail(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as AWS from 'aws-sdk';
|
||||
import { FilesAdapter } from './FilesAdapter';
|
||||
|
||||
const DEFAULT_S3_REGION = "us-east-1";
|
||||
const DEFAULT_S3_BUCKET = "parse-files";
|
||||
|
||||
export class S3Adapter extends FilesAdapter {
|
||||
// Creates an S3 session.
|
||||
@@ -15,8 +14,8 @@ export class S3Adapter extends FilesAdapter {
|
||||
constructor(
|
||||
accessKey,
|
||||
secretKey,
|
||||
bucket,
|
||||
{ region = DEFAULT_S3_REGION,
|
||||
bucket = DEFAULT_S3_BUCKET,
|
||||
bucketPrefix = '',
|
||||
directAccess = false } = {}
|
||||
) {
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ Auth.prototype.getUserRoles = function() {
|
||||
return Promise.resolve(this.userRoles);
|
||||
}
|
||||
if (this.rolePromise) {
|
||||
return rolePromise;
|
||||
return this.rolePromise;
|
||||
}
|
||||
this.rolePromise = this._loadRoles();
|
||||
return this.rolePromise;
|
||||
|
||||
@@ -306,7 +306,8 @@ ExportAdapter.prototype.destroy = function(className, query, options = {}) {
|
||||
|
||||
return coll.remove(mongoWhere);
|
||||
}).then((resp) => {
|
||||
if (resp.result.n === 0) {
|
||||
//Check _Session to avoid changing password failed without any session.
|
||||
if (resp.result.n === 0 && className !== "_Session") {
|
||||
return Promise.reject(
|
||||
new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'));
|
||||
|
||||
+24
-10
@@ -27,6 +27,7 @@ function RestWrite(config, auth, className, query, data, originalData) {
|
||||
this.auth = auth;
|
||||
this.className = className;
|
||||
this.storage = {};
|
||||
this.runOptions = {};
|
||||
|
||||
if (!query && data.objectId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
|
||||
@@ -66,6 +67,8 @@ function RestWrite(config, auth, className, query, data, originalData) {
|
||||
// status and location are optional.
|
||||
RestWrite.prototype.execute = function() {
|
||||
return Promise.resolve().then(() => {
|
||||
return this.getUserAndRoleACL();
|
||||
}).then(() => {
|
||||
return this.validateSchema();
|
||||
}).then(() => {
|
||||
return this.handleInstallation();
|
||||
@@ -88,6 +91,25 @@ RestWrite.prototype.execute = function() {
|
||||
});
|
||||
};
|
||||
|
||||
// Uses the Auth object to get the list of roles, adds the user id
|
||||
RestWrite.prototype.getUserAndRoleACL = function() {
|
||||
if (this.auth.isMaster) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.runOptions.acl = ['*'];
|
||||
|
||||
if( this.auth.user ){
|
||||
return this.auth.getUserRoles().then((roles) => {
|
||||
roles.push(this.auth.user.id);
|
||||
this.runOptions.acl = this.runOptions.acl.concat(roles);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}else{
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
// Validates this operation against the schema.
|
||||
RestWrite.prototype.validateSchema = function() {
|
||||
return this.config.database.validateObject(this.className, this.data);
|
||||
@@ -690,18 +712,10 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
||||
throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.');
|
||||
}
|
||||
|
||||
var options = {};
|
||||
if (!this.auth.isMaster) {
|
||||
options.acl = ['*'];
|
||||
if (this.auth.user) {
|
||||
options.acl.push(this.auth.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.query) {
|
||||
// Run an update
|
||||
return this.config.database.update(
|
||||
this.className, this.query, this.data, options).then((resp) => {
|
||||
this.className, this.query, this.data, this.runOptions).then((resp) => {
|
||||
this.response = resp;
|
||||
this.response.updatedAt = this.updatedAt;
|
||||
});
|
||||
@@ -714,7 +728,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
||||
this.data.ACL = ACL;
|
||||
}
|
||||
// Run a create
|
||||
return this.config.database.create(this.className, this.data, options)
|
||||
return this.config.database.create(this.className, this.data, this.runOptions)
|
||||
.then(() => {
|
||||
var resp = {
|
||||
objectId: this.data.objectId,
|
||||
|
||||
@@ -2,11 +2,22 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import rest from '../rest';
|
||||
|
||||
import url from 'url';
|
||||
|
||||
export class ClassesRouter {
|
||||
// Returns a promise that resolves to a {response} object.
|
||||
handleFind(req) {
|
||||
let body = Object.assign(req.body, req.query);
|
||||
let options = {};
|
||||
let allowConstraints = ['skip', 'limit', 'order', 'count', 'keys',
|
||||
'include', 'redirectClassNameForKey', 'where'];
|
||||
|
||||
for (var key in body) {
|
||||
if (allowConstraints.indexOf(key) === -1) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter');
|
||||
}
|
||||
}
|
||||
|
||||
if (body.skip) {
|
||||
options.skip = Number(body.skip);
|
||||
}
|
||||
@@ -54,10 +65,17 @@ export class ClassesRouter {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
|
||||
if(req.params.className === "_User"){
|
||||
if (req.params.className === "_User") {
|
||||
|
||||
delete response.results[0].sessionToken;
|
||||
}
|
||||
|
||||
|
||||
const user = response.results[0];
|
||||
|
||||
if (req.auth.user && user.objectId == req.auth.user.id) {
|
||||
// Force the session token
|
||||
response.results[0].sessionToken = req.info.sessionToken;
|
||||
}
|
||||
}
|
||||
return { response: response.results[0] };
|
||||
});
|
||||
}
|
||||
|
||||
+2
-1
@@ -521,7 +521,7 @@ Schema.prototype.deleteField = function(fieldName, className, database, prefix)
|
||||
});
|
||||
}
|
||||
|
||||
if (schema.data[className][fieldName].startsWith('relation')) {
|
||||
if (schema.data[className][fieldName].startsWith('relation<')) {
|
||||
//For relations, drop the _Join table
|
||||
return database.dropCollection(prefix + '_Join:' + fieldName + ':' + className)
|
||||
//Save the _SCHEMA object
|
||||
@@ -714,6 +714,7 @@ function getObjectType(obj) {
|
||||
module.exports = {
|
||||
load: load,
|
||||
classNameIsValid: classNameIsValid,
|
||||
invalidClassNameMessage: invalidClassNameMessage,
|
||||
mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName,
|
||||
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
||||
buildMergedSchemaObject: buildMergedSchemaObject,
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Helper functions for accessing the Facebook Graph API.
|
||||
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 graphRequest('me?fields=id&access_token=' + authData.access_token)
|
||||
.then((data) => {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Facebook auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills iff this app id is valid.
|
||||
function validateAppId(appIds, authData) {
|
||||
var access_token = authData.access_token;
|
||||
if (!appIds.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Facebook auth is not configured.');
|
||||
}
|
||||
return graphRequest('app?access_token=' + access_token)
|
||||
.then((data) => {
|
||||
if (data && appIds.indexOf(data.id) != -1) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Facebook auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// A promisey wrapper for FB graph requests.
|
||||
function graphRequest(path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get('https://graph.facebook.com/v2.5/' + path, function(res) {
|
||||
var data = '';
|
||||
res.on('data', function(chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
resolve(data);
|
||||
});
|
||||
}).on('error', function(e) {
|
||||
reject('Failed to validate this access token with Facebook.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
+8
-9
@@ -10,10 +10,15 @@ var router = new PromiseRouter();
|
||||
function handleCloudFunction(req) {
|
||||
if (Parse.Cloud.Functions[req.params.functionName]) {
|
||||
|
||||
const params = Object.assign({}, req.body, req.query);
|
||||
|
||||
var request = {
|
||||
params: Object.assign({}, req.body, req.query),
|
||||
master: req.auth && req.auth.isMaster,
|
||||
user: req.auth && req.auth.user,
|
||||
installationId: req.info.installationId
|
||||
};
|
||||
|
||||
if (Parse.Cloud.Validators[req.params.functionName]) {
|
||||
var result = Parse.Cloud.Validators[req.params.functionName](params);
|
||||
var result = Parse.Cloud.Validators[req.params.functionName](request);
|
||||
if (!result) {
|
||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.');
|
||||
}
|
||||
@@ -21,12 +26,6 @@ function handleCloudFunction(req) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var response = createResponseObject(resolve, reject);
|
||||
var request = {
|
||||
params: params,
|
||||
master: req.auth && req.auth.isMaster,
|
||||
user: req.auth && req.auth.user,
|
||||
installationId: req.info.installationId
|
||||
};
|
||||
Parse.Cloud.Functions[req.params.functionName](request, response);
|
||||
});
|
||||
} else {
|
||||
|
||||
+1
-3
@@ -116,9 +116,7 @@ function ParseServer(args) {
|
||||
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(args.appId, args.javascriptKey || '', args.masterKey);
|
||||
if(args.serverURL) {
|
||||
Parse.serverURL = args.serverURL;
|
||||
}
|
||||
Parse.serverURL = args.serverURL || '';
|
||||
|
||||
// This app serves the Parse API directly.
|
||||
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ function handleParseHeaders(req, res, next) {
|
||||
restAPIKey: req.get('X-Parse-REST-API-Key')
|
||||
};
|
||||
|
||||
if (req.body && req.body._noBody) {
|
||||
if (req.body) {
|
||||
// Unity SDK sends a _noBody key which needs to be removed.
|
||||
// Unclear at this point if action needs to be taken.
|
||||
delete req.body._noBody;
|
||||
|
||||
@@ -16,7 +16,8 @@ function validateAuthData(authData) {
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills iff this app id is valid.
|
||||
function validateAppId(appIds, access_token) {
|
||||
function validateAppId(appIds, authData) {
|
||||
var access_token = authData.access_token;
|
||||
if (!appIds.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
|
||||
@@ -56,12 +56,19 @@ function del(config, auth, className, objectId) {
|
||||
});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
}).then(() => {
|
||||
if (!auth.isMaster) {
|
||||
return auth.getUserRoles();
|
||||
}else{
|
||||
return Promise.resolve();
|
||||
}
|
||||
}).then(() => {
|
||||
var options = {};
|
||||
if (!auth.isMaster) {
|
||||
options.acl = ['*'];
|
||||
if (auth.user) {
|
||||
options.acl.push(auth.user.id);
|
||||
options.acl = options.acl.concat(auth.userRoles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,10 +183,95 @@ function modifySchema(req) {
|
||||
});
|
||||
}
|
||||
|
||||
// A helper function that removes all join tables for a schema. Returns a promise.
|
||||
var removeJoinTables = (database, prefix, mongoSchema) => {
|
||||
return Promise.all(Object.keys(mongoSchema)
|
||||
.filter(field => mongoSchema[field].startsWith('relation<'))
|
||||
.map(field => {
|
||||
var joinCollectionName = prefix + '_Join:' + field + ':' + mongoSchema._id;
|
||||
return new Promise((resolve, reject) => {
|
||||
database.dropCollection(joinCollectionName, (err, results) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
function deleteSchema(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
return masterKeyRequiredResponse();
|
||||
}
|
||||
|
||||
if (!Schema.classNameIsValid(req.params.className)) {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: Schema.invalidClassNameMessage(req.params.className),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return req.config.database.collection(req.params.className)
|
||||
.then(coll => new Promise((resolve, reject) => {
|
||||
coll.count((err, count) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (count > 0) {
|
||||
resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: 255,
|
||||
error: 'class ' + req.params.className + ' not empty, contains ' + count + ' objects, cannot drop schema',
|
||||
}
|
||||
});
|
||||
} else {
|
||||
coll.drop((err, reply) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
// We've dropped the collection now, so delete the item from _SCHEMA
|
||||
// and clear the _Join collections
|
||||
req.config.database.collection('_SCHEMA')
|
||||
.then(coll => new Promise((resolve, reject) => {
|
||||
coll.findAndRemove({ _id: req.params.className }, [], (err, doc) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (doc.value === null) {
|
||||
//tried to delete non-existant class
|
||||
resolve({ response: {}});
|
||||
} else {
|
||||
removeJoinTables(req.config.database.db, req.config.database.collectionPrefix, doc.value)
|
||||
.then(resolve, reject);
|
||||
}
|
||||
});
|
||||
}))
|
||||
.then(resolve.bind(undefined, {response: {}}), reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}))
|
||||
.catch(error => {
|
||||
if (error.message == 'ns not found') {
|
||||
// If they try to delete a non-existant class, thats fine, just let them.
|
||||
return Promise.resolve({ response: {} });
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
router.route('GET', '/schemas', getAllSchemas);
|
||||
router.route('GET', '/schemas/:className', getOneSchema);
|
||||
router.route('POST', '/schemas', createSchema);
|
||||
router.route('POST', '/schemas/:className', createSchema);
|
||||
router.route('PUT', '/schemas/:className', modifySchema);
|
||||
router.route('DELETE', '/schemas/:className', deleteSchema);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user