diff --git a/spec/rest.spec.js b/spec/rest.spec.js index 503a4d77bb..96bb223a16 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -44,6 +44,66 @@ describe('rest create', () => { }); }); + it('should use objectId from client when allowCustomObjectId true', async () => { + config.allowCustomObjectId = true; + + // use time as unique custom id for test reusability + const customId = `${Date.now()}`; + const obj = { + objectId: customId, + }; + + const { + status, + response: { objectId }, + } = await rest.create(config, auth.nobody(config), 'MyClass', obj); + + expect(status).toEqual(201); + expect(objectId).toEqual(customId); + }); + + it('should throw on invalid objectId when allowCustomObjectId true', () => { + config.allowCustomObjectId = true; + + const objIdNull = { + objectId: null, + }; + + const objIdUndef = { + objectId: undefined, + }; + + const objIdEmpty = { + objectId: '', + }; + + const err = 'objectId must not be empty, null or undefined'; + + expect(() => + rest.create(config, auth.nobody(config), 'MyClass', objIdEmpty) + ).toThrowError(err); + + expect(() => + rest.create(config, auth.nobody(config), 'MyClass', objIdNull) + ).toThrowError(err); + + expect(() => + rest.create(config, auth.nobody(config), 'MyClass', objIdUndef) + ).toThrowError(err); + }); + + it('should generate objectId when not set by client with allowCustomObjectId true', async () => { + config.allowCustomObjectId = true; + + const { + status, + response: { objectId }, + } = await rest.create(config, auth.nobody(config), 'MyClass', {}); + + expect(status).toEqual(201); + expect(objectId).toBeDefined(); + }); + it('is backwards compatible when _id size changes', done => { rest .create(config, auth.nobody(config), 'Foo', { size: 10 }) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 35aef3f6bb..a0d8d2c437 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -17,6 +17,12 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: true, }, + allowCustomObjectId: { + env: 'PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID', + help: 'Enable (or disable) custom objectId, defaults to false', + action: parsers.booleanParser, + default: false, + }, allowHeaders: { env: 'PARSE_SERVER_ALLOW_HEADERS', help: 'Add headers to Access-Control-Allow-Headers', diff --git a/src/Options/docs.js b/src/Options/docs.js index 8c73f16d42..7e148874b7 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -2,6 +2,7 @@ * @interface ParseServerOptions * @property {Any} accountLockout account lockout policy for failed login attempts * @property {Boolean} allowClientClassCreation Enable (or disable) client class creation, defaults to true + * @property {Boolean} allowCustomObjectId Enable (or disable) custom objectId, defaults to false * @property {String[]} allowHeaders Add headers to Access-Control-Allow-Headers * @property {Adapter} analyticsAdapter Adapter module for the analytics * @property {String} appId Your Parse Application ID diff --git a/src/Options/index.js b/src/Options/index.js index edbe3204e9..cd5102b8b1 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -102,6 +102,10 @@ export interface ParseServerOptions { :ENV: PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION :DEFAULT: true */ allowClientClassCreation: ?boolean; + /* Enable (or disable) custom objectId + :ENV: PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID + :DEFAULT: false */ + allowCustomObjectId: ?boolean; /* Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication :ENV: PARSE_SERVER_AUTH_PROVIDERS */ auth: ?any; diff --git a/src/RestWrite.js b/src/RestWrite.js index f1d8aafec8..ef376373bd 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -46,17 +46,32 @@ function RestWrite( this.storage = {}; this.runOptions = {}; this.context = {}; - if (!query && data.objectId) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, - 'objectId is an invalid field name.' - ); - } - if (!query && data.id) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, - 'id is an invalid field name.' - ); + + if (!query) { + if (this.config.allowCustomObjectId) { + if ( + Object.prototype.hasOwnProperty.call(data, 'objectId') && + !data.objectId + ) { + throw new Parse.Error( + Parse.Error.MISSING_OBJECT_ID, + 'objectId must not be empty, null or undefined' + ); + } + } else { + if (data.objectId) { + throw new Parse.Error( + Parse.Error.INVALID_KEY_NAME, + 'objectId is an invalid field name.' + ); + } + if (data.id) { + throw new Parse.Error( + Parse.Error.INVALID_KEY_NAME, + 'id is an invalid field name.' + ); + } + } } // When the operation is complete, this.response may have several