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
7 changes: 7 additions & 0 deletions changelogs/CHANGELOG_alpha.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [9.6.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.6.0...9.6.1-alpha.1) (2026-03-22)


### Bug Fixes

* User cannot retrieve own email with `protectedFieldsOwnerExempt: false` despite `email` not in `protectedFields` ([#10284](https://github.com/parse-community/parse-server/issues/10284)) ([4a65d77](https://github.com/parse-community/parse-server/commit/4a65d77ea3fd2ccb121d4bd28e92435295203bf7))

# [9.6.0-alpha.56](https://github.com/parse-community/parse-server/compare/9.6.0-alpha.55...9.6.0-alpha.56) (2026-03-22)


Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "9.6.0",
"version": "9.6.1-alpha.1",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
Expand Down
56 changes: 56 additions & 0 deletions spec/ProtectedFields.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1972,5 +1972,61 @@ describe('ProtectedFields', function () {
expect(response.data.phone).toBeUndefined();
expect(response.data.objectId).toBe(user.id);
});

it('owner sees non-protected fields like email when protectedFieldsOwnerExempt is true', async function () {
await reconfigureServer({
protectedFields: {
_User: {
'*': ['phone'],
},
},
protectedFieldsOwnerExempt: true,
});
const user = await Parse.User.signUp('user1', 'password');
const sessionToken = user.getSessionToken();
user.set('phone', '555-1234');
user.set('email', 'user1@example.com');
await user.save(null, { sessionToken });

// Owner fetches own object — phone and email should be visible (owner exempt)
const response = await request({
url: `http://localhost:8378/1/users/${user.id}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
expect(response.data.phone).toBe('555-1234');
expect(response.data.email).toBe('user1@example.com');
});

it('owner sees non-protected fields like email when protectedFieldsOwnerExempt is false', async function () {
await reconfigureServer({
protectedFields: {
_User: {
'*': ['phone'],
},
},
protectedFieldsOwnerExempt: false,
});
const user = await Parse.User.signUp('user1', 'password');
const sessionToken = user.getSessionToken();
user.set('phone', '555-1234');
user.set('email', 'user1@example.com');
await user.save(null, { sessionToken });

// Owner fetches own object — phone should be hidden, email should be visible
const response = await request({
url: `http://localhost:8378/1/users/${user.id}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
expect(response.data.phone).toBeUndefined();
expect(response.data.email).toBe('user1@example.com');
});
});
});
2 changes: 1 addition & 1 deletion src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ module.exports.ParseServerOptions = {
},
protectedFieldsOwnerExempt: {
env: 'PARSE_SERVER_PROTECTED_FIELDS_OWNER_EXEMPT',
help: "Whether the `_User` class is exempt from `protectedFields` when the logged-in user queries their own user object. If `true` (default), a user can see all their own fields regardless of `protectedFields` configuration. If `false`, `protectedFields` applies equally to the user's own object, consistent with all other classes. Defaults to `true`.",
help: "Whether the `_User` class is exempt from `protectedFields` when the logged-in user queries their own user object. If `true` (default), a user can see all their own fields regardless of `protectedFields` configuration; default protected fields (e.g. `email`) are merged into any custom `protectedFields` configuration. If `false`, `protectedFields` applies equally to the user's own object, consistent with all other classes; only explicitly configured protected fields apply, defaults are not merged. Defaults to `true`.",
action: parsers.booleanParser,
default: true,
},
Expand Down
2 changes: 1 addition & 1 deletion src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export interface ParseServerOptions {
/* Fields per class that are hidden from query results for specific user groups. Protected fields are stripped from the server response, but can still be used internally (e.g. in Cloud Code triggers). Configure as `{ 'ClassName': { 'UserGroup': ['field1', 'field2'] } }` where `UserGroup` is one of: `'*'` (all users), `'authenticated'` (authenticated users), `'role:RoleName'` (users with a specific role), `'userField:FieldName'` (users referenced by a pointer field), or a user `objectId` to target a specific user. When multiple groups apply, the intersection of their protected fields is used. By default, `email` is protected on the `_User` class for all users. On the `_User` class, the object owner is exempt from protected fields by default; see `protectedFieldsOwnerExempt` to change this.
:DEFAULT: {"_User": {"*": ["email"]}} */
protectedFields: ?ProtectedFields;
/* Whether the `_User` class is exempt from `protectedFields` when the logged-in user queries their own user object. If `true` (default), a user can see all their own fields regardless of `protectedFields` configuration. If `false`, `protectedFields` applies equally to the user's own object, consistent with all other classes. Defaults to `true`.
/* Whether the `_User` class is exempt from `protectedFields` when the logged-in user queries their own user object. If `true` (default), a user can see all their own fields regardless of `protectedFields` configuration; default protected fields (e.g. `email`) are merged into any custom `protectedFields` configuration. If `false`, `protectedFields` applies equally to the user's own object, consistent with all other classes; only explicitly configured protected fields apply, defaults are not merged. Defaults to `true`.
:ENV: PARSE_SERVER_PROTECTED_FIELDS_OWNER_EXEMPT
:DEFAULT: true */
protectedFieldsOwnerExempt: ?boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/ParseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ function injectDefaults(options: ParseServerOptions) {
options.protectedFields[c] = defaults.protectedFields[c];
} else {
Object.keys(defaults.protectedFields[c]).forEach(r => {
if (options.protectedFields[c][r] && options.protectedFieldsOwnerExempt === false) {
return;
}
const unq = new Set([
...(options.protectedFields[c][r] || []),
...defaults.protectedFields[c][r],
Expand Down
Loading