Skip to content

Commit 72c5cd3

Browse files
authored
Merge branch 'alpha' into fix-fields
2 parents 6a77722 + 9d461df commit 72c5cd3

26 files changed

+386
-105
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ A big *thank you* 🙏 to our [sponsors](#sponsors) and [backers](#backers) who
6060
- [Configuration](#configuration)
6161
- [Basic Options](#basic-options)
6262
- [Client Key Options](#client-key-options)
63+
- [Access Scopes](#access-scopes)
6364
- [Email Verification and Password Reset](#email-verification-and-password-reset)
6465
- [Password and Account Policy](#password-and-account-policy)
6566
- [Custom Routes](#custom-routes)
@@ -357,6 +358,15 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
357358
* `restAPIKey`
358359
* `dotNetKey`
359360

361+
## Access Scopes
362+
363+
| Scope | Internal data | Custom data | Restricted by CLP, ACL | Key |
364+
|----------------|---------------|-------------|------------------------|---------------------|
365+
| Internal | r/w | r/w | no | `maintenanceKey` |
366+
| Master | -/- | r/w | no | `masterKey` |
367+
| ReadOnlyMaster | -/- | r/- | no | `readOnlyMasterKey` |
368+
| Session | -/- | r/w | yes | `sessionToken` |
369+
360370
## Email Verification and Password Reset
361371

362372
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](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) for more details and a full list of available options.

changelogs/CHANGELOG_alpha.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# [6.0.0-alpha.23](https://github.com/parse-community/parse-server/compare/6.0.0-alpha.22...6.0.0-alpha.23) (2023-01-08)
2+
3+
4+
### Features
5+
6+
* Access the internal scope of Parse Server using the new `maintenanceKey`; the internal scope contains unofficial and undocumented fields (prefixed with underscore `_`) which are used internally by Parse Server; you may want to manipulate these fields for out-of-band changes such as data migration or correction tasks; changes within the internal scope of Parse Server may happen at any time without notice or changelog entry, it is therefore recommended to look at the source code of Parse Server to understand the effects of manipulating internal fields before using the key; it is discouraged to use the `maintenanceKey` for routine operations in a production environment; see [access scopes](https://github.com/parse-community/parse-server#access-scopes) ([#8212](https://github.com/parse-community/parse-server/issues/8212)) ([f3bcc93](https://github.com/parse-community/parse-server/commit/f3bcc9365cd6f08b0a32c132e8e5ff6d1b650863))
7+
8+
9+
### BREAKING CHANGES
10+
11+
* Fields in the internal scope of Parse Server (prefixed with underscore `_`) are only returned using the new `maintenanceKey`; previously the `masterKey` allowed reading of internal fields; see [access scopes](https://github.com/parse-community/parse-server#access-scopes) for a comparison of the keys' access permissions (#8212) ([f3bcc93](f3bcc93))
12+
113
# [6.0.0-alpha.22](https://github.com/parse-community/parse-server/compare/6.0.0-alpha.21...6.0.0-alpha.22) (2023-01-08)
214

315

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "6.0.0-alpha.22",
3+
"version": "6.0.0-alpha.23",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/EmailVerificationToken.spec.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const Auth = require('../lib/Auth');
34
const Config = require('../lib/Config');
45
const request = require('../lib/request');
56

@@ -262,9 +263,14 @@ describe('Email Verification Token Expiration: ', () => {
262263
})
263264
.then(() => {
264265
const config = Config.get('test');
265-
return config.database.find('_User', {
266-
username: 'sets_email_verify_token_expires_at',
267-
});
266+
return config.database.find(
267+
'_User',
268+
{
269+
username: 'sets_email_verify_token_expires_at',
270+
},
271+
{},
272+
Auth.maintenance(config)
273+
);
268274
})
269275
.then(results => {
270276
expect(results.length).toBe(1);
@@ -499,7 +505,12 @@ describe('Email Verification Token Expiration: ', () => {
499505
.then(() => {
500506
const config = Config.get('test');
501507
return config.database
502-
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
508+
.find(
509+
'_User',
510+
{ username: 'newEmailVerifyTokenOnEmailReset' },
511+
{},
512+
Auth.maintenance(config)
513+
)
503514
.then(results => {
504515
return results[0];
505516
});
@@ -582,7 +593,7 @@ describe('Email Verification Token Expiration: ', () => {
582593
// query for this user again
583594
const config = Config.get('test');
584595
return config.database
585-
.find('_User', { username: 'resends_verification_token' })
596+
.find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config))
586597
.then(results => {
587598
return results[0];
588599
});
@@ -599,6 +610,7 @@ describe('Email Verification Token Expiration: ', () => {
599610
done();
600611
})
601612
.catch(error => {
613+
console.log(error);
602614
jfail(error);
603615
done();
604616
});

spec/Middlewares.spec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ describe('middlewares', () => {
162162
expect(fakeReq.auth.isMaster).toBe(false);
163163
});
164164

165+
it('should not succeed if the ip does not belong to maintenanceKeyIps list', async () => {
166+
const logger = require('../lib/logger').logger;
167+
spyOn(logger, 'error').and.callFake(() => {});
168+
AppCache.put(fakeReq.body._ApplicationId, {
169+
maintenanceKey: 'masterKey',
170+
maintenanceKeyIps: ['10.0.0.0', '10.0.0.1'],
171+
});
172+
fakeReq.ip = '10.0.0.2';
173+
fakeReq.headers['x-parse-maintenance-key'] = 'masterKey';
174+
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
175+
expect(fakeReq.auth.isMaintenance).toBe(false);
176+
expect(logger.error).toHaveBeenCalledWith(
177+
`Request using maintenance key rejected as the request IP address '10.0.0.2' is not set in Parse Server option 'maintenanceKeyIps'.`
178+
);
179+
});
180+
165181
it('should succeed if the ip does belong to masterKeyIps list', async () => {
166182
AppCache.put(fakeReq.body._ApplicationId, {
167183
masterKey: 'masterKey',

spec/ParseLiveQuery.spec.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const Auth = require('../lib/Auth');
23
const UserController = require('../lib/Controllers/UserController').UserController;
34
const Config = require('../lib/Config');
45
const validatorFail = () => {
@@ -1020,6 +1021,7 @@ describe('ParseLiveQuery', function () {
10201021
};
10211022

10221023
await reconfigureServer({
1024+
maintenanceKey: 'test2',
10231025
liveQuery: {
10241026
classNames: [Parse.User],
10251027
},
@@ -1041,9 +1043,14 @@ describe('ParseLiveQuery', function () {
10411043
.signUp()
10421044
.then(() => {
10431045
const config = Config.get('test');
1044-
return config.database.find('_User', {
1045-
username: 'zxcv',
1046-
});
1046+
return config.database.find(
1047+
'_User',
1048+
{
1049+
username: 'zxcv',
1050+
},
1051+
{},
1052+
Auth.maintenance(config)
1053+
);
10471054
})
10481055
.then(async results => {
10491056
const foundUser = results[0];

spec/ParseUser.spec.js

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3522,40 +3522,128 @@ describe('Parse.User testing', () => {
35223522
});
35233523
});
35243524

3525-
it('should not allow updates to hidden fields', done => {
3525+
it('should not allow updates to hidden fields', async () => {
35263526
const emailAdapter = {
35273527
sendVerificationEmail: () => {},
35283528
sendPasswordResetEmail: () => Promise.resolve(),
35293529
sendMail: () => Promise.resolve(),
35303530
};
3531-
35323531
const user = new Parse.User();
35333532
user.set({
35343533
username: 'hello',
35353534
password: 'world',
35363535
35373536
});
3537+
await reconfigureServer({
3538+
appName: 'unused',
3539+
verifyUserEmails: true,
3540+
emailAdapter: emailAdapter,
3541+
publicServerURL: 'http://localhost:8378/1',
3542+
});
3543+
await user.signUp();
3544+
user.set('_email_verify_token', 'bad', { ignoreValidation: true });
3545+
await expectAsync(user.save()).toBeRejectedWith(
3546+
new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: _email_verify_token.')
3547+
);
3548+
});
35383549

3539-
reconfigureServer({
3550+
it('should allow updates to fields with maintenanceKey', async () => {
3551+
const emailAdapter = {
3552+
sendVerificationEmail: () => {},
3553+
sendPasswordResetEmail: () => Promise.resolve(),
3554+
sendMail: () => Promise.resolve(),
3555+
};
3556+
const user = new Parse.User();
3557+
user.set({
3558+
username: 'hello',
3559+
password: 'world',
3560+
3561+
});
3562+
await reconfigureServer({
35403563
appName: 'unused',
3564+
maintenanceKey: 'test2',
35413565
verifyUserEmails: true,
3566+
emailVerifyTokenValidityDuration: 5,
3567+
accountLockout: {
3568+
duration: 1,
3569+
threshold: 1,
3570+
},
35423571
emailAdapter: emailAdapter,
35433572
publicServerURL: 'http://localhost:8378/1',
3544-
})
3545-
.then(() => {
3546-
return user.signUp();
3547-
})
3548-
.then(() => {
3549-
return Parse.User.current().set('_email_verify_token', 'bad').save();
3550-
})
3551-
.then(() => {
3552-
fail('Should not be able to update email verification token');
3553-
done();
3554-
})
3555-
.catch(err => {
3556-
expect(err).toBeDefined();
3557-
done();
3558-
});
3573+
});
3574+
await user.signUp();
3575+
for (let i = 0; i < 2; i++) {
3576+
try {
3577+
await Parse.User.logIn(user.getEmail(), 'abc');
3578+
} catch (e) {
3579+
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
3580+
expect(
3581+
e.message === 'Invalid username/password.' ||
3582+
e.message ===
3583+
'Your account is locked due to multiple failed login attempts. Please try again after 1 minute(s)'
3584+
).toBeTrue();
3585+
}
3586+
}
3587+
await Parse.User.requestPasswordReset(user.getEmail());
3588+
const headers = {
3589+
'X-Parse-Application-Id': 'test',
3590+
'X-Parse-Rest-API-Key': 'rest',
3591+
'X-Parse-Maintenance-Key': 'test2',
3592+
'Content-Type': 'application/json',
3593+
};
3594+
const userMaster = await request({
3595+
method: 'GET',
3596+
url: `http://localhost:8378/1/classes/_User`,
3597+
json: true,
3598+
headers,
3599+
}).then(res => res.data.results[0]);
3600+
expect(Object.keys(userMaster).sort()).toEqual(
3601+
[
3602+
'ACL',
3603+
'_account_lockout_expires_at',
3604+
'_email_verify_token',
3605+
'_email_verify_token_expires_at',
3606+
'_failed_login_count',
3607+
'_perishable_token',
3608+
'createdAt',
3609+
'email',
3610+
'emailVerified',
3611+
'objectId',
3612+
'updatedAt',
3613+
'username',
3614+
].sort()
3615+
);
3616+
const toSet = {
3617+
_account_lockout_expires_at: new Date(),
3618+
_email_verify_token: 'abc',
3619+
_email_verify_token_expires_at: new Date(),
3620+
_failed_login_count: 0,
3621+
_perishable_token_expires_at: new Date(),
3622+
_perishable_token: 'abc',
3623+
};
3624+
await request({
3625+
method: 'PUT',
3626+
headers,
3627+
url: Parse.serverURL + '/users/' + userMaster.objectId,
3628+
json: true,
3629+
body: toSet,
3630+
}).then(res => res.data);
3631+
const update = await request({
3632+
method: 'GET',
3633+
url: `http://localhost:8378/1/classes/_User`,
3634+
json: true,
3635+
headers,
3636+
}).then(res => res.data.results[0]);
3637+
for (const key in toSet) {
3638+
const value = toSet[key];
3639+
if (update[key] && update[key].iso) {
3640+
expect(update[key].iso).toEqual(value.toISOString());
3641+
} else if (value.toISOString) {
3642+
expect(update[key]).toEqual(value.toISOString());
3643+
} else {
3644+
expect(update[key]).toEqual(value);
3645+
}
3646+
}
35593647
});
35603648

35613649
it('should revoke sessions when setting paswword with masterKey (#3289)', done => {

spec/PasswordPolicy.spec.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,12 +1677,19 @@ describe('Password Policy: ', () => {
16771677
});
16781678

16791679
it('should not infinitely loop if maxPasswordHistory is 1 (#4918)', async () => {
1680+
const headers = {
1681+
'X-Parse-Application-Id': 'test',
1682+
'X-Parse-Rest-API-Key': 'test',
1683+
'X-Parse-Maintenance-Key': 'test2',
1684+
'Content-Type': 'application/json',
1685+
};
16801686
const user = new Parse.User();
16811687
const query = new Parse.Query(Parse.User);
16821688

16831689
await reconfigureServer({
16841690
appName: 'passwordPolicy',
16851691
verifyUserEmails: false,
1692+
maintenanceKey: 'test2',
16861693
passwordPolicy: {
16871694
maxPasswordHistory: 1,
16881695
},
@@ -1696,15 +1703,28 @@ describe('Password Policy: ', () => {
16961703
user.setPassword('user2');
16971704
await user.save();
16981705

1699-
const result1 = await query.get(user.id, { useMasterKey: true });
1700-
expect(result1.get('_password_history').length).toBe(1);
1706+
const user1 = await query.get(user.id, { useMasterKey: true });
1707+
expect(user1.get('_password_history')).toBeUndefined();
1708+
1709+
const result1 = await request({
1710+
method: 'GET',
1711+
url: `http://localhost:8378/1/classes/_User/${user.id}`,
1712+
json: true,
1713+
headers,
1714+
}).then(res => res.data);
1715+
expect(result1._password_history.length).toBe(1);
17011716

17021717
user.setPassword('user3');
17031718
await user.save();
17041719

1705-
const result2 = await query.get(user.id, { useMasterKey: true });
1706-
expect(result2.get('_password_history').length).toBe(1);
1720+
const result2 = await request({
1721+
method: 'GET',
1722+
url: `http://localhost:8378/1/classes/_User/${user.id}`,
1723+
json: true,
1724+
headers,
1725+
}).then(res => res.data);
1726+
expect(result2._password_history.length).toBe(1);
17071727

1708-
expect(result1.get('_password_history')).not.toEqual(result2.get('_password_history'));
1728+
expect(result1._password_history).not.toEqual(result2._password_history);
17091729
});
17101730
});

0 commit comments

Comments
 (0)