Skip to content

Commit 096516c

Browse files
committed
Add unique indexing for username/email
WIP Notes on how to upgrade to 2.3.0 safely index on unique-indexes: c454180 Revert "Log objects rather than JSON stringified objects (parse-community#1922)" reconfigure username/email tests Start dealing with test shittyness most tests passing Make specific server config for tests async Fix more tests Save callback to variable undo remove uses of _collection reorder some params reorder find() arguments finishsh touching up argument order Accept a database adapter as a parameter First passing test with postgres! Fix tests Setup travis sudo maybe? use postgres username reorder find() arguments Build objects with default fields correctly Don't tell adapter about ACL WIP Passing postgres test with user Fix up createdAt, updatedAt, nad _hashed_password handling
1 parent a009ed2 commit 096516c

File tree

5 files changed

+121
-17
lines changed

5 files changed

+121
-17
lines changed

2.3.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,6 @@ coll.aggregate([
7777
{$match: {count: {"$gt": 1}}},
7878
{$project: {id: "$uniqueIds", username: "$_id", _id : 0} },
7979
{$unwind: "$id" },
80-
{$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates collection. Remove this line to just output the list.
80+
{$out: '_duplicates'} // Save the list of duplicates to a new, "_duplicates" collection. Remove this line to just output the list.
8181
], {allowDiskUse:true})
8282
```

spec/ParseAPI.spec.js

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,100 @@ describe('miscellaneous', function() {
206206
});
207207
});
208208

209-
it('succeed in logging in', function(done) {
209+
it('ensure that email is uniquely indexed', done => {
210+
let numCreated = 0;
211+
let numFailed = 0;
212+
213+
let user1 = new Parse.User();
214+
user1.setPassword('asdf');
215+
user1.setUsername('u1');
216+
user1.setEmail('dupe@dupe.dupe');
217+
let p1 = user1.signUp();
218+
p1.then(user => {
219+
numCreated++;
220+
expect(numCreated).toEqual(1);
221+
})
222+
.catch(error => {
223+
numFailed++;
224+
expect(numFailed).toEqual(1);
225+
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
226+
});
227+
228+
let user2 = new Parse.User();
229+
user2.setPassword('asdf');
230+
user2.setUsername('u2');
231+
user2.setEmail('dupe@dupe.dupe');
232+
let p2 = user2.signUp();
233+
p2.then(user => {
234+
numCreated++;
235+
expect(numCreated).toEqual(1);
236+
})
237+
.catch(error => {
238+
numFailed++;
239+
expect(numFailed).toEqual(1);
240+
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
241+
});
242+
Parse.Promise.all([p1, p2])
243+
.then(() => {
244+
fail('one of the users should not have been created');
245+
done();
246+
})
247+
.catch(done);
248+
});
249+
250+
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
251+
let config = new Config('test');
252+
// Remove existing data to clear out unique index
253+
TestUtils.destroyAllDataPermanently()
254+
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'x', email: 'a@b.c' }))
255+
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'y', email: 'a@b.c' }))
256+
.then(reconfigureServer)
257+
.catch(() => {
258+
let user = new Parse.User();
259+
user.setPassword('asdf');
260+
user.setUsername('qqq');
261+
user.setEmail('unique@unique.unique');
262+
return user.signUp().catch(fail);
263+
})
264+
.then(() => {
265+
let user = new Parse.User();
266+
user.setPassword('asdf');
267+
user.setUsername('www');
268+
user.setEmail('a@b.c');
269+
return user.signUp()
270+
})
271+
.catch(error => {
272+
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
273+
done();
274+
});
275+
});
276+
277+
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 => {
278+
let config = new Config('test');
279+
config.database.adapter.ensureUniqueness('_User', requiredUserFields, ['randomField'])
280+
.then(() => {
281+
let user = new Parse.User();
282+
user.setPassword('asdf');
283+
user.setUsername('1');
284+
user.setEmail('1@b.c');
285+
user.set('randomField', 'a');
286+
return user.signUp()
287+
})
288+
.then(() => {
289+
let user = new Parse.User();
290+
user.setPassword('asdf');
291+
user.setUsername('2');
292+
user.setEmail('2@b.c');
293+
user.set('randomField', 'a');
294+
return user.signUp()
295+
})
296+
.catch(error => {
297+
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
298+
done();
299+
});
300+
});
301+
302+
fit('succeed in logging in', function(done) {
210303
createTestUser(function(u) {
211304
expect(typeof u.id).toEqual('string');
212305

@@ -216,8 +309,9 @@ describe('miscellaneous', function() {
216309
expect(user.get('password')).toBeUndefined();
217310
expect(user.getSessionToken()).not.toBeUndefined();
218311
Parse.User.logOut().then(done);
219-
}, error: function(error) {
220-
fail(error);
312+
}, error: error => {
313+
fail(JSON.stringify(error));
314+
done();
221315
}
222316
});
223317
}, fail);

spec/Uniqueness.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,12 @@ describe('Uniqueness', function() {
100100
done();
101101
});
102102
});
103+
104+
it('adding a unique index to an existing field works even if it has nulls', done => {
105+
106+
});
107+
108+
it('adding a unique index to an existing field doesnt prevent you from adding new documents with nulls', done => {
109+
110+
});
103111
});

src/Adapters/Storage/Mongo/MongoTransform.js

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,12 @@ function transformWhere(className, restWhere, schema) {
197197
return mongoWhere;
198198
}
199199

200-
const parseObjectKeyValueToMongoObjectKeyValue = (className, restKey, restValue, schema) => {
200+
const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) => {
201201
// Check if the schema is known since it's a built-in field.
202202
let transformedValue;
203203
let coercedToDate;
204204
switch(restKey) {
205205
case 'objectId': return {key: '_id', value: restValue};
206-
case 'createdAt':
207-
transformedValue = transformTopLevelAtom(restValue);
208-
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
209-
return {key: '_created_at', value: coercedToDate};
210-
case 'updatedAt':
211-
transformedValue = transformTopLevelAtom(restValue);
212-
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
213-
return {key: '_updated_at', value: coercedToDate};
214206
case 'expiresAt':
215207
transformedValue = transformTopLevelAtom(restValue);
216208
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
@@ -271,8 +263,6 @@ const parseObjectKeyValueToMongoObjectKeyValue = (className, restKey, restValue,
271263
return {key: restKey, value};
272264
}
273265

274-
// Main exposed method to create new objects.
275-
// restCreate is the "create" clause in REST API form.
276266
const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
277267
if (className == '_User') {
278268
restCreate = transformAuthData(restCreate);
@@ -281,7 +271,6 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
281271
let mongoCreate = {}
282272
for (let restKey in restCreate) {
283273
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue(
284-
className,
285274
restKey,
286275
restCreate[restKey],
287276
schema
@@ -290,6 +279,13 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
290279
mongoCreate[key] = value;
291280
}
292281
}
282+
283+
// Use the legacy mongo format for createdAt and updatedAt
284+
mongoCreate._created_at = mongoCreate.createdAt.iso;
285+
delete mongoCreate.createdAt;
286+
mongoCreate._updated_at = mongoCreate.updatedAt.iso;
287+
delete mongoCreate.updatedAt;
288+
293289
return mongoCreate;
294290
}
295291

@@ -735,7 +731,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
735731
restObject['objectId'] = '' + mongoObject[key];
736732
break;
737733
case '_hashed_password':
738-
restObject['password'] = mongoObject[key];
734+
restObject._hashed_password = mongoObject[key];
739735
break;
740736
case '_acl':
741737
case '_email_verify_token':

src/Controllers/DatabaseController.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ const filterSensitiveData = (isMaster, aclGroup, className, object) => {
149149
return object;
150150
}
151151

152+
object.password = object._hashed_password;
153+
delete object._hashed_password;
154+
152155
delete object.sessionToken;
153156

154157
if (isMaster || (aclGroup.indexOf(object.objectId) > -1)) {
@@ -390,6 +393,9 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
390393
let originalObject = object;
391394
object = transformObjectACL(object);
392395

396+
object.createdAt = { iso: object.createdAt, __type: 'Date' };
397+
object.updatedAt = { iso: object.updatedAt, __type: 'Date' };
398+
393399
var isMaster = acl === undefined;
394400
var aclGroup = acl || [];
395401

0 commit comments

Comments
 (0)