Skip to content

Commit 6b91b98

Browse files
committed
Merge remote-tracking branch 'upstream/master' into encode_uri
2 parents 15ab259 + 477a091 commit 6b91b98

15 files changed

+229
-59
lines changed

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
### Contributing to Parse Server
2+
3+
#### Pull Requests Welcome!
4+
5+
We really want Parse to be yours, to see it grow and thrive in the open source community.
6+
7+
##### Please Do's
8+
9+
* Please write tests to cover new methods.
10+
* Please run the tests and make sure you didn't break anything.
11+
12+
##### Code of Conduct
13+
14+
This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
15+
[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Parse Server/[email protected]
16+
17+

DatabaseAdapter.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var adapter = ExportAdapter;
2020
var cache = require('./cache');
2121
var dbConnections = {};
2222
var databaseURI = 'mongodb://localhost:27017/parse';
23+
var appDatabaseURIs = {};
2324

2425
function setAdapter(databaseAdapter) {
2526
adapter = databaseAdapter;
@@ -29,11 +30,17 @@ function setDatabaseURI(uri) {
2930
databaseURI = uri;
3031
}
3132

33+
function setAppDatabaseURI(appId, uri) {
34+
appDatabaseURIs[appId] = uri;
35+
}
36+
3237
function getDatabaseConnection(appId) {
3338
if (dbConnections[appId]) {
3439
return dbConnections[appId];
3540
}
36-
dbConnections[appId] = new adapter(databaseURI, {
41+
42+
var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI);
43+
dbConnections[appId] = new adapter(dbURI, {
3744
collectionPrefix: cache.apps[appId]['collectionPrefix']
3845
});
3946
dbConnections[appId].connect();
@@ -44,5 +51,6 @@ module.exports = {
4451
dbConnections: dbConnections,
4552
getDatabaseConnection: getDatabaseConnection,
4653
setAdapter: setAdapter,
47-
setDatabaseURI: setDatabaseURI
54+
setDatabaseURI: setDatabaseURI,
55+
setAppDatabaseURI: setAppDatabaseURI
4856
};

FilesAdapter.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Adapter classes must implement the following functions:
66
// * create(config, filename, data)
77
// * get(config, filename)
8+
// * location(config, req, filename)
89
//
910
// Default is GridStoreAdapter, which requires mongo
1011
// and for the API server to be using the ExportAdapter

GridStoreAdapter.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Requires the database adapter to be based on mongoclient
55

66
var GridStore = require('mongodb').GridStore;
7+
var path = require('path');
78

89
// For a given config object, filename, and data, store a file
910
// Returns a promise
@@ -32,7 +33,16 @@ function get(config, filename) {
3233
});
3334
}
3435

36+
// Generates and returns the location of a file stored in GridStore for the
37+
// given request and filename
38+
function location(config, req, filename) {
39+
return (req.protocol + '://' + req.get('host') +
40+
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
41+
'/' + encodeURIComponent(filename));
42+
}
43+
3544
module.exports = {
3645
create: create,
37-
get: get
46+
get: get,
47+
location: location
3848
};

RestWrite.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// that writes to the database.
33
// This could be either a "create" or an "update".
44

5+
var crypto = require('crypto');
56
var deepcopy = require('deepcopy');
67
var rack = require('hat').rack();
78

89
var Auth = require('./Auth');
910
var cache = require('./cache');
1011
var Config = require('./Config');
11-
var crypto = require('./crypto');
12+
var passwordCrypto = require('./password');
1213
var facebook = require('./facebook');
1314
var Parse = require('parse/node');
1415
var triggers = require('./triggers');
@@ -299,7 +300,7 @@ RestWrite.prototype.transformUser = function() {
299300
if (this.query) {
300301
this.storage['clearSessions'] = true;
301302
}
302-
return crypto.hash(this.data.password).then((hashedPassword) => {
303+
return passwordCrypto.hash(this.data.password).then((hashedPassword) => {
303304
this.data._hashed_password = hashedPassword;
304305
delete this.data.password;
305306
});
@@ -701,15 +702,18 @@ RestWrite.prototype.objectId = function() {
701702
return this.data.objectId || this.query.objectId;
702703
};
703704

704-
// Returns a string that's usable as an object id.
705-
// Probably unique. Good enough? Probably!
705+
// Returns a unique string that's usable as an object id.
706706
function newObjectId() {
707707
var chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
708708
'abcdefghijklmnopqrstuvwxyz' +
709709
'0123456789');
710710
var objectId = '';
711-
for (var i = 0; i < 10; ++i) {
712-
objectId += chars[Math.floor(Math.random() * chars.length)];
711+
var bytes = crypto.randomBytes(10);
712+
for (var i = 0; i < bytes.length; ++i) {
713+
// Note: there is a slight modulo bias, because chars length
714+
// of 62 doesn't divide the number of all bytes (256) evenly.
715+
// It is acceptable for our purposes.
716+
objectId += chars[bytes.readUInt8(i) % chars.length];
713717
}
714718
return objectId;
715719
}

S3Adapter.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// S3Adapter
2+
//
3+
// Stores Parse files in AWS S3.
4+
5+
var AWS = require('aws-sdk');
6+
var path = require('path');
7+
8+
var DEFAULT_REGION = "us-east-1";
9+
var DEFAULT_BUCKET = "parse-files";
10+
11+
// Creates an S3 session.
12+
// Providing AWS access and secret keys is mandatory
13+
// Region and bucket will use sane defaults if omitted
14+
function S3Adapter(accessKey, secretKey, options) {
15+
options = options || {};
16+
17+
this.region = options.region || DEFAULT_REGION;
18+
this.bucket = options.bucket || DEFAULT_BUCKET;
19+
this.bucketPrefix = options.bucketPrefix || "";
20+
this.directAccess = options.directAccess || false;
21+
22+
s3Options = {
23+
accessKeyId: accessKey,
24+
secretAccessKey: secretKey,
25+
params: {Bucket: this.bucket}
26+
};
27+
AWS.config.region = this.region;
28+
this.s3 = new AWS.S3(s3Options);
29+
}
30+
31+
// For a given config object, filename, and data, store a file in S3
32+
// Returns a promise containing the S3 object creation response
33+
S3Adapter.prototype.create = function(config, filename, data) {
34+
var params = {
35+
Key: this.bucketPrefix + filename,
36+
Body: data,
37+
};
38+
if (this.directAccess) {
39+
params.ACL = "public-read"
40+
}
41+
42+
return new Promise((resolve, reject) => {
43+
this.s3.upload(params, (err, data) => {
44+
if (err !== null) return reject(err);
45+
resolve(data);
46+
});
47+
});
48+
}
49+
50+
// Search for and return a file if found by filename
51+
// Returns a promise that succeeds with the buffer result from S3
52+
S3Adapter.prototype.get = function(config, filename) {
53+
var params = {Key: this.bucketPrefix + filename};
54+
55+
return new Promise((resolve, reject) => {
56+
this.s3.getObject(params, (err, data) => {
57+
if (err !== null) return reject(err);
58+
resolve(data.Body);
59+
});
60+
});
61+
}
62+
63+
// Generates and returns the location of a file stored in S3 for the given request and
64+
// filename
65+
// The location is the direct S3 link if the option is set, otherwise we serve
66+
// the file through parse-server
67+
S3Adapter.prototype.location = function(config, req, filename) {
68+
if (this.directAccess) {
69+
return ('https://' + this.bucket + '.s3.amazonaws.com' + '/' +
70+
this.bucketPrefix + filename);
71+
}
72+
return (req.protocol + '://' + req.get('host') +
73+
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
74+
'/' + encodeURIComponent(filename));
75+
}
76+
77+
module.exports = S3Adapter;

classes.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,36 @@ var router = new PromiseRouter();
1010

1111
// Returns a promise that resolves to a {response} object.
1212
function handleFind(req) {
13+
var body = Object.assign(req.body, req.query);
1314
var options = {};
14-
if (req.body.skip) {
15-
options.skip = Number(req.body.skip);
15+
if (body.skip) {
16+
options.skip = Number(body.skip);
1617
}
17-
if (req.body.limit) {
18-
options.limit = Number(req.body.limit);
18+
if (body.limit) {
19+
options.limit = Number(body.limit);
1920
}
20-
if (req.body.order) {
21-
options.order = String(req.body.order);
21+
if (body.order) {
22+
options.order = String(body.order);
2223
}
23-
if (req.body.count) {
24+
if (body.count) {
2425
options.count = true;
2526
}
26-
if (typeof req.body.keys == 'string') {
27-
options.keys = req.body.keys;
27+
if (typeof body.keys == 'string') {
28+
options.keys = body.keys;
2829
}
29-
if (req.body.include) {
30-
options.include = String(req.body.include);
30+
if (body.include) {
31+
options.include = String(body.include);
3132
}
32-
if (req.body.redirectClassNameForKey) {
33-
options.redirectClassNameForKey = String(req.body.redirectClassNameForKey);
33+
if (body.redirectClassNameForKey) {
34+
options.redirectClassNameForKey = String(body.redirectClassNameForKey);
35+
}
36+
37+
if(typeof body.where === 'string') {
38+
body.where = JSON.parse(body.where);
3439
}
3540

3641
return rest.find(req.config, req.auth,
37-
req.params.className, req.body.where, options)
42+
req.params.className, body.where, options)
3843
.then((response) => {
3944
return {response: response};
4045
});

files.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ var bodyParser = require('body-parser'),
77
middlewares = require('./middlewares.js'),
88
mime = require('mime'),
99
Parse = require('parse/node').Parse,
10-
path = require('path'),
1110
rack = require('hat').rack();
1211

1312
var router = express.Router();
@@ -44,10 +43,7 @@ var processCreate = function(req, res, next) {
4443
FilesAdapter.getAdapter().create(req.config, filename, req.body)
4544
.then(() => {
4645
res.status(201);
47-
var location = (req.protocol + '://' + req.get('host') +
48-
path.dirname(req.originalUrl) + '/' +
49-
req.config.applicationId + '/' +
50-
encodeURIComponent(filename));
46+
var location = FilesAdapter.getAdapter().location(req.config, req, filename);
5147
res.set('Location', location);
5248
res.json({ url: location, name: filename });
5349
}).catch((error) => {

index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var batch = require('./batch'),
66
DatabaseAdapter = require('./DatabaseAdapter'),
77
express = require('express'),
88
FilesAdapter = require('./FilesAdapter'),
9+
S3Adapter = require('./S3Adapter'),
910
middlewares = require('./middlewares'),
1011
multer = require('multer'),
1112
Parse = require('parse/node').Parse,
@@ -47,7 +48,7 @@ function ParseServer(args) {
4748
FilesAdapter.setAdapter(args.filesAdapter);
4849
}
4950
if (args.databaseURI) {
50-
DatabaseAdapter.setDatabaseURI(args.databaseURI);
51+
DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI);
5152
}
5253
if (args.cloud) {
5354
addParseCloud();
@@ -150,6 +151,9 @@ function addParseCloud() {
150151
options.uri = options.url;
151152
delete options.url;
152153
}
154+
if (typeof options.body === 'object') {
155+
options.body = JSON.stringify(options.body);
156+
}
153157
request(options, (error, response, body) => {
154158
if (error) {
155159
if (callbacks.error) {
@@ -176,6 +180,6 @@ function getClassName(parseClass) {
176180
}
177181

178182
module.exports = {
179-
ParseServer: ParseServer
183+
ParseServer: ParseServer,
184+
S3Adapter: S3Adapter
180185
};
181-

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "2.0.3",
3+
"version": "2.0.4",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "index.js",
66
"repository": {
@@ -9,7 +9,8 @@
99
},
1010
"license": "BSD-3-Clause",
1111
"dependencies": {
12-
"bcrypt": "~0.8",
12+
"aws-sdk": "~2.2.33",
13+
"bcrypt-nodejs": "0.0.3",
1314
"body-parser": "~1.12.4",
1415
"deepcopy": "^0.5.0",
1516
"express": "~4.2.x",

crypto.js renamed to password.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Tools for encrypting and decrypting passwords.
22
// Basically promise-friendly wrappers for bcrypt.
3-
var bcrypt = require('bcrypt');
3+
var bcrypt = require('bcrypt-nodejs');
44

55
// Returns a promise for a hashed password string.
66
function hash(password) {
77
return new Promise(function(fulfill, reject) {
8-
bcrypt.hash(password, 8, function(err, hashedPassword) {
8+
bcrypt.hash(password, null, null, function(err, hashedPassword) {
99
if (err) {
1010
reject(err);
1111
} else {

spec/ParseUser.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Tests that involve sending password reset emails.
77

88
var request = require('request');
9-
var crypto = require('../crypto');
9+
var passwordCrypto = require('../password');
1010

1111
describe('Parse.User testing', () => {
1212
it("user sign up class method", (done) => {
@@ -1560,7 +1560,7 @@ describe('Parse.User testing', () => {
15601560

15611561
it('password format matches hosted parse', (done) => {
15621562
var hashed = '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie';
1563-
crypto.compare('test', hashed)
1563+
passwordCrypto.compare('test', hashed)
15641564
.then((pass) => {
15651565
expect(pass).toBe(true);
15661566
done();
@@ -1574,7 +1574,7 @@ describe('Parse.User testing', () => {
15741574
var sessionToken = null;
15751575

15761576
Parse.Promise.as().then(function() {
1577-
return Parse.User.signUp("fosco", "parse");
1577+
return Parse.User.signUp("fosco", "parse");
15781578
}).then(function(newUser) {
15791579
equal(Parse.User.current(), newUser);
15801580
sessionToken = newUser.getSessionToken();

0 commit comments

Comments
 (0)