Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: allow options to be async on Cloud Validator #7155

Merged
merged 10 commits into from
Feb 23, 2021
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
__BREAKING CHANGES:__
- NEW: Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html). [#7071](https://github.com/parse-community/parse-server/pull/7071). Thanks to [dblythy](https://github.com/dblythy), [Manuel Trezza](https://github.com/mtrezza).
___
- IMPROVE: Allow Cloud Validator `options` to be async [#7155](https://github.com/parse-community/parse-server/pull/7155). Thanks to [dblythy](https://github.com/dblythy)
dblythy marked this conversation as resolved.
Show resolved Hide resolved
- NEW (EXPERIMENTAL): Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification. **Caution, this is an experimental feature that may not be appropriate for production.** [#6891](https://github.com/parse-community/parse-server/issues/6891). Thanks to [Manuel Trezza](https://github.com/mtrezza).
- NEW: Added convenience method `Parse.Cloud.sendEmail(...)` to send email via email adapter in Cloud Code. [#7089](https://github.com/parse-community/parse-server/pull/7089). Thanks to [dblythy](https://github.com/dblythy)
- NEW: LiveQuery support for $and, $nor, $containedBy, $geoWithin, $geoIntersects queries [#7113](https://github.com/parse-community/parse-server/pull/7113). Thanks to [dplewis](https://github.com/dplewis)
Expand All @@ -25,6 +26,7 @@ ___
- IMPROVE: Added new account lockout policy option `accountLockout.unlockOnPasswordReset` to automatically unlock account on password reset. [#7146](https://github.com/parse-community/parse-server/pull/7146). Thanks to [Manuel Trezza](https://github.com/mtrezza).
- IMPROVE: Parse Server is from now on continuously tested against all recent MongoDB versions that have not reached their end-of-life support date. Added MongoDB compatibility table to Parse Server docs. [7161](https://github.com/parse-community/parse-server/pull/7161). Thanks to [Manuel Trezza](https://github.com/mtrezza).
- IMPROVE: Parse Server is from now on continuously tested against all recent Node.js versions that have not reached their end-of-life support date. [7161](https://github.com/parse-community/parse-server/pull/7177). Thanks to [Manuel Trezza](https://github.com/mtrezza).
- IMPROVE: Allow Cloud Validator `options` to be async [#7155](https://github.com/parse-community/parse-server/pull/7155). Thanks to [dblythy](https://github.com/dblythy)
- IMPROVE: Optimize queries on classes with pointer permissions. [#7061](https://github.com/parse-community/parse-server/pull/7061). Thanks to [Pedro Diaz](https://github.com/pdiaz)
- IMPROVE: Parse Server will from now on be continuously tested against all relevant Postgres versions (minor versions). Added Postgres compatibility table to Parse Server docs. [#7176](https://github.com/parse-community/parse-server/pull/7176). Thanks to [Corey Baker](https://github.com/cbaker6).
- FIX: Fix error when a not yet inserted job is updated [#7196](https://github.com/parse-community/parse-server/pull/7196). Thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo).
Expand Down
76 changes: 76 additions & 0 deletions spec/CloudCode.Validator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1428,4 +1428,80 @@ describe('cloud validator', () => {
done();
}
});

it('set params options function async', async () => {
Parse.Cloud.define(
'hello',
() => {
return 'Hello world!';
},
{
fields: {
data: {
type: String,
required: true,
options: async val => {
await new Promise(resolve => {
setTimeout(resolve, 500);
});
return val === 'f';
},
error: 'Validation failed.',
},
},
}
);
try {
await Parse.Cloud.run('hello', { data: 'd' });
fail('validation should have failed');
} catch (error) {
expect(error.code).toEqual(Parse.Error.VALIDATION_ERROR);
dblythy marked this conversation as resolved.
Show resolved Hide resolved
expect(error.message).toEqual('Validation failed.');
}
const result = await Parse.Cloud.run('hello', { data: 'f' });
expect(result).toBe('Hello world!');
});

it('basic beforeSave requireUserKey as custom async function', async () => {
Parse.Cloud.beforeSave(Parse.User, () => {}, {
fields: {
accType: {
default: 'normal',
constant: true,
},
},
});
Parse.Cloud.define(
'secureFunction',
() => {
return "Here's all the secure data!";
},
{
requireUserKeys: {
accType: {
options: async val => {
await new Promise(resolve => {
setTimeout(resolve, 500);
});
return ['admin', 'admin2'].includes(val);
},
error: 'Unauthorized.',
},
},
}
);
const user = new Parse.User();
user.set('username', 'testuser');
user.set('password', 'p@ssword');
user.set('accType', 'admin');
await user.signUp();
expect(user.get('accType')).toBe('normal');
try {
await Parse.Cloud.run('secureFunction');
fail('function should only be available to admin users');
} catch (error) {
expect(error.code).toEqual(Parse.Error.VALIDATION_ERROR);
expect(error.message).toEqual('Unauthorized.');
}
});
});
13 changes: 0 additions & 13 deletions spec/PushController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,6 @@ describe('PushController', () => {
done();
});

it('can throw on validateDeviceType when single invalid device type is set', done => {
// Make query condition
const where = {
deviceType: 'osx',
};
const validPushTypes = ['ios', 'android'];

expect(function () {
validatePushType(where, validPushTypes);
}).toThrow();
done();
});

it('can get expiration time in string format', done => {
// Make mock request
const timeStr = '2015-03-19T22:05:08Z';
Expand Down
2 changes: 2 additions & 0 deletions spec/PushWorker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ describe('PushWorker', () => {
amount: 1,
},
count: { __op: 'Increment', amount: -1 },
status: 'running',
});
const query = new Parse.Query('_PushStatus');
return query.get(handler.objectId, { useMasterKey: true });
Expand Down Expand Up @@ -409,6 +410,7 @@ describe('PushWorker', () => {
amount: 1,
},
count: { __op: 'Increment', amount: -1 },
status: 'running',
});
done();
});
Expand Down
3 changes: 1 addition & 2 deletions src/StatusHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,8 @@ export function pushStatusHandler(config, existingObjectId) {
}
);
}

// indicate this batch is complete
incrementOp(update, 'count', -1);
update.status = 'running';

return handler.update({ objectId }, update).then(res => {
if (res && res.count === 0) {
Expand Down
12 changes: 8 additions & 4 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,11 +662,11 @@ async function builtInTriggerValidator(options, request, auth) {
}
};

const validateOptions = (opt, key, val) => {
const validateOptions = async (opt, key, val) => {
let opts = opt.options;
if (typeof opts === 'function') {
try {
const result = opts(val);
const result = await opts(val);
if (!result && result != null) {
throw opt.error || `Validation failed. Invalid value for ${key}.`;
}
Expand Down Expand Up @@ -699,6 +699,7 @@ async function builtInTriggerValidator(options, request, auth) {
requiredParam(key);
}
} else {
const optionPromises = [];
for (const key in options.fields) {
const opt = options.fields[key];
let val = params[key];
Expand Down Expand Up @@ -731,10 +732,11 @@ async function builtInTriggerValidator(options, request, auth) {
}
}
if (opt.options) {
validateOptions(opt, key, val);
optionPromises.push(validateOptions(opt, key, val));
}
}
}
await Promise.all(optionPromises);
}
let userRoles = options.requireAnyUserRoles;
let requireAllRoles = options.requireAllUserRoles;
Expand Down Expand Up @@ -780,12 +782,14 @@ async function builtInTriggerValidator(options, request, auth) {
}
}
} else if (typeof userKeys === 'object') {
const optionPromises = [];
for (const key in options.requireUserKeys) {
const opt = options.requireUserKeys[key];
if (opt.options) {
validateOptions(opt, key, reqUser.get(key));
optionPromises.push(validateOptions(opt, key, reqUser.get(key)));
}
}
await Promise.all(optionPromises);
}
}

Expand Down