Skip to content

feat: Allow setting createdAt and updatedAt during Parse.Object creation with maintenance key #8696

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

Merged
merged 17 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,14 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo

## Access Scopes

| Scope | Internal data | Custom data | Restricted by CLP, ACL | Key |
|----------------|---------------|-------------|------------------------|---------------------|
| Internal | r/w | r/w | no | `maintenanceKey` |
| Master | -/- | r/w | no | `masterKey` |
| ReadOnlyMaster | -/- | r/- | no | `readOnlyMasterKey` |
| Session | -/- | r/w | yes | `sessionToken` |
| Scope | Internal data | Read-only data <sub>(1)</sub> | Custom data | Restricted by CLP, ACL | Key |
|----------------|---------------|-------------------------------|-------------|------------------------|---------------------|
| Internal | r/w | r/w | r/w | no | `maintenanceKey` |
| Master | -/- | r/- | r/w | no | `masterKey` |
| ReadOnlyMaster | -/- | r/- | r/- | no | `readOnlyMasterKey` |
| Session | -/- | r/- | r/w | yes | `sessionToken` |

<sub>(1) `Parse.Object.createdAt`, `Parse.Object.updatedAt`.</sub>

## Email Verification and Password Reset

Expand Down
51 changes: 51 additions & 0 deletions spec/rest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,57 @@ describe('rest create', () => {
expect(objectId).toBeDefined();
});

it('allows createdAt and updatedAt to be set with maintenance key', async () => {
let obj = {
createdAt: { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' },
};

const res = await rest.create(config, auth.maintenance(config), 'MyClass', obj);

expect(res.status).toEqual(201);
expect(res.response.createdAt).toEqual(obj.createdAt.iso);

obj = {
createdAt: { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' },
updatedAt: { __type: 'Date', iso: '2019-02-01T00:00:00.000Z' },
};

const res2 = await rest.create(config, auth.maintenance(config), 'MyClass', obj);

expect(res2.status).toEqual(201);
expect(res2.response.createdAt).toEqual(obj.createdAt.iso);

const res3 = await rest.get(config, auth.nobody(config), 'MyClass', res2.response.objectId);
expect(res3.results[0].updatedAt).toEqual(obj.updatedAt.iso);
});

it('cannot set updatedAt dated before createdAt with maintenance key', async () => {
const obj = {
createdAt: { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' },
updatedAt: { __type: 'Date', iso: '2001-01-01T00:00:00.000Z' },
};

try {
await rest.create(config, auth.maintenance(config), 'MyClass', obj);
fail();
} catch (err) {
expect(err.code).toEqual(Parse.Error.VALIDATION_ERROR);
}
});

it('cannot set updatedAt without createdAt with maintenance key', async () => {
const obj = {
updatedAt: { __type: 'Date', iso: '2019-01-01T00:00:00.000Z' },
};

const res = await rest.create(config, auth.maintenance(config), 'MyClass', obj);
const obj_id = res.response.objectId;

const res2 = await rest.get(config, auth.nobody(config), 'MyClass', obj_id);

expect(res2.results[0].updatedAt).not.toEqual(obj.updatedAt.iso);
});

it('is backwards compatible when _id size changes', done => {
rest
.create(config, auth.nobody(config), 'Foo', { size: 10 })
Expand Down
2 changes: 1 addition & 1 deletion src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ module.exports.ParseServerOptions = {
maintenanceKey: {
env: 'PARSE_SERVER_MAINTENANCE_KEY',
help:
'(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.',
'(Optional) The maintenance key is used for modifying internal fields of Parse Server, including object timestamps.<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.',
required: true,
},
maintenanceKeyIps: {
Expand Down
2 changes: 1 addition & 1 deletion src/Options/docs.js

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

2 changes: 1 addition & 1 deletion src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface ParseServerOptions {
appId: string;
/* Your Parse Master Key */
masterKey: string;
/* (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. */
/* (Optional) The maintenance key is used for modifying internal fields of Parse Server, including object timestamps.<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. */
maintenanceKey: string;
/* URL to your parse server with http:// or https://.
:ENV: PARSE_SERVER_URL */
Expand Down
33 changes: 31 additions & 2 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,36 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
};

// Add default fields
this.data.updatedAt = this.updatedAt;
if (!this.query) {
this.data.createdAt = this.updatedAt;
// allow customizing createdAt and updatedAt when using maintenance key
if (
this.auth.isMaintenance &&
this.data.createdAt &&
this.data.createdAt.__type === 'Date'
) {
this.data.createdAt = this.data.createdAt.iso;

if (this.data.updatedAt && this.data.updatedAt.__type === 'Date') {
const createdAt = new Date(this.data.createdAt);
const updatedAt = new Date(this.data.updatedAt.iso);

if (updatedAt < createdAt) {
throw new Parse.Error(
Parse.Error.VALIDATION_ERROR,
'updatedAt cannot occur before createdAt'
);
}

this.data.updatedAt = this.data.updatedAt.iso;
}
// if no updatedAt is provided, set it to createdAt to match default behavior
else {
this.data.updatedAt = this.data.createdAt;
}
} else {
this.data.updatedAt = this.updatedAt;
this.data.createdAt = this.updatedAt;
}

// Only assign new objectId if we are creating new object
if (!this.data.objectId) {
Expand All @@ -382,6 +409,8 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
});
}
} else if (schema) {
this.data.updatedAt = this.updatedAt;

Object.keys(this.data).forEach(fieldName => {
setRequiredFieldIfNeeded(fieldName, false);
});
Expand Down