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 (#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
This commit is contained in:
2
2.3.0.md
2
2.3.0.md
@@ -77,6 +77,6 @@ coll.aggregate([
|
|||||||
{$match: {count: {"$gt": 1}}},
|
{$match: {count: {"$gt": 1}}},
|
||||||
{$project: {id: "$uniqueIds", username: "$_id", _id : 0} },
|
{$project: {id: "$uniqueIds", username: "$_id", _id : 0} },
|
||||||
{$unwind: "$id" },
|
{$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})
|
], {allowDiskUse:true})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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) {
|
createTestUser(function(u) {
|
||||||
expect(typeof u.id).toEqual('string');
|
expect(typeof u.id).toEqual('string');
|
||||||
|
|
||||||
@@ -217,8 +310,9 @@ describe('miscellaneous', function() {
|
|||||||
expect(user.get('password')).toBeUndefined();
|
expect(user.get('password')).toBeUndefined();
|
||||||
expect(user.getSessionToken()).not.toBeUndefined();
|
expect(user.getSessionToken()).not.toBeUndefined();
|
||||||
Parse.User.logOut().then(done);
|
Parse.User.logOut().then(done);
|
||||||
}, error: function(error) {
|
}, error: error => {
|
||||||
fail(error);
|
fail(JSON.stringify(error));
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, fail);
|
}, fail);
|
||||||
|
|||||||
@@ -100,4 +100,12 @@ describe('Uniqueness', function() {
|
|||||||
done();
|
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 => {
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -197,20 +197,12 @@ function transformWhere(className, restWhere, schema) {
|
|||||||
return mongoWhere;
|
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.
|
// Check if the schema is known since it's a built-in field.
|
||||||
let transformedValue;
|
let transformedValue;
|
||||||
let coercedToDate;
|
let coercedToDate;
|
||||||
switch(restKey) {
|
switch(restKey) {
|
||||||
case 'objectId': return {key: '_id', value: restValue};
|
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':
|
case 'expiresAt':
|
||||||
transformedValue = transformTopLevelAtom(restValue);
|
transformedValue = transformTopLevelAtom(restValue);
|
||||||
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
||||||
@@ -271,8 +263,6 @@ const parseObjectKeyValueToMongoObjectKeyValue = (className, restKey, restValue,
|
|||||||
return {key: restKey, value};
|
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) => {
|
const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
|
||||||
if (className == '_User') {
|
if (className == '_User') {
|
||||||
restCreate = transformAuthData(restCreate);
|
restCreate = transformAuthData(restCreate);
|
||||||
@@ -281,7 +271,6 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
|
|||||||
let mongoCreate = {}
|
let mongoCreate = {}
|
||||||
for (let restKey in restCreate) {
|
for (let restKey in restCreate) {
|
||||||
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue(
|
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue(
|
||||||
className,
|
|
||||||
restKey,
|
restKey,
|
||||||
restCreate[restKey],
|
restCreate[restKey],
|
||||||
schema
|
schema
|
||||||
@@ -290,6 +279,13 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
|
|||||||
mongoCreate[key] = value;
|
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;
|
return mongoCreate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,7 +731,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
|
|||||||
restObject['objectId'] = '' + mongoObject[key];
|
restObject['objectId'] = '' + mongoObject[key];
|
||||||
break;
|
break;
|
||||||
case '_hashed_password':
|
case '_hashed_password':
|
||||||
restObject['password'] = mongoObject[key];
|
restObject._hashed_password = mongoObject[key];
|
||||||
break;
|
break;
|
||||||
case '_acl':
|
case '_acl':
|
||||||
case '_email_verify_token':
|
case '_email_verify_token':
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ const filterSensitiveData = (isMaster, aclGroup, className, object) => {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object.password = object._hashed_password;
|
||||||
|
delete object._hashed_password;
|
||||||
|
|
||||||
delete object.sessionToken;
|
delete object.sessionToken;
|
||||||
|
|
||||||
if (isMaster || (aclGroup.indexOf(object.objectId) > -1)) {
|
if (isMaster || (aclGroup.indexOf(object.objectId) > -1)) {
|
||||||
@@ -400,6 +403,9 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
|
|||||||
let originalObject = object;
|
let originalObject = object;
|
||||||
object = transformObjectACL(object);
|
object = transformObjectACL(object);
|
||||||
|
|
||||||
|
object.createdAt = { iso: object.createdAt, __type: 'Date' };
|
||||||
|
object.updatedAt = { iso: object.updatedAt, __type: 'Date' };
|
||||||
|
|
||||||
var isMaster = acl === undefined;
|
var isMaster = acl === undefined;
|
||||||
var aclGroup = acl || [];
|
var aclGroup = acl || [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user