Merge remote-tracking branch 'ParsePlatform/master' into user-roles
This commit is contained in:
@@ -20,10 +20,12 @@ function Config(applicationId, mount) {
|
||||
this.restAPIKey = cacheInfo.restAPIKey;
|
||||
this.fileKey = cacheInfo.fileKey;
|
||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
||||
|
||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
||||
this.filesController = cacheInfo.filesController;
|
||||
|
||||
this.oauth = cacheInfo.oauth;
|
||||
this.mount = mount;
|
||||
}
|
||||
|
||||
|
||||
@@ -377,7 +377,11 @@ RestQuery.prototype.handleInclude = function() {
|
||||
this.include = this.include.slice(1);
|
||||
return this.handleInclude();
|
||||
});
|
||||
} else if (this.include.length > 0) {
|
||||
this.include = this.include.slice(1);
|
||||
return this.handleInclude();
|
||||
}
|
||||
|
||||
return pathResponse;
|
||||
};
|
||||
|
||||
|
||||
101
src/RestWrite.js
101
src/RestWrite.js
@@ -9,7 +9,7 @@ var cache = require('./cache');
|
||||
var Config = require('./Config');
|
||||
var cryptoUtils = require('./cryptoUtils');
|
||||
var passwordCrypto = require('./password');
|
||||
var facebook = require('./facebook');
|
||||
var oauth = require("./oauth");
|
||||
var Parse = require('parse/node');
|
||||
var triggers = require('./triggers');
|
||||
|
||||
@@ -171,19 +171,26 @@ RestWrite.prototype.validateAuthData = function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var facebookData = this.data.authData.facebook;
|
||||
var authData = this.data.authData;
|
||||
var anonData = this.data.authData.anonymous;
|
||||
|
||||
if (anonData === null ||
|
||||
(anonData && anonData.id)) {
|
||||
|
||||
if (this.config.enableAnonymousUsers === true && (anonData === null ||
|
||||
(anonData && anonData.id))) {
|
||||
return this.handleAnonymousAuthData();
|
||||
} else if (facebookData === null ||
|
||||
(facebookData && facebookData.id && facebookData.access_token)) {
|
||||
return this.handleFacebookAuthData();
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||
'This authentication method is unsupported.');
|
||||
}
|
||||
|
||||
// Not anon, try other providers
|
||||
var providers = Object.keys(authData);
|
||||
if (!anonData && providers.length == 1) {
|
||||
var provider = providers[0];
|
||||
var providerAuthData = authData[provider];
|
||||
var hasToken = (providerAuthData && providerAuthData.id);
|
||||
if (providerAuthData === null || hasToken) {
|
||||
return this.handleOAuthAuthData(provider);
|
||||
}
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||
'This authentication method is unsupported.');
|
||||
};
|
||||
|
||||
RestWrite.prototype.handleAnonymousAuthData = function() {
|
||||
@@ -232,27 +239,71 @@ RestWrite.prototype.handleAnonymousAuthData = function() {
|
||||
|
||||
};
|
||||
|
||||
RestWrite.prototype.handleFacebookAuthData = function() {
|
||||
var facebookData = this.data.authData.facebook;
|
||||
if (facebookData === null && this.query) {
|
||||
// We are unlinking from Facebook.
|
||||
this.data._auth_data_facebook = null;
|
||||
RestWrite.prototype.handleOAuthAuthData = function(provider) {
|
||||
var authData = this.data.authData[provider];
|
||||
|
||||
if (authData === null && this.query) {
|
||||
// We are unlinking from the provider.
|
||||
this.data["_auth_data_" + provider ] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
return facebook.validateUserId(facebookData.id,
|
||||
facebookData.access_token)
|
||||
var appIds;
|
||||
var oauthOptions = this.config.oauth[provider];
|
||||
if (oauthOptions) {
|
||||
appIds = oauthOptions.appIds;
|
||||
} else if (provider == "facebook") {
|
||||
appIds = this.config.facebookAppIds;
|
||||
}
|
||||
|
||||
var validateAuthData;
|
||||
var validateAppId;
|
||||
|
||||
|
||||
if (oauth[provider]) {
|
||||
validateAuthData = oauth[provider].validateAuthData;
|
||||
validateAppId = oauth[provider].validateAppId;
|
||||
}
|
||||
|
||||
// Try the configuration methods
|
||||
if (oauthOptions) {
|
||||
if (oauthOptions.module) {
|
||||
validateAuthData = require(oauthOptions.module).validateAuthData;
|
||||
validateAppId = require(oauthOptions.module).validateAppId;
|
||||
};
|
||||
|
||||
if (oauthOptions.validateAuthData) {
|
||||
validateAuthData = oauthOptions.validateAuthData;
|
||||
}
|
||||
if (oauthOptions.validateAppId) {
|
||||
validateAppId = oauthOptions.validateAppId;
|
||||
}
|
||||
}
|
||||
// try the custom provider first, fallback on the oauth implementation
|
||||
|
||||
if (!validateAuthData || !validateAppId) {
|
||||
return false;
|
||||
};
|
||||
|
||||
return validateAuthData(authData, oauthOptions)
|
||||
.then(() => {
|
||||
return facebook.validateAppId(this.config.facebookAppIds,
|
||||
facebookData.access_token);
|
||||
if (appIds && typeof validateAppId === "function") {
|
||||
return validateAppId(appIds, authData, oauthOptions);
|
||||
}
|
||||
|
||||
// No validation required by the developer
|
||||
return Promise.resolve();
|
||||
|
||||
}).then(() => {
|
||||
// Check if this user already exists
|
||||
// TODO: does this handle re-linking correctly?
|
||||
var query = {};
|
||||
query['authData.' + provider + '.id'] = authData.id;
|
||||
return this.config.database.find(
|
||||
this.className,
|
||||
{'authData.facebook.id': facebookData.id}, {});
|
||||
query, {});
|
||||
}).then((results) => {
|
||||
this.storage['authProvider'] = "facebook";
|
||||
this.storage['authProvider'] = provider;
|
||||
if (results.length > 0) {
|
||||
if (!this.query) {
|
||||
// We're signing up, but this user already exists. Short-circuit
|
||||
@@ -271,7 +322,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
||||
delete this.data.authData;
|
||||
return;
|
||||
}
|
||||
// We're trying to create a duplicate FB auth. Forbid it
|
||||
// We're trying to create a duplicate oauth auth. Forbid it
|
||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||
'this auth is already used');
|
||||
} else {
|
||||
@@ -280,12 +331,12 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
||||
|
||||
// This FB auth does not already exist, so transform it to a
|
||||
// saveable format
|
||||
this.data._auth_data_facebook = facebookData;
|
||||
this.data["_auth_data_" + provider ] = authData;
|
||||
|
||||
// Delete the rest format key before saving
|
||||
delete this.data.authData;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// The non-third-party parts of User transformation
|
||||
RestWrite.prototype.transformUser = function() {
|
||||
|
||||
@@ -23,7 +23,6 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
handleCreate(req) {
|
||||
let data = deepcopy(req.body);
|
||||
data.installationId = req.info.installationId;
|
||||
req.body = data;
|
||||
req.params.className = '_User';
|
||||
return super.handleCreate(req);
|
||||
|
||||
169
src/Schema.js
169
src/Schema.js
@@ -116,7 +116,7 @@ function schemaAPITypeToMongoFieldType(type) {
|
||||
return invalidJsonError;
|
||||
} else if (!classNameIsValid(type.targetClass)) {
|
||||
return { error: invalidClassNameMessage(type.targetClass), code: Parse.Error.INVALID_CLASS_NAME };
|
||||
} else {
|
||||
} else {
|
||||
return { result: '*' + type.targetClass };
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,114 @@ Schema.prototype.reload = function() {
|
||||
return load(this.collection);
|
||||
};
|
||||
|
||||
// Returns { code, error } if invalid, or { result }, an object
|
||||
// suitable for inserting into _SCHEMA collection, otherwise
|
||||
function mongoSchemaFromFieldsAndClassName(fields, className) {
|
||||
if (!classNameIsValid(className)) {
|
||||
return {
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: invalidClassNameMessage(className),
|
||||
};
|
||||
}
|
||||
|
||||
for (var fieldName in fields) {
|
||||
if (!fieldNameIsValid(fieldName)) {
|
||||
return {
|
||||
code: Parse.Error.INVALID_KEY_NAME,
|
||||
error: 'invalid field name: ' + fieldName,
|
||||
};
|
||||
}
|
||||
if (!fieldNameIsValidForClass(fieldName, className)) {
|
||||
return {
|
||||
code: 136,
|
||||
error: 'field ' + fieldName + ' cannot be added',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var mongoObject = {
|
||||
_id: className,
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string'
|
||||
};
|
||||
|
||||
for (var fieldName in defaultColumns[className]) {
|
||||
var validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]);
|
||||
if (!validatedField.result) {
|
||||
return validatedField;
|
||||
}
|
||||
mongoObject[fieldName] = validatedField.result;
|
||||
}
|
||||
|
||||
for (var fieldName in fields) {
|
||||
var validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]);
|
||||
if (!validatedField.result) {
|
||||
return validatedField;
|
||||
}
|
||||
mongoObject[fieldName] = validatedField.result;
|
||||
}
|
||||
|
||||
var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint');
|
||||
if (geoPoints.length > 1) {
|
||||
return {
|
||||
code: Parse.Error.INCORRECT_TYPE,
|
||||
error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.',
|
||||
};
|
||||
}
|
||||
|
||||
return { result: mongoObject };
|
||||
}
|
||||
|
||||
function mongoFieldTypeToSchemaAPIType(type) {
|
||||
if (type[0] === '*') {
|
||||
return {
|
||||
type: 'Pointer',
|
||||
targetClass: type.slice(1),
|
||||
};
|
||||
}
|
||||
if (type.startsWith('relation<')) {
|
||||
return {
|
||||
type: 'Relation',
|
||||
targetClass: type.slice('relation<'.length, type.length - 1),
|
||||
};
|
||||
}
|
||||
switch (type) {
|
||||
case 'number': return {type: 'Number'};
|
||||
case 'string': return {type: 'String'};
|
||||
case 'boolean': return {type: 'Boolean'};
|
||||
case 'date': return {type: 'Date'};
|
||||
case 'map':
|
||||
case 'object': return {type: 'Object'};
|
||||
case 'array': return {type: 'Array'};
|
||||
case 'geopoint': return {type: 'GeoPoint'};
|
||||
case 'file': return {type: 'File'};
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a new schema (in schema API response format) out of an
|
||||
// existing mongo schema + a schemas API put request. This response
|
||||
// does not include the default fields, as it is intended to be passed
|
||||
// to mongoSchemaFromFieldsAndClassName. No validation is done here, it
|
||||
// is done in mongoSchemaFromFieldsAndClassName.
|
||||
function buildMergedSchemaObject(mongoObject, putRequest) {
|
||||
var newSchema = {};
|
||||
for (var oldField in mongoObject) {
|
||||
if (oldField !== '_id' && oldField !== 'ACL' && oldField !== 'updatedAt' && oldField !== 'createdAt' && oldField !== 'objectId') {
|
||||
var fieldIsDeleted = putRequest[oldField] && putRequest[oldField].__op === 'Delete'
|
||||
if (!fieldIsDeleted) {
|
||||
newSchema[oldField] = mongoFieldTypeToSchemaAPIType(mongoObject[oldField]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var newField in putRequest) {
|
||||
if (newField !== 'objectId' && putRequest[newField].__op !== 'Delete') {
|
||||
newSchema[newField] = putRequest[newField];
|
||||
}
|
||||
}
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
// Create a new class that includes the three default fields.
|
||||
// ACL is an implicit column that does not get an entry in the
|
||||
// _SCHEMAS database. Returns a promise that resolves with the
|
||||
@@ -215,58 +323,13 @@ Schema.prototype.addClassIfNotExists = function(className, fields) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!classNameIsValid(className)) {
|
||||
return Promise.reject({
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: invalidClassNameMessage(className),
|
||||
});
|
||||
}
|
||||
for (var fieldName in fields) {
|
||||
if (!fieldNameIsValid(fieldName)) {
|
||||
return Promise.reject({
|
||||
code: Parse.Error.INVALID_KEY_NAME,
|
||||
error: 'invalid field name: ' + fieldName,
|
||||
});
|
||||
}
|
||||
if (!fieldNameIsValidForClass(fieldName, className)) {
|
||||
return Promise.reject({
|
||||
code: 136,
|
||||
error: 'field ' + fieldName + ' cannot be added',
|
||||
});
|
||||
}
|
||||
var mongoObject = mongoSchemaFromFieldsAndClassName(fields, className);
|
||||
|
||||
if (!mongoObject.result) {
|
||||
return Promise.reject(mongoObject);
|
||||
}
|
||||
|
||||
var mongoObject = {
|
||||
_id: className,
|
||||
objectId: 'string',
|
||||
updatedAt: 'string',
|
||||
createdAt: 'string'
|
||||
};
|
||||
for (var fieldName in defaultColumns[className]) {
|
||||
var validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]);
|
||||
if (validatedField.code) {
|
||||
return Promise.reject(validatedField);
|
||||
}
|
||||
mongoObject[fieldName] = validatedField.result;
|
||||
}
|
||||
|
||||
for (var fieldName in fields) {
|
||||
var validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]);
|
||||
if (validatedField.code) {
|
||||
return Promise.reject(validatedField);
|
||||
}
|
||||
mongoObject[fieldName] = validatedField.result;
|
||||
}
|
||||
|
||||
var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint');
|
||||
if (geoPoints.length > 1) {
|
||||
return Promise.reject({
|
||||
code: Parse.Error.INCORRECT_TYPE,
|
||||
error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
return this.collection.insertOne(mongoObject)
|
||||
return this.collection.insertOne(mongoObject.result)
|
||||
.then(result => result.ops[0])
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { //Mongo's duplicate key error
|
||||
@@ -651,4 +714,8 @@ function getObjectType(obj) {
|
||||
module.exports = {
|
||||
load: load,
|
||||
classNameIsValid: classNameIsValid,
|
||||
mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName,
|
||||
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
||||
buildMergedSchemaObject: buildMergedSchemaObject,
|
||||
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
||||
};
|
||||
|
||||
@@ -3,10 +3,10 @@ var https = require('https');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
// Returns a promise that fulfills iff this user id is valid.
|
||||
function validateUserId(userId, access_token) {
|
||||
return graphRequest('me?fields=id&access_token=' + access_token)
|
||||
function validateAuthData(authData) {
|
||||
return graphRequest('me?fields=id&access_token=' + authData.access_token)
|
||||
.then((data) => {
|
||||
if (data && data.id == userId) {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
@@ -16,7 +16,8 @@ function validateUserId(userId, access_token) {
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -53,5 +54,5 @@ function graphRequest(path) {
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateUserId: validateUserId
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
|
||||
@@ -104,7 +104,9 @@ function ParseServer(args) {
|
||||
restAPIKey: args.restAPIKey || '',
|
||||
fileKey: args.fileKey || 'invalid-file-key',
|
||||
facebookAppIds: args.facebookAppIds || [],
|
||||
filesController: filesController
|
||||
filesController: filesController,
|
||||
enableAnonymousUsers: args.enableAnonymousUsers || true,
|
||||
oauth: args.oauth || {},
|
||||
};
|
||||
|
||||
// To maintain compatibility. TODO: Remove in v2.1
|
||||
|
||||
@@ -26,6 +26,12 @@ function handleParseHeaders(req, res, next) {
|
||||
restAPIKey: req.get('X-Parse-REST-API-Key')
|
||||
};
|
||||
|
||||
if (req.body && req.body._noBody) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
var fileViaJSON = false;
|
||||
|
||||
if (!info.appId || !cache.apps[info.appId]) {
|
||||
|
||||
226
src/oauth/OAuth1Client.js
Normal file
226
src/oauth/OAuth1Client.js
Normal file
@@ -0,0 +1,226 @@
|
||||
var https = require('https'),
|
||||
crypto = require('crypto');
|
||||
|
||||
var OAuth = function(options) {
|
||||
this.consumer_key = options.consumer_key;
|
||||
this.consumer_secret = options.consumer_secret;
|
||||
this.auth_token = options.auth_token;
|
||||
this.auth_token_secret = options.auth_token_secret;
|
||||
this.host = options.host;
|
||||
this.oauth_params = options.oauth_params || {};
|
||||
};
|
||||
|
||||
OAuth.prototype.send = function(method, path, params, body){
|
||||
|
||||
var request = this.buildRequest(method, path, params, body);
|
||||
// Encode the body properly, the current Parse Implementation don't do it properly
|
||||
return new Promise(function(resolve, reject) {
|
||||
var httpRequest = https.request(request, 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 make an OAuth request');
|
||||
});
|
||||
if (request.body) {
|
||||
httpRequest.write(request.body);
|
||||
}
|
||||
httpRequest.end();
|
||||
});
|
||||
};
|
||||
|
||||
OAuth.prototype.buildRequest = function(method, path, params, body) {
|
||||
if (path.indexOf("/") != 0) {
|
||||
path = "/"+path;
|
||||
}
|
||||
if (params && Object.keys(params).length > 0) {
|
||||
path += "?" + OAuth.buildParameterString(params);
|
||||
}
|
||||
|
||||
var request = {
|
||||
host: this.host,
|
||||
path: path,
|
||||
method: method.toUpperCase()
|
||||
};
|
||||
|
||||
var oauth_params = this.oauth_params || {};
|
||||
oauth_params.oauth_consumer_key = this.consumer_key;
|
||||
if(this.auth_token){
|
||||
oauth_params["oauth_token"] = this.auth_token;
|
||||
}
|
||||
|
||||
request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret);
|
||||
|
||||
if (body && Object.keys(body).length > 0) {
|
||||
request.body = OAuth.buildParameterString(body);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
OAuth.prototype.get = function(path, params) {
|
||||
return this.send("GET", path, params);
|
||||
}
|
||||
|
||||
OAuth.prototype.post = function(path, params, body) {
|
||||
return this.send("POST", path, params, body);
|
||||
}
|
||||
|
||||
/*
|
||||
Proper string %escape encoding
|
||||
*/
|
||||
OAuth.encode = function(str) {
|
||||
// discuss at: http://phpjs.org/functions/rawurlencode/
|
||||
// original by: Brett Zamir (http://brett-zamir.me)
|
||||
// input by: travc
|
||||
// input by: Brett Zamir (http://brett-zamir.me)
|
||||
// input by: Michael Grier
|
||||
// input by: Ratheous
|
||||
// bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
// bugfixed by: Brett Zamir (http://brett-zamir.me)
|
||||
// bugfixed by: Joris
|
||||
// reimplemented by: Brett Zamir (http://brett-zamir.me)
|
||||
// reimplemented by: Brett Zamir (http://brett-zamir.me)
|
||||
// note: This reflects PHP 5.3/6.0+ behavior
|
||||
// note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on
|
||||
// note: pages served as UTF-8
|
||||
// example 1: rawurlencode('Kevin van Zonneveld!');
|
||||
// returns 1: 'Kevin%20van%20Zonneveld%21'
|
||||
// example 2: rawurlencode('http://kevin.vanzonneveld.net/');
|
||||
// returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
|
||||
// example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
|
||||
// returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'
|
||||
|
||||
str = (str + '')
|
||||
.toString();
|
||||
|
||||
// Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current
|
||||
// PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following.
|
||||
return encodeURIComponent(str)
|
||||
.replace(/!/g, '%21')
|
||||
.replace(/'/g, '%27')
|
||||
.replace(/\(/g, '%28')
|
||||
.replace(/\)/g, '%29')
|
||||
.replace(/\*/g, '%2A');
|
||||
}
|
||||
|
||||
OAuth.signatureMethod = "HMAC-SHA1";
|
||||
OAuth.version = "1.0";
|
||||
|
||||
/*
|
||||
Generate a nonce
|
||||
*/
|
||||
OAuth.nonce = function(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < 30; i++ )
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
OAuth.buildParameterString = function(obj){
|
||||
var result = {};
|
||||
|
||||
// Sort keys and encode values
|
||||
if (obj) {
|
||||
var keys = Object.keys(obj).sort();
|
||||
|
||||
// Map key=value, join them by &
|
||||
return keys.map(function(key){
|
||||
return key + "=" + OAuth.encode(obj[key]);
|
||||
}).join("&");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
Build the signature string from the object
|
||||
*/
|
||||
|
||||
OAuth.buildSignatureString = function(method, url, parameters){
|
||||
return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join("&");
|
||||
}
|
||||
|
||||
/*
|
||||
Retuns encoded HMAC-SHA1 from key and text
|
||||
*/
|
||||
OAuth.signature = function(text, key){
|
||||
crypto = require("crypto");
|
||||
return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64'));
|
||||
}
|
||||
|
||||
OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_token_secret){
|
||||
oauth_parameters = oauth_parameters || {};
|
||||
|
||||
// Set default values
|
||||
if (!oauth_parameters.oauth_nonce) {
|
||||
oauth_parameters.oauth_nonce = OAuth.nonce();
|
||||
}
|
||||
if (!oauth_parameters.oauth_timestamp) {
|
||||
oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime()/1000);
|
||||
}
|
||||
if (!oauth_parameters.oauth_signature_method) {
|
||||
oauth_parameters.oauth_signature_method = OAuth.signatureMethod;
|
||||
}
|
||||
if (!oauth_parameters.oauth_version) {
|
||||
oauth_parameters.oauth_version = OAuth.version;
|
||||
}
|
||||
|
||||
if(!auth_token_secret){
|
||||
auth_token_secret="";
|
||||
}
|
||||
// Force GET method if unset
|
||||
if (!request.method) {
|
||||
request.method = "GET"
|
||||
}
|
||||
|
||||
// Collect all the parameters in one signatureParameters object
|
||||
var signatureParams = {};
|
||||
var parametersToMerge = [request.params, request.body, oauth_parameters];
|
||||
for(var i in parametersToMerge) {
|
||||
var parameters = parametersToMerge[i];
|
||||
for(var k in parameters) {
|
||||
signatureParams[k] = parameters[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Create a string based on the parameters
|
||||
var parameterString = OAuth.buildParameterString(signatureParams);
|
||||
|
||||
// Build the signature string
|
||||
var url = "https://"+request.host+""+request.path;
|
||||
|
||||
var signatureString = OAuth.buildSignatureString(request.method, url, parameterString);
|
||||
// Hash the signature string
|
||||
var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&");
|
||||
|
||||
var signature = OAuth.signature(signatureString, signatureKey);
|
||||
|
||||
// Set the signature in the params
|
||||
oauth_parameters.oauth_signature = signature;
|
||||
if(!request.headers){
|
||||
request.headers = {};
|
||||
}
|
||||
|
||||
// Set the authorization header
|
||||
var signature = Object.keys(oauth_parameters).sort().map(function(key){
|
||||
var value = oauth_parameters[key];
|
||||
return key+'="'+value+'"';
|
||||
}).join(", ")
|
||||
|
||||
request.headers.Authorization = 'OAuth ' + signature;
|
||||
|
||||
// Set the content type header
|
||||
request.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
return request;
|
||||
|
||||
}
|
||||
|
||||
module.exports = OAuth;
|
||||
57
src/oauth/facebook.js
Normal file
57
src/oauth/facebook.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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, 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
|
||||
};
|
||||
51
src/oauth/github.js
Normal file
51
src/oauth/github.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// Helper functions for accessing the github 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 request('user', authData.access_token)
|
||||
.then((data) => {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Github auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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, access_token) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get({
|
||||
host: 'api.github.com',
|
||||
path: '/' + path,
|
||||
headers: {
|
||||
'Authorization': 'bearer '+access_token,
|
||||
'User-Agent': 'parse-server'
|
||||
}
|
||||
}, 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 Github.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
44
src/oauth/google.js
Normal file
44
src/oauth/google.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Helper functions for accessing the google 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 request("tokeninfo?access_token="+authData.access_token)
|
||||
.then((response) => {
|
||||
if (response && response.user_id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Google auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get("https://www.googleapis.com/oauth2/v1/" + 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 Google.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
17
src/oauth/index.js
Normal file
17
src/oauth/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
var facebook = require('./facebook');
|
||||
var instagram = require("./instagram");
|
||||
var linkedin = require("./linkedin");
|
||||
var meetup = require("./meetup");
|
||||
var google = require("./google");
|
||||
var github = require("./github");
|
||||
var twitter = require("./twitter");
|
||||
|
||||
module.exports = {
|
||||
facebook: facebook,
|
||||
github: github,
|
||||
google: google,
|
||||
instagram: instagram,
|
||||
linkedin: linkedin,
|
||||
meetup: meetup,
|
||||
twitter: twitter
|
||||
}
|
||||
44
src/oauth/instagram.js
Normal file
44
src/oauth/instagram.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Helper functions for accessing the instagram 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 request("users/self/?access_token="+authData.access_token)
|
||||
.then((response) => {
|
||||
if (response && response.data && response.data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Instagram auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get("https://api.instagram.com/v1/" + 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 Instagram.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
51
src/oauth/linkedin.js
Normal file
51
src/oauth/linkedin.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// Helper functions for accessing the linkedin 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 request('people/~:(id)', authData.access_token)
|
||||
.then((data) => {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Meetup auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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, access_token) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get({
|
||||
host: 'api.linkedin.com',
|
||||
path: '/v1/' + path,
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+access_token,
|
||||
'x-li-format': 'json'
|
||||
}
|
||||
}, 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 Linkedin.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
50
src/oauth/meetup.js
Normal file
50
src/oauth/meetup.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// Helper functions for accessing the meetup 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 request('member/self', authData.access_token)
|
||||
.then((data) => {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Meetup auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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, access_token) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
https.get({
|
||||
host: 'api.meetup.com',
|
||||
path: '/2/' + path,
|
||||
headers: {
|
||||
'Authorization': 'bearer '+access_token
|
||||
}
|
||||
}, 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 Meetup.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
30
src/oauth/twitter.js
Normal file
30
src/oauth/twitter.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Helper functions for accessing the meetup API.
|
||||
var OAuth = require('./OAuth1Client');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
// Returns a promise that fulfills iff this user id is valid.
|
||||
function validateAuthData(authData, options) {
|
||||
var client = new OAuth(options);
|
||||
client.host = "api.twitter.com";
|
||||
client.auth_token = authData.auth_token;
|
||||
client.auth_token_secret = authData.auth_token_secret;
|
||||
|
||||
return client.get("/1.1/account/verify_credentials.json").then((data) => {
|
||||
if (data && data.id == authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Twitter auth is invalid for this user.');
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a promise that fulfills iff this app id is valid.
|
||||
function validateAppId() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
149
src/schemas.js
149
src/schemas.js
@@ -7,36 +7,27 @@ var express = require('express'),
|
||||
|
||||
var router = new PromiseRouter();
|
||||
|
||||
function mongoFieldTypeToSchemaAPIType(type) {
|
||||
if (type[0] === '*') {
|
||||
return {
|
||||
type: 'Pointer',
|
||||
targetClass: type.slice(1),
|
||||
};
|
||||
}
|
||||
if (type.startsWith('relation<')) {
|
||||
return {
|
||||
type: 'Relation',
|
||||
targetClass: type.slice('relation<'.length, type.length - 1),
|
||||
};
|
||||
}
|
||||
switch (type) {
|
||||
case 'number': return {type: 'Number'};
|
||||
case 'string': return {type: 'String'};
|
||||
case 'boolean': return {type: 'Boolean'};
|
||||
case 'date': return {type: 'Date'};
|
||||
case 'map':
|
||||
case 'object': return {type: 'Object'};
|
||||
case 'array': return {type: 'Array'};
|
||||
case 'geopoint': return {type: 'GeoPoint'};
|
||||
case 'file': return {type: 'File'};
|
||||
}
|
||||
function masterKeyRequiredResponse() {
|
||||
return Promise.resolve({
|
||||
status: 401,
|
||||
response: {error: 'master key not specified'},
|
||||
})
|
||||
}
|
||||
|
||||
function classNameMismatchResponse(bodyClass, pathClass) {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: 'class name mismatch between ' + bodyClass + ' and ' + pathClass,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mongoSchemaAPIResponseFields(schema) {
|
||||
var fieldNames = Object.keys(schema).filter(key => key !== '_id' && key !== '_metadata');
|
||||
var response = fieldNames.reduce((obj, fieldName) => {
|
||||
obj[fieldName] = mongoFieldTypeToSchemaAPIType(schema[fieldName])
|
||||
obj[fieldName] = Schema.mongoFieldTypeToSchemaAPIType(schema[fieldName])
|
||||
return obj;
|
||||
}, {});
|
||||
response.ACL = {type: 'ACL'};
|
||||
@@ -55,10 +46,7 @@ function mongoSchemaToSchemaAPIResponse(schema) {
|
||||
|
||||
function getAllSchemas(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
return Promise.resolve({
|
||||
status: 401,
|
||||
response: {error: 'master key not specified'},
|
||||
});
|
||||
return masterKeyRequiredResponse();
|
||||
}
|
||||
return req.config.database.collection('_SCHEMA')
|
||||
.then(coll => coll.find({}).toArray())
|
||||
@@ -69,10 +57,7 @@ function getAllSchemas(req) {
|
||||
|
||||
function getOneSchema(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
return Promise.resolve({
|
||||
status: 401,
|
||||
response: {error: 'unauthorized'},
|
||||
});
|
||||
return masterKeyRequiredResponse();
|
||||
}
|
||||
return req.config.database.collection('_SCHEMA')
|
||||
.then(coll => coll.findOne({'_id': req.params.className}))
|
||||
@@ -88,20 +73,11 @@ function getOneSchema(req) {
|
||||
|
||||
function createSchema(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
return Promise.resolve({
|
||||
status: 401,
|
||||
response: {error: 'master key not specified'},
|
||||
});
|
||||
return masterKeyRequiredResponse();
|
||||
}
|
||||
if (req.params.className && req.body.className) {
|
||||
if (req.params.className != req.body.className) {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: 'class name mismatch between ' + req.body.className + ' and ' + req.params.className,
|
||||
},
|
||||
});
|
||||
return classNameMismatchResponse(req.body.className, req.params.className);
|
||||
}
|
||||
}
|
||||
var className = req.params.className || req.body.className;
|
||||
@@ -123,9 +99,94 @@ function createSchema(req) {
|
||||
}));
|
||||
}
|
||||
|
||||
function modifySchema(req) {
|
||||
if (!req.auth.isMaster) {
|
||||
return masterKeyRequiredResponse();
|
||||
}
|
||||
|
||||
if (req.body.className && req.body.className != req.params.className) {
|
||||
return classNameMismatchResponse(req.body.className, req.params.className);
|
||||
}
|
||||
|
||||
var submittedFields = req.body.fields || {};
|
||||
var className = req.params.className;
|
||||
|
||||
return req.config.database.loadSchema()
|
||||
.then(schema => {
|
||||
if (!schema.data[className]) {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: Parse.Error.INVALID_CLASS_NAME,
|
||||
error: 'class ' + req.params.className + ' does not exist',
|
||||
}
|
||||
});
|
||||
}
|
||||
var existingFields = schema.data[className];
|
||||
|
||||
for (var submittedFieldName in submittedFields) {
|
||||
if (existingFields[submittedFieldName] && submittedFields[submittedFieldName].__op !== 'Delete') {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: 255,
|
||||
error: 'field ' + submittedFieldName + ' exists, cannot update',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!existingFields[submittedFieldName] && submittedFields[submittedFieldName].__op === 'Delete') {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: {
|
||||
code: 255,
|
||||
error: 'field ' + submittedFieldName + ' does not exist, cannot delete',
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var newSchema = Schema.buildMergedSchemaObject(existingFields, submittedFields);
|
||||
var mongoObject = Schema.mongoSchemaFromFieldsAndClassName(newSchema, className);
|
||||
if (!mongoObject.result) {
|
||||
return Promise.resolve({
|
||||
status: 400,
|
||||
response: mongoObject,
|
||||
});
|
||||
}
|
||||
|
||||
// Finally we have checked to make sure the request is valid and we can start deleting fields.
|
||||
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
|
||||
var deletionPromises = []
|
||||
Object.keys(submittedFields).forEach(submittedFieldName => {
|
||||
if (submittedFields[submittedFieldName].__op === 'Delete') {
|
||||
var promise = req.config.database.connect()
|
||||
.then(() => schema.deleteField(
|
||||
submittedFieldName,
|
||||
className,
|
||||
req.config.database.db,
|
||||
req.config.database.collectionPrefix
|
||||
));
|
||||
deletionPromises.push(promise);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(deletionPromises)
|
||||
.then(() => new Promise((resolve, reject) => {
|
||||
schema.collection.update({_id: className}, mongoObject.result, {w: 1}, (err, docs) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve({ response: mongoSchemaToSchemaAPIResponse(mongoObject.result)});
|
||||
})
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -55,21 +55,6 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
||||
case '_wperm':
|
||||
return {key: key, value: restValue};
|
||||
break;
|
||||
case 'authData.anonymous.id':
|
||||
if (options.query) {
|
||||
return {key: '_auth_data_anonymous.id', value: restValue};
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||
'can only query on ' + key);
|
||||
break;
|
||||
case 'authData.facebook.id':
|
||||
if (options.query) {
|
||||
// Special-case auth data.
|
||||
return {key: '_auth_data_facebook.id', value: restValue};
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||
'can only query on ' + key);
|
||||
break;
|
||||
case '$or':
|
||||
if (!options.query) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||
@@ -97,6 +82,18 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
||||
});
|
||||
return {key: '$and', value: mongoSubqueries};
|
||||
default:
|
||||
// Other auth data
|
||||
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
||||
if (authDataMatch) {
|
||||
if (options.query) {
|
||||
var provider = authDataMatch[1];
|
||||
// Special-case auth data.
|
||||
return {key: '_auth_data_'+provider+'.id', value: restValue};
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||
'can only query on ' + key);
|
||||
break;
|
||||
};
|
||||
if (options.validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||
'invalid key name: ' + key);
|
||||
@@ -646,15 +643,16 @@ function untransformObject(schema, className, mongoObject) {
|
||||
case '_expiresAt':
|
||||
restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key])).iso;
|
||||
break;
|
||||
case '_auth_data_anonymous':
|
||||
restObject['authData'] = restObject['authData'] || {};
|
||||
restObject['authData']['anonymous'] = mongoObject[key];
|
||||
break;
|
||||
case '_auth_data_facebook':
|
||||
restObject['authData'] = restObject['authData'] || {};
|
||||
restObject['authData']['facebook'] = mongoObject[key];
|
||||
break;
|
||||
default:
|
||||
// Check other auth data keys
|
||||
var authDataMatch = key.match(/^_auth_data_([a-zA-Z0-9_]+)$/);
|
||||
if (authDataMatch) {
|
||||
var provider = authDataMatch[1];
|
||||
restObject['authData'] = restObject['authData'] || {};
|
||||
restObject['authData'][provider] = mongoObject[key];
|
||||
break;
|
||||
}
|
||||
|
||||
if (key.indexOf('_p_') == 0) {
|
||||
var newKey = key.substring(3);
|
||||
var expected;
|
||||
|
||||
Reference in New Issue
Block a user