Skip to content

Commit 244950b

Browse files
authored
Merge 9b777e6 into 9b9c3a4
2 parents 9b9c3a4 + 9b777e6 commit 244950b

File tree

7 files changed

+158
-13
lines changed

7 files changed

+158
-13
lines changed

README.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,14 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
358358

359359
## Access Scopes
360360

361-
| Scope | Internal data | Custom data | Restricted by CLP, ACL | Key |
362-
|----------------|---------------|-------------|------------------------|---------------------|
363-
| Internal | r/w | r/w | no | `maintenanceKey` |
364-
| Master | -/- | r/w | no | `masterKey` |
365-
| ReadOnlyMaster | -/- | r/- | no | `readOnlyMasterKey` |
366-
| Session | -/- | r/w | yes | `sessionToken` |
361+
| Scope | Internal data | Read-only data <sub>(1)</sub> | Custom data | Restricted by CLP, ACL | Key |
362+
|----------------|---------------|-------------------------------|-------------|------------------------|---------------------|
363+
| Internal | r/w | r/w | r/w | no | `maintenanceKey` |
364+
| Master | -/- | r/- | r/w | no | `masterKey` |
365+
| ReadOnlyMaster | -/- | r/- | r/- | no | `readOnlyMasterKey` |
366+
| Session | -/- | r/- | r/w | yes | `sessionToken` |
367+
368+
<sub>(1) `Parse.Object.createdAt`, `Parse.Object.updatedAt`.</sub>
367369

368370
## Email Verification and Password Reset
369371

spec/helper.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ const defaultConfiguration = {
103103
restAPIKey: 'rest',
104104
webhookKey: 'hook',
105105
masterKey: 'test',
106+
maintenanceKey: 'testing',
106107
readOnlyMasterKey: 'read-only-test',
107108
fileKey: 'test',
108109
directAccess: true,
@@ -250,8 +251,8 @@ afterEach(function (done) {
250251
})
251252
.then(() => Parse.User.logOut())
252253
.then(
253-
() => {},
254-
() => {}
254+
() => { },
255+
() => { }
255256
) // swallow errors
256257
.then(() => {
257258
// Connection close events are not immediate on node 10+... wait a bit

spec/rest.spec.js

+113
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,119 @@ describe('rest create', () => {
136136
});
137137
});
138138

139+
describe('with maintenance key', () => {
140+
let req;
141+
142+
async function getObject(id) {
143+
const res = await request({
144+
headers: {
145+
'X-Parse-Application-Id': 'test',
146+
'X-Parse-REST-API-Key': 'rest'
147+
},
148+
method: 'GET',
149+
url: `http://localhost:8378/1/classes/TestObject/${id}`
150+
});
151+
152+
return res.data;
153+
}
154+
155+
beforeEach(() => {
156+
req = {
157+
headers: {
158+
'Content-Type': 'application/json',
159+
'X-Parse-Application-Id': 'test',
160+
'X-Parse-REST-API-Key': 'rest',
161+
'X-Parse-Maintenance-Key': 'testing'
162+
},
163+
method: 'POST',
164+
url: 'http://localhost:8378/1/classes/TestObject'
165+
};
166+
});
167+
168+
it('allows createdAt', async () => {
169+
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
170+
req.body = { createdAt };
171+
172+
const res = await request(req);
173+
expect(res.data.createdAt).toEqual(createdAt.iso);
174+
});
175+
176+
it('allows createdAt and updatedAt', async () => {
177+
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
178+
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
179+
req.body = { createdAt, updatedAt };
180+
181+
const res = await request(req);
182+
183+
const obj = await getObject(res.data.objectId);
184+
expect(obj.createdAt).toEqual(createdAt.iso);
185+
expect(obj.updatedAt).toEqual(updatedAt.iso);
186+
});
187+
188+
it('allows createdAt, updatedAt, and additional field', async () => {
189+
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
190+
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
191+
req.body = { createdAt, updatedAt, testing: 123 };
192+
193+
const res = await request(req);
194+
195+
const obj = await getObject(res.data.objectId);
196+
expect(obj.createdAt).toEqual(createdAt.iso);
197+
expect(obj.updatedAt).toEqual(updatedAt.iso);
198+
expect(obj.testing).toEqual(123);
199+
});
200+
201+
it('cannot set updatedAt dated before createdAt', async () => {
202+
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
203+
const updatedAt = { __type: 'Date', iso: '2018-12-01T00:00:00.000Z' };
204+
req.body = { createdAt, updatedAt };
205+
206+
try {
207+
await request(req);
208+
fail();
209+
}
210+
catch (err) {
211+
expect(err.data.code).toEqual(Parse.Error.VALIDATION_ERROR);
212+
}
213+
});
214+
215+
it('cannot set updatedAt without createdAt', async () => {
216+
const updatedAt = { __type: 'Date', iso: '2018-12-01T00:00:00.000Z' };
217+
req.body = { updatedAt };
218+
219+
const res = await request(req);
220+
221+
const obj = await getObject(res.data.objectId);
222+
expect(obj.updatedAt).not.toEqual(updatedAt.iso);
223+
});
224+
225+
it('handles bad types for createdAt and updatedAt', async () => {
226+
const createdAt = 12345;
227+
const updatedAt = true;
228+
req.body = { createdAt, updatedAt };
229+
230+
try {
231+
await request(req);
232+
fail();
233+
}
234+
catch (err) {
235+
expect(err.data.code).toEqual(Parse.Error.INCORRECT_TYPE);
236+
}
237+
});
238+
239+
it('cannot set createdAt or updatedAt without maintenance key', async () => {
240+
const createdAt = { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' };
241+
const updatedAt = { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' };
242+
req.body = { createdAt, updatedAt };
243+
delete req.headers['X-Parse-Maintenance-Key'];
244+
245+
const res = await request(req);
246+
247+
expect(res.data.createdAt).not.toEqual(createdAt.iso);
248+
expect(res.data.updatedAt).not.toEqual(updatedAt.iso);
249+
});
250+
});
251+
139252
it('handles array, object, date', done => {
140253
const now = new Date();
141254
const obj = {

src/Options/Definitions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ module.exports.ParseServerOptions = {
317317
maintenanceKey: {
318318
env: 'PARSE_SERVER_MAINTENANCE_KEY',
319319
help:
320-
'(Optional) The maintenance key is used for modifying internal fields of Parse Server.<br><br>\u26A0\uFE0F This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.',
320+
'(Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.<br><br>\u26A0\uFE0F This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server.',
321321
required: true,
322322
},
323323
maintenanceKeyIps: {

src/Options/docs.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Options/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export interface ParseServerOptions {
4747
appId: string;
4848
/* Your Parse Master Key */
4949
masterKey: string;
50-
/* (Optional) The maintenance key is used for modifying internal fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */
50+
/* (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.<br><br>⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */
5151
maintenanceKey: string;
5252
/* URL to your parse server with http:// or https://.
5353
:ENV: PARSE_SERVER_URL */

src/RestWrite.js

+31-2
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,36 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
368368
};
369369

370370
// Add default fields
371-
this.data.updatedAt = this.updatedAt;
372371
if (!this.query) {
373-
this.data.createdAt = this.updatedAt;
372+
// allow customizing createdAt and updatedAt when using maintenance key
373+
if (
374+
this.auth.isMaintenance &&
375+
this.data.createdAt &&
376+
this.data.createdAt.__type === 'Date'
377+
) {
378+
this.data.createdAt = this.data.createdAt.iso;
379+
380+
if (this.data.updatedAt && this.data.updatedAt.__type === 'Date') {
381+
const createdAt = new Date(this.data.createdAt);
382+
const updatedAt = new Date(this.data.updatedAt.iso);
383+
384+
if (updatedAt < createdAt) {
385+
throw new Parse.Error(
386+
Parse.Error.VALIDATION_ERROR,
387+
'updatedAt cannot occur before createdAt'
388+
);
389+
}
390+
391+
this.data.updatedAt = this.data.updatedAt.iso;
392+
}
393+
// if no updatedAt is provided, set it to createdAt to match default behavior
394+
else {
395+
this.data.updatedAt = this.data.createdAt;
396+
}
397+
} else {
398+
this.data.updatedAt = this.updatedAt;
399+
this.data.createdAt = this.updatedAt;
400+
}
374401

375402
// Only assign new objectId if we are creating new object
376403
if (!this.data.objectId) {
@@ -382,6 +409,8 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
382409
});
383410
}
384411
} else if (schema) {
412+
this.data.updatedAt = this.updatedAt;
413+
385414
Object.keys(this.data).forEach(fieldName => {
386415
setRequiredFieldIfNeeded(fieldName, false);
387416
});

0 commit comments

Comments
 (0)