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:
Diamond Lewis
2020-10-25 15:06:58 -05:00
committed by GitHub
parent c2f2281e6d
commit e6ac3b6932
178 changed files with 5585 additions and 10688 deletions

View File

@@ -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
}
});
}
/**

View File

@@ -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(),
};
};

View File

@@ -1,7 +1,7 @@
var semver = require('semver');
function compatible(compatibleSDK) {
return function(clientSDK) {
return function (clientSDK) {
if (typeof clientSDK === 'string') {
clientSDK = fromString(clientSDK);
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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(

View File

@@ -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`;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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']);

View File

@@ -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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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!'),
}
);
}

View File

@@ -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`

View File

@@ -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));

View File

@@ -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(

View File

@@ -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()';
}

View File

@@ -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;
}

View File

@@ -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);
}
);

View File

@@ -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);

View File

@@ -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',

View File

@@ -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()';
}

View File

@@ -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))
);
}

View File

@@ -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);
});

View File

@@ -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';
}

View File

@@ -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;
}
});

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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);
});
}
}

View File

@@ -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',

View File

@@ -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);
});

View File

@@ -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) {

View File

@@ -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,
},
};
});
}
}

View File

@@ -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';
}

View File

@@ -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}`,
{

View File

@@ -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);
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
});

View File

@@ -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');
}
}

View File

@@ -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);

View File

@@ -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);
});
}
}

View File

@@ -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);

View File

@@ -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',

View File

@@ -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],

View File

@@ -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;

View File

@@ -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 } },

View File

@@ -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 => {

View File

@@ -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(
'[' +

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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');
}

View File

@@ -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,

View File

@@ -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}`;
};
}

View File

@@ -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;
};

View File

@@ -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;
});

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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();
}