Skip to content
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)
- 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);
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