Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow read and write of internal fields with maintenanceKey #8212

Merged
merged 40 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4bd135c
fix: prevent stripping of user fields with masterKey
dblythy Oct 6, 2022
906072d
feat: allow read and write of internal fields with masterKey
dblythy Oct 6, 2022
7e9bb67
Update ParseUser.spec.js
dblythy Oct 6, 2022
1be7ff5
fix lint
dblythy Oct 6, 2022
62c14eb
run prettier
dblythy Oct 6, 2022
5797d43
add maintenance
dblythy Oct 27, 2022
7fc8227
Update UsersRouter.js
dblythy Oct 27, 2022
9473747
Merge branch 'alpha' into fix-master
dblythy Oct 27, 2022
d06ae11
fix tests
dblythy Oct 27, 2022
7538dec
Merge branch 'fix-master' of https://github.com/dblythy/parse-server …
dblythy Oct 27, 2022
bf8c511
Merge branch 'alpha' into fix-master
dblythy Oct 27, 2022
3f14373
run lint
dblythy Oct 27, 2022
4b88000
Merge branch 'fix-master' of https://github.com/dblythy/parse-server …
dblythy Oct 27, 2022
da1f421
Update UserController.js
dblythy Oct 27, 2022
f2bcdcc
fix tests
dblythy Oct 27, 2022
9388f6a
Merge branch 'alpha' into fix-master
dblythy Nov 3, 2022
62f511d
Merge branch 'alpha' into fix-master
dblythy Nov 23, 2022
767e3a8
add ::1
dblythy Nov 23, 2022
d2c9e81
prettier
dblythy Nov 23, 2022
518dec0
Update package-lock.json
dblythy Nov 23, 2022
4814cce
Merge branch 'alpha' into fix-master
mtrezza Nov 26, 2022
2e39b18
Merge branch 'alpha' into fix-master
dblythy Nov 27, 2022
4dc463c
wip
dblythy Nov 27, 2022
e6ef840
Merge branch 'alpha' into fix-master
dblythy Dec 15, 2022
dabd501
prettier
dblythy Dec 15, 2022
179879d
add access scopes to README
mtrezza Dec 17, 2022
80ce4ed
Merge branch 'alpha' into fix-master
mtrezza Dec 17, 2022
cae5680
Merge branch 'alpha' into fix-master
dblythy Dec 22, 2022
1987968
Update Middlewares.spec.js
dblythy Dec 22, 2022
012c1ad
Merge branch 'fix-master' of https://github.com/dblythy/parse-server …
dblythy Dec 22, 2022
57abc1f
Update ParseUser.spec.js
dblythy Dec 22, 2022
82bf8a9
Update src/Options/index.js
dblythy Dec 22, 2022
c57efb1
Update src/Options/index.js
dblythy Dec 22, 2022
687893b
docs
dblythy Dec 22, 2022
5fce261
Update Middlewares.spec.js
dblythy Dec 22, 2022
4e24693
Update ParseUser.spec.js
dblythy Dec 22, 2022
60fd5d6
Merge branch 'alpha' into fix-master
dblythy Jan 8, 2023
746c978
use example domain
mtrezza Jan 8, 2023
1f199fe
Merge branch 'alpha' into fix-master
mtrezza Jan 8, 2023
43f1d0e
Merge branch 'alpha' into fix-master
mtrezza Jan 8, 2023
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
2 changes: 1 addition & 1 deletion package-lock.json

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

22 changes: 17 additions & 5 deletions spec/EmailVerificationToken.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const Auth = require('../lib/Auth');
const Config = require('../lib/Config');
const request = require('../lib/request');

Expand Down Expand Up @@ -262,9 +263,14 @@ describe('Email Verification Token Expiration: ', () => {
})
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {
username: 'sets_email_verify_token_expires_at',
});
return config.database.find(
'_User',
{
username: 'sets_email_verify_token_expires_at',
},
{},
Auth.maintenance(config)
);
})
.then(results => {
expect(results.length).toBe(1);
Expand Down Expand Up @@ -499,7 +505,12 @@ describe('Email Verification Token Expiration: ', () => {
.then(() => {
const config = Config.get('test');
return config.database
.find('_User', { username: 'newEmailVerifyTokenOnEmailReset' })
.find(
'_User',
{ username: 'newEmailVerifyTokenOnEmailReset' },
{},
Auth.maintenance(config)
)
.then(results => {
return results[0];
});
Expand Down Expand Up @@ -582,7 +593,7 @@ describe('Email Verification Token Expiration: ', () => {
// query for this user again
const config = Config.get('test');
return config.database
.find('_User', { username: 'resends_verification_token' })
.find('_User', { username: 'resends_verification_token' }, {}, Auth.maintenance(config))
.then(results => {
return results[0];
});
Expand All @@ -599,6 +610,7 @@ describe('Email Verification Token Expiration: ', () => {
done();
})
.catch(error => {
console.log(error);
jfail(error);
done();
});
Expand Down
11 changes: 11 additions & 0 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should not succeed if the ip does not belong to maintenanceKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
maintenanceKey: 'masterKey',
maintenanceKeyIps: ['ip1', 'ip2'],
dblythy marked this conversation as resolved.
Show resolved Hide resolved
});
fakeReq.ip = 'ip3';
fakeReq.headers['x-parse-maintenance-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed if the ip does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
Expand Down
13 changes: 10 additions & 3 deletions spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
const Auth = require('../lib/Auth');
const UserController = require('../lib/Controllers/UserController').UserController;
const Config = require('../lib/Config');
const validatorFail = () => {
Expand Down Expand Up @@ -977,6 +978,7 @@ describe('ParseLiveQuery', function () {
};

await reconfigureServer({
maintenanceKey: 'test2',
liveQuery: {
classNames: [Parse.User],
},
Expand All @@ -998,9 +1000,14 @@ describe('ParseLiveQuery', function () {
.signUp()
.then(() => {
const config = Config.get('test');
return config.database.find('_User', {
username: 'zxcv',
});
return config.database.find(
'_User',
{
username: 'zxcv',
},
{},
Auth.maintenance(config)
);
})
.then(async results => {
const foundUser = results[0];
Expand Down
119 changes: 101 additions & 18 deletions spec/ParseUser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3448,40 +3448,123 @@ describe('Parse.User testing', () => {
});
});

it('should not allow updates to hidden fields', done => {
it('should not allow updates to hidden fields', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => Promise.resolve(),
};

const user = new Parse.User();
user.set({
username: 'hello',
password: 'world',
email: '[email protected]',
});
await reconfigureServer({
appName: 'unused',
verifyUserEmails: true,
emailAdapter: emailAdapter,
publicServerURL: 'http://localhost:8378/1',
});
await user.signUp();
user.set('_email_verify_token', 'bad', { ignoreValidation: true });
await expectAsync(user.save()).toBeRejectedWith(
new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Invalid field name: _email_verify_token.')
);
});

reconfigureServer({
it('should allow updates to fields with maintenanceKey', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => Promise.resolve(),
};
const user = new Parse.User();
user.set({
username: 'hello',
password: 'world',
email: '[email protected]',
mtrezza marked this conversation as resolved.
Show resolved Hide resolved
});
await reconfigureServer({
appName: 'unused',
maintenanceKey: 'test2',
mtrezza marked this conversation as resolved.
Show resolved Hide resolved
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 5,
accountLockout: {
duration: 1,
threshold: 1,
},
emailAdapter: emailAdapter,
publicServerURL: 'http://localhost:8378/1',
})
.then(() => {
return user.signUp();
})
.then(() => {
return Parse.User.current().set('_email_verify_token', 'bad').save();
})
.then(() => {
fail('Should not be able to update email verification token');
done();
})
.catch(err => {
expect(err).toBeDefined();
done();
});
});
await user.signUp();
for (let i = 0; i < 2; i++) {
try {
await Parse.User.logIn(user.getEmail(), 'abc');
} catch (e) {
/* */
dblythy marked this conversation as resolved.
Show resolved Hide resolved
}
}
await Parse.User.requestPasswordReset(user.getEmail());
const headers = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'test',
'X-Parse-Maintenance-Key': 'test2',
'Content-Type': 'application/json',
};
const userMaster = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User`,
json: true,
headers,
}).then(res => res.data.results[0]);
expect(Object.keys(userMaster).sort()).toEqual(
[
'ACL',
'_account_lockout_expires_at',
'_email_verify_token',
'_email_verify_token_expires_at',
'_failed_login_count',
'_perishable_token',
'createdAt',
'email',
'emailVerified',
'objectId',
'updatedAt',
'username',
].sort()
);
const toSet = {
_account_lockout_expires_at: new Date(),
_email_verify_token: 'abc',
_email_verify_token_expires_at: new Date(),
_failed_login_count: 0,
_perishable_token_expires_at: new Date(),
_perishable_token: 'abc',
};
await request({
method: 'PUT',
headers,
url: Parse.serverURL + '/users/' + userMaster.objectId,
json: true,
body: toSet,
}).then(res => res.data);
const update = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User`,
json: true,
headers,
}).then(res => res.data.results[0]);
for (const key in toSet) {
const value = toSet[key];
if (update[key] && update[key].iso) {
expect(update[key].iso).toEqual(value.toISOString());
} else if (value.toISOString) {
expect(update[key]).toEqual(value.toISOString());
} else {
expect(update[key]).toEqual(value);
}
}
});

it('should revoke sessions when setting paswword with masterKey (#3289)', done => {
Expand Down
30 changes: 25 additions & 5 deletions spec/PasswordPolicy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1677,12 +1677,19 @@ describe('Password Policy: ', () => {
});

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

await reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: false,
maintenanceKey: 'test2',
passwordPolicy: {
maxPasswordHistory: 1,
},
Expand All @@ -1696,15 +1703,28 @@ describe('Password Policy: ', () => {
user.setPassword('user2');
await user.save();

const result1 = await query.get(user.id, { useMasterKey: true });
expect(result1.get('_password_history').length).toBe(1);
const user1 = await query.get(user.id, { useMasterKey: true });
expect(user1.get('_password_history')).toBeUndefined();

const result1 = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers,
}).then(res => res.data);
expect(result1._password_history.length).toBe(1);

user.setPassword('user3');
await user.save();

const result2 = await query.get(user.id, { useMasterKey: true });
expect(result2.get('_password_history').length).toBe(1);
const result2 = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User/${user.id}`,
json: true,
headers,
}).then(res => res.data);
expect(result2._password_history.length).toBe(1);

expect(result1.get('_password_history')).not.toEqual(result2.get('_password_history'));
expect(result1._password_history).not.toEqual(result2._password_history);
});
});
30 changes: 25 additions & 5 deletions spec/RegexVulnerabilities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const publicServerURL = 'http://localhost:8378/1';
describe('Regex Vulnerabilities', function () {
beforeEach(async function () {
await reconfigureServer({
maintenanceKey: 'test2',
verifyUserEmails: true,
emailAdapter,
appName,
Expand Down Expand Up @@ -98,11 +99,20 @@ describe('Regex Vulnerabilities', function () {

it('should work with plain token', async function () {
expect(this.user.get('emailVerified')).toEqual(false);
const current = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User/${this.user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'test',
'X-Parse-Maintenance-Key': 'test2',
'Content-Type': 'application/json',
},
}).then(res => res.data);
// It should work
await request({
url: `${serverURL}/apps/test/[email protected]&token=${this.user.get(
'_email_verify_token'
)}`,
url: `${serverURL}/apps/test/[email protected]&token=${current._email_verify_token}`,
method: 'GET',
});
await this.user.fetch({ useMasterKey: true });
Expand Down Expand Up @@ -164,8 +174,18 @@ describe('Regex Vulnerabilities', function () {
email: '[email protected]',
}),
});
await this.user.fetch({ useMasterKey: true });
const token = this.user.get('_perishable_token');
const current = await request({
method: 'GET',
url: `http://localhost:8378/1/classes/_User/${this.user.id}`,
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'test',
'X-Parse-Maintenance-Key': 'test2',
'Content-Type': 'application/json',
},
}).then(res => res.data);
const token = current._perishable_token;
const passwordResetResponse = await request({
url: `${serverURL}/apps/test/[email protected]&token=${token}`,
method: 'GET',
Expand Down
9 changes: 9 additions & 0 deletions spec/rest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,15 @@ describe('read-only masterKey', () => {
await reconfigureServer();
});

it('should throw when masterKey and maintenanceKey are the same', async () => {
mtrezza marked this conversation as resolved.
Show resolved Hide resolved
await expectAsync(
reconfigureServer({
masterKey: 'yolo',
maintenanceKey: 'yolo',
})
).toBeRejectedWith(new Error('masterKey and maintenanceKey should be different'));
});

it('should throw when trying to create RestWrite', () => {
const config = Config.get('test');
expect(() => {
Expand Down
Loading