Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo

<sub>(1) `Parse.Object.createdAt`, `Parse.Object.updatedAt`.</sub>

> [!NOTE]
> In Cloud Code, both `masterKey` and `readOnlyMasterKey` set `request.master` to `true`. To distinguish between them, check `request.isReadOnly`. For example, use `request.master && !request.isReadOnly` to ensure full master key access.

## Email Verification and Password Reset

Verifying user email addresses and enabling password reset via email requires an email adapter. There are many email adapters provided and maintained by the community. The following is an example configuration with an example email adapter. See the [Parse Server Options][server-options] for more details and a full list of available options.
Expand Down
96 changes: 96 additions & 0 deletions spec/rest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,102 @@ describe('read-only masterKey', () => {
expect(res.data.error).toBe('Permission denied');
}
});

it('should expose isReadOnly in Cloud Function request when using readOnlyMasterKey', async () => {
let receivedMaster;
let receivedIsReadOnly;
Parse.Cloud.define('checkReadOnly', req => {
receivedMaster = req.master;
receivedIsReadOnly = req.isReadOnly;
return 'ok';
});

await request({
method: 'POST',
url: `${Parse.serverURL}/functions/checkReadOnly`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': 'read-only-test',
'Content-Type': 'application/json',
},
body: {},
});

expect(receivedMaster).toBe(true);
expect(receivedIsReadOnly).toBe(true);
});

it('should not set isReadOnly in Cloud Function request when using masterKey', async () => {
let receivedMaster;
let receivedIsReadOnly;
Parse.Cloud.define('checkNotReadOnly', req => {
receivedMaster = req.master;
receivedIsReadOnly = req.isReadOnly;
return 'ok';
});

await request({
method: 'POST',
url: `${Parse.serverURL}/functions/checkNotReadOnly`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
'Content-Type': 'application/json',
},
body: {},
});

expect(receivedMaster).toBe(true);
expect(receivedIsReadOnly).toBe(false);
});

it('should expose isReadOnly in beforeFind trigger when using readOnlyMasterKey', async () => {
let receivedMaster;
let receivedIsReadOnly;
Parse.Cloud.beforeFind('ReadOnlyTriggerTest', req => {
receivedMaster = req.master;
receivedIsReadOnly = req.isReadOnly;
});

const obj = new Parse.Object('ReadOnlyTriggerTest');
await obj.save(null, { useMasterKey: true });

await request({
method: 'GET',
url: `${Parse.serverURL}/classes/ReadOnlyTriggerTest`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': 'read-only-test',
},
});

expect(receivedMaster).toBe(true);
expect(receivedIsReadOnly).toBe(true);
});

it('should not set isReadOnly in beforeFind trigger when using masterKey', async () => {
let receivedMaster;
let receivedIsReadOnly;
Parse.Cloud.beforeFind('ReadOnlyTriggerTestNeg', req => {
receivedMaster = req.master;
receivedIsReadOnly = req.isReadOnly;
});

const obj = new Parse.Object('ReadOnlyTriggerTestNeg');
await obj.save(null, { useMasterKey: true });

await request({
method: 'GET',
url: `${Parse.serverURL}/classes/ReadOnlyTriggerTestNeg`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
},
});

expect(receivedMaster).toBe(true);
expect(receivedIsReadOnly).toBe(false);
});
});

describe('rest context', () => {
Expand Down
1 change: 1 addition & 0 deletions src/Routers/FunctionsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export class FunctionsRouter extends PromiseRouter {
params: params,
config: req.config,
master: req.auth && req.auth.isMaster,
isReadOnly: !!(req.auth && req.auth.isReadOnly),
user: req.auth && req.auth.user,
installationId: req.info.installationId,
log: req.config.loggerController,
Expand Down
15 changes: 10 additions & 5 deletions src/cloud-code/Parse.Cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,8 @@ module.exports = ParseCloud;
/**
* @interface Parse.Cloud.TriggerRequest
* @property {String} installationId If set, the installationId triggering the request.
* @property {Boolean} master If true, means the master key was used.
* @property {Boolean} master If true, means the master key or the read-only master key was used.
* @property {Boolean} isReadOnly If true, means the read-only master key was used. This is a subset of `master`, so `master` will also be true. Use `master && !isReadOnly` to check for full master key access.
* @property {Boolean} isChallenge If true, means the current request is originally triggered by an auth challenge.
* @property {Parse.User} user If set, the user that made the request.
* @property {Parse.Object} object The object triggering the hook.
Expand All @@ -745,7 +746,8 @@ module.exports = ParseCloud;
/**
* @interface Parse.Cloud.FileTriggerRequest
* @property {String} installationId If set, the installationId triggering the request.
* @property {Boolean} master If true, means the master key was used.
* @property {Boolean} master If true, means the master key or the read-only master key was used.
* @property {Boolean} isReadOnly If true, means the read-only master key was used. This is a subset of `master`, so `master` will also be true. Use `master && !isReadOnly` to check for full master key access.
* @property {Parse.User} user If set, the user that made the request.
* @property {Parse.File} file The file that triggered the hook.
* @property {Integer} fileSize The size of the file in bytes.
Expand Down Expand Up @@ -784,7 +786,8 @@ module.exports = ParseCloud;
/**
* @interface Parse.Cloud.BeforeFindRequest
* @property {String} installationId If set, the installationId triggering the request.
* @property {Boolean} master If true, means the master key was used.
* @property {Boolean} master If true, means the master key or the read-only master key was used.
* @property {Boolean} isReadOnly If true, means the read-only master key was used. This is a subset of `master`, so `master` will also be true. Use `master && !isReadOnly` to check for full master key access.
* @property {Parse.User} user If set, the user that made the request.
* @property {Parse.Query} query The query triggering the hook.
* @property {String} ip The IP address of the client making the request.
Expand All @@ -798,7 +801,8 @@ module.exports = ParseCloud;
/**
* @interface Parse.Cloud.AfterFindRequest
* @property {String} installationId If set, the installationId triggering the request.
* @property {Boolean} master If true, means the master key was used.
* @property {Boolean} master If true, means the master key or the read-only master key was used.
* @property {Boolean} isReadOnly If true, means the read-only master key was used. This is a subset of `master`, so `master` will also be true. Use `master && !isReadOnly` to check for full master key access.
* @property {Parse.User} user If set, the user that made the request.
* @property {Parse.Query} query The query triggering the hook.
* @property {Array<Parse.Object>} results The results the query yielded.
Expand All @@ -812,7 +816,8 @@ module.exports = ParseCloud;
/**
* @interface Parse.Cloud.FunctionRequest
* @property {String} installationId If set, the installationId triggering the request.
* @property {Boolean} master If true, means the master key was used.
* @property {Boolean} master If true, means the master key or the read-only master key was used.
* @property {Boolean} isReadOnly If true, means the read-only master key was used. This is a subset of `master`, so `master` will also be true. Use `master && !isReadOnly` to check for full master key access.
* @property {Parse.User} user If set, the user that made the request.
* @property {Object} params The params passed to the cloud function.
* @property {String} ip The IP address of the client making the request.
Expand Down
12 changes: 12 additions & 0 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export function getRequestObject(
triggerName: triggerType,
object: parseObject,
master: false,
isReadOnly: false,
log: config.loggerController,
headers: config.headers,
ip: config.ip,
Expand Down Expand Up @@ -301,6 +302,9 @@ export function getRequestObject(
if (auth.isMaster) {
request['master'] = true;
}
if (auth.isReadOnly) {
request['isReadOnly'] = true;
}
if (auth.user) {
request['user'] = auth.user;
}
Expand All @@ -317,6 +321,7 @@ export function getRequestQueryObject(triggerType, auth, query, count, config, c
triggerName: triggerType,
query,
master: false,
isReadOnly: false,
count,
log: config.loggerController,
isGet,
Expand All @@ -332,6 +337,9 @@ export function getRequestQueryObject(triggerType, auth, query, count, config, c
if (auth.isMaster) {
request['master'] = true;
}
if (auth.isReadOnly) {
request['isReadOnly'] = true;
}
if (auth.user) {
request['user'] = auth.user;
}
Expand Down Expand Up @@ -1019,6 +1027,7 @@ export function getRequestFileObject(triggerType, auth, fileObject, config) {
...fileObject,
triggerName: triggerType,
master: false,
isReadOnly: false,
log: config.loggerController,
headers: config.headers,
ip: config.ip,
Expand All @@ -1031,6 +1040,9 @@ export function getRequestFileObject(triggerType, auth, fileObject, config) {
if (auth.isMaster) {
request['master'] = true;
}
if (auth.isReadOnly) {
request['isReadOnly'] = true;
}
if (auth.user) {
request['user'] = auth.user;
}
Expand Down
Loading