Validation Handler Update (#6968)
* Initial Commit * Update FunctionsRouter.js * Update FunctionsRouter.js * Change params to fields * Changes requested * Fix failing tests * More tests * More tests * Remove existing functionality * Remove legacy tests * fix array typo * Update triggers.js * Docs * Allow requireUserKeys to be object * validateMasterKey * Improve documentation Co-authored-by: Diamond Lewis <findlewis@gmail.com>
This commit is contained in:
1174
spec/CloudCode.Validator.spec.js
Normal file
1174
spec/CloudCode.Validator.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,21 @@ describe('Cloud Code', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty error', done => {
|
||||
Parse.Cloud.define('cloudCodeWithError', () => {
|
||||
throw null;
|
||||
});
|
||||
|
||||
Parse.Cloud.run('cloudCodeWithError').then(
|
||||
() => done.fail('should not succeed'),
|
||||
e => {
|
||||
expect(e.code).toEqual(141);
|
||||
expect(e.message).toEqual('Script failed.');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('beforeSave rejection with custom error code', function (done) {
|
||||
Parse.Cloud.beforeSave('BeforeSaveFailWithErrorCode', function () {
|
||||
throw new Parse.Error(999, 'Nope');
|
||||
@@ -2675,6 +2690,34 @@ describe('beforeLogin hook', () => {
|
||||
expect(result).toBe(file);
|
||||
});
|
||||
|
||||
it('throw custom error from beforeSaveFile', async done => {
|
||||
Parse.Cloud.beforeSaveFile(() => {
|
||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'It should fail');
|
||||
});
|
||||
try {
|
||||
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||
await file.save({ useMasterKey: true });
|
||||
fail('error should have thrown');
|
||||
} catch (e) {
|
||||
expect(e.code).toBe(Parse.Error.SCRIPT_FAILED);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throw empty error from beforeSaveFile', async done => {
|
||||
Parse.Cloud.beforeSaveFile(() => {
|
||||
throw null;
|
||||
});
|
||||
try {
|
||||
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||
await file.save({ useMasterKey: true });
|
||||
fail('error should have thrown');
|
||||
} catch (e) {
|
||||
expect(e.code).toBe(130);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('beforeSaveFile should return file that is already saved and not save anything to files adapter', async () => {
|
||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||
|
||||
@@ -24,7 +24,7 @@ const headers = {
|
||||
};
|
||||
|
||||
describe_only_db('mongo')('miscellaneous', () => {
|
||||
it('test rest_create_app', function(done) {
|
||||
it('test rest_create_app', function (done) {
|
||||
let appId;
|
||||
Parse._request('POST', 'rest_create_app')
|
||||
.then(res => {
|
||||
@@ -57,19 +57,19 @@ describe_only_db('mongo')('miscellaneous', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('miscellaneous', function() {
|
||||
it('create a GameScore object', function(done) {
|
||||
describe('miscellaneous', function () {
|
||||
it('create a GameScore object', function (done) {
|
||||
const obj = new Parse.Object('GameScore');
|
||||
obj.set('score', 1337);
|
||||
obj.save().then(function(obj) {
|
||||
obj.save().then(function (obj) {
|
||||
expect(typeof obj.id).toBe('string');
|
||||
expect(typeof obj.createdAt.toGMTString()).toBe('string');
|
||||
done();
|
||||
}, done.fail);
|
||||
});
|
||||
|
||||
it('get a TestObject', function(done) {
|
||||
create({ bloop: 'blarg' }, async function(obj) {
|
||||
it('get a TestObject', function (done) {
|
||||
create({ bloop: 'blarg' }, async function (obj) {
|
||||
const t2 = new TestObject({ objectId: obj.id });
|
||||
const obj2 = await t2.fetch();
|
||||
expect(obj2.get('bloop')).toEqual('blarg');
|
||||
@@ -79,8 +79,8 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('create a valid parse user', function(done) {
|
||||
createTestUser().then(function(data) {
|
||||
it('create a valid parse user', function (done) {
|
||||
createTestUser().then(function (data) {
|
||||
expect(data.id).not.toBeUndefined();
|
||||
expect(data.getSessionToken()).not.toBeUndefined();
|
||||
expect(data.get('password')).toBeUndefined();
|
||||
@@ -297,8 +297,8 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('succeed in logging in', function(done) {
|
||||
createTestUser().then(async function(u) {
|
||||
it('succeed in logging in', function (done) {
|
||||
createTestUser().then(async function (u) {
|
||||
expect(typeof u.id).toEqual('string');
|
||||
|
||||
const user = await Parse.User.logIn('test', 'moon-y');
|
||||
@@ -310,7 +310,7 @@ describe('miscellaneous', function() {
|
||||
}, fail);
|
||||
});
|
||||
|
||||
it('increment with a user object', function(done) {
|
||||
it('increment with a user object', function (done) {
|
||||
createTestUser()
|
||||
.then(user => {
|
||||
user.increment('foo');
|
||||
@@ -338,7 +338,7 @@ describe('miscellaneous', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('save various data types', function(done) {
|
||||
it('save various data types', function (done) {
|
||||
const obj = new TestObject();
|
||||
obj.set('date', new Date());
|
||||
obj.set('array', [1, 2, 3]);
|
||||
@@ -358,7 +358,7 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('query with limit', function(done) {
|
||||
it('query with limit', function (done) {
|
||||
const baz = new TestObject({ foo: 'baz' });
|
||||
const qux = new TestObject({ foo: 'qux' });
|
||||
baz
|
||||
@@ -383,7 +383,7 @@ describe('miscellaneous', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('query without limit get default 100 records', function(done) {
|
||||
it('query without limit get default 100 records', function (done) {
|
||||
const objects = [];
|
||||
for (let i = 0; i < 150; i++) {
|
||||
objects.push(new TestObject({ name: 'name' + i }));
|
||||
@@ -404,7 +404,7 @@ describe('miscellaneous', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('basic saveAll', function(done) {
|
||||
it('basic saveAll', function (done) {
|
||||
const alpha = new TestObject({ letter: 'alpha' });
|
||||
const beta = new TestObject({ letter: 'beta' });
|
||||
Parse.Object.saveAll([alpha, beta])
|
||||
@@ -425,26 +425,26 @@ describe('miscellaneous', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('test beforeSave set object acl success', function(done) {
|
||||
it('test beforeSave set object acl success', function (done) {
|
||||
const acl = new Parse.ACL({
|
||||
'*': { read: true, write: false },
|
||||
});
|
||||
Parse.Cloud.beforeSave('BeforeSaveAddACL', function(req) {
|
||||
Parse.Cloud.beforeSave('BeforeSaveAddACL', function (req) {
|
||||
req.object.setACL(acl);
|
||||
});
|
||||
|
||||
const obj = new Parse.Object('BeforeSaveAddACL');
|
||||
obj.set('lol', true);
|
||||
obj.save().then(
|
||||
function() {
|
||||
function () {
|
||||
const query = new Parse.Query('BeforeSaveAddACL');
|
||||
query.get(obj.id).then(
|
||||
function(objAgain) {
|
||||
function (objAgain) {
|
||||
expect(objAgain.get('lol')).toBeTruthy();
|
||||
expect(objAgain.getACL().equals(acl));
|
||||
done();
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
fail(error);
|
||||
done();
|
||||
}
|
||||
@@ -667,10 +667,10 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('test afterSave get full object on create and update', function(done) {
|
||||
it('test afterSave get full object on create and update', function (done) {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
expect(object instanceof Parse.Object).toBeTruthy();
|
||||
expect(object.id).not.toBeUndefined();
|
||||
@@ -694,29 +694,29 @@ describe('miscellaneous', function() {
|
||||
obj.set('fooAgain', 'barAgain');
|
||||
obj
|
||||
.save()
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
// We only update foo
|
||||
obj.set('foo', 'baz');
|
||||
return obj.save();
|
||||
})
|
||||
.then(
|
||||
function() {
|
||||
function () {
|
||||
// Make sure the checking has been triggered
|
||||
expect(triggerTime).toBe(2);
|
||||
done();
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
fail(error);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('test afterSave get original object on update', function(done) {
|
||||
it('test afterSave get original object on update', function (done) {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
expect(object instanceof Parse.Object).toBeTruthy();
|
||||
expect(object.get('fooAgain')).toEqual('barAgain');
|
||||
@@ -750,18 +750,18 @@ describe('miscellaneous', function() {
|
||||
obj.set('fooAgain', 'barAgain');
|
||||
obj
|
||||
.save()
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
// We only update foo
|
||||
obj.set('foo', 'baz');
|
||||
return obj.save();
|
||||
})
|
||||
.then(
|
||||
function() {
|
||||
function () {
|
||||
// Make sure the checking has been triggered
|
||||
expect(triggerTime).toBe(2);
|
||||
done();
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
jfail(error);
|
||||
done();
|
||||
}
|
||||
@@ -771,7 +771,7 @@ describe('miscellaneous', function() {
|
||||
it('test afterSave get full original object even req auth can not query it', done => {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
const originalObject = req.original;
|
||||
if (triggerTime == 0) {
|
||||
@@ -802,18 +802,18 @@ describe('miscellaneous', function() {
|
||||
obj.setACL(acl);
|
||||
obj
|
||||
.save()
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
// We only update foo
|
||||
obj.set('foo', 'baz');
|
||||
return obj.save();
|
||||
})
|
||||
.then(
|
||||
function() {
|
||||
function () {
|
||||
// Make sure the checking has been triggered
|
||||
expect(triggerTime).toBe(2);
|
||||
done();
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
jfail(error);
|
||||
done();
|
||||
}
|
||||
@@ -823,7 +823,7 @@ describe('miscellaneous', function() {
|
||||
it('afterSave flattens custom operations', done => {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
expect(object instanceof Parse.Object).toBeTruthy();
|
||||
const originalObject = req.original;
|
||||
@@ -865,7 +865,7 @@ describe('miscellaneous', function() {
|
||||
it('beforeSave receives ACL', done => {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.beforeSave('GameScore', function(req) {
|
||||
Parse.Cloud.beforeSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
if (triggerTime == 0) {
|
||||
const acl = object.getACL();
|
||||
@@ -909,7 +909,7 @@ describe('miscellaneous', function() {
|
||||
it('afterSave receives ACL', done => {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
const object = req.object;
|
||||
if (triggerTime == 0) {
|
||||
const acl = object.getACL();
|
||||
@@ -1057,14 +1057,14 @@ describe('miscellaneous', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('test beforeSave/afterSave get installationId', function(done) {
|
||||
it('test beforeSave/afterSave get installationId', function (done) {
|
||||
let triggerTime = 0;
|
||||
Parse.Cloud.beforeSave('GameScore', function(req) {
|
||||
Parse.Cloud.beforeSave('GameScore', function (req) {
|
||||
triggerTime++;
|
||||
expect(triggerTime).toEqual(1);
|
||||
expect(req.installationId).toEqual('yolo');
|
||||
});
|
||||
Parse.Cloud.afterSave('GameScore', function(req) {
|
||||
Parse.Cloud.afterSave('GameScore', function (req) {
|
||||
triggerTime++;
|
||||
expect(triggerTime).toEqual(2);
|
||||
expect(req.installationId).toEqual('yolo');
|
||||
@@ -1087,14 +1087,14 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('test beforeDelete/afterDelete get installationId', function(done) {
|
||||
it('test beforeDelete/afterDelete get installationId', function (done) {
|
||||
let triggerTime = 0;
|
||||
Parse.Cloud.beforeDelete('GameScore', function(req) {
|
||||
Parse.Cloud.beforeDelete('GameScore', function (req) {
|
||||
triggerTime++;
|
||||
expect(triggerTime).toEqual(1);
|
||||
expect(req.installationId).toEqual('yolo');
|
||||
});
|
||||
Parse.Cloud.afterDelete('GameScore', function(req) {
|
||||
Parse.Cloud.afterDelete('GameScore', function (req) {
|
||||
triggerTime++;
|
||||
expect(triggerTime).toEqual(2);
|
||||
expect(req.installationId).toEqual('yolo');
|
||||
@@ -1170,33 +1170,6 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('test cloud function parameter validation', done => {
|
||||
// Register a function with validation
|
||||
Parse.Cloud.define(
|
||||
'functionWithParameterValidationFailure',
|
||||
() => {
|
||||
return 'noway';
|
||||
},
|
||||
request => {
|
||||
return request.params.success === 100;
|
||||
}
|
||||
);
|
||||
|
||||
Parse.Cloud.run('functionWithParameterValidationFailure', {
|
||||
success: 500,
|
||||
}).then(
|
||||
() => {
|
||||
fail('Validation should not have succeeded');
|
||||
done();
|
||||
},
|
||||
e => {
|
||||
expect(e.code).toEqual(142);
|
||||
expect(e.message).toEqual('Validation failed.');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('can handle null params in cloud functions (regression test for #1742)', done => {
|
||||
Parse.Cloud.define('func', request => {
|
||||
expect(request.params.nullParam).toEqual(null);
|
||||
@@ -1715,10 +1688,7 @@ describe('miscellaneous', function() {
|
||||
|
||||
it('purge empty class', done => {
|
||||
const testSchema = new Parse.Schema('UnknownClass');
|
||||
testSchema
|
||||
.purge()
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
testSchema.purge().then(done).catch(done.fail);
|
||||
});
|
||||
|
||||
it('should not update schema beforeSave #2672', done => {
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
const UserController = require('../lib/Controllers/UserController')
|
||||
.UserController;
|
||||
const Config = require('../lib/Config');
|
||||
const validatorFail = () => {
|
||||
throw 'you are not authorized';
|
||||
};
|
||||
|
||||
describe('ParseLiveQuery', function () {
|
||||
it('can subscribe to query', async done => {
|
||||
await reconfigureServer({
|
||||
@@ -231,6 +235,7 @@ describe('ParseLiveQuery', function () {
|
||||
object.set({ foo: 'bar' });
|
||||
await object.save();
|
||||
});
|
||||
|
||||
it('can handle afterEvent throw', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
@@ -300,6 +305,7 @@ describe('ParseLiveQuery', function () {
|
||||
object.set({ foo: 'bar' });
|
||||
await object.save();
|
||||
});
|
||||
|
||||
it('expect afterEvent create', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
@@ -551,6 +557,79 @@ describe('ParseLiveQuery', function () {
|
||||
await object.save();
|
||||
});
|
||||
|
||||
it('can handle beforeConnect validation function', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
const object = new TestObject();
|
||||
await object.save();
|
||||
|
||||
Parse.Cloud.beforeConnect(() => {}, validatorFail);
|
||||
let complete = false;
|
||||
Parse.LiveQuery.on('error', error => {
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
complete = true;
|
||||
expect(error).toBe('you are not authorized');
|
||||
done();
|
||||
});
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('objectId', object.id);
|
||||
await query.subscribe();
|
||||
});
|
||||
|
||||
it('can handle beforeSubscribe validation function', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
const object = new TestObject();
|
||||
await object.save();
|
||||
|
||||
Parse.Cloud.beforeSubscribe(TestObject, () => {}, validatorFail);
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('objectId', object.id);
|
||||
const subscription = await query.subscribe();
|
||||
subscription.on('error', error => {
|
||||
expect(error).toBe('you are not authorized');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can handle afterEvent validation function', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
classNames: ['TestObject'],
|
||||
},
|
||||
startLiveQueryServer: true,
|
||||
verbose: false,
|
||||
silent: true,
|
||||
});
|
||||
Parse.Cloud.afterLiveQueryEvent('TestObject', () => {}, validatorFail);
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
const subscription = await query.subscribe();
|
||||
subscription.on('error', error => {
|
||||
expect(error).toBe('you are not authorized');
|
||||
done();
|
||||
});
|
||||
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
});
|
||||
|
||||
it('can handle beforeConnect error', async done => {
|
||||
await reconfigureServer({
|
||||
liveQuery: {
|
||||
|
||||
@@ -33,15 +33,6 @@ const addFileDataIfNeeded = async file => {
|
||||
return file;
|
||||
};
|
||||
|
||||
const errorMessageFromError = e => {
|
||||
if (typeof e === 'string') {
|
||||
return e;
|
||||
} else if (e && e.message) {
|
||||
return e.message;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export class FilesRouter {
|
||||
expressRouter({ maxUploadSize = '20Mb' } = {}) {
|
||||
var router = express.Router();
|
||||
@@ -192,10 +183,11 @@ export class FilesRouter {
|
||||
res.json(saveResult);
|
||||
} catch (e) {
|
||||
logger.error('Error creating a file: ', e);
|
||||
const errorMessage =
|
||||
errorMessageFromError(e) ||
|
||||
`Could not store file: ${fileObject.file._name}.`;
|
||||
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, errorMessage));
|
||||
const error = triggers.resolveError(e, {
|
||||
code: Parse.Error.FILE_SAVE_ERROR,
|
||||
message: `Could not store file: ${fileObject.file._name}.`,
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,8 +219,11 @@ export class FilesRouter {
|
||||
res.end();
|
||||
} catch (e) {
|
||||
logger.error('Error deleting a file: ', e);
|
||||
const errorMessage = errorMessageFromError(e) || `Could not delete file.`;
|
||||
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR, errorMessage));
|
||||
const error = triggers.resolveError(e, {
|
||||
code: Parse.Error.FILE_DELETE_ERROR,
|
||||
message: 'Could not delete file.',
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,37 +109,17 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
});
|
||||
},
|
||||
error: function (message) {
|
||||
// parse error, process away
|
||||
if (message instanceof Parse.Error) {
|
||||
return reject(message);
|
||||
}
|
||||
|
||||
const code = Parse.Error.SCRIPT_FAILED;
|
||||
// If it's an error, mark it as a script failed
|
||||
if (typeof message === 'string') {
|
||||
return reject(new Parse.Error(code, message));
|
||||
}
|
||||
const error = new Parse.Error(
|
||||
code,
|
||||
(message && message.message) || message
|
||||
);
|
||||
if (message instanceof Error) {
|
||||
error.stack = message.stack;
|
||||
}
|
||||
const error = triggers.resolveError(message);
|
||||
reject(error);
|
||||
},
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
static handleCloudFunction(req) {
|
||||
const functionName = req.params.functionName;
|
||||
const applicationId = req.config.applicationId;
|
||||
const theFunction = triggers.getFunction(functionName, applicationId);
|
||||
const theValidator = triggers.getValidator(
|
||||
req.params.functionName,
|
||||
applicationId
|
||||
);
|
||||
|
||||
if (!theFunction) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.SCRIPT_FAILED,
|
||||
@@ -160,16 +140,6 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
context: req.info.context,
|
||||
};
|
||||
|
||||
if (theValidator && typeof theValidator === 'function') {
|
||||
var result = theValidator(request);
|
||||
if (!result) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
'Validation failed.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
const userString =
|
||||
req.auth && req.auth.user ? req.auth.user.id : undefined;
|
||||
@@ -212,6 +182,9 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
}
|
||||
);
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return triggers.maybeRunValidator(request, functionName);
|
||||
})
|
||||
.then(() => {
|
||||
return theFunction(request, { message });
|
||||
})
|
||||
|
||||
@@ -32,11 +32,24 @@ var ParseCloud = {};
|
||||
* Defines a Cloud Function.
|
||||
*
|
||||
* **Available in Cloud Code only.**
|
||||
|
||||
*
|
||||
* ```
|
||||
* Parse.Cloud.define('functionName', (request) => {
|
||||
* // code here
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.define('functionName', (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
* ```
|
||||
*
|
||||
* @static
|
||||
* @memberof Parse.Cloud
|
||||
* @param {String} name The name of the Cloud Function
|
||||
* @param {Function} data The Cloud Function to register. This function can be an async function and should take one parameter a {@link Parse.Cloud.FunctionRequest}.
|
||||
* @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(
|
||||
@@ -73,25 +86,29 @@ ParseCloud.job = function (functionName, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeSave('MyCustomClass', (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeSave(Parse.User, (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject })
|
||||
* ```
|
||||
*
|
||||
* @method beforeSave
|
||||
* @name Parse.Cloud.beforeSave
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run before a save. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest};
|
||||
* @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.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeSave = function (parseClass, handler) {
|
||||
ParseCloud.beforeSave = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.beforeSave,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -104,25 +121,29 @@ ParseCloud.beforeSave = function (parseClass, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeDelete('MyCustomClass', (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeDelete(Parse.User, (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject })
|
||||
*```
|
||||
*
|
||||
* @method beforeDelete
|
||||
* @name Parse.Cloud.beforeDelete
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before delete function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run before a delete. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}.
|
||||
* @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.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeDelete = function (parseClass, handler) {
|
||||
ParseCloud.beforeDelete = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.beforeDelete,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -177,8 +198,7 @@ ParseCloud.beforeLogin = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterLogin((request) => {
|
||||
* // code here
|
||||
* })
|
||||
*
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @method afterLogin
|
||||
@@ -212,8 +232,7 @@ ParseCloud.afterLogin = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterLogout((request) => {
|
||||
* // code here
|
||||
* })
|
||||
*
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @method afterLogout
|
||||
@@ -246,25 +265,29 @@ ParseCloud.afterLogout = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterSave('MyCustomClass', async function(request) {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterSave(Parse.User, async function(request) {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject });
|
||||
* ```
|
||||
*
|
||||
* @method afterSave
|
||||
* @name Parse.Cloud.afterSave
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run after a save. This function can be an async function and should take just one parameter, {@link Parse.Cloud.TriggerRequest}.
|
||||
* @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.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterSave = function (parseClass, handler) {
|
||||
ParseCloud.afterSave = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterSave,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -277,25 +300,29 @@ ParseCloud.afterSave = function (parseClass, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterDelete('MyCustomClass', async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterDelete(Parse.User, async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method afterDelete
|
||||
* @name Parse.Cloud.afterDelete
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after delete function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run after a delete. This function can be async and should take just one parameter, {@link Parse.Cloud.TriggerRequest}.
|
||||
* @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.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterDelete = function (parseClass, handler) {
|
||||
ParseCloud.afterDelete = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterDelete,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -308,25 +335,29 @@ ParseCloud.afterDelete = function (parseClass, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeFind('MyCustomClass', async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeFind(Parse.User, async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method beforeFind
|
||||
* @name Parse.Cloud.beforeFind
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before find function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run before a find. This function can be async and should take just one parameter, {@link Parse.Cloud.BeforeFindRequest}.
|
||||
* @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.BeforeFindRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeFind = function (parseClass, handler) {
|
||||
ParseCloud.beforeFind = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.beforeFind,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -339,25 +370,29 @@ ParseCloud.beforeFind = function (parseClass, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterFind('MyCustomClass', async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterFind(Parse.User, async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method afterFind
|
||||
* @name Parse.Cloud.afterFind
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after find function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run before a find. This function can be async and should take just one parameter, {@link Parse.Cloud.AfterFindRequest}.
|
||||
* @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.AfterFindRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterFind = function (parseClass, handler) {
|
||||
ParseCloud.afterFind = function (parseClass, handler, validationHandler) {
|
||||
const className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterFind,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -369,18 +404,26 @@ ParseCloud.afterFind = function (parseClass, handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeSaveFile(async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeSaveFile(async (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method beforeSaveFile
|
||||
* @name Parse.Cloud.beforeSaveFile
|
||||
* @param {Function} func The function to run before saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
|
||||
* @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.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeSaveFile = function (handler) {
|
||||
ParseCloud.beforeSaveFile = function (handler, validationHandler) {
|
||||
triggers.addFileTrigger(
|
||||
triggers.Types.beforeSaveFile,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -392,18 +435,26 @@ ParseCloud.beforeSaveFile = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterSaveFile(async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterSaveFile(async (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method afterSaveFile
|
||||
* @name Parse.Cloud.afterSaveFile
|
||||
* @param {Function} func The function to run after saving a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
|
||||
* @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.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterSaveFile = function (handler) {
|
||||
ParseCloud.afterSaveFile = function (handler, validationHandler) {
|
||||
triggers.addFileTrigger(
|
||||
triggers.Types.afterSaveFile,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -415,18 +466,26 @@ ParseCloud.afterSaveFile = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeDeleteFile(async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeDeleteFile(async (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method beforeDeleteFile
|
||||
* @name Parse.Cloud.beforeDeleteFile
|
||||
* @param {Function} func The function to run before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
|
||||
* @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.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeDeleteFile = function (handler) {
|
||||
ParseCloud.beforeDeleteFile = function (handler, validationHandler) {
|
||||
triggers.addFileTrigger(
|
||||
triggers.Types.beforeDeleteFile,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -438,18 +497,26 @@ ParseCloud.beforeDeleteFile = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterDeleteFile(async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterDeleteFile(async (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method afterDeleteFile
|
||||
* @name Parse.Cloud.afterDeleteFile
|
||||
* @param {Function} func The function to after before deleting a file. This function can be async and should take just one parameter, {@link Parse.Cloud.FileTriggerRequest}.
|
||||
* @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.FileTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.afterDeleteFile = function (handler) {
|
||||
ParseCloud.afterDeleteFile = function (handler, validationHandler) {
|
||||
triggers.addFileTrigger(
|
||||
triggers.Types.afterDeleteFile,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -461,18 +528,26 @@ ParseCloud.afterDeleteFile = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeConnect(async (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeConnect(async (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method beforeConnect
|
||||
* @name Parse.Cloud.beforeConnect
|
||||
* @param {Function} func The function to before connection is made. This function can be async and should take just one parameter, {@link Parse.Cloud.ConnectTriggerRequest}.
|
||||
* @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.ConnectTriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeConnect = function (handler) {
|
||||
ParseCloud.beforeConnect = function (handler, validationHandler) {
|
||||
triggers.addConnectTrigger(
|
||||
triggers.Types.beforeConnect,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -485,25 +560,29 @@ ParseCloud.beforeConnect = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.beforeSubscribe('MyCustomClass', (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.beforeSubscribe(Parse.User, (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method beforeSubscribe
|
||||
* @name Parse.Cloud.beforeSubscribe
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before subscription function for. This can instead be a String that is the className of the subclass.
|
||||
* @param {Function} func The function to run before a subscription. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}.
|
||||
* @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.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}.
|
||||
*/
|
||||
ParseCloud.beforeSubscribe = function (parseClass, handler) {
|
||||
ParseCloud.beforeSubscribe = function (parseClass, handler, validationHandler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.beforeSubscribe,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -519,21 +598,33 @@ ParseCloud.onLiveQueryEvent = function (handler) {
|
||||
* ```
|
||||
* Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {
|
||||
* // code here
|
||||
* })
|
||||
* }, (request) => {
|
||||
* // validation code here
|
||||
* });
|
||||
*
|
||||
* Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {
|
||||
* // code here
|
||||
* }, { ...validationObject });
|
||||
*```
|
||||
*
|
||||
* @method afterLiveQueryEvent
|
||||
* @name Parse.Cloud.afterLiveQueryEvent
|
||||
* @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after live query event function for. This can instead be a String that is the className of the subclass.
|
||||
* @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) {
|
||||
ParseCloud.afterLiveQueryEvent = function (
|
||||
parseClass,
|
||||
handler,
|
||||
validationHandler
|
||||
) {
|
||||
const className = getClassName(parseClass);
|
||||
triggers.addTrigger(
|
||||
triggers.Types.afterEvent,
|
||||
className,
|
||||
handler,
|
||||
Parse.applicationId
|
||||
Parse.applicationId,
|
||||
validationHandler
|
||||
);
|
||||
};
|
||||
|
||||
@@ -642,3 +733,23 @@ module.exports = ParseCloud;
|
||||
* @property {Object} params The params passed to the background job.
|
||||
* @property {function} message If message is called with a string argument, will update the current message to be stored in the job status.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @interface Parse.Cloud.ValidatorObject
|
||||
* @property {Boolean} requireUser whether the cloud trigger requires a user.
|
||||
* @property {Boolean} requireMaster whether the cloud trigger requires a master key.
|
||||
* @property {Boolean} validateMasterKey whether the validator should run if masterKey is provided. Defaults to false.
|
||||
*
|
||||
* @property {Array<String>|Object} requireUserKeys If set, keys required on request.user to make the request.
|
||||
* @property {String} requireUserKeys.field If requireUserKeys is an object, name of field to validate on request user
|
||||
* @property {Array|function|Any} requireUserKeys.field.options array of options that the field can be, function to validate field, or single value. Throw an error if value is invalid.
|
||||
* @property {String} requireUserKeys.field.error custom error message if field is invalid.
|
||||
*
|
||||
* @property {Object|Array<String>} fields if an array of strings, validator will look for keys in request.params, and throw if not provided. If Object, fields to validate. If the trigger is a cloud function, `request.params` will be validated, otherwise `request.object`.
|
||||
* @property {String} fields.field name of field to validate.
|
||||
* @property {String} fields.field.type expected type of data for field.
|
||||
* @property {Boolean} fields.field.constant whether the field can be modified on the object.
|
||||
* @property {Any} fields.field.default default value if field is `null`, or initial value `constant` is `true`.
|
||||
* @property {Array|function|Any} fields.field.options array of options that the field can be, function to validate field, or single value. Throw an error if value is invalid.
|
||||
* @property {String} fields.field.error custom error message if field is invalid.
|
||||
*/
|
||||
|
||||
239
src/triggers.js
239
src/triggers.js
@@ -25,7 +25,10 @@ const FileClassName = '@File';
|
||||
const ConnectClassName = '@Connect';
|
||||
|
||||
const baseStore = function () {
|
||||
const Validators = {};
|
||||
const Validators = Object.keys(Types).reduce(function (base, key) {
|
||||
base[key] = {};
|
||||
return base;
|
||||
}, {});
|
||||
const Functions = {};
|
||||
const Jobs = {};
|
||||
const LiveQuery = [];
|
||||
@@ -132,17 +135,51 @@ export function addJob(jobName, handler, applicationId) {
|
||||
add(Category.Jobs, jobName, handler, applicationId);
|
||||
}
|
||||
|
||||
export function addTrigger(type, className, handler, applicationId) {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
export function addFileTrigger(type, handler, applicationId) {
|
||||
export function addFileTrigger(
|
||||
type,
|
||||
handler,
|
||||
applicationId,
|
||||
validationHandler
|
||||
) {
|
||||
add(Category.Triggers, `${type}.${FileClassName}`, handler, applicationId);
|
||||
add(
|
||||
Category.Validators,
|
||||
`${type}.${FileClassName}`,
|
||||
validationHandler,
|
||||
applicationId
|
||||
);
|
||||
}
|
||||
|
||||
export function addConnectTrigger(type, handler, applicationId) {
|
||||
export function addConnectTrigger(
|
||||
type,
|
||||
handler,
|
||||
applicationId,
|
||||
validationHandler
|
||||
) {
|
||||
add(Category.Triggers, `${type}.${ConnectClassName}`, handler, applicationId);
|
||||
add(
|
||||
Category.Validators,
|
||||
`${type}.${ConnectClassName}`,
|
||||
validationHandler,
|
||||
applicationId
|
||||
);
|
||||
}
|
||||
|
||||
export function addLiveQueryEventHandler(handler, applicationId) {
|
||||
@@ -455,6 +492,9 @@ export function maybeRunAfterFindTrigger(
|
||||
return Parse.Object.fromJSON(object);
|
||||
});
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return maybeRunValidator(request, `${triggerType}.${className}`);
|
||||
})
|
||||
.then(() => {
|
||||
const response = trigger(request);
|
||||
if (response && typeof response.then === 'function') {
|
||||
@@ -514,6 +554,9 @@ export function maybeRunQueryTrigger(
|
||||
isGet
|
||||
);
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return maybeRunValidator(requestObject, `${triggerType}.${className}`);
|
||||
})
|
||||
.then(() => {
|
||||
return trigger(requestObject);
|
||||
})
|
||||
@@ -588,6 +631,184 @@ export function maybeRunQueryTrigger(
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveError(message, defaultOpts) {
|
||||
if (!defaultOpts) {
|
||||
defaultOpts = {};
|
||||
}
|
||||
if (!message) {
|
||||
return new Parse.Error(
|
||||
defaultOpts.code || Parse.Error.SCRIPT_FAILED,
|
||||
defaultOpts.message || 'Script failed.'
|
||||
);
|
||||
}
|
||||
if (message instanceof Parse.Error) {
|
||||
return message;
|
||||
}
|
||||
|
||||
const code = defaultOpts.code || Parse.Error.SCRIPT_FAILED;
|
||||
// If it's an error, mark it as a script failed
|
||||
if (typeof message === 'string') {
|
||||
return new Parse.Error(code, message);
|
||||
}
|
||||
const error = new Parse.Error(code, message.message || message);
|
||||
if (message instanceof Error) {
|
||||
error.stack = message.stack;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
export function maybeRunValidator(request, functionName) {
|
||||
const theValidator = getValidator(functionName, Parse.applicationId);
|
||||
if (!theValidator) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return typeof theValidator === 'object'
|
||||
? builtInTriggerValidator(theValidator, request)
|
||||
: theValidator(request);
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(e => {
|
||||
const error = resolveError(e, {
|
||||
code: Parse.Error.VALIDATION_ERROR,
|
||||
message: 'Validation failed.',
|
||||
});
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
function builtInTriggerValidator(options, request) {
|
||||
if (request.master && !options.validateMasterKey) {
|
||||
return;
|
||||
}
|
||||
let reqUser = request.user;
|
||||
if (
|
||||
!reqUser &&
|
||||
request.object &&
|
||||
request.object.className === '_User' &&
|
||||
!request.object.existed()
|
||||
) {
|
||||
reqUser = request.object;
|
||||
}
|
||||
if (options.requireUser && !reqUser) {
|
||||
throw 'Validation failed. Please login to continue.';
|
||||
}
|
||||
if (options.requireMaster && !request.master) {
|
||||
throw 'Validation failed. Master key is required to complete this request.';
|
||||
}
|
||||
let params = request.params || {};
|
||||
if (request.object) {
|
||||
params = request.object.toJSON();
|
||||
}
|
||||
const requiredParam = key => {
|
||||
const value = params[key];
|
||||
if (value == null) {
|
||||
throw `Validation failed. Please specify data for ${key}.`;
|
||||
}
|
||||
};
|
||||
|
||||
const validateOptions = (opt, key, val) => {
|
||||
let opts = opt.options;
|
||||
if (typeof opts === 'function') {
|
||||
try {
|
||||
const result = opts(val);
|
||||
if (!result && result != null) {
|
||||
throw opt.error || `Validation failed. Invalid value for ${key}.`;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!e) {
|
||||
throw opt.error || `Validation failed. Invalid value for ${key}.`;
|
||||
}
|
||||
|
||||
throw opt.error || e.message || e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(opts)) {
|
||||
opts = [opt.options];
|
||||
}
|
||||
|
||||
if (!opts.includes(val)) {
|
||||
throw (
|
||||
opt.error ||
|
||||
`Validation failed. Invalid option for ${key}. Expected: ${opts.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getType = fn => {
|
||||
const match = fn && fn.toString().match(/^\s*function (\w+)/);
|
||||
return (match ? match[1] : '').toLowerCase();
|
||||
};
|
||||
if (Array.isArray(options.fields)) {
|
||||
for (const key of options.fields) {
|
||||
requiredParam(key);
|
||||
}
|
||||
} else {
|
||||
for (const key in options.fields) {
|
||||
const opt = options.fields[key];
|
||||
let val = params[key];
|
||||
if (typeof opt === 'string') {
|
||||
requiredParam(opt);
|
||||
}
|
||||
if (typeof opt === 'object') {
|
||||
if (opt.default != null && val == null) {
|
||||
val = opt.default;
|
||||
params[key] = val;
|
||||
if (request.object) {
|
||||
request.object.set(key, val);
|
||||
}
|
||||
}
|
||||
if (opt.constant && request.object) {
|
||||
if (request.original) {
|
||||
request.object.set(key, request.original.get(key));
|
||||
} else if (opt.default != null) {
|
||||
request.object.set(key, opt.default);
|
||||
}
|
||||
}
|
||||
if (opt.required) {
|
||||
requiredParam(key);
|
||||
}
|
||||
if (opt.type) {
|
||||
const type = getType(opt.type);
|
||||
if (type == 'array' && !Array.isArray(val)) {
|
||||
throw `Validation failed. Invalid type for ${key}. Expected: array`;
|
||||
} else if (typeof val !== type) {
|
||||
throw `Validation failed. Invalid type for ${key}. Expected: ${type}`;
|
||||
}
|
||||
}
|
||||
if (opt.options) {
|
||||
validateOptions(opt, key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const userKeys = options.requireUserKeys || [];
|
||||
if (Array.isArray(userKeys)) {
|
||||
for (const key of userKeys) {
|
||||
if (!reqUser) {
|
||||
throw 'Please login to make this request.';
|
||||
}
|
||||
|
||||
if (reqUser.get(key) == null) {
|
||||
throw `Validation failed. Please set data for ${key} on your account.`;
|
||||
}
|
||||
}
|
||||
} else if (typeof userKeys === 'object') {
|
||||
for (const key in options.requireUserKeys) {
|
||||
const opt = options.requireUserKeys[key];
|
||||
if (opt.options) {
|
||||
validateOptions(opt, key, reqUser.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To be used as part of the promise chain when saving/deleting an object
|
||||
// Will resolve successfully if no trigger is configured
|
||||
// Resolves to an object, empty or containing an object key. A beforeSave
|
||||
@@ -657,6 +878,12 @@ export function maybeRunTrigger(
|
||||
// If triggers do not return a promise, they can run async code parallel
|
||||
// to the RestWrite.execute() call.
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return maybeRunValidator(
|
||||
request,
|
||||
`${triggerType}.${parseObject.className}`
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
const promise = trigger(request);
|
||||
if (
|
||||
@@ -755,6 +982,7 @@ export async function maybeRunFileTrigger(
|
||||
fileObject,
|
||||
config
|
||||
);
|
||||
await maybeRunValidator(request, `${triggerType}.${FileClassName}`);
|
||||
const result = await fileTrigger(request);
|
||||
logTriggerSuccessBeforeHook(
|
||||
triggerType,
|
||||
@@ -788,6 +1016,7 @@ export async function maybeRunConnectTrigger(triggerType, request) {
|
||||
return;
|
||||
}
|
||||
request.user = await userForSessionToken(request.sessionToken);
|
||||
await maybeRunValidator(request, `${triggerType}.${ConnectClassName}`);
|
||||
return trigger(request);
|
||||
}
|
||||
|
||||
@@ -804,6 +1033,7 @@ export async function maybeRunSubscribeTrigger(
|
||||
parseQuery.withJSON(request.query);
|
||||
request.query = parseQuery;
|
||||
request.user = await userForSessionToken(request.sessionToken);
|
||||
await maybeRunValidator(request, `${triggerType}.${className}`);
|
||||
await trigger(request);
|
||||
const query = request.query.toJSON();
|
||||
if (query.keys) {
|
||||
@@ -828,6 +1058,7 @@ export async function maybeRunAfterEventTrigger(
|
||||
request.original = Parse.Object.fromJSON(request.original);
|
||||
}
|
||||
request.user = await userForSessionToken(request.sessionToken);
|
||||
await maybeRunValidator(request, `${triggerType}.${className}`);
|
||||
return trigger(request);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user