mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: User cannot retrieve own email with protectedFieldsOwnerExempt: false despite email not in protectedFields (#10284)
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@
|
||||
* @property {Boolean} preventLoginWithUnverifiedEmail Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.<br><br>The `createdWith` values per scenario:<ul><li>Password signup: `{ action: 'signup', authProvider: 'password' }`</li><li>Auth provider signup: `{ action: 'signup', authProvider: '<provider>' }`</li><li>Password login: `{ action: 'login', authProvider: 'password' }`</li><li>Auth provider login: function not invoked; auth provider login bypasses email verification</li></ul>Default is `false`.<br>Requires option `verifyUserEmails: true`.
|
||||
* @property {Boolean} preventSignupWithUnverifiedEmail If set to `true` it prevents a user from signing up if the email has not yet been verified and email verification is required. In that case the server responds to the sign-up with HTTP status 400 and a Parse Error 205 `EMAIL_NOT_FOUND`. If set to `false` the server responds with HTTP status 200, and client SDKs return an unauthenticated Parse User without session token. In that case subsequent requests fail until the user's email address is verified.<br><br>Default is `false`.<br>Requires option `verifyUserEmails: true`.
|
||||
* @property {ProtectedFields} protectedFields 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.
|
||||
* @property {Boolean} protectedFieldsOwnerExempt 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`.
|
||||
* @property {Boolean} protectedFieldsOwnerExempt 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`.
|
||||
* @property {Union} publicServerURL Optional. The public URL to Parse Server. This URL will be used to reach Parse Server publicly for features like password reset and email verification links. The option can be set to a string or a function that can be asynchronously resolved. The returned URL string must start with `http://` or `https://`.
|
||||
* @property {Any} push Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications
|
||||
* @property {RateLimitOptions[]} rateLimit Options to limit repeated requests to Parse Server APIs. This can be used to protect sensitive endpoints such as `/requestPasswordReset` from brute-force attacks or Parse Server as a whole from denial-of-service (DoS) attacks.<br><br>ℹ️ Mind the following limitations:<br>- rate limits applied per IP address; this limits protection against distributed denial-of-service (DDoS) attacks where many requests are coming from various IP addresses<br>- if multiple Parse Server instances are behind a load balancer or ran in a cluster, each instance will calculate it's own request rates, independent from other instances; this limits the applicability of this feature when using a load balancer and another rate limiting solution that takes requests across all instances into account may be more suitable<br>- this feature provides basic protection against denial-of-service attacks, but a more sophisticated solution works earlier in the request flow and prevents a malicious requests to even reach a server instance; it's therefore recommended to implement a solution according to architecture and use case.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user