Fix when multiple authData keys are passed
This commit is contained in:
@@ -905,6 +905,50 @@ describe('Parse.User testing', () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getMockMyOauthProvider = function() {
|
||||||
|
return {
|
||||||
|
authData: {
|
||||||
|
id: "12345",
|
||||||
|
access_token: "12345",
|
||||||
|
expiration_date: new Date().toJSON(),
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
loggedOut: false,
|
||||||
|
synchronizedUserId: null,
|
||||||
|
synchronizedAuthToken: null,
|
||||||
|
synchronizedExpiration: null,
|
||||||
|
|
||||||
|
authenticate: function(options) {
|
||||||
|
if (this.shouldError) {
|
||||||
|
options.error(this, "An error occurred");
|
||||||
|
} else if (this.shouldCancel) {
|
||||||
|
options.error(this, null);
|
||||||
|
} else {
|
||||||
|
options.success(this, this.authData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restoreAuthentication: function(authData) {
|
||||||
|
if (!authData) {
|
||||||
|
this.synchronizedUserId = null;
|
||||||
|
this.synchronizedAuthToken = null;
|
||||||
|
this.synchronizedExpiration = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.synchronizedUserId = authData.id;
|
||||||
|
this.synchronizedAuthToken = authData.access_token;
|
||||||
|
this.synchronizedExpiration = authData.expiration_date;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getAuthType: function() {
|
||||||
|
return "myoauth";
|
||||||
|
},
|
||||||
|
deauthenticate: function() {
|
||||||
|
this.loggedOut = true;
|
||||||
|
this.restoreAuthentication(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
var ExtendedUser = Parse.User.extend({
|
var ExtendedUser = Parse.User.extend({
|
||||||
extended: function() {
|
extended: function() {
|
||||||
return true;
|
return true;
|
||||||
@@ -1285,6 +1329,114 @@ describe('Parse.User testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("link multiple providers", (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
var mockProvider = getMockMyOauthProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||||
|
strictEqual(Parse.User.current(), model);
|
||||||
|
ok(model.extended(), "Should have used the subclass.");
|
||||||
|
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||||
|
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||||
|
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
Parse.User._registerAuthenticationProvider(mockProvider);
|
||||||
|
let objectId = model.id;
|
||||||
|
model._linkWith("myoauth", {
|
||||||
|
success: function(model) {
|
||||||
|
expect(model.id).toEqual(objectId);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error(error);
|
||||||
|
fail('SHould not fail');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(model, error) {
|
||||||
|
ok(false, "linking should have worked");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("link multiple providers and update token", (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
var mockProvider = getMockMyOauthProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||||
|
strictEqual(Parse.User.current(), model);
|
||||||
|
ok(model.extended(), "Should have used the subclass.");
|
||||||
|
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||||
|
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||||
|
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
Parse.User._registerAuthenticationProvider(mockProvider);
|
||||||
|
let objectId = model.id;
|
||||||
|
model._linkWith("myoauth", {
|
||||||
|
success: function(model) {
|
||||||
|
expect(model.id).toEqual(objectId);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
model._linkWith("facebook", {
|
||||||
|
success: () => {
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
fail('should link again');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error(error);
|
||||||
|
fail('SHould not fail');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(model, error) {
|
||||||
|
ok(false, "linking should have worked");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail linking with existing', (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
Parse.User.logOut().then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('password');
|
||||||
|
return user.signUp().then(() => {
|
||||||
|
// try to link here
|
||||||
|
user._linkWith('facebook', {
|
||||||
|
success: () => {
|
||||||
|
fail('should not succeed');
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('set password then change password', (done) => {
|
it('set password then change password', (done) => {
|
||||||
Parse.User.signUp('bob', 'barker').then((bob) => {
|
Parse.User.signUp('bob', 'barker').then((bob) => {
|
||||||
bob.setPassword('meower');
|
bob.setPassword('meower');
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ describe('rest create', () => {
|
|||||||
}, (err) => {
|
}, (err) => {
|
||||||
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
||||||
expect(err.message).toEqual('This authentication method is unsupported.');
|
expect(err.message).toEqual('This authentication method is unsupported.');
|
||||||
|
NoAnnonConfig.authDataManager.setEnableAnonymousUsers(true);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
132
src/RestWrite.js
132
src/RestWrite.js
@@ -211,74 +211,98 @@ RestWrite.prototype.validateAuthData = function() {
|
|||||||
|
|
||||||
var authData = this.data.authData;
|
var authData = this.data.authData;
|
||||||
var providers = Object.keys(authData);
|
var providers = Object.keys(authData);
|
||||||
if (providers.length == 1) {
|
if (providers.length > 0) {
|
||||||
var provider = providers[0];
|
var provider = providers[providers.length-1];
|
||||||
var providerAuthData = authData[provider];
|
var providerAuthData = authData[provider];
|
||||||
var hasToken = (providerAuthData && providerAuthData.id);
|
var hasToken = (providerAuthData && providerAuthData.id);
|
||||||
if (providerAuthData === null || hasToken) {
|
if (providerAuthData === null || hasToken) {
|
||||||
return this.handleOAuthAuthData(provider);
|
return this.handleAuthData(authData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||||
'This authentication method is unsupported.');
|
'This authentication method is unsupported.');
|
||||||
};
|
};
|
||||||
|
|
||||||
RestWrite.prototype.handleOAuthAuthData = function(provider) {
|
RestWrite.prototype.handleAuthDataValidation = function(authData) {
|
||||||
var authData = this.data.authData[provider];
|
let validations = Object.keys(authData).map((provider) => {
|
||||||
if (authData === null && this.query) {
|
if (authData[provider] === null) {
|
||||||
// We are unlinking from the provider.
|
return Promise.resolve();
|
||||||
this.data["_auth_data_" + provider ] = null;
|
}
|
||||||
return;
|
let validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
|
||||||
|
if (!validateAuthData) {
|
||||||
|
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||||
|
'This authentication method is unsupported.');
|
||||||
|
};
|
||||||
|
return validateAuthData(authData[provider]);
|
||||||
|
});
|
||||||
|
return Promise.all(validations);
|
||||||
|
}
|
||||||
|
|
||||||
|
RestWrite.prototype.findUsersWithAuthData = function(authData) {
|
||||||
|
let providers = Object.keys(authData);
|
||||||
|
let query = providers.reduce((memo, provider) => {
|
||||||
|
if (!authData[provider]) {
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
let queryKey = `authData.${provider}.id`;
|
||||||
|
let query = {};
|
||||||
|
query[queryKey] = authData[provider].id;
|
||||||
|
memo.push(query);
|
||||||
|
return memo;
|
||||||
|
}, []).filter((q) => {
|
||||||
|
return typeof q !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
let findPromise = Promise.resolve([]);
|
||||||
|
if (query.length > 0) {
|
||||||
|
findPromise = this.config.database.find(
|
||||||
|
this.className,
|
||||||
|
{'$or': query}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
let validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
|
return findPromise;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateAuthData) {
|
RestWrite.prototype.handleAuthData = function(authData) {
|
||||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
let results;
|
||||||
'This authentication method is unsupported.');
|
return this.handleAuthDataValidation(authData).then(() => {
|
||||||
};
|
return this.findUsersWithAuthData(authData);
|
||||||
|
}).then((r) => {
|
||||||
return validateAuthData(authData)
|
results = r;
|
||||||
.then(() => {
|
if (results.length > 1) {
|
||||||
// Check if this user already exists
|
// More than 1 user with the passed id's
|
||||||
// TODO: does this handle re-linking correctly?
|
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||||
var query = {};
|
|
||||||
query['authData.' + provider + '.id'] = authData.id;
|
|
||||||
return this.config.database.find(
|
|
||||||
this.className,
|
|
||||||
query, {});
|
|
||||||
}).then((results) => {
|
|
||||||
this.storage['authProvider'] = provider;
|
|
||||||
|
|
||||||
// Put the data in the proper format
|
|
||||||
this.data["_auth_data_" + provider ] = authData;
|
|
||||||
|
|
||||||
if (results.length == 0) {
|
|
||||||
// this a new user
|
|
||||||
this.data.username = cryptoUtils.newToken();
|
|
||||||
} else if (!this.query) {
|
|
||||||
// Login with auth data
|
|
||||||
// Short circuit
|
|
||||||
delete results[0].password;
|
|
||||||
this.response = {
|
|
||||||
response: results[0],
|
|
||||||
location: this.location()
|
|
||||||
};
|
|
||||||
this.data.objectId = results[0].objectId;
|
|
||||||
} else if (this.query && this.query.objectId) {
|
|
||||||
// Trying to update auth data but users
|
|
||||||
// are different
|
|
||||||
if (results[0].objectId !== this.query.objectId) {
|
|
||||||
delete this.data["_auth_data_" + provider ];
|
|
||||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
|
||||||
'this auth is already used');
|
'this auth is already used');
|
||||||
}
|
}
|
||||||
} else {
|
// set the proper keys
|
||||||
|
Object.keys(authData).forEach((provider) => {
|
||||||
delete this.data["_auth_data_" + provider ];
|
this.data[`_auth_data_${provider}`] = authData[provider];
|
||||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'THis should not be reached...');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (results.length == 0) {
|
||||||
|
this.data.username = cryptoUtils.newToken();
|
||||||
|
} else if (!this.query) {
|
||||||
|
// Login with auth data
|
||||||
|
// Short circuit
|
||||||
|
delete results[0].password;
|
||||||
|
this.response = {
|
||||||
|
response: results[0],
|
||||||
|
location: this.location()
|
||||||
|
};
|
||||||
|
this.data.objectId = results[0].objectId;
|
||||||
|
} else if (this.query && this.query.objectId) {
|
||||||
|
// Trying to update auth data but users
|
||||||
|
// are different
|
||||||
|
if (results[0].objectId !== this.query.objectId) {
|
||||||
|
Object.keys(authData).forEach((provider) => {
|
||||||
|
delete this.data[`_auth_data_${provider}`];
|
||||||
|
});
|
||||||
|
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||||
|
'this auth is already used');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The non-third-party parts of User transformation
|
// The non-third-party parts of User transformation
|
||||||
|
|||||||
Reference in New Issue
Block a user