Skip to content

Commit 8a3db3b

Browse files
authored
fix: Endpoints /login and /verifyPassword ignore _User protectedFields (#10409)
1 parent 57a4fba commit 8a3db3b

File tree

2 files changed

+116
-5
lines changed

2 files changed

+116
-5
lines changed

spec/ProtectedFields.spec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,64 @@ describe('ProtectedFields', function () {
19731973
expect(response.data.objectId).toBe(user.id);
19741974
});
19751975

1976+
it('/login respects protectedFieldsOwnerExempt: false', async function () {
1977+
await reconfigureServer({
1978+
protectedFields: {
1979+
_User: {
1980+
'*': ['phone'],
1981+
},
1982+
},
1983+
protectedFieldsOwnerExempt: false,
1984+
});
1985+
const user = await Parse.User.signUp('user1', 'password');
1986+
const sessionToken = user.getSessionToken();
1987+
user.set('phone', '555-1234');
1988+
await user.save(null, { sessionToken });
1989+
1990+
const response = await request({
1991+
method: 'POST',
1992+
url: 'http://localhost:8378/1/login',
1993+
headers: {
1994+
'X-Parse-Application-Id': 'test',
1995+
'X-Parse-REST-API-Key': 'rest',
1996+
'Content-Type': 'application/json',
1997+
},
1998+
body: JSON.stringify({ username: 'user1', password: 'password' }),
1999+
});
2000+
expect(response.data.phone).toBeUndefined();
2001+
expect(response.data.objectId).toBe(user.id);
2002+
expect(response.data.sessionToken).toBeDefined();
2003+
});
2004+
2005+
it('/verifyPassword respects protectedFieldsOwnerExempt: false', async function () {
2006+
await reconfigureServer({
2007+
protectedFields: {
2008+
_User: {
2009+
'*': ['phone'],
2010+
},
2011+
},
2012+
protectedFieldsOwnerExempt: false,
2013+
verifyUserEmails: false,
2014+
});
2015+
const user = await Parse.User.signUp('user1', 'password');
2016+
const sessionToken = user.getSessionToken();
2017+
user.set('phone', '555-1234');
2018+
await user.save(null, { sessionToken });
2019+
2020+
const response = await request({
2021+
method: 'POST',
2022+
url: 'http://localhost:8378/1/verifyPassword',
2023+
headers: {
2024+
'X-Parse-Application-Id': 'test',
2025+
'X-Parse-REST-API-Key': 'rest',
2026+
'Content-Type': 'application/json',
2027+
},
2028+
body: JSON.stringify({ username: 'user1', password: 'password' }),
2029+
});
2030+
expect(response.data.phone).toBeUndefined();
2031+
expect(response.data.objectId).toBe(user.id);
2032+
});
2033+
19762034
it('owner sees non-protected fields like email when protectedFieldsOwnerExempt is true', async function () {
19772035
await reconfigureServer({
19782036
protectedFields: {

src/Routers/UsersRouter.js

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,39 @@ export class UsersRouter extends ClassesRouter {
364364
req.info.context
365365
);
366366

367+
// Re-fetch the user with the caller's auth context so that
368+
// protectedFields and CLP apply correctly
369+
const userAuth = new Auth.Auth({
370+
config: req.config,
371+
isMaster: false,
372+
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
373+
installationId: req.info.installationId,
374+
});
375+
let filteredUser;
376+
try {
377+
const filteredUserResponse = await rest.get(
378+
req.config,
379+
userAuth,
380+
'_User',
381+
user.objectId,
382+
{},
383+
req.info.clientSDK,
384+
req.info.context
385+
);
386+
filteredUser = filteredUserResponse.results?.[0];
387+
} catch {
388+
// re-fetch may fail for legacy users without ACL; fall through
389+
}
390+
if (!filteredUser) {
391+
filteredUser = user;
392+
}
393+
UsersRouter.removeHiddenProperties(filteredUser);
394+
filteredUser.sessionToken = user.sessionToken;
367395
if (authDataResponse) {
368-
user.authDataResponse = authDataResponse;
396+
filteredUser.authDataResponse = authDataResponse;
369397
}
370-
await req.config.authDataManager.runAfterFind(req, user.authData);
371398

372-
return { response: user };
399+
return { response: filteredUser };
373400
}
374401

375402
/**
@@ -436,8 +463,34 @@ export class UsersRouter extends ClassesRouter {
436463
.then(async user => {
437464
// Remove hidden properties.
438465
UsersRouter.removeHiddenProperties(user);
439-
await req.config.authDataManager.runAfterFind(req, user.authData);
440-
return { response: user };
466+
// Re-fetch the user with the caller's auth context so that
467+
// protectedFields and CLP apply correctly
468+
const userAuth = new Auth.Auth({
469+
config: req.config,
470+
isMaster: false,
471+
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
472+
installationId: req.info.installationId,
473+
});
474+
let filteredUser;
475+
try {
476+
const filteredUserResponse = await rest.get(
477+
req.config,
478+
userAuth,
479+
'_User',
480+
user.objectId,
481+
{},
482+
req.info.clientSDK,
483+
req.info.context
484+
);
485+
filteredUser = filteredUserResponse.results?.[0];
486+
} catch {
487+
// re-fetch may fail for legacy users without ACL; fall through
488+
}
489+
if (!filteredUser) {
490+
filteredUser = user;
491+
}
492+
UsersRouter.removeHiddenProperties(filteredUser);
493+
return { response: filteredUser };
441494
})
442495
.catch(error => {
443496
throw error;

0 commit comments

Comments
 (0)