diff --git a/2.3.0.md b/2.3.0.md index 2528c290..fe588354 100644 --- a/2.3.0.md +++ b/2.3.0.md @@ -77,6 +77,6 @@ coll.aggregate([ {$match: {count: {"$gt": 1}}}, {$project: {id: "$uniqueIds", username: "$_id", _id : 0} }, {$unwind: "$id" }, - {$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates collection. Remove this line to just output the list. + {$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates" collection. Remove this line to just output the list. ], {allowDiskUse:true}) ``` diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 64d49053..28a7d856 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -207,7 +207,100 @@ describe('miscellaneous', function() { }); }); - it('succeed in logging in', function(done) { + it('ensure that email is uniquely indexed', done => { + let numCreated = 0; + let numFailed = 0; + + let user1 = new Parse.User(); + user1.setPassword('asdf'); + user1.setUsername('u1'); + user1.setEmail('dupe@dupe.dupe'); + let p1 = user1.signUp(); + p1.then(user => { + numCreated++; + expect(numCreated).toEqual(1); + }) + .catch(error => { + numFailed++; + expect(numFailed).toEqual(1); + expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN); + }); + + let user2 = new Parse.User(); + user2.setPassword('asdf'); + user2.setUsername('u2'); + user2.setEmail('dupe@dupe.dupe'); + let p2 = user2.signUp(); + p2.then(user => { + numCreated++; + expect(numCreated).toEqual(1); + }) + .catch(error => { + numFailed++; + expect(numFailed).toEqual(1); + expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN); + }); + Parse.Promise.all([p1, p2]) + .then(() => { + fail('one of the users should not have been created'); + done(); + }) + .catch(done); + }); + + it('ensure that if people already have duplicate emails, they can still sign up new users', done => { + let config = new Config('test'); + // Remove existing data to clear out unique index + TestUtils.destroyAllDataPermanently() + .then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'x', email: 'a@b.c' })) + .then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'y', email: 'a@b.c' })) + .then(reconfigureServer) + .catch(() => { + let user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('qqq'); + user.setEmail('unique@unique.unique'); + return user.signUp().catch(fail); + }) + .then(() => { + let user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('www'); + user.setEmail('a@b.c'); + return user.signUp() + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN); + done(); + }); + }); + + it('ensure that if you try to sign up a user with a unique username and email, but duplicates in some other field that has a uniqueness constraint, you get a regular duplicate value error', done => { + let config = new Config('test'); + config.database.adapter.ensureUniqueness('_User', requiredUserFields, ['randomField']) + .then(() => { + let user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('1'); + user.setEmail('1@b.c'); + user.set('randomField', 'a'); + return user.signUp() + }) + .then(() => { + let user = new Parse.User(); + user.setPassword('asdf'); + user.setUsername('2'); + user.setEmail('2@b.c'); + user.set('randomField', 'a'); + return user.signUp() + }) + .catch(error => { + expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE); + done(); + }); + }); + + fit('succeed in logging in', function(done) { createTestUser(function(u) { expect(typeof u.id).toEqual('string'); @@ -217,8 +310,9 @@ describe('miscellaneous', function() { expect(user.get('password')).toBeUndefined(); expect(user.getSessionToken()).not.toBeUndefined(); Parse.User.logOut().then(done); - }, error: function(error) { - fail(error); + }, error: error => { + fail(JSON.stringify(error)); + done(); } }); }, fail); diff --git a/spec/Uniqueness.spec.js b/spec/Uniqueness.spec.js index 129ec075..58924954 100644 --- a/spec/Uniqueness.spec.js +++ b/spec/Uniqueness.spec.js @@ -100,4 +100,12 @@ describe('Uniqueness', function() { done(); }); }); + + it('adding a unique index to an existing field works even if it has nulls', done => { + + }); + + it('adding a unique index to an existing field doesnt prevent you from adding new documents with nulls', done => { + + }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 4858a436..ce020278 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -197,20 +197,12 @@ function transformWhere(className, restWhere, schema) { return mongoWhere; } -const parseObjectKeyValueToMongoObjectKeyValue = (className, restKey, restValue, schema) => { +const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) => { // Check if the schema is known since it's a built-in field. let transformedValue; let coercedToDate; switch(restKey) { case 'objectId': return {key: '_id', value: restValue}; - case 'createdAt': - transformedValue = transformTopLevelAtom(restValue); - coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue - return {key: '_created_at', value: coercedToDate}; - case 'updatedAt': - transformedValue = transformTopLevelAtom(restValue); - coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue - return {key: '_updated_at', value: coercedToDate}; case 'expiresAt': transformedValue = transformTopLevelAtom(restValue); coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue @@ -271,8 +263,6 @@ const parseObjectKeyValueToMongoObjectKeyValue = (className, restKey, restValue, return {key: restKey, value}; } -// Main exposed method to create new objects. -// restCreate is the "create" clause in REST API form. const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => { if (className == '_User') { restCreate = transformAuthData(restCreate); @@ -281,7 +271,6 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => { let mongoCreate = {} for (let restKey in restCreate) { let { key, value } = parseObjectKeyValueToMongoObjectKeyValue( - className, restKey, restCreate[restKey], schema @@ -290,6 +279,13 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => { mongoCreate[key] = value; } } + + // Use the legacy mongo format for createdAt and updatedAt + mongoCreate._created_at = mongoCreate.createdAt.iso; + delete mongoCreate.createdAt; + mongoCreate._updated_at = mongoCreate.updatedAt.iso; + delete mongoCreate.updatedAt; + return mongoCreate; } @@ -735,7 +731,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => { restObject['objectId'] = '' + mongoObject[key]; break; case '_hashed_password': - restObject['password'] = mongoObject[key]; + restObject._hashed_password = mongoObject[key]; break; case '_acl': case '_email_verify_token': diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index e9bf04ac..bed0e154 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -159,6 +159,9 @@ const filterSensitiveData = (isMaster, aclGroup, className, object) => { return object; } + object.password = object._hashed_password; + delete object._hashed_password; + delete object.sessionToken; if (isMaster || (aclGroup.indexOf(object.objectId) > -1)) { @@ -400,6 +403,9 @@ DatabaseController.prototype.create = function(className, object, { acl } = {}) let originalObject = object; object = transformObjectACL(object); + object.createdAt = { iso: object.createdAt, __type: 'Date' }; + object.updatedAt = { iso: object.updatedAt, __type: 'Date' }; + var isMaster = acl === undefined; var aclGroup = acl || [];