fix(prettier): Properly handle lint-stage files (#6970)
Now handles top level files and recursive files in folders. Set max line length to be 100
This commit is contained in:
@@ -82,27 +82,23 @@ export class AccountLockout {
|
||||
|
||||
const updateFields = {
|
||||
_account_lockout_expires_at: Parse._encode(
|
||||
new Date(
|
||||
now.getTime() + this._config.accountLockout.duration * 60 * 1000
|
||||
)
|
||||
new Date(now.getTime() + this._config.accountLockout.duration * 60 * 1000)
|
||||
),
|
||||
};
|
||||
|
||||
return this._config.database
|
||||
.update('_User', query, updateFields)
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err.code &&
|
||||
err.message &&
|
||||
err.code === 101 &&
|
||||
err.message === 'Object not found.'
|
||||
) {
|
||||
return; // nothing to update so we are good
|
||||
} else {
|
||||
throw err; // unknown error
|
||||
}
|
||||
});
|
||||
return this._config.database.update('_User', query, updateFields).catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err.code &&
|
||||
err.message &&
|
||||
err.code === 101 &&
|
||||
err.message === 'Object not found.'
|
||||
) {
|
||||
return; // nothing to update so we are good
|
||||
} else {
|
||||
throw err; // unknown error
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
114
src/Auth.js
114
src/Auth.js
@@ -29,7 +29,7 @@ function Auth({
|
||||
|
||||
// Whether this auth could possibly modify the given user id.
|
||||
// It still could be forbidden via ACLs even if this returns true.
|
||||
Auth.prototype.isUnauthenticated = function() {
|
||||
Auth.prototype.isUnauthenticated = function () {
|
||||
if (this.isMaster) {
|
||||
return false;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ function nobody(config) {
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to an Auth object
|
||||
const getAuthForSessionToken = async function({
|
||||
const getAuthForSessionToken = async function ({
|
||||
config,
|
||||
cacheController,
|
||||
sessionToken,
|
||||
@@ -85,37 +85,25 @@ const getAuthForSessionToken = async function({
|
||||
include: 'user',
|
||||
};
|
||||
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
master(config),
|
||||
'_Session',
|
||||
{ sessionToken },
|
||||
restOptions
|
||||
);
|
||||
const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
|
||||
results = (await query.execute()).results;
|
||||
} else {
|
||||
results = (await new Parse.Query(Parse.Session)
|
||||
.limit(1)
|
||||
.include('user')
|
||||
.equalTo('sessionToken', sessionToken)
|
||||
.find({ useMasterKey: true })).map(obj => obj.toJSON());
|
||||
results = (
|
||||
await new Parse.Query(Parse.Session)
|
||||
.limit(1)
|
||||
.include('user')
|
||||
.equalTo('sessionToken', sessionToken)
|
||||
.find({ useMasterKey: true })
|
||||
).map(obj => obj.toJSON());
|
||||
}
|
||||
|
||||
if (results.length !== 1 || !results[0]['user']) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Invalid session token'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const now = new Date(),
|
||||
expiresAt = results[0].expiresAt
|
||||
? new Date(results[0].expiresAt.iso)
|
||||
: undefined;
|
||||
expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined;
|
||||
if (expiresAt < now) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token is expired.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
|
||||
}
|
||||
const obj = results[0]['user'];
|
||||
delete obj.password;
|
||||
@@ -134,28 +122,15 @@ const getAuthForSessionToken = async function({
|
||||
});
|
||||
};
|
||||
|
||||
var getAuthForLegacySessionToken = function({
|
||||
config,
|
||||
sessionToken,
|
||||
installationId,
|
||||
}) {
|
||||
var getAuthForLegacySessionToken = function ({ config, sessionToken, installationId }) {
|
||||
var restOptions = {
|
||||
limit: 1,
|
||||
};
|
||||
var query = new RestQuery(
|
||||
config,
|
||||
master(config),
|
||||
'_User',
|
||||
{ sessionToken },
|
||||
restOptions
|
||||
);
|
||||
var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
|
||||
return query.execute().then(response => {
|
||||
var results = response.results;
|
||||
if (results.length !== 1) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'invalid legacy session token'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid legacy session token');
|
||||
}
|
||||
const obj = results[0];
|
||||
obj.className = '_User';
|
||||
@@ -170,7 +145,7 @@ var getAuthForLegacySessionToken = function({
|
||||
};
|
||||
|
||||
// Returns a promise that resolves to an array of role names
|
||||
Auth.prototype.getUserRoles = function() {
|
||||
Auth.prototype.getUserRoles = function () {
|
||||
if (this.isMaster || !this.user) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@@ -184,7 +159,7 @@ Auth.prototype.getUserRoles = function() {
|
||||
return this.rolePromise;
|
||||
};
|
||||
|
||||
Auth.prototype.getRolesForUser = async function() {
|
||||
Auth.prototype.getRolesForUser = async function () {
|
||||
//Stack all Parse.Role
|
||||
const results = [];
|
||||
if (this.config) {
|
||||
@@ -195,13 +170,9 @@ Auth.prototype.getRolesForUser = async function() {
|
||||
objectId: this.user.id,
|
||||
},
|
||||
};
|
||||
await new RestQuery(
|
||||
this.config,
|
||||
master(this.config),
|
||||
'_Role',
|
||||
restWhere,
|
||||
{}
|
||||
).each(result => results.push(result));
|
||||
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
|
||||
results.push(result)
|
||||
);
|
||||
} else {
|
||||
await new Parse.Query(Parse.Role)
|
||||
.equalTo('users', this.user)
|
||||
@@ -211,7 +182,7 @@ Auth.prototype.getRolesForUser = async function() {
|
||||
};
|
||||
|
||||
// Iterates through the role tree and compiles a user's roles
|
||||
Auth.prototype._loadRoles = async function() {
|
||||
Auth.prototype._loadRoles = async function () {
|
||||
if (this.cacheController) {
|
||||
const cachedRoles = await this.cacheController.role.get(this.user.id);
|
||||
if (cachedRoles != null) {
|
||||
@@ -242,10 +213,7 @@ Auth.prototype._loadRoles = async function() {
|
||||
);
|
||||
|
||||
// run the recursive finding
|
||||
const roleNames = await this._getAllRolesNamesForRoleIds(
|
||||
rolesMap.ids,
|
||||
rolesMap.names
|
||||
);
|
||||
const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names);
|
||||
this.userRoles = roleNames.map(r => {
|
||||
return 'role:' + r;
|
||||
});
|
||||
@@ -255,7 +223,7 @@ Auth.prototype._loadRoles = async function() {
|
||||
return this.userRoles;
|
||||
};
|
||||
|
||||
Auth.prototype.cacheRoles = function() {
|
||||
Auth.prototype.cacheRoles = function () {
|
||||
if (!this.cacheController) {
|
||||
return false;
|
||||
}
|
||||
@@ -263,7 +231,7 @@ Auth.prototype.cacheRoles = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
Auth.prototype.getRolesByIds = async function(ins) {
|
||||
Auth.prototype.getRolesByIds = async function (ins) {
|
||||
const results = [];
|
||||
// Build an OR query across all parentRoles
|
||||
if (!this.config) {
|
||||
@@ -286,23 +254,15 @@ Auth.prototype.getRolesByIds = async function(ins) {
|
||||
};
|
||||
});
|
||||
const restWhere = { roles: { $in: roles } };
|
||||
await new RestQuery(
|
||||
this.config,
|
||||
master(this.config),
|
||||
'_Role',
|
||||
restWhere,
|
||||
{}
|
||||
).each(result => results.push(result));
|
||||
await new RestQuery(this.config, master(this.config), '_Role', restWhere, {}).each(result =>
|
||||
results.push(result)
|
||||
);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
// Given a list of roleIds, find all the parent roles, returns a promise with all names
|
||||
Auth.prototype._getAllRolesNamesForRoleIds = function(
|
||||
roleIDs,
|
||||
names = [],
|
||||
queriedRoles = {}
|
||||
) {
|
||||
Auth.prototype._getAllRolesNamesForRoleIds = function (roleIDs, names = [], queriedRoles = {}) {
|
||||
const ins = roleIDs.filter(roleID => {
|
||||
const wasQueried = queriedRoles[roleID] !== true;
|
||||
queriedRoles[roleID] = true;
|
||||
@@ -332,18 +292,14 @@ Auth.prototype._getAllRolesNamesForRoleIds = function(
|
||||
// store the new found names
|
||||
names = names.concat(resultMap.names);
|
||||
// find the next ones, circular roles will be cut
|
||||
return this._getAllRolesNamesForRoleIds(
|
||||
resultMap.ids,
|
||||
names,
|
||||
queriedRoles
|
||||
);
|
||||
return this._getAllRolesNamesForRoleIds(resultMap.ids, names, queriedRoles);
|
||||
})
|
||||
.then(names => {
|
||||
return Promise.resolve([...new Set(names)]);
|
||||
});
|
||||
};
|
||||
|
||||
const createSession = function(
|
||||
const createSession = function (
|
||||
config,
|
||||
{ userId, createdWith, installationId, additionalSessionData }
|
||||
) {
|
||||
@@ -372,13 +328,7 @@ const createSession = function(
|
||||
return {
|
||||
sessionData,
|
||||
createSession: () =>
|
||||
new RestWrite(
|
||||
config,
|
||||
master(config),
|
||||
'_Session',
|
||||
null,
|
||||
sessionData
|
||||
).execute(),
|
||||
new RestWrite(config, master(config), '_Session', null, sessionData).execute(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var semver = require('semver');
|
||||
|
||||
function compatible(compatibleSDK) {
|
||||
return function(clientSDK) {
|
||||
return function (clientSDK) {
|
||||
if (typeof clientSDK === 'string') {
|
||||
clientSDK = fromString(clientSDK);
|
||||
}
|
||||
|
||||
@@ -33,18 +33,13 @@ export class Config {
|
||||
cacheInfo.schemaCacheTTL,
|
||||
cacheInfo.enableSingleSchemaCache
|
||||
);
|
||||
config.database = new DatabaseController(
|
||||
cacheInfo.databaseController.adapter,
|
||||
schemaCache
|
||||
);
|
||||
config.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
|
||||
} else {
|
||||
config[key] = cacheInfo[key];
|
||||
}
|
||||
});
|
||||
config.mount = removeTrailingSlash(mount);
|
||||
config.generateSessionExpiresAt = config.generateSessionExpiresAt.bind(
|
||||
config
|
||||
);
|
||||
config.generateSessionExpiresAt = config.generateSessionExpiresAt.bind(config);
|
||||
config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind(
|
||||
config
|
||||
);
|
||||
@@ -99,10 +94,7 @@ export class Config {
|
||||
}
|
||||
|
||||
if (publicServerURL) {
|
||||
if (
|
||||
!publicServerURL.startsWith('http://') &&
|
||||
!publicServerURL.startsWith('https://')
|
||||
) {
|
||||
if (!publicServerURL.startsWith('http://') && !publicServerURL.startsWith('https://')) {
|
||||
throw 'publicServerURL should be a valid HTTPS URL starting with https://';
|
||||
}
|
||||
}
|
||||
@@ -155,8 +147,7 @@ export class Config {
|
||||
if (passwordPolicy) {
|
||||
if (
|
||||
passwordPolicy.maxPasswordAge !== undefined &&
|
||||
(typeof passwordPolicy.maxPasswordAge !== 'number' ||
|
||||
passwordPolicy.maxPasswordAge < 0)
|
||||
(typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0)
|
||||
) {
|
||||
throw 'passwordPolicy.maxPasswordAge must be a positive number';
|
||||
}
|
||||
@@ -171,9 +162,7 @@ export class Config {
|
||||
|
||||
if (passwordPolicy.validatorPattern) {
|
||||
if (typeof passwordPolicy.validatorPattern === 'string') {
|
||||
passwordPolicy.validatorPattern = new RegExp(
|
||||
passwordPolicy.validatorPattern
|
||||
);
|
||||
passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern);
|
||||
} else if (!(passwordPolicy.validatorPattern instanceof RegExp)) {
|
||||
throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.';
|
||||
}
|
||||
@@ -294,22 +283,15 @@ export class Config {
|
||||
return undefined;
|
||||
}
|
||||
var now = new Date();
|
||||
return new Date(
|
||||
now.getTime() + this.emailVerifyTokenValidityDuration * 1000
|
||||
);
|
||||
return new Date(now.getTime() + this.emailVerifyTokenValidityDuration * 1000);
|
||||
}
|
||||
|
||||
generatePasswordResetTokenExpiresAt() {
|
||||
if (
|
||||
!this.passwordPolicy ||
|
||||
!this.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
if (!this.passwordPolicy || !this.passwordPolicy.resetTokenValidityDuration) {
|
||||
return undefined;
|
||||
}
|
||||
const now = new Date();
|
||||
return new Date(
|
||||
now.getTime() + this.passwordPolicy.resetTokenValidityDuration * 1000
|
||||
);
|
||||
return new Date(now.getTime() + this.passwordPolicy.resetTokenValidityDuration * 1000);
|
||||
}
|
||||
|
||||
generateSessionExpiresAt() {
|
||||
@@ -321,10 +303,7 @@ export class Config {
|
||||
}
|
||||
|
||||
get invalidLinkURL() {
|
||||
return (
|
||||
this.customPages.invalidLink ||
|
||||
`${this.publicServerURL}/apps/invalid_link.html`
|
||||
);
|
||||
return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`;
|
||||
}
|
||||
|
||||
get invalidVerificationLinkURL() {
|
||||
@@ -336,16 +315,12 @@ export class Config {
|
||||
|
||||
get linkSendSuccessURL() {
|
||||
return (
|
||||
this.customPages.linkSendSuccess ||
|
||||
`${this.publicServerURL}/apps/link_send_success.html`
|
||||
this.customPages.linkSendSuccess || `${this.publicServerURL}/apps/link_send_success.html`
|
||||
);
|
||||
}
|
||||
|
||||
get linkSendFailURL() {
|
||||
return (
|
||||
this.customPages.linkSendFail ||
|
||||
`${this.publicServerURL}/apps/link_send_fail.html`
|
||||
);
|
||||
return this.customPages.linkSendFail || `${this.publicServerURL}/apps/link_send_fail.html`;
|
||||
}
|
||||
|
||||
get verifyEmailSuccessURL() {
|
||||
@@ -356,10 +331,7 @@ export class Config {
|
||||
}
|
||||
|
||||
get choosePasswordURL() {
|
||||
return (
|
||||
this.customPages.choosePassword ||
|
||||
`${this.publicServerURL}/apps/choose_password`
|
||||
);
|
||||
return this.customPages.choosePassword || `${this.publicServerURL}/apps/choose_password`;
|
||||
}
|
||||
|
||||
get requestResetPasswordURL() {
|
||||
|
||||
@@ -52,27 +52,20 @@ export class AdaptableController {
|
||||
}
|
||||
|
||||
// Makes sure the prototype matches
|
||||
const mismatches = Object.getOwnPropertyNames(Type.prototype).reduce(
|
||||
(obj, key) => {
|
||||
const adapterType = typeof adapter[key];
|
||||
const expectedType = typeof Type.prototype[key];
|
||||
if (adapterType !== expectedType) {
|
||||
obj[key] = {
|
||||
expected: expectedType,
|
||||
actual: adapterType,
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const mismatches = Object.getOwnPropertyNames(Type.prototype).reduce((obj, key) => {
|
||||
const adapterType = typeof adapter[key];
|
||||
const expectedType = typeof Type.prototype[key];
|
||||
if (adapterType !== expectedType) {
|
||||
obj[key] = {
|
||||
expected: expectedType,
|
||||
actual: adapterType,
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
if (Object.keys(mismatches).length > 0) {
|
||||
throw new Error(
|
||||
"Adapter prototype don't match expected prototype",
|
||||
adapter,
|
||||
mismatches
|
||||
);
|
||||
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,14 +31,12 @@ export class FilesController extends AdaptableController {
|
||||
}
|
||||
|
||||
const location = this.adapter.getFileLocation(config, filename);
|
||||
return this.adapter
|
||||
.createFile(filename, data, contentType, options)
|
||||
.then(() => {
|
||||
return Promise.resolve({
|
||||
url: location,
|
||||
name: filename,
|
||||
});
|
||||
return this.adapter.createFile(filename, data, contentType, options).then(() => {
|
||||
return Promise.resolve({
|
||||
url: location,
|
||||
name: filename,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteFile(config, filename) {
|
||||
@@ -80,16 +78,10 @@ export class FilesController extends AdaptableController {
|
||||
} else {
|
||||
if (filename.indexOf('tfss-') === 0) {
|
||||
fileObject['url'] =
|
||||
'http://files.parsetfss.com/' +
|
||||
config.fileKey +
|
||||
'/' +
|
||||
encodeURIComponent(filename);
|
||||
'http://files.parsetfss.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
||||
} else if (legacyFilesRegex.test(filename)) {
|
||||
fileObject['url'] =
|
||||
'http://files.parse.com/' +
|
||||
config.fileKey +
|
||||
'/' +
|
||||
encodeURIComponent(filename);
|
||||
'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
||||
} else {
|
||||
fileObject['url'] = this.adapter.getFileLocation(config, filename);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,7 @@ export class HooksController {
|
||||
}
|
||||
|
||||
getFunction(functionName) {
|
||||
return this._getHooks({ functionName: functionName }).then(
|
||||
results => results[0]
|
||||
);
|
||||
return this._getHooks({ functionName: functionName }).then(results => results[0]);
|
||||
}
|
||||
|
||||
getFunctions() {
|
||||
@@ -73,14 +71,12 @@ export class HooksController {
|
||||
}
|
||||
|
||||
_getHooks(query = {}) {
|
||||
return this.database
|
||||
.find(DefaultHooksCollectionName, query)
|
||||
.then(results => {
|
||||
return results.map(result => {
|
||||
delete result.objectId;
|
||||
return result;
|
||||
});
|
||||
return this.database.find(DefaultHooksCollectionName, query).then(results => {
|
||||
return results.map(result => {
|
||||
delete result.objectId;
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_removeHooks(query) {
|
||||
@@ -109,19 +105,9 @@ export class HooksController {
|
||||
var wrappedFunction = wrapToHTTPRequest(hook, this._webhookKey);
|
||||
wrappedFunction.url = hook.url;
|
||||
if (hook.className) {
|
||||
triggers.addTrigger(
|
||||
hook.triggerName,
|
||||
hook.className,
|
||||
wrappedFunction,
|
||||
this._applicationId
|
||||
);
|
||||
triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this._applicationId);
|
||||
} else {
|
||||
triggers.addFunction(
|
||||
hook.functionName,
|
||||
wrappedFunction,
|
||||
null,
|
||||
this._applicationId
|
||||
);
|
||||
triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,26 +144,21 @@ export class HooksController {
|
||||
if (aHook.functionName) {
|
||||
return this.getFunction(aHook.functionName).then(result => {
|
||||
if (result) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`function name: ${aHook.functionName} already exits`
|
||||
);
|
||||
throw new Parse.Error(143, `function name: ${aHook.functionName} already exits`);
|
||||
} else {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
});
|
||||
} else if (aHook.className && aHook.triggerName) {
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(
|
||||
result => {
|
||||
if (result) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`class ${aHook.className} already has trigger ${aHook.triggerName}`
|
||||
);
|
||||
}
|
||||
return this.createOrUpdateHook(aHook);
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(result => {
|
||||
if (result) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`class ${aHook.className} already has trigger ${aHook.triggerName}`
|
||||
);
|
||||
}
|
||||
);
|
||||
return this.createOrUpdateHook(aHook);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
@@ -189,20 +170,15 @@ export class HooksController {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`no function named: ${aHook.functionName} is defined`
|
||||
);
|
||||
throw new Parse.Error(143, `no function named: ${aHook.functionName} is defined`);
|
||||
});
|
||||
} else if (aHook.className && aHook.triggerName) {
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(
|
||||
result => {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
throw new Parse.Error(143, `class ${aHook.className} does not exist`);
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(result => {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
);
|
||||
throw new Parse.Error(143, `class ${aHook.className} does not exist`);
|
||||
});
|
||||
}
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
}
|
||||
@@ -231,17 +207,13 @@ function wrapToHTTPRequest(hook, key) {
|
||||
method: 'POST',
|
||||
};
|
||||
|
||||
const agent = hook.url.startsWith('https')
|
||||
? HTTPAgents['https']
|
||||
: HTTPAgents['http'];
|
||||
const agent = hook.url.startsWith('https') ? HTTPAgents['https'] : HTTPAgents['http'];
|
||||
jsonRequest.agent = agent;
|
||||
|
||||
if (key) {
|
||||
jsonRequest.headers['X-Parse-Webhook-Key'] = key;
|
||||
} else {
|
||||
logger.warn(
|
||||
'Making outgoing webhook request without webhookKey being set!'
|
||||
);
|
||||
logger.warn('Making outgoing webhook request without webhookKey being set!');
|
||||
}
|
||||
return request(jsonRequest).then(response => {
|
||||
let err;
|
||||
|
||||
@@ -25,11 +25,7 @@ export class LiveQueryController {
|
||||
if (!this.hasLiveQuery(className)) {
|
||||
return;
|
||||
}
|
||||
const req = this._makePublisherRequest(
|
||||
currentObject,
|
||||
originalObject,
|
||||
classLevelPermissions
|
||||
);
|
||||
const req = this._makePublisherRequest(currentObject, originalObject, classLevelPermissions);
|
||||
this.liveQueryPublisher.onCloudCodeAfterSave(req);
|
||||
}
|
||||
|
||||
@@ -42,11 +38,7 @@ export class LiveQueryController {
|
||||
if (!this.hasLiveQuery(className)) {
|
||||
return;
|
||||
}
|
||||
const req = this._makePublisherRequest(
|
||||
currentObject,
|
||||
originalObject,
|
||||
classLevelPermissions
|
||||
);
|
||||
const req = this._makePublisherRequest(currentObject, originalObject, classLevelPermissions);
|
||||
this.liveQueryPublisher.onCloudCodeAfterDelete(req);
|
||||
}
|
||||
|
||||
@@ -54,11 +46,7 @@ export class LiveQueryController {
|
||||
return this.classNames.has(className);
|
||||
}
|
||||
|
||||
_makePublisherRequest(
|
||||
currentObject: any,
|
||||
originalObject: any,
|
||||
classLevelPermissions: ?any
|
||||
): any {
|
||||
_makePublisherRequest(currentObject: any, originalObject: any, classLevelPermissions: ?any): any {
|
||||
const req = {
|
||||
object: currentObject,
|
||||
};
|
||||
|
||||
@@ -189,8 +189,7 @@ export class LoggerController extends AdaptableController {
|
||||
|
||||
truncateLogMessage(string) {
|
||||
if (string && string.length > LOG_STRING_TRUNCATE_LENGTH) {
|
||||
const truncated =
|
||||
string.substring(0, LOG_STRING_TRUNCATE_LENGTH) + truncationMarker;
|
||||
const truncated = string.substring(0, LOG_STRING_TRUNCATE_LENGTH) + truncationMarker;
|
||||
return truncated;
|
||||
}
|
||||
|
||||
@@ -224,10 +223,7 @@ export class LoggerController extends AdaptableController {
|
||||
// size (optional) Number of rows returned by search. Defaults to 10
|
||||
getLogs(options = {}) {
|
||||
if (!this.adapter) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Logger adapter is not available'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not available');
|
||||
}
|
||||
if (typeof this.adapter.query !== 'function') {
|
||||
throw new Parse.Error(
|
||||
|
||||
@@ -57,9 +57,7 @@ class ParseGraphQLController {
|
||||
return graphQLConfig;
|
||||
}
|
||||
|
||||
async updateGraphQLConfig(
|
||||
graphQLConfig: ParseGraphQLConfig
|
||||
): Promise<ParseGraphQLConfig> {
|
||||
async updateGraphQLConfig(graphQLConfig: ParseGraphQLConfig): Promise<ParseGraphQLConfig> {
|
||||
// throws if invalid
|
||||
this._validateGraphQLConfig(
|
||||
graphQLConfig || requiredParameter('You must provide a graphQLConfig!')
|
||||
@@ -97,11 +95,7 @@ class ParseGraphQLController {
|
||||
}
|
||||
|
||||
_putCachedGraphQLConfig(graphQLConfig: ParseGraphQLConfig) {
|
||||
return this.cacheController.graphQL.put(
|
||||
this.configCacheKey,
|
||||
graphQLConfig,
|
||||
60000
|
||||
);
|
||||
return this.cacheController.graphQL.put(this.configCacheKey, graphQLConfig, 60000);
|
||||
}
|
||||
|
||||
_validateGraphQLConfig(graphQLConfig: ?ParseGraphQLConfig): void {
|
||||
@@ -119,20 +113,12 @@ class ParseGraphQLController {
|
||||
} = graphQLConfig;
|
||||
|
||||
if (Object.keys(invalidKeys).length) {
|
||||
errorMessages.push(
|
||||
`encountered invalid keys: [${Object.keys(invalidKeys)}]`
|
||||
);
|
||||
errorMessages.push(`encountered invalid keys: [${Object.keys(invalidKeys)}]`);
|
||||
}
|
||||
if (
|
||||
enabledForClasses !== null &&
|
||||
!isValidStringArray(enabledForClasses)
|
||||
) {
|
||||
if (enabledForClasses !== null && !isValidStringArray(enabledForClasses)) {
|
||||
errorMessages.push(`"enabledForClasses" is not a valid array`);
|
||||
}
|
||||
if (
|
||||
disabledForClasses !== null &&
|
||||
!isValidStringArray(disabledForClasses)
|
||||
) {
|
||||
if (disabledForClasses !== null && !isValidStringArray(disabledForClasses)) {
|
||||
errorMessages.push(`"disabledForClasses" is not a valid array`);
|
||||
}
|
||||
if (classConfigs !== null) {
|
||||
@@ -159,17 +145,9 @@ class ParseGraphQLController {
|
||||
if (!isValidSimpleObject(classConfig)) {
|
||||
return 'it must be a valid object';
|
||||
} else {
|
||||
const {
|
||||
className,
|
||||
type = null,
|
||||
query = null,
|
||||
mutation = null,
|
||||
...invalidKeys
|
||||
} = classConfig;
|
||||
const { className, type = null, query = null, mutation = null, ...invalidKeys } = classConfig;
|
||||
if (Object.keys(invalidKeys).length) {
|
||||
return `"invalidKeys" [${Object.keys(
|
||||
invalidKeys
|
||||
)}] should not be present`;
|
||||
return `"invalidKeys" [${Object.keys(invalidKeys)}] should not be present`;
|
||||
}
|
||||
if (typeof className !== 'string' || !className.trim().length) {
|
||||
// TODO consider checking class exists in schema?
|
||||
@@ -190,10 +168,7 @@ class ParseGraphQLController {
|
||||
return `"type" contains invalid keys, [${Object.keys(invalidKeys)}]`;
|
||||
} else if (outputFields !== null && !isValidStringArray(outputFields)) {
|
||||
return `"outputFields" must be a valid string array`;
|
||||
} else if (
|
||||
constraintFields !== null &&
|
||||
!isValidStringArray(constraintFields)
|
||||
) {
|
||||
} else if (constraintFields !== null && !isValidStringArray(constraintFields)) {
|
||||
return `"constraintFields" must be a valid string array`;
|
||||
}
|
||||
if (sortFields !== null) {
|
||||
@@ -214,10 +189,7 @@ class ParseGraphQLController {
|
||||
if (typeof field !== 'string' || field.trim().length === 0) {
|
||||
errorMessage = `"sortField" at index ${index} did not provide the "field" as a string`;
|
||||
return false;
|
||||
} else if (
|
||||
typeof asc !== 'boolean' ||
|
||||
typeof desc !== 'boolean'
|
||||
) {
|
||||
} else if (typeof asc !== 'boolean' || typeof desc !== 'boolean') {
|
||||
errorMessage = `"sortField" at index ${index} did not provide "asc" or "desc" as booleans`;
|
||||
return false;
|
||||
}
|
||||
@@ -234,15 +206,9 @@ class ParseGraphQLController {
|
||||
}
|
||||
if (inputFields !== null) {
|
||||
if (isValidSimpleObject(inputFields)) {
|
||||
const {
|
||||
create = null,
|
||||
update = null,
|
||||
...invalidKeys
|
||||
} = inputFields;
|
||||
const { create = null, update = null, ...invalidKeys } = inputFields;
|
||||
if (Object.keys(invalidKeys).length) {
|
||||
return `"inputFields" contains invalid keys: [${Object.keys(
|
||||
invalidKeys
|
||||
)}]`;
|
||||
return `"inputFields" contains invalid keys: [${Object.keys(invalidKeys)}]`;
|
||||
} else {
|
||||
if (update !== null && !isValidStringArray(update)) {
|
||||
return `"inputFields.update" must be a valid string array`;
|
||||
@@ -250,10 +216,7 @@ class ParseGraphQLController {
|
||||
if (!isValidStringArray(create)) {
|
||||
return `"inputFields.create" must be a valid string array`;
|
||||
} else if (className === '_User') {
|
||||
if (
|
||||
!create.includes('username') ||
|
||||
!create.includes('password')
|
||||
) {
|
||||
if (!create.includes('username') || !create.includes('password')) {
|
||||
return `"inputFields.create" must include required fields, username and password`;
|
||||
}
|
||||
}
|
||||
@@ -274,9 +237,7 @@ class ParseGraphQLController {
|
||||
...invalidKeys
|
||||
} = query;
|
||||
if (Object.keys(invalidKeys).length) {
|
||||
return `"query" contains invalid keys, [${Object.keys(
|
||||
invalidKeys
|
||||
)}]`;
|
||||
return `"query" contains invalid keys, [${Object.keys(invalidKeys)}]`;
|
||||
} else if (find !== null && typeof find !== 'boolean') {
|
||||
return `"query.find" must be a boolean`;
|
||||
} else if (get !== null && typeof get !== 'boolean') {
|
||||
@@ -302,9 +263,7 @@ class ParseGraphQLController {
|
||||
...invalidKeys
|
||||
} = mutation;
|
||||
if (Object.keys(invalidKeys).length) {
|
||||
return `"mutation" contains invalid keys, [${Object.keys(
|
||||
invalidKeys
|
||||
)}]`;
|
||||
return `"mutation" contains invalid keys, [${Object.keys(invalidKeys)}]`;
|
||||
}
|
||||
if (create !== null && typeof create !== 'boolean') {
|
||||
return `"mutation.create" must be a boolean`;
|
||||
|
||||
@@ -6,19 +6,9 @@ import { pushStatusHandler } from '../StatusHandler';
|
||||
import { applyDeviceTokenExists } from '../Push/utils';
|
||||
|
||||
export class PushController {
|
||||
sendPush(
|
||||
body = {},
|
||||
where = {},
|
||||
config,
|
||||
auth,
|
||||
onPushStatusSaved = () => {},
|
||||
now = new Date()
|
||||
) {
|
||||
sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}, now = new Date()) {
|
||||
if (!config.hasPushSupport) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Missing push configuration'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration');
|
||||
}
|
||||
|
||||
// Replace the expiration_time and push_time with a valid Unix epoch milliseconds time
|
||||
@@ -32,10 +22,7 @@ export class PushController {
|
||||
}
|
||||
|
||||
// Immediate push
|
||||
if (
|
||||
body.expiration_interval &&
|
||||
!Object.prototype.hasOwnProperty.call(body, 'push_time')
|
||||
) {
|
||||
if (body.expiration_interval && !Object.prototype.hasOwnProperty.call(body, 'push_time')) {
|
||||
const ttlMs = body.expiration_interval * 1000;
|
||||
body.expiration_time = new Date(now.valueOf() + ttlMs).valueOf();
|
||||
}
|
||||
@@ -73,12 +60,7 @@ export class PushController {
|
||||
const updateWhere = applyDeviceTokenExists(where);
|
||||
badgeUpdate = () => {
|
||||
// Build a real RestQuery so we can use it in RestWrite
|
||||
const restQuery = new RestQuery(
|
||||
config,
|
||||
master(config),
|
||||
'_Installation',
|
||||
updateWhere
|
||||
);
|
||||
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
|
||||
return restQuery.buildRestWhere().then(() => {
|
||||
const write = new RestWrite(
|
||||
config,
|
||||
@@ -129,13 +111,7 @@ export class PushController {
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return config.pushControllerQueue.enqueue(
|
||||
body,
|
||||
where,
|
||||
config,
|
||||
auth,
|
||||
pushStatus
|
||||
);
|
||||
return config.pushControllerQueue.enqueue(body, where, config, auth, pushStatus);
|
||||
})
|
||||
.catch(err => {
|
||||
return pushStatus.fail(err).then(() => {
|
||||
@@ -150,10 +126,7 @@ export class PushController {
|
||||
* @returns {Number|undefined} The expiration time if it exists in the request
|
||||
*/
|
||||
static getExpirationTime(body = {}) {
|
||||
var hasExpirationTime = Object.prototype.hasOwnProperty.call(
|
||||
body,
|
||||
'expiration_time'
|
||||
);
|
||||
var hasExpirationTime = Object.prototype.hasOwnProperty.call(body, 'expiration_time');
|
||||
if (!hasExpirationTime) {
|
||||
return;
|
||||
}
|
||||
@@ -180,19 +153,13 @@ export class PushController {
|
||||
}
|
||||
|
||||
static getExpirationInterval(body = {}) {
|
||||
const hasExpirationInterval = Object.prototype.hasOwnProperty.call(
|
||||
body,
|
||||
'expiration_interval'
|
||||
);
|
||||
const hasExpirationInterval = Object.prototype.hasOwnProperty.call(body, 'expiration_interval');
|
||||
if (!hasExpirationInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
var expirationIntervalParam = body['expiration_interval'];
|
||||
if (
|
||||
typeof expirationIntervalParam !== 'number' ||
|
||||
expirationIntervalParam <= 0
|
||||
) {
|
||||
if (typeof expirationIntervalParam !== 'number' || expirationIntervalParam <= 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
`expiration_interval must be a number greater than 0`
|
||||
@@ -248,8 +215,7 @@ export class PushController {
|
||||
static pushTimeHasTimezoneComponent(pushTimeParam: string): boolean {
|
||||
const offsetPattern = /(.+)([+-])\d\d:\d\d$/;
|
||||
return (
|
||||
pushTimeParam.indexOf('Z') === pushTimeParam.length - 1 || // 2007-04-05T12:30Z
|
||||
offsetPattern.test(pushTimeParam)
|
||||
pushTimeParam.indexOf('Z') === pushTimeParam.length - 1 || offsetPattern.test(pushTimeParam) // 2007-04-05T12:30Z
|
||||
); // 2007-04-05T12:30.000+02:00, 2007-04-05T12:30.000-02:00
|
||||
}
|
||||
|
||||
@@ -259,13 +225,7 @@ export class PushController {
|
||||
* @param isLocalTime {boolean}
|
||||
* @returns {string}
|
||||
*/
|
||||
static formatPushTime({
|
||||
date,
|
||||
isLocalTime,
|
||||
}: {
|
||||
date: Date,
|
||||
isLocalTime: boolean,
|
||||
}) {
|
||||
static formatPushTime({ date, isLocalTime }: { date: Date, isLocalTime: boolean }) {
|
||||
if (isLocalTime) {
|
||||
// Strip 'Z'
|
||||
const isoString = date.toISOString();
|
||||
|
||||
@@ -7,11 +7,7 @@ import defaults from '../defaults';
|
||||
export default class SchemaCache {
|
||||
cache: Object;
|
||||
|
||||
constructor(
|
||||
cacheController,
|
||||
ttl = defaults.schemaCacheTTL,
|
||||
singleCache = false
|
||||
) {
|
||||
constructor(cacheController, ttl = defaults.schemaCacheTTL, singleCache = false) {
|
||||
this.ttl = ttl;
|
||||
if (typeof ttl == 'string') {
|
||||
this.ttl = parseInt(ttl);
|
||||
|
||||
@@ -260,11 +260,7 @@ const CLPValidKeys = Object.freeze([
|
||||
]);
|
||||
|
||||
// validation before setting class-level permissions on collection
|
||||
function validateCLP(
|
||||
perms: ClassLevelPermissions,
|
||||
fields: SchemaFields,
|
||||
userIdRegExp: RegExp
|
||||
) {
|
||||
function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdRegExp: RegExp) {
|
||||
if (!perms) {
|
||||
return;
|
||||
}
|
||||
@@ -282,10 +278,7 @@ function validateCLP(
|
||||
// throws when root fields are of wrong type
|
||||
validateCLPjson(operation, operationKey);
|
||||
|
||||
if (
|
||||
operationKey === 'readUserFields' ||
|
||||
operationKey === 'writeUserFields'
|
||||
) {
|
||||
if (operationKey === 'readUserFields' || operationKey === 'writeUserFields') {
|
||||
// validate grouped pointer permissions
|
||||
// must be an array with field names
|
||||
for (const fieldName of operation) {
|
||||
@@ -397,11 +390,7 @@ function validateCLPjson(operation: any, operationKey: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function validatePointerPermission(
|
||||
fieldName: string,
|
||||
fields: Object,
|
||||
operation: string
|
||||
) {
|
||||
function validatePointerPermission(fieldName: string, fields: Object, operation: string) {
|
||||
// Uses collection schema to ensure the field is of type:
|
||||
// - Pointer<_User> (pointers)
|
||||
// - Array
|
||||
@@ -412,8 +401,7 @@ function validatePointerPermission(
|
||||
if (
|
||||
!(
|
||||
fields[fieldName] &&
|
||||
((fields[fieldName].type == 'Pointer' &&
|
||||
fields[fieldName].targetClass == '_User') ||
|
||||
((fields[fieldName].type == 'Pointer' && fields[fieldName].targetClass == '_User') ||
|
||||
fields[fieldName].type == 'Array')
|
||||
)
|
||||
) {
|
||||
@@ -444,10 +432,7 @@ function fieldNameIsValid(fieldName: string): boolean {
|
||||
}
|
||||
|
||||
// Checks that it's not trying to clobber one of the default fields of the class.
|
||||
function fieldNameIsValidForClass(
|
||||
fieldName: string,
|
||||
className: string
|
||||
): boolean {
|
||||
function fieldNameIsValidForClass(fieldName: string, className: string): boolean {
|
||||
if (!fieldNameIsValid(fieldName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -468,10 +453,7 @@ function invalidClassNameMessage(className: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
const invalidJsonError = new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'invalid JSON'
|
||||
);
|
||||
const invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, 'invalid JSON');
|
||||
const validNonRelationOrPointerTypes = [
|
||||
'Number',
|
||||
'String',
|
||||
@@ -492,10 +474,7 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => {
|
||||
} else if (typeof targetClass !== 'string') {
|
||||
return invalidJsonError;
|
||||
} else if (!classNameIsValid(targetClass)) {
|
||||
return new Parse.Error(
|
||||
Parse.Error.INVALID_CLASS_NAME,
|
||||
invalidClassNameMessage(targetClass)
|
||||
);
|
||||
return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass));
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
@@ -504,10 +483,7 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => {
|
||||
return invalidJsonError;
|
||||
}
|
||||
if (validNonRelationOrPointerTypes.indexOf(type) < 0) {
|
||||
return new Parse.Error(
|
||||
Parse.Error.INCORRECT_TYPE,
|
||||
`invalid field type: ${type}`
|
||||
);
|
||||
return new Parse.Error(Parse.Error.INCORRECT_TYPE, `invalid field type: ${type}`);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -563,18 +539,14 @@ class SchemaData {
|
||||
data.classLevelPermissions = deepcopy(schema.classLevelPermissions);
|
||||
data.indexes = schema.indexes;
|
||||
|
||||
const classProtectedFields = this.__protectedFields[
|
||||
schema.className
|
||||
];
|
||||
const classProtectedFields = this.__protectedFields[schema.className];
|
||||
if (classProtectedFields) {
|
||||
for (const key in classProtectedFields) {
|
||||
const unq = new Set([
|
||||
...(data.classLevelPermissions.protectedFields[key] || []),
|
||||
...classProtectedFields[key],
|
||||
]);
|
||||
data.classLevelPermissions.protectedFields[key] = Array.from(
|
||||
unq
|
||||
);
|
||||
data.classLevelPermissions.protectedFields[key] = Array.from(unq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,12 +580,7 @@ class SchemaData {
|
||||
}
|
||||
}
|
||||
|
||||
const injectDefaultSchema = ({
|
||||
className,
|
||||
fields,
|
||||
classLevelPermissions,
|
||||
indexes,
|
||||
}: Schema) => {
|
||||
const injectDefaultSchema = ({ className, fields, classLevelPermissions, indexes }: Schema) => {
|
||||
const defaultSchema: Schema = {
|
||||
className,
|
||||
fields: {
|
||||
@@ -684,10 +651,7 @@ const VolatileClassesSchemas = [
|
||||
_IdempotencySchema,
|
||||
];
|
||||
|
||||
const dbTypeMatchesObjectType = (
|
||||
dbType: SchemaField | string,
|
||||
objectType: SchemaField
|
||||
) => {
|
||||
const dbTypeMatchesObjectType = (dbType: SchemaField | string, objectType: SchemaField) => {
|
||||
if (dbType.type !== objectType.type) return false;
|
||||
if (dbType.targetClass !== objectType.targetClass) return false;
|
||||
if (dbType === objectType.type) return true;
|
||||
@@ -749,9 +713,7 @@ export default class SchemaController {
|
||||
return this.reloadDataPromise;
|
||||
}
|
||||
|
||||
getAllClasses(
|
||||
options: LoadSchemaOptions = { clearCache: false }
|
||||
): Promise<Array<Schema>> {
|
||||
getAllClasses(options: LoadSchemaOptions = { clearCache: false }): Promise<Array<Schema>> {
|
||||
if (options.clearCache) {
|
||||
return this.setAllClasses();
|
||||
}
|
||||
@@ -771,9 +733,7 @@ export default class SchemaController {
|
||||
/* eslint-disable no-console */
|
||||
this._cache
|
||||
.setAllClasses(allSchemas)
|
||||
.catch(error =>
|
||||
console.error('Error saving schema to cache:', error)
|
||||
);
|
||||
.catch(error => console.error('Error saving schema to cache:', error));
|
||||
/* eslint-enable no-console */
|
||||
return allSchemas;
|
||||
});
|
||||
@@ -803,9 +763,7 @@ export default class SchemaController {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
return this.setAllClasses().then(allSchemas => {
|
||||
const oneSchema = allSchemas.find(
|
||||
schema => schema.className === className
|
||||
);
|
||||
const oneSchema = allSchemas.find(schema => schema.className === className);
|
||||
if (!oneSchema) {
|
||||
return Promise.reject(undefined);
|
||||
}
|
||||
@@ -828,18 +786,12 @@ export default class SchemaController {
|
||||
classLevelPermissions: any,
|
||||
indexes: any = {}
|
||||
): Promise<void | Schema> {
|
||||
var validationError = this.validateNewClass(
|
||||
className,
|
||||
fields,
|
||||
classLevelPermissions
|
||||
);
|
||||
var validationError = this.validateNewClass(className, fields, classLevelPermissions);
|
||||
if (validationError) {
|
||||
if (validationError instanceof Parse.Error) {
|
||||
return Promise.reject(validationError);
|
||||
} else if (validationError.code && validationError.error) {
|
||||
return Promise.reject(
|
||||
new Parse.Error(validationError.code, validationError.error)
|
||||
);
|
||||
return Promise.reject(new Parse.Error(validationError.code, validationError.error));
|
||||
}
|
||||
return Promise.reject(validationError);
|
||||
}
|
||||
@@ -883,21 +835,14 @@ export default class SchemaController {
|
||||
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
|
||||
}
|
||||
if (!existingFields[name] && field.__op === 'Delete') {
|
||||
throw new Parse.Error(
|
||||
255,
|
||||
`Field ${name} does not exist, cannot delete.`
|
||||
);
|
||||
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
|
||||
}
|
||||
});
|
||||
|
||||
delete existingFields._rperm;
|
||||
delete existingFields._wperm;
|
||||
const newSchema = buildMergedSchemaObject(
|
||||
existingFields,
|
||||
submittedFields
|
||||
);
|
||||
const defaultFields =
|
||||
defaultColumns[className] || defaultColumns._Default;
|
||||
const newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
||||
const defaultFields = defaultColumns[className] || defaultColumns._Default;
|
||||
const fullNewSchema = Object.assign({}, newSchema, defaultFields);
|
||||
const validationError = this.validateSchemaData(
|
||||
className,
|
||||
@@ -938,11 +883,7 @@ export default class SchemaController {
|
||||
})
|
||||
.then(results => {
|
||||
enforceFields = results.filter(result => !!result);
|
||||
return this.setPermissions(
|
||||
className,
|
||||
classLevelPermissions,
|
||||
newSchema
|
||||
);
|
||||
return this.setPermissions(className, classLevelPermissions, newSchema);
|
||||
})
|
||||
.then(() =>
|
||||
this._dbAdapter.setIndexesWithSchemaFormat(
|
||||
@@ -1004,32 +945,19 @@ export default class SchemaController {
|
||||
if (this.schemaData[className]) {
|
||||
return this;
|
||||
} else {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
`Failed to add ${className}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// The schema still doesn't validate. Give up
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'schema class name does not revalidate'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
validateNewClass(
|
||||
className: string,
|
||||
fields: SchemaFields = {},
|
||||
classLevelPermissions: any
|
||||
): any {
|
||||
validateNewClass(className: string, fields: SchemaFields = {}, classLevelPermissions: any): any {
|
||||
if (this.schemaData[className]) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_CLASS_NAME,
|
||||
`Class ${className} already exists.`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
||||
}
|
||||
if (!classNameIsValid(className)) {
|
||||
return {
|
||||
@@ -1037,12 +965,7 @@ export default class SchemaController {
|
||||
error: invalidClassNameMessage(className),
|
||||
};
|
||||
}
|
||||
return this.validateSchemaData(
|
||||
className,
|
||||
fields,
|
||||
classLevelPermissions,
|
||||
[]
|
||||
);
|
||||
return this.validateSchemaData(className, fields, classLevelPermissions, []);
|
||||
}
|
||||
|
||||
validateSchemaData(
|
||||
@@ -1072,15 +995,10 @@ export default class SchemaController {
|
||||
let defaultValueType = getType(fieldType.defaultValue);
|
||||
if (typeof defaultValueType === 'string') {
|
||||
defaultValueType = { type: defaultValueType };
|
||||
} else if (
|
||||
typeof defaultValueType === 'object' &&
|
||||
fieldType.type === 'Relation'
|
||||
) {
|
||||
} else if (typeof defaultValueType === 'object' && fieldType.type === 'Relation') {
|
||||
return {
|
||||
code: Parse.Error.INCORRECT_TYPE,
|
||||
error: `The 'default value' option is not applicable for ${typeToString(
|
||||
fieldType
|
||||
)}`,
|
||||
error: `The 'default value' option is not applicable for ${typeToString(fieldType)}`,
|
||||
};
|
||||
}
|
||||
if (!dbTypeMatchesObjectType(fieldType, defaultValueType)) {
|
||||
@@ -1095,9 +1013,7 @@ export default class SchemaController {
|
||||
if (typeof fieldType === 'object' && fieldType.type === 'Relation') {
|
||||
return {
|
||||
code: Parse.Error.INCORRECT_TYPE,
|
||||
error: `The 'required' option is not applicable for ${typeToString(
|
||||
fieldType
|
||||
)}`,
|
||||
error: `The 'required' option is not applicable for ${typeToString(fieldType)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1138,21 +1054,14 @@ export default class SchemaController {
|
||||
// object if the provided className-fieldName-type tuple is valid.
|
||||
// The className must already be validated.
|
||||
// If 'freeze' is true, refuse to update the schema for this field.
|
||||
enforceFieldExists(
|
||||
className: string,
|
||||
fieldName: string,
|
||||
type: string | SchemaField
|
||||
) {
|
||||
enforceFieldExists(className: string, fieldName: string, type: string | SchemaField) {
|
||||
if (fieldName.indexOf('.') > 0) {
|
||||
// subdocument key (x.y) => ok if x is of type 'object'
|
||||
fieldName = fieldName.split('.')[0];
|
||||
type = 'Object';
|
||||
}
|
||||
if (!fieldNameIsValid(fieldName)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
`Invalid field name: ${fieldName}.`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
||||
}
|
||||
|
||||
// If someone tries to create a new field with null/undefined as the value, return;
|
||||
@@ -1222,20 +1131,13 @@ export default class SchemaController {
|
||||
type = { type: type };
|
||||
}
|
||||
if (!expectedType || !dbTypeMatchesObjectType(expectedType, type)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
`Could not add field ${fieldName}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `Could not add field ${fieldName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// maintain compatibility
|
||||
deleteField(
|
||||
fieldName: string,
|
||||
className: string,
|
||||
database: DatabaseController
|
||||
) {
|
||||
deleteField(fieldName: string, className: string, database: DatabaseController) {
|
||||
return this.deleteFields([fieldName], className, database);
|
||||
}
|
||||
|
||||
@@ -1246,24 +1148,14 @@ export default class SchemaController {
|
||||
// Passing the database and prefix is necessary in order to drop relation collections
|
||||
// and remove fields from objects. Ideally the database would belong to
|
||||
// a database adapter and this function would close over it or access it via member.
|
||||
deleteFields(
|
||||
fieldNames: Array<string>,
|
||||
className: string,
|
||||
database: DatabaseController
|
||||
) {
|
||||
deleteFields(fieldNames: Array<string>, className: string, database: DatabaseController) {
|
||||
if (!classNameIsValid(className)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_CLASS_NAME,
|
||||
invalidClassNameMessage(className)
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(className));
|
||||
}
|
||||
|
||||
fieldNames.forEach(fieldName => {
|
||||
if (!fieldNameIsValid(fieldName)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
`invalid field name: ${fieldName}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `invalid field name: ${fieldName}`);
|
||||
}
|
||||
//Don't allow deleting the default fields.
|
||||
if (!fieldNameIsValidForClass(fieldName, className)) {
|
||||
@@ -1285,30 +1177,23 @@ export default class SchemaController {
|
||||
.then(schema => {
|
||||
fieldNames.forEach(fieldName => {
|
||||
if (!schema.fields[fieldName]) {
|
||||
throw new Parse.Error(
|
||||
255,
|
||||
`Field ${fieldName} does not exist, cannot delete.`
|
||||
);
|
||||
throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`);
|
||||
}
|
||||
});
|
||||
|
||||
const schemaFields = { ...schema.fields };
|
||||
return database.adapter
|
||||
.deleteFields(className, schema, fieldNames)
|
||||
.then(() => {
|
||||
return Promise.all(
|
||||
fieldNames.map(fieldName => {
|
||||
const field = schemaFields[fieldName];
|
||||
if (field && field.type === 'Relation') {
|
||||
//For relations, drop the _Join table
|
||||
return database.adapter.deleteClass(
|
||||
`_Join:${fieldName}:${className}`
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
);
|
||||
});
|
||||
return database.adapter.deleteFields(className, schema, fieldNames).then(() => {
|
||||
return Promise.all(
|
||||
fieldNames.map(fieldName => {
|
||||
const field = schemaFields[fieldName];
|
||||
if (field && field.type === 'Relation') {
|
||||
//For relations, drop the _Join table
|
||||
return database.adapter.deleteClass(`_Join:${fieldName}:${className}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(() => this._cache.clear());
|
||||
}
|
||||
@@ -1380,19 +1265,12 @@ export default class SchemaController {
|
||||
});
|
||||
|
||||
if (missingColumns.length > 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INCORRECT_TYPE,
|
||||
missingColumns[0] + ' is required.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, missingColumns[0] + ' is required.');
|
||||
}
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
testPermissionsForClassName(
|
||||
className: string,
|
||||
aclGroup: string[],
|
||||
operation: string
|
||||
) {
|
||||
testPermissionsForClassName(className: string, aclGroup: string[], operation: string) {
|
||||
return SchemaController.testPermissions(
|
||||
this.getClassLevelPermissions(className),
|
||||
aclGroup,
|
||||
@@ -1401,11 +1279,7 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
// Tests that the class level permission let pass the operation for a given aclGroup
|
||||
static testPermissions(
|
||||
classPermissions: ?any,
|
||||
aclGroup: string[],
|
||||
operation: string
|
||||
): boolean {
|
||||
static testPermissions(classPermissions: ?any, aclGroup: string[], operation: string): boolean {
|
||||
if (!classPermissions || !classPermissions[operation]) {
|
||||
return true;
|
||||
}
|
||||
@@ -1432,9 +1306,7 @@ export default class SchemaController {
|
||||
operation: string,
|
||||
action?: string
|
||||
) {
|
||||
if (
|
||||
SchemaController.testPermissions(classPermissions, aclGroup, operation)
|
||||
) {
|
||||
if (SchemaController.testPermissions(classPermissions, aclGroup, operation)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -1465,9 +1337,7 @@ export default class SchemaController {
|
||||
// No matching CLP, let's check the Pointer permissions
|
||||
// And handle those later
|
||||
const permissionField =
|
||||
['get', 'find', 'count'].indexOf(operation) > -1
|
||||
? 'readUserFields'
|
||||
: 'writeUserFields';
|
||||
['get', 'find', 'count'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields';
|
||||
|
||||
// Reject create when write lockdown
|
||||
if (permissionField == 'writeUserFields' && operation == 'create') {
|
||||
@@ -1501,12 +1371,7 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
// Validates an operation passes class-level-permissions set in the schema
|
||||
validatePermission(
|
||||
className: string,
|
||||
aclGroup: string[],
|
||||
operation: string,
|
||||
action?: string
|
||||
) {
|
||||
validatePermission(className: string, aclGroup: string[], operation: string, action?: string) {
|
||||
return SchemaController.validatePermission(
|
||||
this.getClassLevelPermissions(className),
|
||||
className,
|
||||
@@ -1517,18 +1382,12 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
getClassLevelPermissions(className: string): any {
|
||||
return (
|
||||
this.schemaData[className] &&
|
||||
this.schemaData[className].classLevelPermissions
|
||||
);
|
||||
return this.schemaData[className] && this.schemaData[className].classLevelPermissions;
|
||||
}
|
||||
|
||||
// Returns the expected type for a className+key combination
|
||||
// or undefined if the schema is not set
|
||||
getExpectedType(
|
||||
className: string,
|
||||
fieldName: string
|
||||
): ?(SchemaField | string) {
|
||||
getExpectedType(className: string, fieldName: string): ?(SchemaField | string) {
|
||||
if (this.schemaData[className]) {
|
||||
const expectedType = this.schemaData[className].fields[fieldName];
|
||||
return expectedType === 'map' ? 'Object' : expectedType;
|
||||
@@ -1560,10 +1419,7 @@ const load = (
|
||||
// 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(
|
||||
existingFields: SchemaFields,
|
||||
putRequest: any
|
||||
): SchemaFields {
|
||||
function buildMergedSchemaObject(existingFields: SchemaFields, putRequest: any): SchemaFields {
|
||||
const newSchema = {};
|
||||
// @flow-disable-next
|
||||
const sysSchemaField =
|
||||
@@ -1578,14 +1434,10 @@ function buildMergedSchemaObject(
|
||||
oldField !== 'createdAt' &&
|
||||
oldField !== 'objectId'
|
||||
) {
|
||||
if (
|
||||
sysSchemaField.length > 0 &&
|
||||
sysSchemaField.indexOf(oldField) !== -1
|
||||
) {
|
||||
if (sysSchemaField.length > 0 && sysSchemaField.indexOf(oldField) !== -1) {
|
||||
continue;
|
||||
}
|
||||
const fieldIsDeleted =
|
||||
putRequest[oldField] && putRequest[oldField].__op === 'Delete';
|
||||
const fieldIsDeleted = putRequest[oldField] && putRequest[oldField].__op === 'Delete';
|
||||
if (!fieldIsDeleted) {
|
||||
newSchema[oldField] = existingFields[oldField];
|
||||
}
|
||||
@@ -1593,10 +1445,7 @@ function buildMergedSchemaObject(
|
||||
}
|
||||
for (const newField in putRequest) {
|
||||
if (newField !== 'objectId' && putRequest[newField].__op !== 'Delete') {
|
||||
if (
|
||||
sysSchemaField.length > 0 &&
|
||||
sysSchemaField.indexOf(newField) !== -1
|
||||
) {
|
||||
if (sysSchemaField.length > 0 && sysSchemaField.indexOf(newField) !== -1) {
|
||||
continue;
|
||||
}
|
||||
newSchema[newField] = putRequest[newField];
|
||||
@@ -1692,10 +1541,7 @@ function getObjectType(obj): ?(SchemaField | string) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INCORRECT_TYPE,
|
||||
'This is not a valid ' + obj.__type
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'This is not a valid ' + obj.__type);
|
||||
}
|
||||
if (obj['$ne']) {
|
||||
return getObjectType(obj['$ne']);
|
||||
|
||||
@@ -95,16 +95,12 @@ export class UserController extends AdaptableController {
|
||||
throw 'Failed to reset password: username / email / token is invalid';
|
||||
}
|
||||
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
let expiresDate = results[0]._perishable_token_expires_at;
|
||||
if (expiresDate && expiresDate.__type == 'Date') {
|
||||
expiresDate = new Date(expiresDate.iso);
|
||||
}
|
||||
if (expiresDate < new Date())
|
||||
throw 'The password reset link has expired';
|
||||
if (expiresDate < new Date()) throw 'The password reset link has expired';
|
||||
}
|
||||
|
||||
return results[0];
|
||||
@@ -123,12 +119,7 @@ export class UserController extends AdaptableController {
|
||||
where.email = user.email;
|
||||
}
|
||||
|
||||
var query = new RestQuery(
|
||||
this.config,
|
||||
Auth.master(this.config),
|
||||
'_User',
|
||||
where
|
||||
);
|
||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||
return query.execute().then(function (result) {
|
||||
if (result.results.length != 1) {
|
||||
throw undefined;
|
||||
@@ -146,12 +137,7 @@ export class UserController extends AdaptableController {
|
||||
this.getUserIfNeeded(user).then(user => {
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(
|
||||
this.config.verifyEmailURL,
|
||||
username,
|
||||
token,
|
||||
this.config
|
||||
);
|
||||
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
@@ -173,11 +159,7 @@ export class UserController extends AdaptableController {
|
||||
*/
|
||||
regenerateEmailVerifyToken(user) {
|
||||
this.setEmailVerifyToken(user);
|
||||
return this.config.database.update(
|
||||
'_User',
|
||||
{ username: user.username },
|
||||
user
|
||||
);
|
||||
return this.config.database.update('_User', { username: user.username }, user);
|
||||
}
|
||||
|
||||
resendVerificationEmail(username) {
|
||||
@@ -194,10 +176,7 @@ export class UserController extends AdaptableController {
|
||||
setPasswordResetToken(email) {
|
||||
const token = { _perishable_token: randomString(25) };
|
||||
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
token._perishable_token_expires_at = Parse._encode(
|
||||
this.config.generatePasswordResetTokenExpiresAt()
|
||||
);
|
||||
@@ -278,9 +257,7 @@ export class UserController extends AdaptableController {
|
||||
'Hi,\n\n' +
|
||||
'You requested to reset your password for ' +
|
||||
appName +
|
||||
(user.get('username')
|
||||
? " (your username is '" + user.get('username') + "')"
|
||||
: '') +
|
||||
(user.get('username') ? " (your username is '" + user.get('username') + "')" : '') +
|
||||
'.\n\n' +
|
||||
'' +
|
||||
'Click here to reset it:\n' +
|
||||
@@ -308,10 +285,7 @@ function buildEmailLink(destination, username, token, config) {
|
||||
const usernameAndToken = `token=${token}&username=${username}`;
|
||||
|
||||
if (config.parseFrameURL) {
|
||||
const destinationWithoutHost = destination.replace(
|
||||
config.publicServerURL,
|
||||
''
|
||||
);
|
||||
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
||||
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(
|
||||
destinationWithoutHost
|
||||
|
||||
@@ -67,9 +67,7 @@ export function getControllers(options: ParseServerOptions) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getLoggerController(
|
||||
options: ParseServerOptions
|
||||
): LoggerController {
|
||||
export function getLoggerController(options: ParseServerOptions): LoggerController {
|
||||
const {
|
||||
appId,
|
||||
jsonLogs,
|
||||
@@ -88,25 +86,12 @@ export function getLoggerController(
|
||||
silent,
|
||||
maxLogFiles,
|
||||
};
|
||||
const loggerControllerAdapter = loadAdapter(
|
||||
loggerAdapter,
|
||||
WinstonLoggerAdapter,
|
||||
loggerOptions
|
||||
);
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, loggerOptions);
|
||||
return new LoggerController(loggerControllerAdapter, appId, loggerOptions);
|
||||
}
|
||||
|
||||
export function getFilesController(
|
||||
options: ParseServerOptions
|
||||
): FilesController {
|
||||
const {
|
||||
appId,
|
||||
databaseURI,
|
||||
filesAdapter,
|
||||
databaseAdapter,
|
||||
preserveFileName,
|
||||
fileKey,
|
||||
} = options;
|
||||
export function getFilesController(options: ParseServerOptions): FilesController {
|
||||
const { appId, databaseURI, filesAdapter, databaseAdapter, preserveFileName, fileKey } = options;
|
||||
if (!filesAdapter && databaseAdapter) {
|
||||
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
|
||||
}
|
||||
@@ -126,15 +111,13 @@ export function getUserController(options: ParseServerOptions): UserController {
|
||||
});
|
||||
}
|
||||
|
||||
export function getCacheController(
|
||||
options: ParseServerOptions
|
||||
): CacheController {
|
||||
export function getCacheController(options: ParseServerOptions): CacheController {
|
||||
const { appId, cacheAdapter, cacheTTL, cacheMaxSize } = options;
|
||||
const cacheControllerAdapter = loadAdapter(
|
||||
cacheAdapter,
|
||||
InMemoryCacheAdapter,
|
||||
{ appId: appId, ttl: cacheTTL, maxSize: cacheMaxSize }
|
||||
);
|
||||
const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {
|
||||
appId: appId,
|
||||
ttl: cacheTTL,
|
||||
maxSize: cacheMaxSize,
|
||||
});
|
||||
return new CacheController(cacheControllerAdapter, appId);
|
||||
}
|
||||
|
||||
@@ -148,20 +131,13 @@ export function getParseGraphQLController(
|
||||
});
|
||||
}
|
||||
|
||||
export function getAnalyticsController(
|
||||
options: ParseServerOptions
|
||||
): AnalyticsController {
|
||||
export function getAnalyticsController(options: ParseServerOptions): AnalyticsController {
|
||||
const { analyticsAdapter } = options;
|
||||
const analyticsControllerAdapter = loadAdapter(
|
||||
analyticsAdapter,
|
||||
AnalyticsAdapter
|
||||
);
|
||||
const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter);
|
||||
return new AnalyticsController(analyticsControllerAdapter);
|
||||
}
|
||||
|
||||
export function getLiveQueryController(
|
||||
options: ParseServerOptions
|
||||
): LiveQueryController {
|
||||
export function getLiveQueryController(options: ParseServerOptions): LiveQueryController {
|
||||
return new LiveQueryController(options.liveQuery);
|
||||
}
|
||||
|
||||
@@ -185,11 +161,7 @@ export function getDatabaseController(
|
||||
) {
|
||||
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/collectionPrefix.';
|
||||
} else if (!databaseAdapter) {
|
||||
databaseAdapter = getDatabaseAdapter(
|
||||
databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
);
|
||||
databaseAdapter = getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions);
|
||||
} else {
|
||||
databaseAdapter = loadAdapter(databaseAdapter);
|
||||
}
|
||||
@@ -214,9 +186,7 @@ interface PushControlling {
|
||||
pushWorker: PushWorker;
|
||||
}
|
||||
|
||||
export function getPushController(
|
||||
options: ParseServerOptions
|
||||
): PushControlling {
|
||||
export function getPushController(options: ParseServerOptions): PushControlling {
|
||||
const { scheduledPush, push } = options;
|
||||
|
||||
const pushOptions = Object.assign({}, push);
|
||||
@@ -258,11 +228,7 @@ export function getAuthDataManager(options: ParseServerOptions) {
|
||||
return authDataManager(auth, enableAnonymousUsers);
|
||||
}
|
||||
|
||||
export function getDatabaseAdapter(
|
||||
databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
) {
|
||||
export function getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) {
|
||||
let protocol;
|
||||
try {
|
||||
const parsedURI = url.parse(databaseURI);
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import Parse from 'parse/node';
|
||||
import {
|
||||
GraphQLSchema,
|
||||
GraphQLObjectType,
|
||||
DocumentNode,
|
||||
GraphQLNamedType,
|
||||
} from 'graphql';
|
||||
import { GraphQLSchema, GraphQLObjectType, DocumentNode, GraphQLNamedType } from 'graphql';
|
||||
import { stitchSchemas } from '@graphql-tools/stitch';
|
||||
import { SchemaDirectiveVisitor } from '@graphql-tools/utils';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
@@ -14,9 +9,7 @@ import * as parseClassQueries from './loaders/parseClassQueries';
|
||||
import * as parseClassMutations from './loaders/parseClassMutations';
|
||||
import * as defaultGraphQLQueries from './loaders/defaultGraphQLQueries';
|
||||
import * as defaultGraphQLMutations from './loaders/defaultGraphQLMutations';
|
||||
import ParseGraphQLController, {
|
||||
ParseGraphQLConfig,
|
||||
} from '../Controllers/ParseGraphQLController';
|
||||
import ParseGraphQLController, { ParseGraphQLConfig } from '../Controllers/ParseGraphQLController';
|
||||
import DatabaseController from '../Controllers/DatabaseController';
|
||||
import { toGraphQLError } from './parseGraphQLUtils';
|
||||
import * as schemaDirectives from './loaders/schemaDirectives';
|
||||
@@ -72,12 +65,7 @@ class ParseGraphQLSchema {
|
||||
parseGraphQLConfig: ParseGraphQLConfig;
|
||||
log: any;
|
||||
appId: string;
|
||||
graphQLCustomTypeDefs: ?(
|
||||
| string
|
||||
| GraphQLSchema
|
||||
| DocumentNode
|
||||
| GraphQLNamedType[]
|
||||
);
|
||||
graphQLCustomTypeDefs: ?(string | GraphQLSchema | DocumentNode | GraphQLNamedType[]);
|
||||
|
||||
constructor(
|
||||
params: {
|
||||
@@ -85,12 +73,7 @@ class ParseGraphQLSchema {
|
||||
parseGraphQLController: ParseGraphQLController,
|
||||
log: any,
|
||||
appId: string,
|
||||
graphQLCustomTypeDefs: ?(
|
||||
| string
|
||||
| GraphQLSchema
|
||||
| DocumentNode
|
||||
| GraphQLNamedType[]
|
||||
),
|
||||
graphQLCustomTypeDefs: ?(string | GraphQLSchema | DocumentNode | GraphQLNamedType[]),
|
||||
} = {}
|
||||
) {
|
||||
this.parseGraphQLController =
|
||||
@@ -99,11 +82,9 @@ class ParseGraphQLSchema {
|
||||
this.databaseController =
|
||||
params.databaseController ||
|
||||
requiredParameter('You must provide a databaseController instance!');
|
||||
this.log =
|
||||
params.log || requiredParameter('You must provide a log instance!');
|
||||
this.log = params.log || requiredParameter('You must provide a log instance!');
|
||||
this.graphQLCustomTypeDefs = params.graphQLCustomTypeDefs;
|
||||
this.appId =
|
||||
params.appId || requiredParameter('You must provide the appId!');
|
||||
this.appId = params.appId || requiredParameter('You must provide the appId!');
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -216,59 +197,45 @@ class ParseGraphQLSchema {
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.values(customGraphQLSchemaTypeMap).forEach(
|
||||
customGraphQLSchemaType => {
|
||||
if (
|
||||
!customGraphQLSchemaType ||
|
||||
!customGraphQLSchemaType.name ||
|
||||
customGraphQLSchemaType.name.startsWith('__')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType(
|
||||
customGraphQLSchemaType.name
|
||||
);
|
||||
if (!autoGraphQLSchemaType) {
|
||||
this.graphQLAutoSchema._typeMap[
|
||||
customGraphQLSchemaType.name
|
||||
] = customGraphQLSchemaType;
|
||||
}
|
||||
Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => {
|
||||
if (
|
||||
!customGraphQLSchemaType ||
|
||||
!customGraphQLSchemaType.name ||
|
||||
customGraphQLSchemaType.name.startsWith('__')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
Object.values(customGraphQLSchemaTypeMap).forEach(
|
||||
customGraphQLSchemaType => {
|
||||
if (
|
||||
!customGraphQLSchemaType ||
|
||||
!customGraphQLSchemaType.name ||
|
||||
customGraphQLSchemaType.name.startsWith('__')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType(
|
||||
customGraphQLSchemaType.name
|
||||
);
|
||||
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType(
|
||||
customGraphQLSchemaType.name
|
||||
);
|
||||
if (!autoGraphQLSchemaType) {
|
||||
this.graphQLAutoSchema._typeMap[customGraphQLSchemaType.name] = customGraphQLSchemaType;
|
||||
}
|
||||
});
|
||||
Object.values(customGraphQLSchemaTypeMap).forEach(customGraphQLSchemaType => {
|
||||
if (
|
||||
!customGraphQLSchemaType ||
|
||||
!customGraphQLSchemaType.name ||
|
||||
customGraphQLSchemaType.name.startsWith('__')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const autoGraphQLSchemaType = this.graphQLAutoSchema.getType(
|
||||
customGraphQLSchemaType.name
|
||||
);
|
||||
|
||||
if (
|
||||
autoGraphQLSchemaType &&
|
||||
typeof customGraphQLSchemaType.getFields === 'function'
|
||||
) {
|
||||
Object.values(customGraphQLSchemaType.getFields()).forEach(
|
||||
field => {
|
||||
findAndReplaceLastType(field, 'type');
|
||||
}
|
||||
);
|
||||
autoGraphQLSchemaType._fields = {
|
||||
...autoGraphQLSchemaType.getFields(),
|
||||
...customGraphQLSchemaType.getFields(),
|
||||
};
|
||||
}
|
||||
if (autoGraphQLSchemaType && typeof customGraphQLSchemaType.getFields === 'function') {
|
||||
Object.values(customGraphQLSchemaType.getFields()).forEach(field => {
|
||||
findAndReplaceLastType(field, 'type');
|
||||
});
|
||||
autoGraphQLSchemaType._fields = {
|
||||
...autoGraphQLSchemaType.getFields(),
|
||||
...customGraphQLSchemaType.getFields(),
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
this.graphQLSchema = stitchSchemas({
|
||||
schemas: [
|
||||
this.graphQLSchemaDirectivesDefinitions,
|
||||
this.graphQLAutoSchema,
|
||||
],
|
||||
schemas: [this.graphQLSchemaDirectivesDefinitions, this.graphQLAutoSchema],
|
||||
mergeDirectives: true,
|
||||
});
|
||||
} else if (typeof this.graphQLCustomTypeDefs === 'function') {
|
||||
@@ -300,20 +267,17 @@ class ParseGraphQLSchema {
|
||||
);
|
||||
if (graphQLCustomTypeDef) {
|
||||
const graphQLSchemaTypeFieldMap = graphQLSchemaType.getFields();
|
||||
Object.keys(graphQLSchemaTypeFieldMap).forEach(
|
||||
graphQLSchemaTypeFieldName => {
|
||||
const graphQLSchemaTypeField =
|
||||
graphQLSchemaTypeFieldMap[graphQLSchemaTypeFieldName];
|
||||
if (!graphQLSchemaTypeField.astNode) {
|
||||
const astNode = graphQLCustomTypeDef.fields.find(
|
||||
field => field.name.value === graphQLSchemaTypeFieldName
|
||||
);
|
||||
if (astNode) {
|
||||
graphQLSchemaTypeField.astNode = astNode;
|
||||
}
|
||||
Object.keys(graphQLSchemaTypeFieldMap).forEach(graphQLSchemaTypeFieldName => {
|
||||
const graphQLSchemaTypeField = graphQLSchemaTypeFieldMap[graphQLSchemaTypeFieldName];
|
||||
if (!graphQLSchemaTypeField.astNode) {
|
||||
const astNode = graphQLCustomTypeDef.fields.find(
|
||||
field => field.name.value === graphQLSchemaTypeFieldName
|
||||
);
|
||||
if (astNode) {
|
||||
graphQLSchemaTypeField.astNode = astNode;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -329,12 +293,7 @@ class ParseGraphQLSchema {
|
||||
return this.graphQLSchema;
|
||||
}
|
||||
|
||||
addGraphQLType(
|
||||
type,
|
||||
throwError = false,
|
||||
ignoreReserved = false,
|
||||
ignoreConnection = false
|
||||
) {
|
||||
addGraphQLType(type, throwError = false, ignoreReserved = false, ignoreConnection = false) {
|
||||
if (
|
||||
(!ignoreReserved && RESERVED_GRAPHQL_TYPE_NAMES.includes(type.name)) ||
|
||||
this.graphQLTypes.find(existingType => existingType.name === type.name) ||
|
||||
@@ -351,12 +310,7 @@ class ParseGraphQLSchema {
|
||||
return type;
|
||||
}
|
||||
|
||||
addGraphQLQuery(
|
||||
fieldName,
|
||||
field,
|
||||
throwError = false,
|
||||
ignoreReserved = false
|
||||
) {
|
||||
addGraphQLQuery(fieldName, field, throwError = false, ignoreReserved = false) {
|
||||
if (
|
||||
(!ignoreReserved && RESERVED_GRAPHQL_QUERY_NAMES.includes(fieldName)) ||
|
||||
this.graphQLQueries[fieldName]
|
||||
@@ -372,15 +326,9 @@ class ParseGraphQLSchema {
|
||||
return field;
|
||||
}
|
||||
|
||||
addGraphQLMutation(
|
||||
fieldName,
|
||||
field,
|
||||
throwError = false,
|
||||
ignoreReserved = false
|
||||
) {
|
||||
addGraphQLMutation(fieldName, field, throwError = false, ignoreReserved = false) {
|
||||
if (
|
||||
(!ignoreReserved &&
|
||||
RESERVED_GRAPHQL_MUTATION_NAMES.includes(fieldName)) ||
|
||||
(!ignoreReserved && RESERVED_GRAPHQL_MUTATION_NAMES.includes(fieldName)) ||
|
||||
this.graphQLMutations[fieldName]
|
||||
) {
|
||||
const message = `Mutation ${fieldName} could not be added to the auto schema because it collided with an existing field.`;
|
||||
@@ -455,10 +403,7 @@ class ParseGraphQLSchema {
|
||||
* that provide the parseClass along with
|
||||
* its parseClassConfig where provided.
|
||||
*/
|
||||
_getParseClassesWithConfig(
|
||||
parseClasses,
|
||||
parseGraphQLConfig: ParseGraphQLConfig
|
||||
) {
|
||||
_getParseClassesWithConfig(parseClasses, parseGraphQLConfig: ParseGraphQLConfig) {
|
||||
const { classConfigs } = parseGraphQLConfig;
|
||||
|
||||
// Make sures that the default classes and classes that
|
||||
@@ -488,9 +433,7 @@ class ParseGraphQLSchema {
|
||||
return parseClasses.sort(sortClasses).map(parseClass => {
|
||||
let parseClassConfig;
|
||||
if (classConfigs) {
|
||||
parseClassConfig = classConfigs.find(
|
||||
c => c.className === parseClass.className
|
||||
);
|
||||
parseClassConfig = classConfigs.find(c => c.className === parseClass.className);
|
||||
}
|
||||
return [parseClass, parseClassConfig];
|
||||
});
|
||||
@@ -521,16 +464,10 @@ class ParseGraphQLSchema {
|
||||
parseGraphQLConfig: ?ParseGraphQLConfig,
|
||||
functionNamesString: string,
|
||||
}): boolean {
|
||||
const {
|
||||
parseClasses,
|
||||
parseClassesString,
|
||||
parseGraphQLConfig,
|
||||
functionNamesString,
|
||||
} = params;
|
||||
const { parseClasses, parseClassesString, parseGraphQLConfig, functionNamesString } = params;
|
||||
|
||||
if (
|
||||
JSON.stringify(this.parseGraphQLConfig) ===
|
||||
JSON.stringify(parseGraphQLConfig) &&
|
||||
JSON.stringify(this.parseGraphQLConfig) === JSON.stringify(parseGraphQLConfig) &&
|
||||
this.functionNamesString === functionNamesString
|
||||
) {
|
||||
if (this.parseClasses === parseClasses) {
|
||||
|
||||
@@ -9,25 +9,20 @@ import { handleParseErrors, handleParseHeaders } from '../middlewares';
|
||||
import requiredParameter from '../requiredParameter';
|
||||
import defaultLogger from '../logger';
|
||||
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
||||
import ParseGraphQLController, {
|
||||
ParseGraphQLConfig,
|
||||
} from '../Controllers/ParseGraphQLController';
|
||||
import ParseGraphQLController, { ParseGraphQLConfig } from '../Controllers/ParseGraphQLController';
|
||||
|
||||
class ParseGraphQLServer {
|
||||
parseGraphQLController: ParseGraphQLController;
|
||||
|
||||
constructor(parseServer, config) {
|
||||
this.parseServer =
|
||||
parseServer ||
|
||||
requiredParameter('You must provide a parseServer instance!');
|
||||
this.parseServer = parseServer || requiredParameter('You must provide a parseServer instance!');
|
||||
if (!config || !config.graphQLPath) {
|
||||
requiredParameter('You must provide a config.graphQLPath!');
|
||||
}
|
||||
this.config = config;
|
||||
this.parseGraphQLController = this.parseServer.config.parseGraphQLController;
|
||||
this.log =
|
||||
(this.parseServer.config && this.parseServer.config.loggerController) ||
|
||||
defaultLogger;
|
||||
(this.parseServer.config && this.parseServer.config.loggerController) || defaultLogger;
|
||||
this.parseGraphQLSchema = new ParseGraphQLSchema({
|
||||
parseGraphQLController: this.parseGraphQLController,
|
||||
databaseController: this.parseServer.config.databaseController,
|
||||
@@ -52,9 +47,7 @@ class ParseGraphQLServer {
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
this.log.error(
|
||||
e.stack || (typeof e.toString === 'function' && e.toString()) || e
|
||||
);
|
||||
this.log.error(e.stack || (typeof e.toString === 'function' && e.toString()) || e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -101,9 +94,7 @@ class ParseGraphQLServer {
|
||||
}
|
||||
app.get(
|
||||
this.config.playgroundPath ||
|
||||
requiredParameter(
|
||||
'You must provide a config.playgroundPath to applyPlayground!'
|
||||
),
|
||||
requiredParameter('You must provide a config.playgroundPath to applyPlayground!'),
|
||||
(_req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(
|
||||
@@ -128,19 +119,13 @@ class ParseGraphQLServer {
|
||||
execute,
|
||||
subscribe,
|
||||
onOperation: async (_message, params, webSocket) =>
|
||||
Object.assign(
|
||||
{},
|
||||
params,
|
||||
await this._getGraphQLOptions(webSocket.upgradeReq)
|
||||
),
|
||||
Object.assign({}, params, await this._getGraphQLOptions(webSocket.upgradeReq)),
|
||||
},
|
||||
{
|
||||
server,
|
||||
path:
|
||||
this.config.subscriptionsPath ||
|
||||
requiredParameter(
|
||||
'You must provide a config.subscriptionsPath to createSubscriptions!'
|
||||
),
|
||||
requiredParameter('You must provide a config.subscriptionsPath to createSubscriptions!'),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@ import { ApolloError } from 'apollo-server-core';
|
||||
|
||||
export function enforceMasterKeyAccess(auth) {
|
||||
if (!auth.isMaster) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'unauthorized: master key is required'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized: master key is required');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +19,10 @@ export function toGraphQLError(error) {
|
||||
return new ApolloError(message, code);
|
||||
}
|
||||
|
||||
export const extractKeysAndInclude = (selectedFields) => {
|
||||
selectedFields = selectedFields.filter(
|
||||
(field) => !field.includes('__typename')
|
||||
);
|
||||
export const extractKeysAndInclude = selectedFields => {
|
||||
selectedFields = selectedFields.filter(field => !field.includes('__typename'));
|
||||
// Handles "id" field for both current and included objects
|
||||
selectedFields = selectedFields.map((field) => {
|
||||
selectedFields = selectedFields.map(field => {
|
||||
if (field === 'id') return 'objectId';
|
||||
return field.endsWith('.id')
|
||||
? `${field.substring(0, field.lastIndexOf('.id'))}.objectId`
|
||||
|
||||
@@ -3,13 +3,7 @@ import logger from '../logger';
|
||||
import type { FlattenedObjectData } from './Subscription';
|
||||
export type Message = { [attr: string]: any };
|
||||
|
||||
const dafaultFields = [
|
||||
'className',
|
||||
'objectId',
|
||||
'updatedAt',
|
||||
'createdAt',
|
||||
'ACL',
|
||||
];
|
||||
const dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL'];
|
||||
|
||||
class Client {
|
||||
id: number;
|
||||
@@ -110,10 +104,7 @@ class Client {
|
||||
}
|
||||
response['object'] = this._toJSONWithFields(parseObjectJSON, fields);
|
||||
if (parseOriginalObjectJSON) {
|
||||
response['original'] = this._toJSONWithFields(
|
||||
parseOriginalObjectJSON,
|
||||
fields
|
||||
);
|
||||
response['original'] = this._toJSONWithFields(parseOriginalObjectJSON, fields);
|
||||
}
|
||||
}
|
||||
Client.pushResponse(this.parseWebSocket, JSON.stringify(response));
|
||||
|
||||
@@ -93,11 +93,7 @@ class ParseLiveQueryServer {
|
||||
} else if (channel === Parse.applicationId + 'afterDelete') {
|
||||
this._onAfterDelete(message);
|
||||
} else {
|
||||
logger.error(
|
||||
'Get message %s from unknown channel %j',
|
||||
message,
|
||||
channel
|
||||
);
|
||||
logger.error('Get message %s from unknown channel %j', message, channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -131,11 +127,7 @@ class ParseLiveQueryServer {
|
||||
let deletedParseObject = message.currentParseObject.toJSON();
|
||||
const classLevelPermissions = message.classLevelPermissions;
|
||||
const className = deletedParseObject.className;
|
||||
logger.verbose(
|
||||
'ClassName: %j | ObjectId: %s',
|
||||
className,
|
||||
deletedParseObject.id
|
||||
);
|
||||
logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id);
|
||||
logger.verbose('Current client number : %d', this.clients.size);
|
||||
|
||||
const classSubscriptions = this.subscriptions.get(className);
|
||||
@@ -144,16 +136,11 @@ class ParseLiveQueryServer {
|
||||
return;
|
||||
}
|
||||
for (const subscription of classSubscriptions.values()) {
|
||||
const isSubscriptionMatched = this._matchesSubscription(
|
||||
deletedParseObject,
|
||||
subscription
|
||||
);
|
||||
const isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription);
|
||||
if (!isSubscriptionMatched) {
|
||||
continue;
|
||||
}
|
||||
for (const [clientId, requestIds] of _.entries(
|
||||
subscription.clientRequestIds
|
||||
)) {
|
||||
for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
@@ -163,13 +150,7 @@ class ParseLiveQueryServer {
|
||||
// Check CLP
|
||||
const op = this._getCLPOperation(subscription.query);
|
||||
let res = {};
|
||||
this._matchesCLP(
|
||||
classLevelPermissions,
|
||||
message.currentParseObject,
|
||||
client,
|
||||
requestId,
|
||||
op
|
||||
)
|
||||
this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
|
||||
.then(() => {
|
||||
// Check ACL
|
||||
return this._matchesACL(acl, client, requestId);
|
||||
@@ -230,11 +211,7 @@ class ParseLiveQueryServer {
|
||||
const classLevelPermissions = message.classLevelPermissions;
|
||||
let currentParseObject = message.currentParseObject.toJSON();
|
||||
const className = currentParseObject.className;
|
||||
logger.verbose(
|
||||
'ClassName: %s | ObjectId: %s',
|
||||
className,
|
||||
currentParseObject.id
|
||||
);
|
||||
logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id);
|
||||
logger.verbose('Current client number : %d', this.clients.size);
|
||||
|
||||
const classSubscriptions = this.subscriptions.get(className);
|
||||
@@ -251,9 +228,7 @@ class ParseLiveQueryServer {
|
||||
currentParseObject,
|
||||
subscription
|
||||
);
|
||||
for (const [clientId, requestIds] of _.entries(
|
||||
subscription.clientRequestIds
|
||||
)) {
|
||||
for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
@@ -269,11 +244,7 @@ class ParseLiveQueryServer {
|
||||
if (message.originalParseObject) {
|
||||
originalACL = message.originalParseObject.getACL();
|
||||
}
|
||||
originalACLCheckingPromise = this._matchesACL(
|
||||
originalACL,
|
||||
client,
|
||||
requestId
|
||||
);
|
||||
originalACLCheckingPromise = this._matchesACL(originalACL, client, requestId);
|
||||
}
|
||||
// Set current ParseObject ACL checking promise, if the object does not match
|
||||
// subscription, we do not need to check ACL
|
||||
@@ -283,25 +254,12 @@ class ParseLiveQueryServer {
|
||||
currentACLCheckingPromise = Promise.resolve(false);
|
||||
} else {
|
||||
const currentACL = message.currentParseObject.getACL();
|
||||
currentACLCheckingPromise = this._matchesACL(
|
||||
currentACL,
|
||||
client,
|
||||
requestId
|
||||
);
|
||||
currentACLCheckingPromise = this._matchesACL(currentACL, client, requestId);
|
||||
}
|
||||
const op = this._getCLPOperation(subscription.query);
|
||||
this._matchesCLP(
|
||||
classLevelPermissions,
|
||||
message.currentParseObject,
|
||||
client,
|
||||
requestId,
|
||||
op
|
||||
)
|
||||
this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
|
||||
.then(() => {
|
||||
return Promise.all([
|
||||
originalACLCheckingPromise,
|
||||
currentACLCheckingPromise,
|
||||
]);
|
||||
return Promise.all([originalACLCheckingPromise, currentACLCheckingPromise]);
|
||||
})
|
||||
.then(([isOriginalMatched, isCurrentMatched]) => {
|
||||
logger.verbose(
|
||||
@@ -350,22 +308,16 @@ class ParseLiveQueryServer {
|
||||
}
|
||||
if (res.object && typeof res.object.toJSON === 'function') {
|
||||
currentParseObject = res.object.toJSON();
|
||||
currentParseObject.className =
|
||||
res.object.className || className;
|
||||
currentParseObject.className = res.object.className || className;
|
||||
}
|
||||
|
||||
if (res.original && typeof res.original.toJSON === 'function') {
|
||||
originalParseObject = res.original.toJSON();
|
||||
originalParseObject.className =
|
||||
res.original.className || className;
|
||||
originalParseObject.className = res.original.className || className;
|
||||
}
|
||||
const functionName = 'push' + message.event;
|
||||
if (client[functionName]) {
|
||||
client[functionName](
|
||||
requestId,
|
||||
currentParseObject,
|
||||
originalParseObject
|
||||
);
|
||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
@@ -447,16 +399,12 @@ class ParseLiveQueryServer {
|
||||
this.clients.delete(clientId);
|
||||
|
||||
// Delete client from subscriptions
|
||||
for (const [requestId, subscriptionInfo] of _.entries(
|
||||
client.subscriptionInfos
|
||||
)) {
|
||||
for (const [requestId, subscriptionInfo] of _.entries(client.subscriptionInfos)) {
|
||||
const subscription = subscriptionInfo.subscription;
|
||||
subscription.deleteClientSubscription(clientId, requestId);
|
||||
|
||||
// If there is no client which is subscribing this subscription, remove it from subscriptions
|
||||
const classSubscriptions = this.subscriptions.get(
|
||||
subscription.className
|
||||
);
|
||||
const classSubscriptions = this.subscriptions.get(subscription.className);
|
||||
if (!subscription.hasSubscribingClient()) {
|
||||
classSubscriptions.delete(subscription.hash);
|
||||
}
|
||||
@@ -492,9 +440,7 @@ class ParseLiveQueryServer {
|
||||
return matchesQuery(parseObject, subscription.query);
|
||||
}
|
||||
|
||||
getAuthForSessionToken(
|
||||
sessionToken: ?string
|
||||
): Promise<{ auth: ?Auth, userId: ?string }> {
|
||||
getAuthForSessionToken(sessionToken: ?string): Promise<{ auth: ?Auth, userId: ?string }> {
|
||||
if (!sessionToken) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
@@ -514,11 +460,7 @@ class ParseLiveQueryServer {
|
||||
const result = {};
|
||||
if (error && error.code === Parse.Error.INVALID_SESSION_TOKEN) {
|
||||
result.error = error;
|
||||
this.authCache.set(
|
||||
sessionToken,
|
||||
Promise.resolve(result),
|
||||
this.config.cacheTimeout
|
||||
);
|
||||
this.authCache.set(sessionToken, Promise.resolve(result), this.config.cacheTimeout);
|
||||
} else {
|
||||
this.authCache.del(sessionToken);
|
||||
}
|
||||
@@ -540,9 +482,7 @@ class ParseLiveQueryServer {
|
||||
const aclGroup = ['*'];
|
||||
let userId;
|
||||
if (typeof subscriptionInfo !== 'undefined') {
|
||||
const { userId } = await this.getAuthForSessionToken(
|
||||
subscriptionInfo.sessionToken
|
||||
);
|
||||
const { userId } = await this.getAuthForSessionToken(subscriptionInfo.sessionToken);
|
||||
if (userId) {
|
||||
aclGroup.push(userId);
|
||||
}
|
||||
@@ -602,9 +542,7 @@ class ParseLiveQueryServer {
|
||||
return Promise.resolve()
|
||||
.then(async () => {
|
||||
// Resolve false right away if the acl doesn't have any roles
|
||||
const acl_has_roles = Object.keys(acl.permissionsById).some(key =>
|
||||
key.startsWith('role:')
|
||||
);
|
||||
const acl_has_roles = Object.keys(acl.permissionsById).some(key => key.startsWith('role:'));
|
||||
if (!acl_has_roles) {
|
||||
return false;
|
||||
}
|
||||
@@ -624,11 +562,7 @@ class ParseLiveQueryServer {
|
||||
});
|
||||
}
|
||||
|
||||
async _matchesACL(
|
||||
acl: any,
|
||||
client: any,
|
||||
requestId: number
|
||||
): Promise<boolean> {
|
||||
async _matchesACL(acl: any, client: any, requestId: number): Promise<boolean> {
|
||||
// Return true directly if ACL isn't present, ACL is public read, or client has master key
|
||||
if (!acl || acl.getPublicReadAccess() || client.hasMasterKey) {
|
||||
return true;
|
||||
@@ -685,12 +619,7 @@ class ParseLiveQueryServer {
|
||||
client.pushConnect();
|
||||
runLiveQueryEventHandlers(req);
|
||||
} catch (error) {
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
error.code || 141,
|
||||
error.message || error,
|
||||
false
|
||||
);
|
||||
Client.pushError(parseWebsocket, error.code || 141, error.message || error, false);
|
||||
logger.error(
|
||||
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(error)
|
||||
@@ -699,17 +628,10 @@ class ParseLiveQueryServer {
|
||||
}
|
||||
|
||||
_hasMasterKey(request: any, validKeyPairs: any): boolean {
|
||||
if (
|
||||
!validKeyPairs ||
|
||||
validKeyPairs.size == 0 ||
|
||||
!validKeyPairs.has('masterKey')
|
||||
) {
|
||||
if (!validKeyPairs || validKeyPairs.size == 0 || !validKeyPairs.has('masterKey')) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!request ||
|
||||
!Object.prototype.hasOwnProperty.call(request, 'masterKey')
|
||||
) {
|
||||
if (!request || !Object.prototype.hasOwnProperty.call(request, 'masterKey')) {
|
||||
return false;
|
||||
}
|
||||
return request.masterKey === validKeyPairs.get('masterKey');
|
||||
@@ -738,9 +660,7 @@ class ParseLiveQueryServer {
|
||||
2,
|
||||
'Can not find this client, make sure you connect to server before subscribing'
|
||||
);
|
||||
logger.error(
|
||||
'Can not find this client, make sure you connect to server before subscribing'
|
||||
);
|
||||
logger.error('Can not find this client, make sure you connect to server before subscribing');
|
||||
return;
|
||||
}
|
||||
const client = this.clients.get(parseWebsocket.clientId);
|
||||
@@ -760,11 +680,7 @@ class ParseLiveQueryServer {
|
||||
if (classSubscriptions.has(subscriptionHash)) {
|
||||
subscription = classSubscriptions.get(subscriptionHash);
|
||||
} else {
|
||||
subscription = new Subscription(
|
||||
className,
|
||||
request.query.where,
|
||||
subscriptionHash
|
||||
);
|
||||
subscription = new Subscription(className, request.query.where, subscriptionHash);
|
||||
classSubscriptions.set(subscriptionHash, subscription);
|
||||
}
|
||||
|
||||
@@ -782,10 +698,7 @@ class ParseLiveQueryServer {
|
||||
client.addSubscriptionInfo(request.requestId, subscriptionInfo);
|
||||
|
||||
// Add clientId to subscription
|
||||
subscription.addClientSubscription(
|
||||
parseWebsocket.clientId,
|
||||
request.requestId
|
||||
);
|
||||
subscription.addClientSubscription(parseWebsocket.clientId, request.requestId);
|
||||
|
||||
client.pushSubscribe(request.requestId);
|
||||
|
||||
@@ -803,13 +716,7 @@ class ParseLiveQueryServer {
|
||||
installationId: client.installationId,
|
||||
});
|
||||
} catch (e) {
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
e.code || 141,
|
||||
e.message || e,
|
||||
false,
|
||||
request.requestId
|
||||
);
|
||||
Client.pushError(parseWebsocket, e.code || 141, e.message || e, false, request.requestId);
|
||||
logger.error(
|
||||
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
|
||||
JSON.stringify(e)
|
||||
@@ -822,11 +729,7 @@ class ParseLiveQueryServer {
|
||||
this._handleSubscribe(parseWebsocket, request);
|
||||
}
|
||||
|
||||
_handleUnsubscribe(
|
||||
parseWebsocket: any,
|
||||
request: any,
|
||||
notifyClient: boolean = true
|
||||
): any {
|
||||
_handleUnsubscribe(parseWebsocket: any, request: any, notifyClient: boolean = true): any {
|
||||
// If we can not find this client, return error to client
|
||||
if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) {
|
||||
Client.pushError(
|
||||
|
||||
@@ -14,11 +14,7 @@ ParsePubSub.createPublisher = function (config: any): any {
|
||||
if (useRedis(config)) {
|
||||
return RedisPubSub.createPublisher(config);
|
||||
} else {
|
||||
const adapter = loadAdapter(
|
||||
config.pubSubAdapter,
|
||||
EventEmitterPubSub,
|
||||
config
|
||||
);
|
||||
const adapter = loadAdapter(config.pubSubAdapter, EventEmitterPubSub, config);
|
||||
if (typeof adapter.createPublisher !== 'function') {
|
||||
throw 'pubSubAdapter should have createPublisher()';
|
||||
}
|
||||
@@ -30,11 +26,7 @@ ParsePubSub.createSubscriber = function (config: any): void {
|
||||
if (useRedis(config)) {
|
||||
return RedisPubSub.createSubscriber(config);
|
||||
} else {
|
||||
const adapter = loadAdapter(
|
||||
config.pubSubAdapter,
|
||||
EventEmitterPubSub,
|
||||
config
|
||||
);
|
||||
const adapter = loadAdapter(config.pubSubAdapter, EventEmitterPubSub, config);
|
||||
if (typeof adapter.createSubscriber !== 'function') {
|
||||
throw 'pubSubAdapter should have createSubscriber()';
|
||||
}
|
||||
|
||||
@@ -99,10 +99,7 @@ function contains(haystack: Array, needle: any): boolean {
|
||||
if (typeof ptr === 'string' && ptr === needle.objectId) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
ptr.className === needle.className &&
|
||||
ptr.objectId === needle.objectId
|
||||
) {
|
||||
if (ptr.className === needle.className && ptr.objectId === needle.objectId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -118,8 +115,7 @@ function contains(haystack: Array, needle: any): boolean {
|
||||
*/
|
||||
function matchesQuery(object: any, query: any): boolean {
|
||||
if (query instanceof Parse.Query) {
|
||||
var className =
|
||||
object.id instanceof Id ? object.id.className : object.className;
|
||||
var className = object.id instanceof Id ? object.id.className : object.className;
|
||||
if (className !== query.className) {
|
||||
return false;
|
||||
}
|
||||
@@ -158,11 +154,7 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
var keyComponents = key.split('.');
|
||||
var subObjectKey = keyComponents[0];
|
||||
var keyRemainder = keyComponents.slice(1).join('.');
|
||||
return matchesKeyConstraints(
|
||||
object[subObjectKey] || {},
|
||||
keyRemainder,
|
||||
constraints
|
||||
);
|
||||
return matchesKeyConstraints(object[subObjectKey] || {}, keyRemainder, constraints);
|
||||
}
|
||||
var i;
|
||||
if (key === '$or') {
|
||||
@@ -200,11 +192,7 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
});
|
||||
}
|
||||
|
||||
return equalObjectsGeneric(
|
||||
object[key],
|
||||
Parse._decode(key, constraints),
|
||||
equalObjects
|
||||
);
|
||||
return equalObjectsGeneric(object[key], Parse._decode(key, constraints), equalObjects);
|
||||
}
|
||||
// More complex cases
|
||||
for (var condition in constraints) {
|
||||
@@ -263,10 +251,7 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if (
|
||||
(!propertyExists && existenceIsRequired) ||
|
||||
(propertyExists && !existenceIsRequired)
|
||||
) {
|
||||
if ((!propertyExists && existenceIsRequired) || (propertyExists && !existenceIsRequired)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -311,10 +296,7 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
}
|
||||
var southWest = compareTo.$box[0];
|
||||
var northEast = compareTo.$box[1];
|
||||
if (
|
||||
southWest.latitude > northEast.latitude ||
|
||||
southWest.longitude > northEast.longitude
|
||||
) {
|
||||
if (southWest.latitude > northEast.latitude || southWest.longitude > northEast.longitude) {
|
||||
// Invalid box, crosses the date line
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,7 @@ function userForSessionToken(sessionToken) {
|
||||
class SessionTokenCache {
|
||||
cache: Object;
|
||||
|
||||
constructor(
|
||||
timeout: number = 30 * 24 * 60 * 60 * 1000,
|
||||
maxSize: number = 10000
|
||||
) {
|
||||
constructor(timeout: number = 30 * 24 * 60 * 60 * 1000, maxSize: number = 10000) {
|
||||
this.cache = new LRU({
|
||||
max: maxSize,
|
||||
maxAge: timeout,
|
||||
@@ -32,30 +29,18 @@ class SessionTokenCache {
|
||||
}
|
||||
const userId = this.cache.get(sessionToken);
|
||||
if (userId) {
|
||||
logger.verbose(
|
||||
'Fetch userId %s of sessionToken %s from Cache',
|
||||
userId,
|
||||
sessionToken
|
||||
);
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken);
|
||||
return Promise.resolve(userId);
|
||||
}
|
||||
return userForSessionToken(sessionToken).then(
|
||||
user => {
|
||||
logger.verbose(
|
||||
'Fetch userId %s of sessionToken %s from Parse',
|
||||
user.id,
|
||||
sessionToken
|
||||
);
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken);
|
||||
const userId = user.id;
|
||||
this.cache.set(sessionToken, userId);
|
||||
return Promise.resolve(userId);
|
||||
},
|
||||
error => {
|
||||
logger.error(
|
||||
'Can not fetch userId for sessionToken %j, error %j',
|
||||
sessionToken,
|
||||
error
|
||||
);
|
||||
logger.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -34,11 +34,7 @@ class Subscription {
|
||||
|
||||
const index = requestIds.indexOf(requestId);
|
||||
if (index < 0) {
|
||||
logger.error(
|
||||
'Can not find client %d subscription %d to delete',
|
||||
clientId,
|
||||
requestId
|
||||
);
|
||||
logger.error('Can not find client %d subscription %d to delete', clientId, requestId);
|
||||
return;
|
||||
}
|
||||
requestIds.splice(index, 1);
|
||||
|
||||
@@ -65,8 +65,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
cacheTTL: {
|
||||
env: 'PARSE_SERVER_CACHE_TTL',
|
||||
help:
|
||||
'Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)',
|
||||
help: 'Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)',
|
||||
action: parsers.numberParser('cacheTTL'),
|
||||
default: 5000,
|
||||
},
|
||||
@@ -80,8 +79,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
cluster: {
|
||||
env: 'PARSE_SERVER_CLUSTER',
|
||||
help:
|
||||
'Run with cluster, optionally set the number of processes default to os.cpus().length',
|
||||
help: 'Run with cluster, optionally set the number of processes default to os.cpus().length',
|
||||
action: parsers.numberOrBooleanParser,
|
||||
},
|
||||
collectionPrefix: {
|
||||
@@ -107,8 +105,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
databaseURI: {
|
||||
env: 'PARSE_SERVER_DATABASE_URI',
|
||||
help:
|
||||
'The full URI to your database. Supported databases are mongodb or postgres.',
|
||||
help: 'The full URI to your database. Supported databases are mongodb or postgres.',
|
||||
required: true,
|
||||
default: 'mongodb://localhost:27017/parse',
|
||||
},
|
||||
@@ -154,8 +151,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
expireInactiveSessions: {
|
||||
env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS',
|
||||
help:
|
||||
'Sets wether we should expire the inactive sessions, defaults to true',
|
||||
help: 'Sets wether we should expire the inactive sessions, defaults to true',
|
||||
action: parsers.booleanParser,
|
||||
default: true,
|
||||
},
|
||||
@@ -205,8 +201,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
liveQueryServerOptions: {
|
||||
env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS',
|
||||
help:
|
||||
'Live query server configuration options (will start the liveQuery server)',
|
||||
help: 'Live query server configuration options (will start the liveQuery server)',
|
||||
action: parsers.objectParser,
|
||||
},
|
||||
loggerAdapter: {
|
||||
@@ -220,8 +215,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
logsFolder: {
|
||||
env: 'PARSE_SERVER_LOGS_FOLDER',
|
||||
help:
|
||||
"Folder for the logs (defaults to './logs'); set to null to disable file based logging",
|
||||
help: "Folder for the logs (defaults to './logs'); set to null to disable file based logging",
|
||||
default: './logs',
|
||||
},
|
||||
masterKey: {
|
||||
@@ -231,8 +225,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
masterKeyIps: {
|
||||
env: 'PARSE_SERVER_MASTER_KEY_IPS',
|
||||
help:
|
||||
'Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)',
|
||||
help: 'Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)',
|
||||
action: parsers.arrayParser,
|
||||
default: [],
|
||||
},
|
||||
@@ -310,8 +303,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
protectedFields: {
|
||||
env: 'PARSE_SERVER_PROTECTED_FIELDS',
|
||||
help:
|
||||
'Protected fields that should be treated with extra security when fetching details.',
|
||||
help: 'Protected fields that should be treated with extra security when fetching details.',
|
||||
action: parsers.objectParser,
|
||||
default: {
|
||||
_User: {
|
||||
@@ -331,8 +323,7 @@ module.exports.ParseServerOptions = {
|
||||
},
|
||||
readOnlyMasterKey: {
|
||||
env: 'PARSE_SERVER_READ_ONLY_MASTER_KEY',
|
||||
help:
|
||||
'Read-only key, which has the same capabilities as MasterKey without writes',
|
||||
help: 'Read-only key, which has the same capabilities as MasterKey without writes',
|
||||
},
|
||||
restAPIKey: {
|
||||
env: 'PARSE_SERVER_REST_API_KEY',
|
||||
|
||||
@@ -3,24 +3,16 @@ import { EventEmitterMQ } from './Adapters/MessageQueue/EventEmitterMQ';
|
||||
|
||||
const ParseMessageQueue = {};
|
||||
|
||||
ParseMessageQueue.createPublisher = function(config: any): any {
|
||||
const adapter = loadAdapter(
|
||||
config.messageQueueAdapter,
|
||||
EventEmitterMQ,
|
||||
config
|
||||
);
|
||||
ParseMessageQueue.createPublisher = function (config: any): any {
|
||||
const adapter = loadAdapter(config.messageQueueAdapter, EventEmitterMQ, config);
|
||||
if (typeof adapter.createPublisher !== 'function') {
|
||||
throw 'pubSubAdapter should have createPublisher()';
|
||||
}
|
||||
return adapter.createPublisher(config);
|
||||
};
|
||||
|
||||
ParseMessageQueue.createSubscriber = function(config: any): void {
|
||||
const adapter = loadAdapter(
|
||||
config.messageQueueAdapter,
|
||||
EventEmitterMQ,
|
||||
config
|
||||
);
|
||||
ParseMessageQueue.createSubscriber = function (config: any): void {
|
||||
const adapter = loadAdapter(config.messageQueueAdapter, EventEmitterMQ, config);
|
||||
if (typeof adapter.createSubscriber !== 'function') {
|
||||
throw 'messageQueueAdapter should have createSubscriber()';
|
||||
}
|
||||
|
||||
@@ -67,11 +67,7 @@ class ParseServer {
|
||||
|
||||
const allControllers = controllers.getControllers(options);
|
||||
|
||||
const {
|
||||
loggerController,
|
||||
databaseController,
|
||||
hooksController,
|
||||
} = allControllers;
|
||||
const { loggerController, databaseController, hooksController } = allControllers;
|
||||
this.config = Config.put(Object.assign({}, options, allControllers));
|
||||
|
||||
logging.setLogger(loggerController);
|
||||
@@ -116,10 +112,7 @@ class ParseServer {
|
||||
handleShutdown() {
|
||||
const promises = [];
|
||||
const { adapter: databaseAdapter } = this.config.databaseController;
|
||||
if (
|
||||
databaseAdapter &&
|
||||
typeof databaseAdapter.handleShutdown === 'function'
|
||||
) {
|
||||
if (databaseAdapter && typeof databaseAdapter.handleShutdown === 'function') {
|
||||
promises.push(databaseAdapter.handleShutdown());
|
||||
}
|
||||
const { adapter: fileAdapter } = this.config.filesController;
|
||||
@@ -130,10 +123,7 @@ class ParseServer {
|
||||
if (cacheAdapter && typeof cacheAdapter.handleShutdown === 'function') {
|
||||
promises.push(cacheAdapter.handleShutdown());
|
||||
}
|
||||
return (promises.length > 0
|
||||
? Promise.all(promises)
|
||||
: Promise.resolve()
|
||||
).then(() => {
|
||||
return (promises.length > 0 ? Promise.all(promises) : Promise.resolve()).then(() => {
|
||||
if (this.config.serverCloseComplete) {
|
||||
this.config.serverCloseComplete();
|
||||
}
|
||||
@@ -164,11 +154,7 @@ class ParseServer {
|
||||
});
|
||||
});
|
||||
|
||||
api.use(
|
||||
'/',
|
||||
bodyParser.urlencoded({ extended: false }),
|
||||
new PublicAPIRouter().expressRouter()
|
||||
);
|
||||
api.use('/', bodyParser.urlencoded({ extended: false }), new PublicAPIRouter().expressRouter());
|
||||
|
||||
api.use(bodyParser.json({ type: '*/*', limit: maxUploadSize }));
|
||||
api.use(middlewares.allowMethodOverride);
|
||||
@@ -186,9 +172,7 @@ class ParseServer {
|
||||
process.on('uncaughtException', err => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
// user-friendly message for this common error
|
||||
process.stderr.write(
|
||||
`Unable to listen on port ${err.port}. The port is already in use.`
|
||||
);
|
||||
process.stderr.write(`Unable to listen on port ${err.port}. The port is already in use.`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw err;
|
||||
@@ -200,13 +184,8 @@ class ParseServer {
|
||||
ParseServer.verifyServerUrl();
|
||||
});
|
||||
}
|
||||
if (
|
||||
process.env.PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS === '1' ||
|
||||
directAccess
|
||||
) {
|
||||
Parse.CoreManager.setRESTController(
|
||||
ParseServerRESTController(appId, appRouter)
|
||||
);
|
||||
if (process.env.PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS === '1' || directAccess) {
|
||||
Parse.CoreManager.setRESTController(ParseServerRESTController(appId, appRouter));
|
||||
}
|
||||
return api;
|
||||
}
|
||||
@@ -267,9 +246,7 @@ class ParseServer {
|
||||
if (options.mountGraphQL === true || options.mountPlayground === true) {
|
||||
let graphQLCustomTypeDefs = undefined;
|
||||
if (typeof options.graphQLSchema === 'string') {
|
||||
graphQLCustomTypeDefs = parse(
|
||||
fs.readFileSync(options.graphQLSchema, 'utf8')
|
||||
);
|
||||
graphQLCustomTypeDefs = parse(fs.readFileSync(options.graphQLSchema, 'utf8'));
|
||||
} else if (
|
||||
typeof options.graphQLSchema === 'object' ||
|
||||
typeof options.graphQLSchema === 'function'
|
||||
@@ -350,11 +327,7 @@ class ParseServer {
|
||||
.catch(response => response)
|
||||
.then(response => {
|
||||
const json = response.data || null;
|
||||
if (
|
||||
response.status !== 200 ||
|
||||
!json ||
|
||||
(json && json.status !== 'ok')
|
||||
) {
|
||||
if (response.status !== 200 || !json || (json && json.status !== 'ok')) {
|
||||
/* eslint-disable no-console */
|
||||
console.warn(
|
||||
`\nWARNING, Unable to connect to '${Parse.serverURL}'.` +
|
||||
@@ -411,10 +384,7 @@ function injectDefaults(options: ParseServerOptions) {
|
||||
/* eslint-enable no-console */
|
||||
|
||||
const userSensitiveFields = Array.from(
|
||||
new Set([
|
||||
...(defaults.userSensitiveFields || []),
|
||||
...(options.userSensitiveFields || []),
|
||||
])
|
||||
new Set([...(defaults.userSensitiveFields || []), ...(options.userSensitiveFields || [])])
|
||||
);
|
||||
|
||||
// If the options.protectedFields is unset,
|
||||
@@ -422,17 +392,11 @@ function injectDefaults(options: ParseServerOptions) {
|
||||
// Here, protect against the case where protectedFields
|
||||
// is set, but doesn't have _User.
|
||||
if (!('_User' in options.protectedFields)) {
|
||||
options.protectedFields = Object.assign(
|
||||
{ _User: [] },
|
||||
options.protectedFields
|
||||
);
|
||||
options.protectedFields = Object.assign({ _User: [] }, options.protectedFields);
|
||||
}
|
||||
|
||||
options.protectedFields['_User']['*'] = Array.from(
|
||||
new Set([
|
||||
...(options.protectedFields['_User']['*'] || []),
|
||||
...userSensitiveFields,
|
||||
])
|
||||
new Set([...(options.protectedFields['_User']['*'] || []), ...userSensitiveFields])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -453,9 +417,7 @@ function injectDefaults(options: ParseServerOptions) {
|
||||
});
|
||||
|
||||
options.masterKeyIps = Array.from(
|
||||
new Set(
|
||||
options.masterKeyIps.concat(defaults.masterKeyIps, options.masterKeyIps)
|
||||
)
|
||||
new Set(options.masterKeyIps.concat(defaults.masterKeyIps, options.masterKeyIps))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ function getSessionToken(options) {
|
||||
function getAuth(options = {}, config) {
|
||||
const installationId = options.installationId || 'cloud';
|
||||
if (options.useMasterKey) {
|
||||
return Promise.resolve(
|
||||
new Auth.Auth({ config, isMaster: true, installationId })
|
||||
);
|
||||
return Promise.resolve(new Auth.Auth({ config, isMaster: true, installationId }));
|
||||
}
|
||||
return getSessionToken(options).then(sessionToken => {
|
||||
if (sessionToken) {
|
||||
@@ -56,13 +54,7 @@ function ParseServerRESTController(applicationId, router) {
|
||||
}
|
||||
return initialPromise.then(() => {
|
||||
const promises = data.requests.map(request => {
|
||||
return handleRequest(
|
||||
request.method,
|
||||
request.path,
|
||||
request.body,
|
||||
options,
|
||||
config
|
||||
).then(
|
||||
return handleRequest(request.method, request.path, request.body, options, config).then(
|
||||
response => {
|
||||
if (options.returnStatus) {
|
||||
const status = response._status;
|
||||
@@ -80,9 +72,7 @@ function ParseServerRESTController(applicationId, router) {
|
||||
});
|
||||
return Promise.all(promises).then(result => {
|
||||
if (data.transaction === true) {
|
||||
if (
|
||||
result.find(resultItem => typeof resultItem.error === 'object')
|
||||
) {
|
||||
if (result.find(resultItem => typeof resultItem.error === 'object')) {
|
||||
return config.database.abortTransactionalSession().then(() => {
|
||||
return Promise.reject(result);
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ export default class PromiseRouter {
|
||||
let handler = handlers[0];
|
||||
|
||||
if (handlers.length > 1) {
|
||||
handler = function(req) {
|
||||
handler = function (req) {
|
||||
return handlers.reduce((promise, handler) => {
|
||||
return promise.then(() => {
|
||||
return handler(req);
|
||||
@@ -121,10 +121,7 @@ export default class PromiseRouter {
|
||||
tryRouteRequest(method, path, request) {
|
||||
var match = this.match(method, path);
|
||||
if (!match) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'cannot route ' + method + ' ' + path
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route ' + method + ' ' + path);
|
||||
}
|
||||
request.params = match.params;
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -138,7 +135,7 @@ export default class PromiseRouter {
|
||||
// Express handlers should never throw; if a promise handler throws we
|
||||
// just treat it like it resolved to an error.
|
||||
function makeExpressHandler(appId, promiseHandler) {
|
||||
return function(req, res, next) {
|
||||
return function (req, res, next) {
|
||||
try {
|
||||
const url = maskSensitiveUrl(req);
|
||||
const body = Object.assign({}, req.body);
|
||||
@@ -155,9 +152,7 @@ function makeExpressHandler(appId, promiseHandler) {
|
||||
result => {
|
||||
clearSchemaCache(req);
|
||||
if (!result.response && !result.location && !result.text) {
|
||||
log.error(
|
||||
'the handler did not include a "response" or a "location" field'
|
||||
);
|
||||
log.error('the handler did not include a "response" or a "location" field');
|
||||
throw 'control should not get here';
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,7 @@ export class PushQueue {
|
||||
pushStatus: { objectId: pushStatus.objectId },
|
||||
applicationId: config.applicationId,
|
||||
};
|
||||
this.parsePublisher.publish(
|
||||
this.channel,
|
||||
JSON.stringify(pushWorkItem)
|
||||
);
|
||||
this.parsePublisher.publish(this.channel, JSON.stringify(pushWorkItem));
|
||||
skip += limit;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,14 +48,12 @@ export class PushWorker {
|
||||
const where = utils.applyDeviceTokenExists(query.where);
|
||||
delete query.where;
|
||||
pushStatus = pushStatusHandler(config, pushStatus.objectId);
|
||||
return rest
|
||||
.find(config, auth, '_Installation', where, query)
|
||||
.then(({ results }) => {
|
||||
if (results.length == 0) {
|
||||
return;
|
||||
}
|
||||
return this.sendToAdapter(body, results, pushStatus, config, UTCOffset);
|
||||
});
|
||||
return rest.find(config, auth, '_Installation', where, query).then(({ results }) => {
|
||||
if (results.length == 0) {
|
||||
return;
|
||||
}
|
||||
return this.sendToAdapter(body, results, pushStatus, config, UTCOffset);
|
||||
});
|
||||
}
|
||||
|
||||
sendToAdapter(
|
||||
@@ -72,31 +70,20 @@ export class PushWorker {
|
||||
const bodiesPerLocales = utils.bodiesPerLocales(body, locales);
|
||||
|
||||
// Group installations on the specified locales (en, fr, default etc...)
|
||||
const grouppedInstallations = utils.groupByLocaleIdentifier(
|
||||
installations,
|
||||
locales
|
||||
);
|
||||
const grouppedInstallations = utils.groupByLocaleIdentifier(installations, locales);
|
||||
const promises = Object.keys(grouppedInstallations).map(locale => {
|
||||
const installations = grouppedInstallations[locale];
|
||||
const body = bodiesPerLocales[locale];
|
||||
return this.sendToAdapter(
|
||||
body,
|
||||
installations,
|
||||
pushStatus,
|
||||
config,
|
||||
UTCOffset
|
||||
);
|
||||
return this.sendToAdapter(body, installations, pushStatus, config, UTCOffset);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
if (!utils.isPushIncrementing(body)) {
|
||||
logger.verbose(`Sending push to ${installations.length}`);
|
||||
return this.adapter
|
||||
.send(body, installations, pushStatus.objectId)
|
||||
.then(results => {
|
||||
return pushStatus.trackSent(results, UTCOffset).then(() => results);
|
||||
});
|
||||
return this.adapter.send(body, installations, pushStatus.objectId).then(results => {
|
||||
return pushStatus.trackSent(results, UTCOffset).then(() => results);
|
||||
});
|
||||
}
|
||||
|
||||
// Collect the badges to reduce the # of calls
|
||||
@@ -107,13 +94,7 @@ export class PushWorker {
|
||||
const payload = deepcopy(body);
|
||||
payload.data.badge = parseInt(badge);
|
||||
const installations = badgeInstallationsMap[badge];
|
||||
return this.sendToAdapter(
|
||||
payload,
|
||||
installations,
|
||||
pushStatus,
|
||||
config,
|
||||
UTCOffset
|
||||
);
|
||||
return this.sendToAdapter(payload, installations, pushStatus, config, UTCOffset);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
@@ -88,10 +88,7 @@ export function groupByLocaleIdentifier(installations, locales = []) {
|
||||
if (added) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
installation.localeIdentifier &&
|
||||
installation.localeIdentifier.indexOf(locale) === 0
|
||||
) {
|
||||
if (installation.localeIdentifier && installation.localeIdentifier.indexOf(locale) === 0) {
|
||||
added = true;
|
||||
map[locale] = map[locale] || [];
|
||||
map[locale].push(installation);
|
||||
|
||||
@@ -40,10 +40,7 @@ function RestQuery(
|
||||
if (!this.auth.isMaster) {
|
||||
if (this.className == '_Session') {
|
||||
if (!this.auth.user) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Invalid session token'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
this.restWhere = {
|
||||
$and: [
|
||||
@@ -175,10 +172,7 @@ function RestQuery(
|
||||
case 'subqueryReadPreference':
|
||||
break;
|
||||
default:
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'bad option: ' + option
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad option: ' + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,14 +222,7 @@ RestQuery.prototype.each = function (callback) {
|
||||
return !finished;
|
||||
},
|
||||
async () => {
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK
|
||||
);
|
||||
const query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||
const { results } = await query.execute();
|
||||
results.forEach(callback);
|
||||
finished = results.length < restOptions.limit;
|
||||
@@ -286,9 +273,7 @@ RestQuery.prototype.getUserAndRoleACL = function () {
|
||||
|
||||
if (this.auth.user) {
|
||||
return this.auth.getUserRoles().then(roles => {
|
||||
this.findOptions.acl = this.findOptions.acl.concat(roles, [
|
||||
this.auth.user.id,
|
||||
]);
|
||||
this.findOptions.acl = this.findOptions.acl.concat(roles, [this.auth.user.id]);
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
@@ -326,9 +311,7 @@ RestQuery.prototype.validateClientClassCreation = function () {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' +
|
||||
'non-existent class: ' +
|
||||
this.className
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -367,10 +350,7 @@ RestQuery.prototype.replaceInQuery = function () {
|
||||
// The inQuery value must have precisely two keys - where and className
|
||||
var inQueryValue = inQueryObject['$inQuery'];
|
||||
if (!inQueryValue.where || !inQueryValue.className) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $inQuery'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $inQuery');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -428,10 +408,7 @@ RestQuery.prototype.replaceNotInQuery = function () {
|
||||
// The notInQuery value must have precisely two keys - where and className
|
||||
var notInQueryValue = notInQueryObject['$notInQuery'];
|
||||
if (!notInQueryValue.where || !notInQueryValue.className) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $notInQuery'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $notInQuery');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -501,10 +478,7 @@ RestQuery.prototype.replaceSelect = function () {
|
||||
!selectValue.query.className ||
|
||||
Object.keys(selectValue).length !== 2
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $select'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $select');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -565,10 +539,7 @@ RestQuery.prototype.replaceDontSelect = function () {
|
||||
!dontSelectValue.query.className ||
|
||||
Object.keys(dontSelectValue).length !== 2
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $dontSelect'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $dontSelect');
|
||||
}
|
||||
const additionalOptions = {
|
||||
redirectClassNameForKey: dontSelectValue.query.redirectClassNameForKey,
|
||||
@@ -589,11 +560,7 @@ RestQuery.prototype.replaceDontSelect = function () {
|
||||
additionalOptions
|
||||
);
|
||||
return subquery.execute().then(response => {
|
||||
transformDontSelect(
|
||||
dontSelectObject,
|
||||
dontSelectValue.key,
|
||||
response.results
|
||||
);
|
||||
transformDontSelect(dontSelectObject, dontSelectValue.key, response.results);
|
||||
// Keep replacing $dontSelect clauses
|
||||
return this.replaceDontSelect();
|
||||
});
|
||||
@@ -692,11 +659,9 @@ RestQuery.prototype.runCount = function () {
|
||||
this.findOptions.count = true;
|
||||
delete this.findOptions.skip;
|
||||
delete this.findOptions.limit;
|
||||
return this.config.database
|
||||
.find(this.className, this.restWhere, this.findOptions)
|
||||
.then(c => {
|
||||
this.response.count = c;
|
||||
});
|
||||
return this.config.database.find(this.className, this.restWhere, this.findOptions).then(c => {
|
||||
this.response.count = c;
|
||||
});
|
||||
};
|
||||
|
||||
// Augments this.response with all pointers on an object
|
||||
@@ -711,10 +676,7 @@ RestQuery.prototype.handleIncludeAll = function () {
|
||||
const includeFields = [];
|
||||
const keyFields = [];
|
||||
for (const field in schema.fields) {
|
||||
if (
|
||||
schema.fields[field].type &&
|
||||
schema.fields[field].type === 'Pointer'
|
||||
) {
|
||||
if (schema.fields[field].type && schema.fields[field].type === 'Pointer') {
|
||||
includeFields.push([field]);
|
||||
keyFields.push(field);
|
||||
}
|
||||
@@ -868,8 +830,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
|
||||
if (restOptions.includeReadPreference) {
|
||||
includeRestOptions.readPreference = restOptions.includeReadPreference;
|
||||
includeRestOptions.includeReadPreference =
|
||||
restOptions.includeReadPreference;
|
||||
includeRestOptions.includeReadPreference = restOptions.includeReadPreference;
|
||||
} else if (restOptions.readPreference) {
|
||||
includeRestOptions.readPreference = restOptions.readPreference;
|
||||
}
|
||||
@@ -882,13 +843,7 @@ function includePath(config, auth, response, path, restOptions = {}) {
|
||||
} else {
|
||||
where = { objectId: { $in: objectIds } };
|
||||
}
|
||||
var query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
where,
|
||||
includeRestOptions
|
||||
);
|
||||
var query = new RestQuery(config, auth, className, where, includeRestOptions);
|
||||
return query.execute({ op: 'get' }).then(results => {
|
||||
results.className = className;
|
||||
return Promise.resolve(results);
|
||||
|
||||
321
src/RestWrite.js
321
src/RestWrite.js
@@ -24,17 +24,7 @@ import logger from './logger';
|
||||
// RestWrite will handle objectId, createdAt, and updatedAt for
|
||||
// everything. It also knows to use triggers and special modifications
|
||||
// for the _User class.
|
||||
function RestWrite(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
query,
|
||||
data,
|
||||
originalData,
|
||||
clientSDK,
|
||||
context,
|
||||
action
|
||||
) {
|
||||
function RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action) {
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
@@ -55,10 +45,7 @@ function RestWrite(
|
||||
|
||||
if (!query) {
|
||||
if (this.config.allowCustomObjectId) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(data, 'objectId') &&
|
||||
!data.objectId
|
||||
) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'objectId') && !data.objectId) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.MISSING_OBJECT_ID,
|
||||
'objectId must not be empty, null or undefined'
|
||||
@@ -66,16 +53,10 @@ function RestWrite(
|
||||
}
|
||||
} else {
|
||||
if (data.objectId) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
'objectId is an invalid field name.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
|
||||
}
|
||||
if (data.id) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
'id is an invalid field name.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'id is an invalid field name.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,9 +156,7 @@ RestWrite.prototype.getUserAndRoleACL = function () {
|
||||
|
||||
if (this.auth.user) {
|
||||
return this.auth.getUserRoles().then(roles => {
|
||||
this.runOptions.acl = this.runOptions.acl.concat(roles, [
|
||||
this.auth.user.id,
|
||||
]);
|
||||
this.runOptions.acl = this.runOptions.acl.concat(roles, [this.auth.user.id]);
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
@@ -199,9 +178,7 @@ RestWrite.prototype.validateClientClassCreation = function () {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' +
|
||||
'non-existent class: ' +
|
||||
this.className
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -229,11 +206,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
|
||||
|
||||
// Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class.
|
||||
if (
|
||||
!triggers.triggerExists(
|
||||
this.className,
|
||||
triggers.Types.beforeSave,
|
||||
this.config.applicationId
|
||||
)
|
||||
!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -277,10 +250,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
|
||||
// In the case that there is no permission for the operation, it throws an error
|
||||
return databasePromise.then(result => {
|
||||
if (!result || result.length <= 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -318,11 +288,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
|
||||
RestWrite.prototype.runBeforeLoginTrigger = async function (userData) {
|
||||
// Avoid doing any setup for triggers if there is no 'beforeLogin' trigger
|
||||
if (
|
||||
!triggers.triggerExists(
|
||||
this.className,
|
||||
triggers.Types.beforeLogin,
|
||||
this.config.applicationId
|
||||
)
|
||||
!triggers.triggerExists(this.className, triggers.Types.beforeLogin, this.config.applicationId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -349,16 +315,13 @@ RestWrite.prototype.runBeforeLoginTrigger = async function (userData) {
|
||||
RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
|
||||
if (this.data) {
|
||||
return this.validSchemaController.getAllClasses().then(allClasses => {
|
||||
const schema = allClasses.find(
|
||||
oneClass => oneClass.className === this.className
|
||||
);
|
||||
const schema = allClasses.find(oneClass => oneClass.className === this.className);
|
||||
const setRequiredFieldIfNeeded = (fieldName, setDefault) => {
|
||||
if (
|
||||
this.data[fieldName] === undefined ||
|
||||
this.data[fieldName] === null ||
|
||||
this.data[fieldName] === '' ||
|
||||
(typeof this.data[fieldName] === 'object' &&
|
||||
this.data[fieldName].__op === 'Delete')
|
||||
(typeof this.data[fieldName] === 'object' && this.data[fieldName].__op === 'Delete')
|
||||
) {
|
||||
if (
|
||||
setDefault &&
|
||||
@@ -366,23 +329,15 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
|
||||
schema.fields[fieldName].defaultValue !== null &&
|
||||
schema.fields[fieldName].defaultValue !== undefined &&
|
||||
(this.data[fieldName] === undefined ||
|
||||
(typeof this.data[fieldName] === 'object' &&
|
||||
this.data[fieldName].__op === 'Delete'))
|
||||
(typeof this.data[fieldName] === 'object' && this.data[fieldName].__op === 'Delete'))
|
||||
) {
|
||||
this.data[fieldName] = schema.fields[fieldName].defaultValue;
|
||||
this.storage.fieldsChangedByTrigger =
|
||||
this.storage.fieldsChangedByTrigger || [];
|
||||
this.storage.fieldsChangedByTrigger = this.storage.fieldsChangedByTrigger || [];
|
||||
if (this.storage.fieldsChangedByTrigger.indexOf(fieldName) < 0) {
|
||||
this.storage.fieldsChangedByTrigger.push(fieldName);
|
||||
}
|
||||
} else if (
|
||||
schema.fields[fieldName] &&
|
||||
schema.fields[fieldName].required === true
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
`${fieldName} is required`
|
||||
);
|
||||
} else if (schema.fields[fieldName] && schema.fields[fieldName].required === true) {
|
||||
throw new Parse.Error(Parse.Error.VALIDATION_ERROR, `${fieldName} is required`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -394,9 +349,7 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
|
||||
|
||||
// Only assign new objectId if we are creating new object
|
||||
if (!this.data.objectId) {
|
||||
this.data.objectId = cryptoUtils.newObjectId(
|
||||
this.config.objectIdSize
|
||||
);
|
||||
this.data.objectId = cryptoUtils.newObjectId(this.config.objectIdSize);
|
||||
}
|
||||
if (schema) {
|
||||
Object.keys(schema.fields).forEach(fieldName => {
|
||||
@@ -422,23 +375,11 @@ RestWrite.prototype.validateAuthData = function () {
|
||||
}
|
||||
|
||||
if (!this.query && !this.data.authData) {
|
||||
if (
|
||||
typeof this.data.username !== 'string' ||
|
||||
_.isEmpty(this.data.username)
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.USERNAME_MISSING,
|
||||
'bad or missing username'
|
||||
);
|
||||
if (typeof this.data.username !== 'string' || _.isEmpty(this.data.username)) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'bad or missing username');
|
||||
}
|
||||
if (
|
||||
typeof this.data.password !== 'string' ||
|
||||
_.isEmpty(this.data.password)
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PASSWORD_MISSING,
|
||||
'password is required'
|
||||
);
|
||||
if (typeof this.data.password !== 'string' || _.isEmpty(this.data.password)) {
|
||||
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,10 +389,7 @@ RestWrite.prototype.validateAuthData = function () {
|
||||
) {
|
||||
// Handle saving authData to {} or if authData doesn't exist
|
||||
return;
|
||||
} else if (
|
||||
Object.prototype.hasOwnProperty.call(this.data, 'authData') &&
|
||||
!this.data.authData
|
||||
) {
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.data, 'authData') && !this.data.authData) {
|
||||
// Handle saving authData to null
|
||||
throw new Parse.Error(
|
||||
Parse.Error.UNSUPPORTED_SERVICE,
|
||||
@@ -482,9 +420,7 @@ RestWrite.prototype.handleAuthDataValidation = function (authData) {
|
||||
if (authData[provider] === null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const validateAuthData = this.config.authDataManager.getValidatorForProvider(
|
||||
provider
|
||||
);
|
||||
const validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
|
||||
if (!validateAuthData) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.UNSUPPORTED_SERVICE,
|
||||
@@ -595,8 +531,7 @@ RestWrite.prototype.handleAuthData = function (authData) {
|
||||
if (this.response) {
|
||||
// Assign the new authData in the response
|
||||
Object.keys(mutatedAuthData).forEach(provider => {
|
||||
this.response.response.authData[provider] =
|
||||
mutatedAuthData[provider];
|
||||
this.response.response.authData[provider] = mutatedAuthData[provider];
|
||||
});
|
||||
|
||||
// Run the DB update directly, as 'master'
|
||||
@@ -614,10 +549,7 @@ RestWrite.prototype.handleAuthData = function (authData) {
|
||||
// Trying to update auth data but users
|
||||
// are different
|
||||
if (userResult.objectId !== userId) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||
'this auth is already used'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used');
|
||||
}
|
||||
// No auth data was mutated, just keep going
|
||||
if (!hasMutatedAuthData) {
|
||||
@@ -628,10 +560,7 @@ RestWrite.prototype.handleAuthData = function (authData) {
|
||||
return this.handleAuthDataValidation(authData).then(() => {
|
||||
if (results.length > 1) {
|
||||
// More than 1 user with the passed id's
|
||||
throw new Parse.Error(
|
||||
Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||
'this auth is already used'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -757,10 +686,7 @@ RestWrite.prototype._validateEmail = function () {
|
||||
// Validate basic email address format
|
||||
if (!this.data.email.match(/^.+@.+$/)) {
|
||||
return Promise.reject(
|
||||
new Parse.Error(
|
||||
Parse.Error.INVALID_EMAIL_ADDRESS,
|
||||
'Email address format is invalid.'
|
||||
)
|
||||
new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.')
|
||||
);
|
||||
}
|
||||
// Case insensitive match, see note above function.
|
||||
@@ -823,9 +749,7 @@ RestWrite.prototype._validatePasswordRequirements = function () {
|
||||
(this.config.passwordPolicy.validatorCallback &&
|
||||
!this.config.passwordPolicy.validatorCallback(this.data.password))
|
||||
) {
|
||||
return Promise.reject(
|
||||
new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError)
|
||||
);
|
||||
return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError));
|
||||
}
|
||||
|
||||
// check whether password contain username
|
||||
@@ -833,26 +757,19 @@ RestWrite.prototype._validatePasswordRequirements = function () {
|
||||
if (this.data.username) {
|
||||
// username is not passed during password reset
|
||||
if (this.data.password.indexOf(this.data.username) >= 0)
|
||||
return Promise.reject(
|
||||
new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError)
|
||||
);
|
||||
return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError));
|
||||
} else {
|
||||
// retrieve the User object using objectId during password reset
|
||||
return this.config.database
|
||||
.find('_User', { objectId: this.objectId() })
|
||||
.then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
if (this.data.password.indexOf(results[0].username) >= 0)
|
||||
return Promise.reject(
|
||||
new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
containsUsernameError
|
||||
)
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
return this.config.database.find('_User', { objectId: this.objectId() }).then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
if (this.data.password.indexOf(results[0].username) >= 0)
|
||||
return Promise.reject(
|
||||
new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError)
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
@@ -999,11 +916,7 @@ RestWrite.prototype.destroyDuplicatedSessions = function () {
|
||||
|
||||
// Handles any followup logic
|
||||
RestWrite.prototype.handleFollowup = function () {
|
||||
if (
|
||||
this.storage &&
|
||||
this.storage['clearSessions'] &&
|
||||
this.config.revokeSessionOnPasswordReset
|
||||
) {
|
||||
if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {
|
||||
var sessionQuery = {
|
||||
user: {
|
||||
__type: 'Pointer',
|
||||
@@ -1038,26 +951,16 @@ RestWrite.prototype.handleSession = function () {
|
||||
}
|
||||
|
||||
if (!this.auth.user && !this.auth.isMaster) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token required.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.');
|
||||
}
|
||||
|
||||
// TODO: Verify proper error to throw
|
||||
if (this.data.ACL) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_KEY_NAME,
|
||||
'Cannot set ' + 'ACL on a Session.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot set ' + 'ACL on a Session.');
|
||||
}
|
||||
|
||||
if (this.query) {
|
||||
if (
|
||||
this.data.user &&
|
||||
!this.auth.isMaster &&
|
||||
this.data.user.objectId != this.auth.user.id
|
||||
) {
|
||||
if (this.data.user && !this.auth.isMaster && this.data.user.objectId != this.auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
} else if (this.data.installationId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
|
||||
@@ -1085,10 +988,7 @@ RestWrite.prototype.handleSession = function () {
|
||||
|
||||
return createSession().then(results => {
|
||||
if (!results.response) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INTERNAL_SERVER_ERROR,
|
||||
'Error creating session.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Error creating session.');
|
||||
}
|
||||
sessionData['objectId'] = results.response['objectId'];
|
||||
this.response = {
|
||||
@@ -1118,8 +1018,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
135,
|
||||
'at least one ID field (deviceToken, installationId) ' +
|
||||
'must be specified in this operation'
|
||||
'at least one ID field (deviceToken, installationId) ' + 'must be specified in this operation'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1146,12 +1045,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
}
|
||||
|
||||
// Updating _Installation but not updating anything critical
|
||||
if (
|
||||
this.query &&
|
||||
!this.data.deviceToken &&
|
||||
!installationId &&
|
||||
!this.data.deviceType
|
||||
) {
|
||||
if (this.query && !this.data.deviceToken && !installationId && !this.data.deviceType) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1194,11 +1088,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
})
|
||||
.then(results => {
|
||||
results.forEach(result => {
|
||||
if (
|
||||
this.query &&
|
||||
this.query.objectId &&
|
||||
result.objectId == this.query.objectId
|
||||
) {
|
||||
if (this.query && this.query.objectId && result.objectId == this.query.objectId) {
|
||||
objectIdMatch = result;
|
||||
}
|
||||
if (result.installationId == installationId) {
|
||||
@@ -1212,20 +1102,14 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
// Sanity checks when running a query
|
||||
if (this.query && this.query.objectId) {
|
||||
if (!objectIdMatch) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for update.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for update.');
|
||||
}
|
||||
if (
|
||||
this.data.installationId &&
|
||||
objectIdMatch.installationId &&
|
||||
this.data.installationId !== objectIdMatch.installationId
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
136,
|
||||
'installationId may not be changed in this ' + 'operation'
|
||||
);
|
||||
throw new Parse.Error(136, 'installationId may not be changed in this ' + 'operation');
|
||||
}
|
||||
if (
|
||||
this.data.deviceToken &&
|
||||
@@ -1234,20 +1118,14 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
!this.data.installationId &&
|
||||
!objectIdMatch.installationId
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
136,
|
||||
'deviceToken may not be changed in this ' + 'operation'
|
||||
);
|
||||
throw new Parse.Error(136, 'deviceToken may not be changed in this ' + 'operation');
|
||||
}
|
||||
if (
|
||||
this.data.deviceType &&
|
||||
this.data.deviceType &&
|
||||
this.data.deviceType !== objectIdMatch.deviceType
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
136,
|
||||
'deviceType may not be changed in this ' + 'operation'
|
||||
);
|
||||
throw new Parse.Error(136, 'deviceType may not be changed in this ' + 'operation');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1260,10 +1138,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
}
|
||||
// need to specify deviceType only if it's new
|
||||
if (!this.query && !this.data.deviceType && !idMatch) {
|
||||
throw new Parse.Error(
|
||||
135,
|
||||
'deviceType must be specified in this operation'
|
||||
);
|
||||
throw new Parse.Error(135, 'deviceType must be specified in this operation');
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
@@ -1310,10 +1185,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
deviceTokenMatches.length == 1 &&
|
||||
!deviceTokenMatches[0]['installationId']
|
||||
) {
|
||||
if (deviceTokenMatches.length == 1 && !deviceTokenMatches[0]['installationId']) {
|
||||
// Exactly one device token match and it doesn't have an installation
|
||||
// ID. This is the one case where we want to merge with the existing
|
||||
// object.
|
||||
@@ -1332,10 +1204,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
this.data.deviceToken &&
|
||||
idMatch.deviceToken != this.data.deviceToken
|
||||
) {
|
||||
if (this.data.deviceToken && idMatch.deviceToken != this.data.deviceToken) {
|
||||
// We're setting the device token on an existing installation, so
|
||||
// we should try cleaning out old installations that match this
|
||||
// device token.
|
||||
@@ -1364,16 +1233,14 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
if (this.data.appIdentifier) {
|
||||
delQuery['appIdentifier'] = this.data.appIdentifier;
|
||||
}
|
||||
this.config.database
|
||||
.destroy('_Installation', delQuery)
|
||||
.catch(err => {
|
||||
if (err.code == Parse.Error.OBJECT_NOT_FOUND) {
|
||||
// no deletions were made. Can be ignored.
|
||||
return;
|
||||
}
|
||||
// rethrow the error
|
||||
throw err;
|
||||
});
|
||||
this.config.database.destroy('_Installation', delQuery).catch(err => {
|
||||
if (err.code == Parse.Error.OBJECT_NOT_FOUND) {
|
||||
// no deletions were made. Can be ignored.
|
||||
return;
|
||||
}
|
||||
// rethrow the error
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
// In non-merge scenarios, just return the installation match id
|
||||
return idMatch.objectId;
|
||||
@@ -1397,10 +1264,7 @@ RestWrite.prototype.handleInstallation = function () {
|
||||
RestWrite.prototype.expandFilesForExistingObjects = function () {
|
||||
// Check whether we have a short-circuited response - only then run expansion.
|
||||
if (this.response && this.response.response) {
|
||||
this.config.filesController.expandFilesInObject(
|
||||
this.config,
|
||||
this.response.response
|
||||
);
|
||||
this.config.filesController.expandFilesInObject(this.config, this.response.response);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1413,11 +1277,7 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
this.config.cacheController.role.clear();
|
||||
}
|
||||
|
||||
if (
|
||||
this.className === '_User' &&
|
||||
this.query &&
|
||||
this.auth.isUnauthenticated()
|
||||
) {
|
||||
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.SESSION_MISSING,
|
||||
`Cannot modify user ${this.query.objectId}.`
|
||||
@@ -1437,11 +1297,7 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
if (this.query) {
|
||||
// Force the user to not lockout
|
||||
// Matched with parse.com
|
||||
if (
|
||||
this.className === '_User' &&
|
||||
this.data.ACL &&
|
||||
this.auth.isMaster !== true
|
||||
) {
|
||||
if (this.className === '_User' && this.data.ACL && this.auth.isMaster !== true) {
|
||||
this.data.ACL[this.query.objectId] = { read: true, write: true };
|
||||
}
|
||||
// update password timestamp if user password is being changed
|
||||
@@ -1484,8 +1340,7 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
}
|
||||
//n-1 passwords go into history including last password
|
||||
while (
|
||||
oldPasswords.length >
|
||||
Math.max(0, this.config.passwordPolicy.maxPasswordHistory - 2)
|
||||
oldPasswords.length > Math.max(0, this.config.passwordPolicy.maxPasswordHistory - 2)
|
||||
) {
|
||||
oldPasswords.shift();
|
||||
}
|
||||
@@ -1525,48 +1380,28 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
ACL[this.data.objectId] = { read: true, write: true };
|
||||
this.data.ACL = ACL;
|
||||
// password timestamp to be used when password expiry policy is enforced
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.maxPasswordAge
|
||||
) {
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordAge) {
|
||||
this.data._password_changed_at = Parse._encode(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
// Run a create
|
||||
return this.config.database
|
||||
.create(
|
||||
this.className,
|
||||
this.data,
|
||||
this.runOptions,
|
||||
false,
|
||||
this.validSchemaController
|
||||
)
|
||||
.create(this.className, this.data, this.runOptions, false, this.validSchemaController)
|
||||
.catch(error => {
|
||||
if (
|
||||
this.className !== '_User' ||
|
||||
error.code !== Parse.Error.DUPLICATE_VALUE
|
||||
) {
|
||||
if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Quick check, if we were able to infer the duplicated field name
|
||||
if (
|
||||
error &&
|
||||
error.userInfo &&
|
||||
error.userInfo.duplicated_field === 'username'
|
||||
) {
|
||||
if (error && error.userInfo && error.userInfo.duplicated_field === 'username') {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.USERNAME_TAKEN,
|
||||
'Account already exists for this username.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
error &&
|
||||
error.userInfo &&
|
||||
error.userInfo.duplicated_field === 'email'
|
||||
) {
|
||||
if (error && error.userInfo && error.userInfo.duplicated_field === 'email') {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.EMAIL_TAKEN,
|
||||
'Account already exists for this email address.'
|
||||
@@ -1641,9 +1476,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
|
||||
triggers.Types.afterSave,
|
||||
this.config.applicationId
|
||||
);
|
||||
const hasLiveQuery = this.config.liveQueryController.hasLiveQuery(
|
||||
this.className
|
||||
);
|
||||
const hasLiveQuery = this.config.liveQueryController.hasLiveQuery(this.className);
|
||||
if (!hasAfterSaveHook && !hasLiveQuery) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -1662,16 +1495,11 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
|
||||
// Build the inflated object, different from beforeSave, originalData is not empty
|
||||
// since developers can change data in the beforeSave.
|
||||
const updatedObject = this.buildUpdatedObject(extraData);
|
||||
updatedObject._handleSaveResponse(
|
||||
this.response.response,
|
||||
this.response.status || 200
|
||||
);
|
||||
updatedObject._handleSaveResponse(this.response.response, this.response.status || 200);
|
||||
|
||||
this.config.database.loadSchema().then(schemaController => {
|
||||
// Notifiy LiveQueryServer if possible
|
||||
const perms = schemaController.getClassLevelPermissions(
|
||||
updatedObject.className
|
||||
);
|
||||
const perms = schemaController.getClassLevelPermissions(updatedObject.className);
|
||||
this.config.liveQueryController.onAfterSave(
|
||||
updatedObject.className,
|
||||
updatedObject,
|
||||
@@ -1702,8 +1530,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
|
||||
|
||||
// A helper to figure out what location this operation happens at.
|
||||
RestWrite.prototype.location = function () {
|
||||
var middle =
|
||||
this.className === '_User' ? '/users/' : '/classes/' + this.className + '/';
|
||||
var middle = this.className === '_User' ? '/users/' : '/classes/' + this.className + '/';
|
||||
const mount = this.config.mount || this.config.serverURL;
|
||||
return mount + middle + this.data.objectId;
|
||||
};
|
||||
|
||||
@@ -38,10 +38,7 @@ const ALLOWED_KEYS = [...BASE_KEYS, ...PIPELINE_KEYS];
|
||||
|
||||
export class AggregateRouter extends ClassesRouter {
|
||||
handleFind(req) {
|
||||
const body = Object.assign(
|
||||
req.body,
|
||||
ClassesRouter.JSONFromQuery(req.query)
|
||||
);
|
||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
const options = {};
|
||||
if (body.distinct) {
|
||||
options.distinct = String(body.distinct);
|
||||
@@ -118,9 +115,7 @@ export class AggregateRouter extends ClassesRouter {
|
||||
return pipeline.map(stage => {
|
||||
const keys = Object.keys(stage);
|
||||
if (keys.length != 1) {
|
||||
throw new Error(
|
||||
`Pipeline stages should only have one key found ${keys.join(', ')}`
|
||||
);
|
||||
throw new Error(`Pipeline stages should only have one key found ${keys.join(', ')}`);
|
||||
}
|
||||
return AggregateRouter.transformStage(keys[0], stage);
|
||||
});
|
||||
@@ -128,10 +123,7 @@ export class AggregateRouter extends ClassesRouter {
|
||||
|
||||
static transformStage(stageName, stage) {
|
||||
if (ALLOWED_KEYS.indexOf(stageName) === -1) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
`Invalid parameter for query: ${stageName}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid parameter for query: ${stageName}`);
|
||||
}
|
||||
if (stageName === 'group') {
|
||||
if (Object.prototype.hasOwnProperty.call(stage[stageName], '_id')) {
|
||||
@@ -153,14 +145,9 @@ export class AggregateRouter extends ClassesRouter {
|
||||
}
|
||||
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'GET',
|
||||
'/aggregate/:className',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.handleFind(req);
|
||||
}
|
||||
);
|
||||
this.route('GET', '/aggregate/:className', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.handleFind(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ export class AudiencesRouter extends ClassesRouter {
|
||||
}
|
||||
|
||||
handleFind(req) {
|
||||
const body = Object.assign(
|
||||
req.body,
|
||||
ClassesRouter.JSONFromQuery(req.query)
|
||||
);
|
||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
const options = ClassesRouter.optionsFromBody(body);
|
||||
|
||||
return rest
|
||||
@@ -42,14 +39,9 @@ export class AudiencesRouter extends ClassesRouter {
|
||||
}
|
||||
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'GET',
|
||||
'/push_audiences',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.handleFind(req);
|
||||
}
|
||||
);
|
||||
this.route('GET', '/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.handleFind(req);
|
||||
});
|
||||
this.route(
|
||||
'GET',
|
||||
'/push_audiences/:objectId',
|
||||
@@ -58,14 +50,9 @@ export class AudiencesRouter extends ClassesRouter {
|
||||
return this.handleGet(req);
|
||||
}
|
||||
);
|
||||
this.route(
|
||||
'POST',
|
||||
'/push_audiences',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.handleCreate(req);
|
||||
}
|
||||
);
|
||||
this.route('POST', '/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.handleCreate(req);
|
||||
});
|
||||
this.route(
|
||||
'PUT',
|
||||
'/push_audiences/:objectId',
|
||||
|
||||
@@ -19,10 +19,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
handleFind(req) {
|
||||
const body = Object.assign(
|
||||
req.body,
|
||||
ClassesRouter.JSONFromQuery(req.query)
|
||||
);
|
||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
const options = ClassesRouter.optionsFromBody(body);
|
||||
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
|
||||
// Silently replace the limit on the query with the max configured
|
||||
@@ -51,18 +48,12 @@ export class ClassesRouter extends PromiseRouter {
|
||||
|
||||
// Returns a promise for a {response} object.
|
||||
handleGet(req) {
|
||||
const body = Object.assign(
|
||||
req.body,
|
||||
ClassesRouter.JSONFromQuery(req.query)
|
||||
);
|
||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
const options = {};
|
||||
|
||||
for (const key of Object.keys(body)) {
|
||||
if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'Improper encode of parameter'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +87,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
|
||||
if (this.className(req) === '_User') {
|
||||
@@ -142,13 +130,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
|
||||
handleDelete(req) {
|
||||
return rest
|
||||
.del(
|
||||
req.config,
|
||||
req.auth,
|
||||
this.className(req),
|
||||
req.params.objectId,
|
||||
req.info.context
|
||||
)
|
||||
.del(req.config, req.auth, this.className(req), req.params.objectId, req.info.context)
|
||||
.then(() => {
|
||||
return { response: {} };
|
||||
});
|
||||
@@ -187,10 +169,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
|
||||
for (const key of Object.keys(body)) {
|
||||
if (allowConstraints.indexOf(key) === -1) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
`Invalid parameter for query: ${key}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid parameter for query: ${key}`);
|
||||
}
|
||||
}
|
||||
const options = {};
|
||||
@@ -229,10 +208,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
if (typeof body.subqueryReadPreference === 'string') {
|
||||
options.subqueryReadPreference = body.subqueryReadPreference;
|
||||
}
|
||||
if (
|
||||
body.hint &&
|
||||
(typeof body.hint === 'string' || typeof body.hint === 'object')
|
||||
) {
|
||||
if (body.hint && (typeof body.hint === 'string' || typeof body.hint === 'object')) {
|
||||
options.hint = body.hint;
|
||||
}
|
||||
if (body.explain) {
|
||||
@@ -251,14 +227,9 @@ export class ClassesRouter extends PromiseRouter {
|
||||
this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => {
|
||||
return this.handleCreate(req);
|
||||
});
|
||||
this.route(
|
||||
'PUT',
|
||||
'/classes/:className/:objectId',
|
||||
promiseEnsureIdempotency,
|
||||
req => {
|
||||
return this.handleUpdate(req);
|
||||
}
|
||||
);
|
||||
this.route('PUT', '/classes/:className/:objectId', promiseEnsureIdempotency, req => {
|
||||
return this.handleUpdate(req);
|
||||
});
|
||||
this.route('DELETE', '/classes/:className/:objectId', req => {
|
||||
return this.handleDelete(req);
|
||||
});
|
||||
|
||||
@@ -56,28 +56,24 @@ export class CloudCodeRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
static getJobs(req) {
|
||||
return rest
|
||||
.find(req.config, req.auth, '_JobSchedule', {}, {})
|
||||
.then(scheduledJobs => {
|
||||
return {
|
||||
response: scheduledJobs.results,
|
||||
};
|
||||
});
|
||||
return rest.find(req.config, req.auth, '_JobSchedule', {}, {}).then(scheduledJobs => {
|
||||
return {
|
||||
response: scheduledJobs.results,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static getJobsData(req) {
|
||||
const config = req.config;
|
||||
const jobs = triggers.getJobs(config.applicationId) || {};
|
||||
return rest
|
||||
.find(req.config, req.auth, '_JobSchedule', {}, {})
|
||||
.then(scheduledJobs => {
|
||||
return {
|
||||
response: {
|
||||
in_use: scheduledJobs.results.map(job => job.jobName),
|
||||
jobs: Object.keys(jobs),
|
||||
},
|
||||
};
|
||||
});
|
||||
return rest.find(req.config, req.auth, '_JobSchedule', {}, {}).then(scheduledJobs => {
|
||||
return {
|
||||
response: {
|
||||
in_use: scheduledJobs.results.map(job => job.jobName),
|
||||
jobs: Object.keys(jobs),
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static createJob(req) {
|
||||
|
||||
@@ -4,61 +4,56 @@ import * as middleware from '../middlewares';
|
||||
|
||||
export class FeaturesRouter extends PromiseRouter {
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'GET',
|
||||
'/serverInfo',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
const { config } = req;
|
||||
const features = {
|
||||
globalConfig: {
|
||||
create: true,
|
||||
read: true,
|
||||
update: true,
|
||||
delete: true,
|
||||
},
|
||||
hooks: {
|
||||
create: true,
|
||||
read: true,
|
||||
update: true,
|
||||
delete: true,
|
||||
},
|
||||
cloudCode: {
|
||||
jobs: true,
|
||||
},
|
||||
logs: {
|
||||
level: true,
|
||||
size: true,
|
||||
order: true,
|
||||
until: true,
|
||||
from: true,
|
||||
},
|
||||
push: {
|
||||
immediatePush: config.hasPushSupport,
|
||||
scheduledPush: config.hasPushScheduledSupport,
|
||||
storedPushData: config.hasPushSupport,
|
||||
pushAudiences: true,
|
||||
localization: true,
|
||||
},
|
||||
schemas: {
|
||||
addField: true,
|
||||
removeField: true,
|
||||
addClass: true,
|
||||
removeClass: true,
|
||||
clearAllDataFromClass: true,
|
||||
exportClass: false,
|
||||
editClassLevelPermissions: true,
|
||||
editPointerPermissions: true,
|
||||
},
|
||||
};
|
||||
this.route('GET', '/serverInfo', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
const { config } = req;
|
||||
const features = {
|
||||
globalConfig: {
|
||||
create: true,
|
||||
read: true,
|
||||
update: true,
|
||||
delete: true,
|
||||
},
|
||||
hooks: {
|
||||
create: true,
|
||||
read: true,
|
||||
update: true,
|
||||
delete: true,
|
||||
},
|
||||
cloudCode: {
|
||||
jobs: true,
|
||||
},
|
||||
logs: {
|
||||
level: true,
|
||||
size: true,
|
||||
order: true,
|
||||
until: true,
|
||||
from: true,
|
||||
},
|
||||
push: {
|
||||
immediatePush: config.hasPushSupport,
|
||||
scheduledPush: config.hasPushScheduledSupport,
|
||||
storedPushData: config.hasPushSupport,
|
||||
pushAudiences: true,
|
||||
localization: true,
|
||||
},
|
||||
schemas: {
|
||||
addField: true,
|
||||
removeField: true,
|
||||
addClass: true,
|
||||
removeClass: true,
|
||||
clearAllDataFromClass: true,
|
||||
exportClass: false,
|
||||
editClassLevelPermissions: true,
|
||||
editPointerPermissions: true,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
response: {
|
||||
features: features,
|
||||
parseServerVersion: version,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
response: {
|
||||
features: features,
|
||||
parseServerVersion: version,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ export class FilesRouter {
|
||||
router.get('/files/:appId/metadata/:filename', this.metadataHandler);
|
||||
|
||||
router.post('/files', function (req, res, next) {
|
||||
next(
|
||||
new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.')
|
||||
);
|
||||
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.'));
|
||||
});
|
||||
|
||||
router.post(
|
||||
@@ -72,13 +70,11 @@ export class FilesRouter {
|
||||
const filename = req.params.filename;
|
||||
const contentType = mime.getType(filename);
|
||||
if (isFileStreamable(req, filesController)) {
|
||||
filesController
|
||||
.handleFileStream(config, filename, req, res, contentType)
|
||||
.catch(() => {
|
||||
res.status(404);
|
||||
res.set('Content-Type', 'text/plain');
|
||||
res.end('File not found.');
|
||||
});
|
||||
filesController.handleFileStream(config, filename, req, res, contentType).catch(() => {
|
||||
res.status(404);
|
||||
res.set('Content-Type', 'text/plain');
|
||||
res.end('File not found.');
|
||||
});
|
||||
} else {
|
||||
filesController
|
||||
.getFileData(config, filename)
|
||||
@@ -103,9 +99,7 @@ export class FilesRouter {
|
||||
const contentType = req.get('Content-type');
|
||||
|
||||
if (!req.body || !req.body.length) {
|
||||
next(
|
||||
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')
|
||||
);
|
||||
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,8 +237,5 @@ export class FilesRouter {
|
||||
}
|
||||
|
||||
function isFileStreamable(req, filesController) {
|
||||
return (
|
||||
req.get('Range') &&
|
||||
typeof filesController.adapter.handleFileStream === 'function'
|
||||
);
|
||||
return req.get('Range') && typeof filesController.adapter.handleFileStream === 'function';
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ var Parse = require('parse/node').Parse,
|
||||
triggers = require('../triggers');
|
||||
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import {
|
||||
promiseEnforceMasterKeyAccess,
|
||||
promiseEnsureIdempotency,
|
||||
} from '../middlewares';
|
||||
import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../middlewares';
|
||||
import { jobStatusHandler } from '../StatusHandler';
|
||||
import _ from 'lodash';
|
||||
import { logger } from '../logger';
|
||||
@@ -121,10 +118,7 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
const theFunction = triggers.getFunction(functionName, applicationId);
|
||||
|
||||
if (!theFunction) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.SCRIPT_FAILED,
|
||||
`Invalid function: "${functionName}"`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`);
|
||||
}
|
||||
let params = Object.assign({}, req.body, req.query);
|
||||
params = parseParams(params);
|
||||
@@ -141,15 +135,12 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
};
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
const userString =
|
||||
req.auth && req.auth.user ? req.auth.user.id : undefined;
|
||||
const userString = req.auth && req.auth.user ? req.auth.user.id : undefined;
|
||||
const cleanInput = logger.truncateLogMessage(JSON.stringify(params));
|
||||
const { success, error, message } = FunctionsRouter.createResponseObject(
|
||||
result => {
|
||||
try {
|
||||
const cleanResult = logger.truncateLogMessage(
|
||||
JSON.stringify(result.response.result)
|
||||
);
|
||||
const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result));
|
||||
logger.info(
|
||||
`Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Result: ${cleanResult}`,
|
||||
{
|
||||
|
||||
@@ -54,14 +54,9 @@ export class GlobalConfigRouter extends PromiseRouter {
|
||||
this.route('GET', '/config', req => {
|
||||
return this.getGlobalConfig(req);
|
||||
});
|
||||
this.route(
|
||||
'PUT',
|
||||
'/config',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.updateGlobalConfig(req);
|
||||
}
|
||||
);
|
||||
this.route('PUT', '/config', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.updateGlobalConfig(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,31 +19,19 @@ export class GraphQLRouter extends PromiseRouter {
|
||||
"read-only masterKey isn't allowed to update the GraphQL config."
|
||||
);
|
||||
}
|
||||
const data = await req.config.parseGraphQLController.updateGraphQLConfig(
|
||||
req.body.params
|
||||
);
|
||||
const data = await req.config.parseGraphQLController.updateGraphQLConfig(req.body.params);
|
||||
return {
|
||||
response: data,
|
||||
};
|
||||
}
|
||||
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'GET',
|
||||
GraphQLConfigPath,
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.getGraphQLConfig(req);
|
||||
}
|
||||
);
|
||||
this.route(
|
||||
'PUT',
|
||||
GraphQLConfigPath,
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.updateGraphQLConfig(req);
|
||||
}
|
||||
);
|
||||
this.route('GET', GraphQLConfigPath, middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.getGraphQLConfig(req);
|
||||
});
|
||||
this.route('PUT', GraphQLConfigPath, middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.updateGraphQLConfig(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,11 @@ import * as middleware from '../middlewares';
|
||||
|
||||
export class HooksRouter extends PromiseRouter {
|
||||
createHook(aHook, config) {
|
||||
return config.hooksController
|
||||
.createHook(aHook)
|
||||
.then(hook => ({ response: hook }));
|
||||
return config.hooksController.createHook(aHook).then(hook => ({ response: hook }));
|
||||
}
|
||||
|
||||
updateHook(aHook, config) {
|
||||
return config.hooksController
|
||||
.updateHook(aHook)
|
||||
.then(hook => ({ response: hook }));
|
||||
return config.hooksController.updateHook(aHook).then(hook => ({ response: hook }));
|
||||
}
|
||||
|
||||
handlePost(req) {
|
||||
@@ -22,17 +18,12 @@ export class HooksRouter extends PromiseRouter {
|
||||
handleGetFunctions(req) {
|
||||
var hooksController = req.config.hooksController;
|
||||
if (req.params.functionName) {
|
||||
return hooksController
|
||||
.getFunction(req.params.functionName)
|
||||
.then(foundFunction => {
|
||||
if (!foundFunction) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`no function named: ${req.params.functionName} is defined`
|
||||
);
|
||||
}
|
||||
return Promise.resolve({ response: foundFunction });
|
||||
});
|
||||
return hooksController.getFunction(req.params.functionName).then(foundFunction => {
|
||||
if (!foundFunction) {
|
||||
throw new Parse.Error(143, `no function named: ${req.params.functionName} is defined`);
|
||||
}
|
||||
return Promise.resolve({ response: foundFunction });
|
||||
});
|
||||
}
|
||||
|
||||
return hooksController.getFunctions().then(
|
||||
@@ -52,26 +43,19 @@ export class HooksRouter extends PromiseRouter {
|
||||
.getTrigger(req.params.className, req.params.triggerName)
|
||||
.then(foundTrigger => {
|
||||
if (!foundTrigger) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`class ${req.params.className} does not exist`
|
||||
);
|
||||
throw new Parse.Error(143, `class ${req.params.className} does not exist`);
|
||||
}
|
||||
return Promise.resolve({ response: foundTrigger });
|
||||
});
|
||||
}
|
||||
|
||||
return hooksController
|
||||
.getTriggers()
|
||||
.then(triggers => ({ response: triggers || [] }));
|
||||
return hooksController.getTriggers().then(triggers => ({ response: triggers || [] }));
|
||||
}
|
||||
|
||||
handleDelete(req) {
|
||||
var hooksController = req.config.hooksController;
|
||||
if (req.params.functionName) {
|
||||
return hooksController
|
||||
.deleteFunction(req.params.functionName)
|
||||
.then(() => ({ response: {} }));
|
||||
return hooksController.deleteFunction(req.params.functionName).then(() => ({ response: {} }));
|
||||
} else if (req.params.className && req.params.triggerName) {
|
||||
return hooksController
|
||||
.deleteTrigger(req.params.className, req.params.triggerName)
|
||||
|
||||
@@ -58,10 +58,7 @@ function getFileForProductIdentifier(productIdentifier, req) {
|
||||
const products = result.results;
|
||||
if (!products || products.length != 1) {
|
||||
// Error not found or too many
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
|
||||
var download = products[0].download;
|
||||
@@ -76,10 +73,7 @@ export class IAPValidationRouter extends PromiseRouter {
|
||||
|
||||
if (!receipt || !productIdentifier) {
|
||||
// TODO: Error, malformed request
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'missing receipt or productIdentifier'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'missing receipt or productIdentifier');
|
||||
}
|
||||
|
||||
// Transform the object if there
|
||||
|
||||
@@ -10,10 +10,7 @@ export class InstallationsRouter extends ClassesRouter {
|
||||
}
|
||||
|
||||
handleFind(req) {
|
||||
const body = Object.assign(
|
||||
req.body,
|
||||
ClassesRouter.JSONFromQuery(req.query)
|
||||
);
|
||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
const options = ClassesRouter.optionsFromBody(body);
|
||||
return rest
|
||||
.find(
|
||||
@@ -40,14 +37,9 @@ export class InstallationsRouter extends ClassesRouter {
|
||||
this.route('POST', '/installations', promiseEnsureIdempotency, req => {
|
||||
return this.handleCreate(req);
|
||||
});
|
||||
this.route(
|
||||
'PUT',
|
||||
'/installations/:objectId',
|
||||
promiseEnsureIdempotency,
|
||||
req => {
|
||||
return this.handleUpdate(req);
|
||||
}
|
||||
);
|
||||
this.route('PUT', '/installations/:objectId', promiseEnsureIdempotency, req => {
|
||||
return this.handleUpdate(req);
|
||||
});
|
||||
this.route('DELETE', '/installations/:objectId', req => {
|
||||
return this.handleDelete(req);
|
||||
});
|
||||
|
||||
@@ -17,10 +17,7 @@ export class LogsRouter extends PromiseRouter {
|
||||
|
||||
validateRequest(req) {
|
||||
if (!req.config || !req.config.loggerController) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Logger adapter is not available'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not available');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ const views = path.resolve(__dirname, '../../views');
|
||||
export class PublicAPIRouter extends PromiseRouter {
|
||||
verifyEmail(req) {
|
||||
const { username, token: rawToken } = req.query;
|
||||
const token =
|
||||
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
const appId = req.params.appId;
|
||||
const config = Config.get(appId);
|
||||
@@ -95,22 +94,15 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
});
|
||||
}
|
||||
// Should we keep the file in memory or leave like that?
|
||||
fs.readFile(
|
||||
path.resolve(views, 'choose_password'),
|
||||
'utf-8',
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
data = data.replace(
|
||||
'PARSE_SERVER_URL',
|
||||
`'${config.publicServerURL}'`
|
||||
);
|
||||
resolve({
|
||||
text: data,
|
||||
});
|
||||
fs.readFile(path.resolve(views, 'choose_password'), 'utf-8', (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
);
|
||||
data = data.replace('PARSE_SERVER_URL', `'${config.publicServerURL}'`);
|
||||
resolve({
|
||||
text: data,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,8 +118,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
const { username, token: rawToken } = req.query;
|
||||
const token =
|
||||
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if (!username || !token) {
|
||||
return this.invalidLink(req);
|
||||
@@ -164,8 +155,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
const { username, new_password, token: rawToken } = req.body;
|
||||
const token =
|
||||
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if ((!username || !token || !new_password) && req.xhr === false) {
|
||||
return this.invalidLink(req);
|
||||
|
||||
@@ -30,14 +30,9 @@ export class PurgeRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'DELETE',
|
||||
'/purge/:className',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
req => {
|
||||
return this.handlePurge(req);
|
||||
}
|
||||
);
|
||||
this.route('DELETE', '/purge/:className', middleware.promiseEnforceMasterKeyAccess, req => {
|
||||
return this.handlePurge(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,7 @@ import { Parse } from 'parse/node';
|
||||
|
||||
export class PushRouter extends PromiseRouter {
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'POST',
|
||||
'/push',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
PushRouter.handlePOST
|
||||
);
|
||||
this.route('POST', '/push', middleware.promiseEnforceMasterKeyAccess, PushRouter.handlePOST);
|
||||
}
|
||||
|
||||
static handlePOST(req) {
|
||||
@@ -21,10 +16,7 @@ export class PushRouter extends PromiseRouter {
|
||||
}
|
||||
const pushController = req.config.pushController;
|
||||
if (!pushController) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Push controller is not set'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Push controller is not set');
|
||||
}
|
||||
|
||||
const where = PushRouter.getQueryCondition(req);
|
||||
|
||||
@@ -28,15 +28,9 @@ function getOneSchema(req) {
|
||||
.then(schema => ({ response: schema }))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_CLASS_NAME,
|
||||
`Class ${className} does not exist.`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INTERNAL_SERVER_ERROR,
|
||||
'Database adapter error.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -50,10 +44,7 @@ function createSchema(req) {
|
||||
}
|
||||
if (req.params.className && req.body.className) {
|
||||
if (req.params.className != req.body.className) {
|
||||
return classNameMismatchResponse(
|
||||
req.body.className,
|
||||
req.params.className
|
||||
);
|
||||
return classNameMismatchResponse(req.body.className, req.params.className);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,31 +107,19 @@ const deleteSchema = req => {
|
||||
SchemaController.invalidClassNameMessage(req.params.className)
|
||||
);
|
||||
}
|
||||
return req.config.database
|
||||
.deleteSchema(req.params.className)
|
||||
.then(() => ({ response: {} }));
|
||||
return req.config.database.deleteSchema(req.params.className).then(() => ({ response: {} }));
|
||||
};
|
||||
|
||||
export class SchemasRouter extends PromiseRouter {
|
||||
mountRoutes() {
|
||||
this.route(
|
||||
'GET',
|
||||
'/schemas',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
getAllSchemas
|
||||
);
|
||||
this.route('GET', '/schemas', middleware.promiseEnforceMasterKeyAccess, getAllSchemas);
|
||||
this.route(
|
||||
'GET',
|
||||
'/schemas/:className',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
getOneSchema
|
||||
);
|
||||
this.route(
|
||||
'POST',
|
||||
'/schemas',
|
||||
middleware.promiseEnforceMasterKeyAccess,
|
||||
createSchema
|
||||
);
|
||||
this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema);
|
||||
this.route(
|
||||
'POST',
|
||||
'/schemas/:className',
|
||||
|
||||
@@ -11,10 +11,7 @@ export class SessionsRouter extends ClassesRouter {
|
||||
handleMe(req) {
|
||||
// TODO: Verify correct behavior
|
||||
if (!req.info || !req.info.sessionToken) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token required.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.');
|
||||
}
|
||||
return rest
|
||||
.find(
|
||||
@@ -28,10 +25,7 @@ export class SessionsRouter extends ClassesRouter {
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token not found.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.');
|
||||
}
|
||||
return {
|
||||
response: response.results[0],
|
||||
|
||||
@@ -50,26 +50,17 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
// TODO: use the right error codes / descriptions.
|
||||
if (!username && !email) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.USERNAME_MISSING,
|
||||
'username/email is required.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'username/email is required.');
|
||||
}
|
||||
if (!password) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PASSWORD_MISSING,
|
||||
'password is required.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.');
|
||||
}
|
||||
if (
|
||||
typeof password !== 'string' ||
|
||||
(email && typeof email !== 'string') ||
|
||||
(username && typeof username !== 'string')
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Invalid username/password.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
|
||||
}
|
||||
|
||||
let user;
|
||||
@@ -86,10 +77,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
.find('_User', query)
|
||||
.then(results => {
|
||||
if (!results.length) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Invalid username/password.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
|
||||
}
|
||||
|
||||
if (results.length > 1) {
|
||||
@@ -111,34 +99,21 @@ export class UsersRouter extends ClassesRouter {
|
||||
})
|
||||
.then(() => {
|
||||
if (!isValidPassword) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Invalid username/password.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
|
||||
}
|
||||
// Ensure the user isn't locked out
|
||||
// A locked out user won't be able to login
|
||||
// To lock a user out, just set the ACL to `masterKey` only ({}).
|
||||
// Empty ACL is OK
|
||||
if (
|
||||
!req.auth.isMaster &&
|
||||
user.ACL &&
|
||||
Object.keys(user.ACL).length == 0
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Invalid username/password.'
|
||||
);
|
||||
if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
|
||||
}
|
||||
if (
|
||||
req.config.verifyUserEmails &&
|
||||
req.config.preventLoginWithUnverifiedEmail &&
|
||||
!user.emailVerified
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.EMAIL_NOT_FOUND,
|
||||
'User email is not verified.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
|
||||
}
|
||||
|
||||
delete user.password;
|
||||
@@ -166,10 +141,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
handleMe(req) {
|
||||
if (!req.info || !req.info.sessionToken) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Invalid session token'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const sessionToken = req.info.sessionToken;
|
||||
return rest
|
||||
@@ -183,15 +155,8 @@ export class UsersRouter extends ClassesRouter {
|
||||
req.info.context
|
||||
)
|
||||
.then(response => {
|
||||
if (
|
||||
!response.results ||
|
||||
response.results.length == 0 ||
|
||||
!response.results[0].user
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Invalid session token'
|
||||
);
|
||||
if (!response.results || response.results.length == 0 || !response.results[0].user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
} else {
|
||||
const user = response.results[0].user;
|
||||
// Send token back on the login, because SDKs expect that.
|
||||
@@ -228,8 +193,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
}
|
||||
// Calculate the expiry time.
|
||||
const expiresAt = new Date(
|
||||
changedAt.getTime() +
|
||||
86400000 * req.config.passwordPolicy.maxPasswordAge
|
||||
changedAt.getTime() + 86400000 * req.config.passwordPolicy.maxPasswordAge
|
||||
);
|
||||
if (expiresAt < new Date())
|
||||
// fail of current time is past password expiry time
|
||||
@@ -267,9 +231,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
await createSession();
|
||||
|
||||
const afterLoginUser = Parse.User.fromJSON(
|
||||
Object.assign({ className: '_User' }, user)
|
||||
);
|
||||
const afterLoginUser = Parse.User.fromJSON(Object.assign({ className: '_User' }, user));
|
||||
maybeRunTrigger(
|
||||
TriggerTypes.afterLogin,
|
||||
{ ...req.auth, user: afterLoginUser },
|
||||
@@ -345,8 +307,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
emailAdapter: req.config.userController.adapter,
|
||||
appName: req.config.appName,
|
||||
publicServerURL: req.config.publicServerURL,
|
||||
emailVerifyTokenValidityDuration:
|
||||
req.config.emailVerifyTokenValidityDuration,
|
||||
emailVerifyTokenValidityDuration: req.config.emailVerifyTokenValidityDuration,
|
||||
});
|
||||
} catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
@@ -366,10 +327,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.EMAIL_MISSING,
|
||||
'you must provide an email'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email');
|
||||
}
|
||||
if (typeof email !== 'string') {
|
||||
throw new Parse.Error(
|
||||
@@ -403,10 +361,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.EMAIL_MISSING,
|
||||
'you must provide an email'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email');
|
||||
}
|
||||
if (typeof email !== 'string') {
|
||||
throw new Parse.Error(
|
||||
@@ -417,10 +372,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
return req.config.database.find('_User', { email: email }).then(results => {
|
||||
if (!results.length || results.length < 1) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.EMAIL_NOT_FOUND,
|
||||
`No user found with email ${email}`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`);
|
||||
}
|
||||
const user = results[0];
|
||||
|
||||
@@ -428,10 +380,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
delete user.password;
|
||||
|
||||
if (user.emailVerified) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OTHER_CAUSE,
|
||||
`Email ${email} is already verified.`
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OTHER_CAUSE, `Email ${email} is already verified.`);
|
||||
}
|
||||
|
||||
const userController = req.config.userController;
|
||||
|
||||
@@ -57,12 +57,10 @@ function restStatusHandler(className, config) {
|
||||
const auth = Auth.master(config);
|
||||
function create(object) {
|
||||
lastPromise = lastPromise.then(() => {
|
||||
return rest
|
||||
.create(config, auth, className, object)
|
||||
.then(({ response }) => {
|
||||
// merge the objects
|
||||
return Promise.resolve(Object.assign({}, object, response));
|
||||
});
|
||||
return rest.create(config, auth, className, object).then(({ response }) => {
|
||||
// merge the objects
|
||||
return Promise.resolve(Object.assign({}, object, response));
|
||||
});
|
||||
});
|
||||
return lastPromise;
|
||||
}
|
||||
@@ -156,9 +154,7 @@ export function pushStatusHandler(config, existingObjectId) {
|
||||
pushTime = body.push_time;
|
||||
status = 'scheduled';
|
||||
} else {
|
||||
logger.warn(
|
||||
'Trying to schedule a push while server is not configured.'
|
||||
);
|
||||
logger.warn('Trying to schedule a push while server is not configured.');
|
||||
logger.warn('Push will be sent immediately');
|
||||
}
|
||||
}
|
||||
@@ -216,8 +212,7 @@ export function pushStatusHandler(config, existingObjectId) {
|
||||
const trackSent = function (
|
||||
results,
|
||||
UTCOffset,
|
||||
cleanupInstallations = process.env
|
||||
.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS
|
||||
cleanupInstallations = process.env.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS
|
||||
) {
|
||||
const update = {
|
||||
numSent: 0,
|
||||
@@ -289,9 +284,7 @@ export function pushStatusHandler(config, existingObjectId) {
|
||||
});
|
||||
|
||||
if (devicesToRemove.length > 0 && cleanupInstallations) {
|
||||
logger.info(
|
||||
`Removing device tokens on ${devicesToRemove.length} _Installations`
|
||||
);
|
||||
logger.info(`Removing device tokens on ${devicesToRemove.length} _Installations`);
|
||||
database.update(
|
||||
'_Installation',
|
||||
{ deviceToken: { $in: devicesToRemove } },
|
||||
|
||||
35
src/batch.js
35
src/batch.js
@@ -28,10 +28,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
const makeRoutablePath = function (requestPath) {
|
||||
// The routablePath is the path minus the api prefix
|
||||
if (requestPath.slice(0, apiPrefix.length) != apiPrefix) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'cannot route batch path ' + requestPath
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route batch path ' + requestPath);
|
||||
}
|
||||
return path.posix.join('/', requestPath.slice(apiPrefix.length));
|
||||
};
|
||||
@@ -44,12 +41,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
return function (requestPath) {
|
||||
// Build the new path by removing the public path
|
||||
// and joining with the local path
|
||||
const newPath = path.posix.join(
|
||||
'/',
|
||||
localPath,
|
||||
'/',
|
||||
requestPath.slice(publicPath.length)
|
||||
);
|
||||
const newPath = path.posix.join('/', localPath, '/', requestPath.slice(publicPath.length));
|
||||
// Use the method for local routing
|
||||
return makeRoutablePath(newPath);
|
||||
};
|
||||
@@ -62,10 +54,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
// TODO: pass along auth correctly
|
||||
function handleBatch(router, req) {
|
||||
if (!Array.isArray(req.body.requests)) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'requests must be an array'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array');
|
||||
}
|
||||
|
||||
// The batch paths are all from the root of our domain.
|
||||
@@ -100,16 +89,14 @@ function handleBatch(router, req) {
|
||||
info: req.info,
|
||||
};
|
||||
|
||||
return router
|
||||
.tryRouteRequest(restRequest.method, routablePath, request)
|
||||
.then(
|
||||
response => {
|
||||
return { success: response.response };
|
||||
},
|
||||
error => {
|
||||
return { error: { code: error.code, error: error.message } };
|
||||
}
|
||||
);
|
||||
return router.tryRouteRequest(restRequest.method, routablePath, request).then(
|
||||
response => {
|
||||
return { success: response.response };
|
||||
},
|
||||
error => {
|
||||
return { error: { code: error.code, error: error.message } };
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(results => {
|
||||
|
||||
@@ -15,23 +15,15 @@ const help = function () {
|
||||
console.log(' Usage with npm start');
|
||||
console.log('');
|
||||
console.log(' $ npm start -- path/to/config.json');
|
||||
console.log(
|
||||
' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'
|
||||
);
|
||||
console.log(
|
||||
' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'
|
||||
);
|
||||
console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
|
||||
console.log(' $ npm start -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
|
||||
console.log('');
|
||||
console.log('');
|
||||
console.log(' Usage:');
|
||||
console.log('');
|
||||
console.log(' $ parse-server path/to/config.json');
|
||||
console.log(
|
||||
' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'
|
||||
);
|
||||
console.log(
|
||||
' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'
|
||||
);
|
||||
console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
|
||||
console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL');
|
||||
console.log('');
|
||||
};
|
||||
|
||||
@@ -43,9 +35,7 @@ runner({
|
||||
if (!options.appId || !options.masterKey) {
|
||||
program.outputHelp();
|
||||
console.error('');
|
||||
console.error(
|
||||
'\u001b[31mERROR: appId and masterKey are required\u001b[0m'
|
||||
);
|
||||
console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m');
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -67,19 +57,14 @@ runner({
|
||||
}
|
||||
|
||||
if (options.cluster) {
|
||||
const numCPUs =
|
||||
typeof options.cluster === 'number'
|
||||
? options.cluster
|
||||
: os.cpus().length;
|
||||
const numCPUs = typeof options.cluster === 'number' ? options.cluster : os.cpus().length;
|
||||
if (cluster.isMaster) {
|
||||
logOptions();
|
||||
for (let i = 0; i < numCPUs; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
cluster.on('exit', (worker, code) => {
|
||||
console.log(
|
||||
`worker ${worker.process.pid} died (${code})... Restarting`
|
||||
);
|
||||
console.log(`worker ${worker.process.pid} died (${code})... Restarting`);
|
||||
cluster.fork();
|
||||
});
|
||||
} else {
|
||||
@@ -96,9 +81,7 @@ runner({
|
||||
}
|
||||
|
||||
function printSuccessMessage() {
|
||||
console.log(
|
||||
'[' + process.pid + '] parse-server running on ' + options.serverURL
|
||||
);
|
||||
console.log('[' + process.pid + '] parse-server running on ' + options.serverURL);
|
||||
if (options.mountGraphQL) {
|
||||
console.log(
|
||||
'[' +
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Parse } from 'parse/node';
|
||||
import * as triggers from '../triggers';
|
||||
|
||||
function isParseObjectConstructor(object) {
|
||||
return (
|
||||
typeof object === 'function' &&
|
||||
Object.prototype.hasOwnProperty.call(object, 'className')
|
||||
);
|
||||
return typeof object === 'function' && Object.prototype.hasOwnProperty.call(object, 'className');
|
||||
}
|
||||
|
||||
function getClassName(parseClass) {
|
||||
@@ -52,12 +49,7 @@ var ParseCloud = {};
|
||||
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.define = function (functionName, handler, validationHandler) {
|
||||
triggers.addFunction(
|
||||
functionName,
|
||||
handler,
|
||||
validationHandler,
|
||||
Parse.applicationId
|
||||
);
|
||||
triggers.addFunction(functionName, handler, validationHandler, Parse.applicationId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -178,12 +170,7 @@ ParseCloud.beforeLogin = function (handler) {
|
||||
className = getClassName(handler);
|
||||
handler = arguments[1];
|
||||
}
|
||||
triggers.addTrigger(
|
||||
triggers.Types.beforeLogin,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
);
|
||||
triggers.addTrigger(triggers.Types.beforeLogin, className, handler, Parse.applicationId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -213,12 +200,7 @@ ParseCloud.afterLogin = function (handler) {
|
||||
className = getClassName(handler);
|
||||
handler = arguments[1];
|
||||
}
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterLogin,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
);
|
||||
triggers.addTrigger(triggers.Types.afterLogin, className, handler, Parse.applicationId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -247,12 +229,7 @@ ParseCloud.afterLogout = function (handler) {
|
||||
className = getClassName(handler);
|
||||
handler = arguments[1];
|
||||
}
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterLogout,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
);
|
||||
triggers.addTrigger(triggers.Types.afterLogout, className, handler, Parse.applicationId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -613,11 +590,7 @@ ParseCloud.onLiveQueryEvent = function (handler) {
|
||||
* @param {Function} func The function to run after a live query event. This function can be async and should take one parameter, a {@link Parse.Cloud.LiveQueryEventTrigger}.
|
||||
* @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.LiveQueryEventTrigger}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterLiveQueryEvent = function (
|
||||
parseClass,
|
||||
handler,
|
||||
validationHandler
|
||||
) {
|
||||
ParseCloud.afterLiveQueryEvent = function (parseClass, handler, validationHandler) {
|
||||
const className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterEvent,
|
||||
|
||||
@@ -47,18 +47,13 @@ const encodeBody = function ({ body, headers = {} }) {
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
if (contentTypeKeys.length > 1) {
|
||||
log.error(
|
||||
'Parse.Cloud.httpRequest',
|
||||
'multiple content-type headers are set.'
|
||||
);
|
||||
log.error('Parse.Cloud.httpRequest', 'multiple content-type headers are set.');
|
||||
}
|
||||
// There maybe many, we'll just take the 1st one
|
||||
var contentType = contentTypeKeys[0];
|
||||
if (headers[contentType].match(/application\/json/i)) {
|
||||
body = JSON.stringify(body);
|
||||
} else if (
|
||||
headers[contentType].match(/application\/x-www-form-urlencoded/i)
|
||||
) {
|
||||
} else if (headers[contentType].match(/application\/x-www-form-urlencoded/i)) {
|
||||
body = querystring.stringify(body);
|
||||
}
|
||||
}
|
||||
@@ -137,10 +132,7 @@ module.exports = function httpRequest(options) {
|
||||
requestOptions.agent = options.agent;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.request(
|
||||
requestOptions,
|
||||
makeCallback(resolve, reject, options)
|
||||
);
|
||||
const req = client.request(requestOptions, makeCallback(resolve, reject, options));
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ export function randomString(size: number): string {
|
||||
if (size === 0) {
|
||||
throw new Error('Zero-length randomString is useless.');
|
||||
}
|
||||
const chars =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789';
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789';
|
||||
let objectId = '';
|
||||
const bytes = randomBytes(size);
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
@@ -44,7 +43,5 @@ export function newToken(): string {
|
||||
}
|
||||
|
||||
export function md5Hash(string: string): string {
|
||||
return createHash('md5')
|
||||
.update(string)
|
||||
.digest('hex');
|
||||
return createHash('md5').update(string).digest('hex');
|
||||
}
|
||||
|
||||
@@ -16,16 +16,13 @@ const { verbose, level } = (() => {
|
||||
return { verbose, level: verbose ? 'verbose' : undefined };
|
||||
})();
|
||||
|
||||
const DefinitionDefaults = Object.keys(ParseServerOptions).reduce(
|
||||
(memo, key) => {
|
||||
const def = ParseServerOptions[key];
|
||||
if (Object.prototype.hasOwnProperty.call(def, 'default')) {
|
||||
memo[key] = def.default;
|
||||
}
|
||||
return memo;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const DefinitionDefaults = Object.keys(ParseServerOptions).reduce((memo, key) => {
|
||||
const def = ParseServerOptions[key];
|
||||
if (Object.prototype.hasOwnProperty.call(def, 'default')) {
|
||||
memo[key] = def.default;
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
const computedDefaults = {
|
||||
jsonLogs: process.env.JSON_LOGS || false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export function useExternal(name, moduleName) {
|
||||
return function() {
|
||||
return function () {
|
||||
throw `${name} is not provided by parse-server anymore; please install ${moduleName}`;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ParseServerOptions } from './Options';
|
||||
import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';
|
||||
|
||||
// Factory function
|
||||
const _ParseServer = function(options: ParseServerOptions) {
|
||||
const _ParseServer = function (options: ParseServerOptions) {
|
||||
const server = new ParseServer(options);
|
||||
return server.app;
|
||||
};
|
||||
|
||||
@@ -81,8 +81,7 @@ export function handleParseHeaders(req, res, next) {
|
||||
req.body &&
|
||||
req.body._ApplicationId &&
|
||||
AppCache.get(req.body._ApplicationId) &&
|
||||
(!info.masterKey ||
|
||||
AppCache.get(req.body._ApplicationId).masterKey === info.masterKey)
|
||||
(!info.masterKey || AppCache.get(req.body._ApplicationId).masterKey === info.masterKey)
|
||||
) {
|
||||
info.appId = req.body._ApplicationId;
|
||||
info.javascriptKey = req.body._JavaScriptKey || '';
|
||||
@@ -251,10 +250,7 @@ export function handleParseHeaders(req, res, next) {
|
||||
return;
|
||||
} else {
|
||||
// TODO: Determine the correct error scenario.
|
||||
req.config.loggerController.error(
|
||||
'error getting auth for sessionToken',
|
||||
error
|
||||
);
|
||||
req.config.loggerController.error('error getting auth for sessionToken', error);
|
||||
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
|
||||
}
|
||||
});
|
||||
@@ -327,10 +323,7 @@ export function allowCrossDomain(appId) {
|
||||
res.header('Access-Control-Allow-Origin', allowOrigin);
|
||||
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', allowHeaders);
|
||||
res.header(
|
||||
'Access-Control-Expose-Headers',
|
||||
'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id'
|
||||
);
|
||||
res.header('Access-Control-Expose-Headers', 'X-Parse-Job-Status-Id, X-Parse-Push-Status-Id');
|
||||
// intercept OPTIONS method
|
||||
if ('OPTIONS' == req.method) {
|
||||
res.sendStatus(200);
|
||||
@@ -443,9 +436,7 @@ export function promiseEnsureIdempotency(req) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Try to store request
|
||||
const expiryDate = new Date(
|
||||
new Date().setSeconds(new Date().getSeconds() + ttl)
|
||||
);
|
||||
const expiryDate = new Date(new Date().setSeconds(new Date().getSeconds() + ttl));
|
||||
return rest
|
||||
.create(config, auth.master(config), '_Idempotency', {
|
||||
reqId: requestId,
|
||||
@@ -453,10 +444,7 @@ export function promiseEnsureIdempotency(req) {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e.code == Parse.Error.DUPLICATE_VALUE) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.DUPLICATE_REQUEST,
|
||||
'Duplicate request'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_REQUEST, 'Duplicate request');
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
||||
113
src/rest.js
113
src/rest.js
@@ -15,31 +15,16 @@ var triggers = require('./triggers');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some(triggerType => {
|
||||
return triggers.getTrigger(
|
||||
className,
|
||||
triggers.Types[triggerType],
|
||||
config.applicationId
|
||||
);
|
||||
return triggers.getTrigger(className, triggers.Types[triggerType], config.applicationId);
|
||||
});
|
||||
}
|
||||
|
||||
function checkLiveQuery(className, config) {
|
||||
return (
|
||||
config.liveQueryController &&
|
||||
config.liveQueryController.hasLiveQuery(className)
|
||||
);
|
||||
return config.liveQueryController && config.liveQueryController.hasLiveQuery(className);
|
||||
}
|
||||
|
||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||
function find(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context
|
||||
) {
|
||||
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
|
||||
enforceRoleSecurity('find', className, auth);
|
||||
return triggers
|
||||
.maybeRunQueryTrigger(
|
||||
@@ -54,28 +39,13 @@ function find(
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK
|
||||
);
|
||||
const query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||
return query.execute();
|
||||
});
|
||||
}
|
||||
|
||||
// get is just like find but only queries an objectId.
|
||||
const get = (
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
objectId,
|
||||
restOptions,
|
||||
clientSDK,
|
||||
context
|
||||
) => {
|
||||
const get = (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
||||
var restWhere = { objectId };
|
||||
enforceRoleSecurity('get', className, auth);
|
||||
return triggers
|
||||
@@ -92,14 +62,7 @@ const get = (
|
||||
.then(result => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
const query = new RestQuery(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restOptions,
|
||||
clientSDK
|
||||
);
|
||||
const query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||
return query.execute();
|
||||
});
|
||||
};
|
||||
@@ -111,10 +74,7 @@ function del(config, auth, className, objectId, context) {
|
||||
}
|
||||
|
||||
if (className === '_User' && auth.isUnauthenticated()) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.SESSION_MISSING,
|
||||
'Insufficient auth to delete user'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth to delete user');
|
||||
}
|
||||
|
||||
enforceRoleSecurity('delete', className, auth);
|
||||
@@ -124,10 +84,7 @@ function del(config, auth, className, objectId, context) {
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const hasTriggers = checkTriggers(className, config, [
|
||||
'beforeDelete',
|
||||
'afterDelete',
|
||||
]);
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||
return new RestQuery(config, auth, className, { objectId })
|
||||
@@ -138,10 +95,7 @@ function del(config, auth, className, objectId, context) {
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Invalid session token'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
@@ -156,10 +110,7 @@ function del(config, auth, className, objectId, context) {
|
||||
context
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for delete.'
|
||||
);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.');
|
||||
});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
@@ -195,12 +146,7 @@ function del(config, auth, className, objectId, context) {
|
||||
.then(() => {
|
||||
// Notify LiveQuery server if possible
|
||||
const perms = schemaController.getClassLevelPermissions(className);
|
||||
config.liveQueryController.onAfterDelete(
|
||||
className,
|
||||
inflatedObject,
|
||||
null,
|
||||
perms
|
||||
);
|
||||
config.liveQueryController.onAfterDelete(className, inflatedObject, null, perms);
|
||||
return triggers.maybeRunTrigger(
|
||||
triggers.Types.afterDelete,
|
||||
auth,
|
||||
@@ -218,39 +164,19 @@ function del(config, auth, className, objectId, context) {
|
||||
// Returns a promise for a {response, status, location} object.
|
||||
function create(config, auth, className, restObject, clientSDK, context) {
|
||||
enforceRoleSecurity('create', className, auth);
|
||||
var write = new RestWrite(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
null,
|
||||
restObject,
|
||||
null,
|
||||
clientSDK,
|
||||
context
|
||||
);
|
||||
var write = new RestWrite(config, auth, className, null, restObject, null, clientSDK, context);
|
||||
return write.execute();
|
||||
}
|
||||
|
||||
// Returns a promise that contains the fields of the update that the
|
||||
// REST API is supposed to return.
|
||||
// Usually, this is just updatedAt.
|
||||
function update(
|
||||
config,
|
||||
auth,
|
||||
className,
|
||||
restWhere,
|
||||
restObject,
|
||||
clientSDK,
|
||||
context
|
||||
) {
|
||||
function update(config, auth, className, restWhere, restObject, clientSDK, context) {
|
||||
enforceRoleSecurity('update', className, auth);
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const hasTriggers = checkTriggers(className, config, [
|
||||
'beforeSave',
|
||||
'afterSave',
|
||||
]);
|
||||
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery) {
|
||||
// Do not use find, as it runs the before finds
|
||||
@@ -292,11 +218,7 @@ function update(
|
||||
|
||||
function handleSessionMissingError(error, className, auth) {
|
||||
// If we're trying to update a user without / with bad session token
|
||||
if (
|
||||
className === '_User' &&
|
||||
error.code === Parse.Error.OBJECT_NOT_FOUND &&
|
||||
!auth.isMaster
|
||||
) {
|
||||
if (className === '_User' && error.code === Parse.Error.OBJECT_NOT_FOUND && !auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
|
||||
}
|
||||
throw error;
|
||||
@@ -326,10 +248,7 @@ function enforceRoleSecurity(method, className, auth) {
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (
|
||||
auth.isReadOnly &&
|
||||
(method === 'delete' || method === 'create' || method === 'update')
|
||||
) {
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
|
||||
178
src/triggers.js
178
src/triggers.js
@@ -53,10 +53,7 @@ function validateClassNameForTriggers(className, type) {
|
||||
// TODO: Allow proper documented way of using nested increment ops
|
||||
throw 'Only afterSave is allowed on _PushStatus';
|
||||
}
|
||||
if (
|
||||
(type === Types.beforeLogin || type === Types.afterLogin) &&
|
||||
className !== '_User'
|
||||
) {
|
||||
if ((type === Types.beforeLogin || type === Types.afterLogin) && className !== '_User') {
|
||||
// TODO: check if upstream code will handle `Error` instance rather
|
||||
// than this anti-pattern of throwing strings
|
||||
throw 'Only the _User class is allowed for the beforeLogin and afterLogin triggers';
|
||||
@@ -121,12 +118,7 @@ function get(category, name, applicationId) {
|
||||
return store[lastComponent];
|
||||
}
|
||||
|
||||
export function addFunction(
|
||||
functionName,
|
||||
handler,
|
||||
validationHandler,
|
||||
applicationId
|
||||
) {
|
||||
export function addFunction(functionName, handler, validationHandler, applicationId) {
|
||||
add(Category.Functions, functionName, handler, applicationId);
|
||||
add(Category.Validators, functionName, validationHandler, applicationId);
|
||||
}
|
||||
@@ -135,51 +127,20 @@ export function addJob(jobName, handler, applicationId) {
|
||||
add(Category.Jobs, jobName, handler, applicationId);
|
||||
}
|
||||
|
||||
export function addTrigger(
|
||||
type,
|
||||
className,
|
||||
handler,
|
||||
applicationId,
|
||||
validationHandler
|
||||
) {
|
||||
export function addTrigger(type, className, handler, applicationId, validationHandler) {
|
||||
validateClassNameForTriggers(className, type);
|
||||
add(Category.Triggers, `${type}.${className}`, handler, applicationId);
|
||||
add(
|
||||
Category.Validators,
|
||||
`${type}.${className}`,
|
||||
validationHandler,
|
||||
applicationId
|
||||
);
|
||||
add(Category.Validators, `${type}.${className}`, validationHandler, applicationId);
|
||||
}
|
||||
|
||||
export function addFileTrigger(
|
||||
type,
|
||||
handler,
|
||||
applicationId,
|
||||
validationHandler
|
||||
) {
|
||||
export function addFileTrigger(type, handler, applicationId, validationHandler) {
|
||||
add(Category.Triggers, `${type}.${FileClassName}`, handler, applicationId);
|
||||
add(
|
||||
Category.Validators,
|
||||
`${type}.${FileClassName}`,
|
||||
validationHandler,
|
||||
applicationId
|
||||
);
|
||||
add(Category.Validators, `${type}.${FileClassName}`, validationHandler, applicationId);
|
||||
}
|
||||
|
||||
export function addConnectTrigger(
|
||||
type,
|
||||
handler,
|
||||
applicationId,
|
||||
validationHandler
|
||||
) {
|
||||
export function addConnectTrigger(type, handler, applicationId, validationHandler) {
|
||||
add(Category.Triggers, `${type}.${ConnectClassName}`, handler, applicationId);
|
||||
add(
|
||||
Category.Validators,
|
||||
`${type}.${ConnectClassName}`,
|
||||
validationHandler,
|
||||
applicationId
|
||||
);
|
||||
add(Category.Validators, `${type}.${ConnectClassName}`, validationHandler, applicationId);
|
||||
}
|
||||
|
||||
export function addLiveQueryEventHandler(handler, applicationId) {
|
||||
@@ -211,11 +172,7 @@ export function getFileTrigger(type, applicationId) {
|
||||
return getTrigger(FileClassName, type, applicationId);
|
||||
}
|
||||
|
||||
export function triggerExists(
|
||||
className: string,
|
||||
type: string,
|
||||
applicationId: string
|
||||
): boolean {
|
||||
export function triggerExists(className: string, type: string, applicationId: string): boolean {
|
||||
return getTrigger(className, type, applicationId) != undefined;
|
||||
}
|
||||
|
||||
@@ -225,9 +182,7 @@ export function getFunction(functionName, applicationId) {
|
||||
|
||||
export function getFunctionNames(applicationId) {
|
||||
const store =
|
||||
(_triggerStore[applicationId] &&
|
||||
_triggerStore[applicationId][Category.Functions]) ||
|
||||
{};
|
||||
(_triggerStore[applicationId] && _triggerStore[applicationId][Category.Functions]) || {};
|
||||
const functionNames = [];
|
||||
const extractFunctionNames = (namespace, store) => {
|
||||
Object.keys(store).forEach(name => {
|
||||
@@ -308,15 +263,7 @@ export function getRequestObject(
|
||||
return request;
|
||||
}
|
||||
|
||||
export function getRequestQueryObject(
|
||||
triggerType,
|
||||
auth,
|
||||
query,
|
||||
count,
|
||||
config,
|
||||
context,
|
||||
isGet
|
||||
) {
|
||||
export function getRequestQueryObject(triggerType, auth, query, count, config, context, isGet) {
|
||||
isGet = !!isGet;
|
||||
|
||||
var request = {
|
||||
@@ -371,11 +318,7 @@ export function getResponseObject(request, resolve, reject) {
|
||||
) {
|
||||
return resolve(response);
|
||||
}
|
||||
if (
|
||||
response &&
|
||||
typeof response === 'object' &&
|
||||
request.triggerName === Types.afterSave
|
||||
) {
|
||||
if (response && typeof response === 'object' && request.triggerName === Types.afterSave) {
|
||||
return resolve(response);
|
||||
}
|
||||
if (request.triggerName === Types.afterSave) {
|
||||
@@ -417,13 +360,7 @@ function logTriggerAfterHook(triggerType, className, input, auth) {
|
||||
);
|
||||
}
|
||||
|
||||
function logTriggerSuccessBeforeHook(
|
||||
triggerType,
|
||||
className,
|
||||
input,
|
||||
result,
|
||||
auth
|
||||
) {
|
||||
function logTriggerSuccessBeforeHook(triggerType, className, input, result, auth) {
|
||||
const cleanInput = logger.truncateLogMessage(JSON.stringify(input));
|
||||
const cleanResult = logger.truncateLogMessage(JSON.stringify(result));
|
||||
logger.info(
|
||||
@@ -453,14 +390,7 @@ function logTriggerErrorBeforeHook(triggerType, className, input, auth, error) {
|
||||
);
|
||||
}
|
||||
|
||||
export function maybeRunAfterFindTrigger(
|
||||
triggerType,
|
||||
auth,
|
||||
className,
|
||||
objects,
|
||||
config,
|
||||
query
|
||||
) {
|
||||
export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, config, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const trigger = getTrigger(className, triggerType, config.applicationId);
|
||||
if (!trigger) {
|
||||
@@ -479,13 +409,7 @@ export function maybeRunAfterFindTrigger(
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
logTriggerSuccessBeforeHook(
|
||||
triggerType,
|
||||
className,
|
||||
'AfterFind',
|
||||
JSON.stringify(objects),
|
||||
auth
|
||||
);
|
||||
logTriggerSuccessBeforeHook(triggerType, className, 'AfterFind', JSON.stringify(objects), auth);
|
||||
request.objects = objects.map(object => {
|
||||
//setting the class name to transform into parse object
|
||||
object.className = className;
|
||||
@@ -608,13 +532,11 @@ export function maybeRunQueryTrigger(
|
||||
}
|
||||
if (requestObject.includeReadPreference) {
|
||||
restOptions = restOptions || {};
|
||||
restOptions.includeReadPreference =
|
||||
requestObject.includeReadPreference;
|
||||
restOptions.includeReadPreference = requestObject.includeReadPreference;
|
||||
}
|
||||
if (requestObject.subqueryReadPreference) {
|
||||
restOptions = restOptions || {};
|
||||
restOptions.subqueryReadPreference =
|
||||
requestObject.subqueryReadPreference;
|
||||
restOptions.subqueryReadPreference = requestObject.subqueryReadPreference;
|
||||
}
|
||||
return {
|
||||
restWhere,
|
||||
@@ -733,10 +655,7 @@ function builtInTriggerValidator(options, request) {
|
||||
|
||||
if (!opts.includes(val)) {
|
||||
throw (
|
||||
opt.error ||
|
||||
`Validation failed. Invalid option for ${key}. Expected: ${opts.join(
|
||||
', '
|
||||
)}`
|
||||
opt.error || `Validation failed. Invalid option for ${key}. Expected: ${opts.join(', ')}`
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -826,11 +745,7 @@ export function maybeRunTrigger(
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var trigger = getTrigger(
|
||||
parseObject.className,
|
||||
triggerType,
|
||||
config.applicationId
|
||||
);
|
||||
var trigger = getTrigger(parseObject.className, triggerType, config.applicationId);
|
||||
if (!trigger) return resolve();
|
||||
var request = getRequestObject(
|
||||
triggerType,
|
||||
@@ -879,10 +794,7 @@ export function maybeRunTrigger(
|
||||
// to the RestWrite.execute() call.
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return maybeRunValidator(
|
||||
request,
|
||||
`${triggerType}.${parseObject.className}`
|
||||
);
|
||||
return maybeRunValidator(request, `${triggerType}.${parseObject.className}`);
|
||||
})
|
||||
.then(() => {
|
||||
const promise = trigger(request);
|
||||
@@ -891,12 +803,7 @@ export function maybeRunTrigger(
|
||||
triggerType === Types.afterDelete ||
|
||||
triggerType === Types.afterLogin
|
||||
) {
|
||||
logTriggerAfterHook(
|
||||
triggerType,
|
||||
parseObject.className,
|
||||
parseObject.toJSON(),
|
||||
auth
|
||||
);
|
||||
logTriggerAfterHook(triggerType, parseObject.className, parseObject.toJSON(), auth);
|
||||
}
|
||||
// beforeSave is expected to return null (nothing)
|
||||
if (triggerType === Types.beforeSave) {
|
||||
@@ -928,15 +835,8 @@ export function inflate(data, restObject) {
|
||||
return Parse.Object.fromJSON(copy);
|
||||
}
|
||||
|
||||
export function runLiveQueryEventHandlers(
|
||||
data,
|
||||
applicationId = Parse.applicationId
|
||||
) {
|
||||
if (
|
||||
!_triggerStore ||
|
||||
!_triggerStore[applicationId] ||
|
||||
!_triggerStore[applicationId].LiveQuery
|
||||
) {
|
||||
export function runLiveQueryEventHandlers(data, applicationId = Parse.applicationId) {
|
||||
if (!_triggerStore || !_triggerStore[applicationId] || !_triggerStore[applicationId].LiveQuery) {
|
||||
return;
|
||||
}
|
||||
_triggerStore[applicationId].LiveQuery.forEach(handler => handler(data));
|
||||
@@ -967,21 +867,11 @@ export function getRequestFileObject(triggerType, auth, fileObject, config) {
|
||||
return request;
|
||||
}
|
||||
|
||||
export async function maybeRunFileTrigger(
|
||||
triggerType,
|
||||
fileObject,
|
||||
config,
|
||||
auth
|
||||
) {
|
||||
export async function maybeRunFileTrigger(triggerType, fileObject, config, auth) {
|
||||
const fileTrigger = getFileTrigger(triggerType, config.applicationId);
|
||||
if (typeof fileTrigger === 'function') {
|
||||
try {
|
||||
const request = getRequestFileObject(
|
||||
triggerType,
|
||||
auth,
|
||||
fileObject,
|
||||
config
|
||||
);
|
||||
const request = getRequestFileObject(triggerType, auth, fileObject, config);
|
||||
await maybeRunValidator(request, `${triggerType}.${FileClassName}`);
|
||||
const result = await fileTrigger(request);
|
||||
logTriggerSuccessBeforeHook(
|
||||
@@ -1007,11 +897,7 @@ export async function maybeRunFileTrigger(
|
||||
}
|
||||
|
||||
export async function maybeRunConnectTrigger(triggerType, request) {
|
||||
const trigger = getTrigger(
|
||||
ConnectClassName,
|
||||
triggerType,
|
||||
Parse.applicationId
|
||||
);
|
||||
const trigger = getTrigger(ConnectClassName, triggerType, Parse.applicationId);
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
@@ -1020,11 +906,7 @@ export async function maybeRunConnectTrigger(triggerType, request) {
|
||||
return trigger(request);
|
||||
}
|
||||
|
||||
export async function maybeRunSubscribeTrigger(
|
||||
triggerType,
|
||||
className,
|
||||
request
|
||||
) {
|
||||
export async function maybeRunSubscribeTrigger(triggerType, className, request) {
|
||||
const trigger = getTrigger(className, triggerType, Parse.applicationId);
|
||||
if (!trigger) {
|
||||
return;
|
||||
@@ -1042,11 +924,7 @@ export async function maybeRunSubscribeTrigger(
|
||||
request.query = query;
|
||||
}
|
||||
|
||||
export async function maybeRunAfterEventTrigger(
|
||||
triggerType,
|
||||
className,
|
||||
request
|
||||
) {
|
||||
export async function maybeRunAfterEventTrigger(triggerType, className, request) {
|
||||
const trigger = getTrigger(className, triggerType, Parse.applicationId);
|
||||
if (!trigger) {
|
||||
return;
|
||||
|
||||
90
src/vendor/mongodbUrl.js
vendored
90
src/vendor/mongodbUrl.js
vendored
@@ -191,18 +191,14 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) {
|
||||
// resolution will treat //foo/bar as host=foo,path=bar because that's
|
||||
// how the browser resolves relative URLs.
|
||||
if (slashesDenoteHost || proto || /^\/\/[^@\/]+@[^@\/]+/.test(rest)) {
|
||||
var slashes =
|
||||
rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47; /*/*/
|
||||
var slashes = rest.charCodeAt(0) === 47 /*/*/ && rest.charCodeAt(1) === 47; /*/*/
|
||||
if (slashes && !(proto && hostlessProtocol[proto])) {
|
||||
rest = rest.slice(2);
|
||||
this.slashes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!hostlessProtocol[proto] &&
|
||||
(slashes || (proto && !slashedProtocol[proto]))
|
||||
) {
|
||||
if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) {
|
||||
// there's a hostname.
|
||||
// the first instance of /, ?, ;, or # ends the host.
|
||||
//
|
||||
@@ -283,8 +279,7 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) {
|
||||
// if hostname begins with [ and ends with ]
|
||||
// assume that it's an IPv6 address.
|
||||
var ipv6Hostname =
|
||||
hostname.charCodeAt(0) === 91 /*[*/ &&
|
||||
hostname.charCodeAt(hostname.length - 1) === 93; /*]*/
|
||||
hostname.charCodeAt(0) === 91 /*[*/ && hostname.charCodeAt(hostname.length - 1) === 93; /*]*/
|
||||
|
||||
// validate a little.
|
||||
if (!ipv6Hostname) {
|
||||
@@ -358,9 +353,7 @@ Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) {
|
||||
}
|
||||
|
||||
var firstIdx =
|
||||
questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx)
|
||||
? questionIdx
|
||||
: hashIdx;
|
||||
questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx) ? questionIdx : hashIdx;
|
||||
if (firstIdx === -1) {
|
||||
if (rest.length > 0) this.pathname = rest;
|
||||
} else if (firstIdx > 0) {
|
||||
@@ -513,9 +506,7 @@ function urlFormat(obj) {
|
||||
if (typeof obj === 'string') obj = urlParse(obj);
|
||||
else if (typeof obj !== 'object' || obj === null)
|
||||
throw new TypeError(
|
||||
'Parameter "urlObj" must be an object, not ' + obj === null
|
||||
? 'null'
|
||||
: typeof obj
|
||||
'Parameter "urlObj" must be an object, not ' + obj === null ? 'null' : typeof obj
|
||||
);
|
||||
else if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
|
||||
|
||||
@@ -539,11 +530,7 @@ Url.prototype.format = function () {
|
||||
if (this.host) {
|
||||
host = auth + this.host;
|
||||
} else if (this.hostname) {
|
||||
host =
|
||||
auth +
|
||||
(this.hostname.indexOf(':') === -1
|
||||
? this.hostname
|
||||
: '[' + this.hostname + ']');
|
||||
host = auth + (this.hostname.indexOf(':') === -1 ? this.hostname : '[' + this.hostname + ']');
|
||||
if (this.port) {
|
||||
host += ':' + this.port;
|
||||
}
|
||||
@@ -554,8 +541,7 @@ Url.prototype.format = function () {
|
||||
|
||||
var search = this.search || (query && '?' + query) || '';
|
||||
|
||||
if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/)
|
||||
protocol += ':';
|
||||
if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /*:*/) protocol += ':';
|
||||
|
||||
var newPathname = '';
|
||||
var lastPos = 0;
|
||||
@@ -574,20 +560,15 @@ Url.prototype.format = function () {
|
||||
}
|
||||
}
|
||||
if (lastPos > 0) {
|
||||
if (lastPos !== pathname.length)
|
||||
pathname = newPathname + pathname.slice(lastPos);
|
||||
if (lastPos !== pathname.length) pathname = newPathname + pathname.slice(lastPos);
|
||||
else pathname = newPathname;
|
||||
}
|
||||
|
||||
// only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
|
||||
// unless they had them to begin with.
|
||||
if (
|
||||
this.slashes ||
|
||||
((!protocol || slashedProtocol[protocol]) && host !== false)
|
||||
) {
|
||||
if (this.slashes || ((!protocol || slashedProtocol[protocol]) && host !== false)) {
|
||||
host = '//' + (host || '');
|
||||
if (pathname && pathname.charCodeAt(0) !== 47 /*/*/)
|
||||
pathname = '/' + pathname;
|
||||
if (pathname && pathname.charCodeAt(0) !== 47 /*/*/) pathname = '/' + pathname;
|
||||
} else if (!host) {
|
||||
host = '';
|
||||
}
|
||||
@@ -651,11 +632,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
}
|
||||
|
||||
//urlParse appends trailing / to urls like http://www.example.com
|
||||
if (
|
||||
slashedProtocol[result.protocol] &&
|
||||
result.hostname &&
|
||||
!result.pathname
|
||||
) {
|
||||
if (slashedProtocol[result.protocol] && result.hostname && !result.pathname) {
|
||||
result.path = result.pathname = '/';
|
||||
}
|
||||
|
||||
@@ -716,10 +693,8 @@ Url.prototype.resolveObject = function (relative) {
|
||||
}
|
||||
|
||||
var isSourceAbs = result.pathname && result.pathname.charAt(0) === '/';
|
||||
var isRelAbs =
|
||||
relative.host || (relative.pathname && relative.pathname.charAt(0) === '/');
|
||||
var mustEndAbs =
|
||||
isRelAbs || isSourceAbs || (result.host && relative.pathname);
|
||||
var isRelAbs = relative.host || (relative.pathname && relative.pathname.charAt(0) === '/');
|
||||
var mustEndAbs = isRelAbs || isSourceAbs || (result.host && relative.pathname);
|
||||
var removeAllDots = mustEndAbs;
|
||||
var srcPath = (result.pathname && result.pathname.split('/')) || [];
|
||||
var relPath = (relative.pathname && relative.pathname.split('/')) || [];
|
||||
@@ -752,12 +727,9 @@ Url.prototype.resolveObject = function (relative) {
|
||||
|
||||
if (isRelAbs) {
|
||||
// it's absolute.
|
||||
result.host =
|
||||
relative.host || relative.host === '' ? relative.host : result.host;
|
||||
result.host = relative.host || relative.host === '' ? relative.host : result.host;
|
||||
result.hostname =
|
||||
relative.hostname || relative.hostname === ''
|
||||
? relative.hostname
|
||||
: result.hostname;
|
||||
relative.hostname || relative.hostname === '' ? relative.hostname : result.hostname;
|
||||
result.search = relative.search;
|
||||
result.query = relative.query;
|
||||
srcPath = relPath;
|
||||
@@ -780,9 +752,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
//this especially happens in cases like
|
||||
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
||||
const authInHost =
|
||||
result.host && result.host.indexOf('@') > 0
|
||||
? result.host.split('@')
|
||||
: false;
|
||||
result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false;
|
||||
if (authInHost) {
|
||||
result.auth = authInHost.shift();
|
||||
result.host = result.hostname = authInHost.shift();
|
||||
@@ -792,9 +762,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
result.query = relative.query;
|
||||
//to support http.request
|
||||
if (result.pathname !== null || result.search !== null) {
|
||||
result.path =
|
||||
(result.pathname ? result.pathname : '') +
|
||||
(result.search ? result.search : '');
|
||||
result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : '');
|
||||
}
|
||||
result.href = result.format();
|
||||
return result;
|
||||
@@ -819,8 +787,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
// then it must NOT get a trailing slash.
|
||||
var last = srcPath.slice(-1)[0];
|
||||
var hasTrailingSlash =
|
||||
((result.host || relative.host || srcPath.length > 1) &&
|
||||
(last === '.' || last === '..')) ||
|
||||
((result.host || relative.host || srcPath.length > 1) && (last === '.' || last === '..')) ||
|
||||
last === '';
|
||||
|
||||
// strip single dots, resolve double dots to parent dir
|
||||
@@ -846,11 +813,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
mustEndAbs &&
|
||||
srcPath[0] !== '' &&
|
||||
(!srcPath[0] || srcPath[0].charAt(0) !== '/')
|
||||
) {
|
||||
if (mustEndAbs && srcPath[0] !== '' && (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
|
||||
srcPath.unshift('');
|
||||
}
|
||||
|
||||
@@ -858,8 +821,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
srcPath.push('');
|
||||
}
|
||||
|
||||
var isAbsolute =
|
||||
srcPath[0] === '' || (srcPath[0] && srcPath[0].charAt(0) === '/');
|
||||
var isAbsolute = srcPath[0] === '' || (srcPath[0] && srcPath[0].charAt(0) === '/');
|
||||
|
||||
// put the host back
|
||||
if (psychotic) {
|
||||
@@ -871,10 +833,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
//occasionally the auth can get stuck only in host
|
||||
//this especially happens in cases like
|
||||
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
||||
const authInHost =
|
||||
result.host && result.host.indexOf('@') > 0
|
||||
? result.host.split('@')
|
||||
: false;
|
||||
const authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false;
|
||||
if (authInHost) {
|
||||
result.auth = authInHost.shift();
|
||||
result.host = result.hostname = authInHost.shift();
|
||||
@@ -896,9 +855,7 @@ Url.prototype.resolveObject = function (relative) {
|
||||
|
||||
//to support request.http
|
||||
if (result.pathname !== null || result.search !== null) {
|
||||
result.path =
|
||||
(result.pathname ? result.pathname : '') +
|
||||
(result.search ? result.search : '');
|
||||
result.path = (result.pathname ? result.pathname : '') + (result.search ? result.search : '');
|
||||
}
|
||||
result.auth = relative.auth || result.auth;
|
||||
result.slashes = result.slashes || relative.slashes;
|
||||
@@ -923,8 +880,7 @@ Url.prototype.parseHost = function () {
|
||||
// About 1.5x faster than the two-arg version of Array#splice().
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function spliceOne(list, index) {
|
||||
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
|
||||
list[i] = list[k];
|
||||
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) list[i] = list[k];
|
||||
list.pop();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user