0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Fixes error in validation

closes #6826

- refactors the validation of facebook and twitter input field in `general.js` and `user.js` controller
	- Example validations for facebook:
		- `facebook.com/username` will be corrected to the full URL
		- `user` will show error `Your Page name is not a valid Facebook Page name' for `general.js` and `Your Username is not a valid Facebook Username` for `user.js` as the username in facebook has to be at least 5 characters long
		- `twitter.com/username` will be autocorrected to the valid facebook URL incl. the `username`
	- Example validations for twitter:
		- `twitter.com/user_` will be corrected to the full URL
                - `user:99` will show error `Your Username is not a valid Twitter Username`
                - `facebook.com/username` will be autocorrected to the valid twitter URL incl. the `username`
- updates both acceptance tests
- adds further validation for facebook pages in general settings and user. Submitting a url which incl. `/page/` or `/pages/` will now accept any username followed incl. further `/`.
- adds a custom transform `facebook-url-user` which will extract the username (if it's a facebook page, incl. `pages/`) to store only this in the backend
- uses the `twitter-url-user` transform now also for user
This commit is contained in:
Aileen Nowak 2016-05-16 20:16:40 +02:00 committed by Hannah Wolfe
parent 29c9e8b147
commit 6dbf610c8f
9 changed files with 288 additions and 111 deletions

View file

@ -134,39 +134,51 @@ export default Controller.extend(SettingsSaveMixin, {
// If new url didn't change, exit
if (newUrl === oldUrl) {
this.get('model.errors').remove('facebook');
return;
}
if (!newUrl.match(/(^https:\/\/www\.facebook\.com\/)(\S+)/g)) {
if (newUrl.match(/(?:facebook\.com\/)(\S+)/) || (!validator.isURL(newUrl) && newUrl.match(/([a-zA-Z0-9\.]+)/))) {
let [ , username] = newUrl.match(/(?:facebook\.com\/)(\S+)/) || newUrl.match(/([a-zA-Z0-9\.]+)/);
newUrl = `https://www.facebook.com/${username}`;
if (newUrl.match(/(?:facebook\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) {
let username = [];
this.set('model.facebook', newUrl);
this.get('model.errors').remove('facebook');
this.get('model.hasValidated').pushObject('facebook');
// User input is validated
return this.save().then(() => {
this.set('model.facebook', '');
run.schedule('afterRender', this, function () {
this.set('model.facebook', newUrl);
});
});
} else if (validator.isURL(newUrl)) {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourPage';
this.get('model.errors').add('facebook', errMessage);
this.get('model.hasValidated').pushObject('facebook');
return;
if (newUrl.match(/(?:facebook\.com\/)(\S+)/)) {
[ , username ] = newUrl.match(/(?:facebook\.com\/)(\S+)/);
} else {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourPage';
[ , username ] = newUrl.match(/(?:https\:\/\/|http\:\/\/)?(?:www\.)?(?:\w+\.\w+\/+)?(\S+)/mi);
}
// check if we have a /page/username or without
if (username.match(/^(?:\/)?(pages?\/\S+)/mi)) {
// we got a page url, now save the username without the / in the beginning
[ , username ] = username.match(/^(?:\/)?(pages?\/\S+)/mi);
} else if (username.match(/^(http|www)|(\/)/) || !username.match(/^([a-z\d\.]{5,50})$/mi)) {
errMessage = !username.match(/^([a-z\d\.]{5,50})$/mi) ? 'Your Page name is not a valid Facebook Page name' : 'The URL must be in a format like https://www.facebook.com/yourPage';
this.get('model.errors').add('facebook', errMessage);
this.get('model.hasValidated').pushObject('facebook');
return;
}
newUrl = `https://www.facebook.com/${username}`;
this.set('model.facebook', newUrl);
this.get('model.errors').remove('facebook');
this.get('model.hasValidated').pushObject('facebook');
// User input is validated
return this.save().then(() => {
this.set('model.facebook', '');
run.schedule('afterRender', this, function () {
this.set('model.facebook', newUrl);
});
});
} else {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourPage';
this.get('model.errors').add('facebook', errMessage);
this.get('model.hasValidated').pushObject('facebook');
return;
}
},
@ -184,39 +196,47 @@ export default Controller.extend(SettingsSaveMixin, {
// If new url didn't change, exit
if (newUrl === oldUrl) {
this.get('model.errors').remove('twitter');
return;
}
if (!newUrl.match(/(^https:\/\/twitter\.com\/)(\S+)/g)) {
if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || (!validator.isURL(newUrl) && newUrl.match(/([a-zA-Z0-9\.]+)/))) {
let [ , username] = newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-zA-Z0-9\.]+)/);
newUrl = `https://twitter.com/${username}`;
if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) {
let username = [];
this.set('model.twitter', newUrl);
this.get('model.errors').remove('twitter');
this.get('model.hasValidated').pushObject('twitter');
// User input is validated
return this.save().then(() => {
this.set('model.twitter', '');
run.schedule('afterRender', this, function () {
this.set('model.twitter', newUrl);
});
});
} else if (validator.isURL(newUrl)) {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
this.get('model.errors').add('twitter', errMessage);
this.get('model.hasValidated').pushObject('twitter');
return;
if (newUrl.match(/(?:twitter\.com\/)(\S+)/)) {
[ , username] = newUrl.match(/(?:twitter\.com\/)(\S+)/);
} else {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
[username] = newUrl.match(/([^/]+)\/?$/mi);
}
// check if username starts with http or www and show error if so
if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d\.\_]{1,15}$/mi)) {
errMessage = !username.match(/^[a-z\d\.\_]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername';
this.get('model.errors').add('twitter', errMessage);
this.get('model.hasValidated').pushObject('twitter');
return;
}
newUrl = `https://twitter.com/${username}`;
this.set('model.twitter', newUrl);
this.get('model.errors').remove('twitter');
this.get('model.hasValidated').pushObject('twitter');
// User input is validated
return this.save().then(() => {
this.set('model.twitter', '');
run.schedule('afterRender', this, function () {
this.set('model.twitter', newUrl);
});
});
} else {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
this.get('model.errors').add('twitter', errMessage);
this.get('model.hasValidated').pushObject('twitter');
return;
}
}
}

View file

@ -258,41 +258,53 @@ export default Controller.extend({
// If new url didn't change, exit
if (newUrl === oldUrl) {
this.get('user.errors').remove('facebook');
return;
}
// TODO: put the validation here into a validator
if (!newUrl.match(/(^https:\/\/www\.facebook\.com\/)(\S+)/g)) {
if (newUrl.match(/(?:facebook\.com\/)(\S+)/) || (!validator.isURL(newUrl) && newUrl.match(/([a-zA-Z0-9\.]+)/))) {
let [ , username] = newUrl.match(/(?:facebook\.com\/)(\S+)/) || newUrl.match(/([a-zA-Z0-9\.]+)/);
newUrl = `https://www.facebook.com/${username}`;
if (newUrl.match(/(?:facebook\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) {
let username = [];
this.set('user.facebook', newUrl);
this.get('user.errors').remove('facebook');
this.get('user.hasValidated').pushObject('facebook');
// User input is validated
invoke(this, 'save').then(() => {
// necessary to update the value in the input field
this.set('user.facebook', '');
run.schedule('afterRender', this, function () {
this.set('user.facebook', newUrl);
});
});
} else if (validator.isURL(newUrl)) {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourUsername';
this.get('user.errors').add('facebook', errMessage);
this.get('user.hasValidated').pushObject('facebook');
return;
if (newUrl.match(/(?:facebook\.com\/)(\S+)/)) {
[ , username ] = newUrl.match(/(?:facebook\.com\/)(\S+)/);
} else {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourUsername';
[ , username ] = newUrl.match(/(?:https\:\/\/|http\:\/\/)?(?:www\.)?(?:\w+\.\w+\/+)?(\S+)/mi);
}
// check if we have a /page/username or without
if (username.match(/^(?:\/)?(pages?\/\S+)/mi)) {
// we got a page url, now save the username without the / in the beginning
[ , username ] = username.match(/^(?:\/)?(pages?\/\S+)/mi);
} else if (username.match(/^(http|www)|(\/)/) || !username.match(/^([a-z\d\.]{5,50})$/mi)) {
errMessage = !username.match(/^([a-z\d\.]{5,50})$/mi) ? 'Your Username is not a valid Facebook Username' : 'The URL must be in a format like https://www.facebook.com/yourUsername';
this.get('user.errors').add('facebook', errMessage);
this.get('user.hasValidated').pushObject('facebook');
return;
}
newUrl = `https://www.facebook.com/${username}`;
this.set('user.facebook', newUrl);
this.get('user.errors').remove('facebook');
this.get('user.hasValidated').pushObject('facebook');
// User input is validated
invoke(this, 'save').then(() => {
// necessary to update the value in the input field
this.set('user.facebook', '');
run.schedule('afterRender', this, function () {
this.set('user.facebook', newUrl);
});
});
} else {
errMessage = 'The URL must be in a format like ' +
'https://www.facebook.com/yourUsername';
this.get('user.errors').add('facebook', errMessage);
this.get('user.hasValidated').pushObject('facebook');
return;
}
},
@ -310,41 +322,49 @@ export default Controller.extend({
// If new url didn't change, exit
if (newUrl === oldUrl) {
this.get('user.errors').remove('twitter');
return;
}
// TODO: put the validation here into a validator
if (!newUrl.match(/(^https:\/\/twitter\.com\/)(\S+)/g)) {
if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || (!validator.isURL(newUrl) && newUrl.match(/([a-zA-Z0-9\.]+)/))) {
let [ , username] = newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-zA-Z0-9\.]+)/);
newUrl = `https://twitter.com/${username}`;
if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) {
let username = [];
this.set('user.twitter', newUrl);
this.get('user.errors').remove('twitter');
this.get('user.hasValidated').pushObject('twitter');
// User input is validated
invoke(this, 'save').then(() => {
// necessary to update the value in the input field
this.set('user.twitter', '');
run.schedule('afterRender', this, function () {
this.set('user.twitter', newUrl);
});
});
} else if (validator.isURL(newUrl)) {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
this.get('user.errors').add('twitter', errMessage);
this.get('user.hasValidated').pushObject('twitter');
return;
if (newUrl.match(/(?:twitter\.com\/)(\S+)/)) {
[ , username] = newUrl.match(/(?:twitter\.com\/)(\S+)/);
} else {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
[username] = newUrl.match(/([^/]+)\/?$/mi);
}
// check if username starts with http or www and show error if so
if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d\.\_]{1,15}$/mi)) {
errMessage = !username.match(/^[a-z\d\.\_]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername';
this.get('user.errors').add('twitter', errMessage);
this.get('user.hasValidated').pushObject('twitter');
return;
}
newUrl = `https://twitter.com/${username}`;
this.set('user.twitter', newUrl);
this.get('user.errors').remove('twitter');
this.get('user.hasValidated').pushObject('twitter');
// User input is validated
invoke(this, 'save').then(() => {
// necessary to update the value in the input field
this.set('user.twitter', '');
run.schedule('afterRender', this, function () {
this.set('user.twitter', newUrl);
});
});
} else {
errMessage = 'The URL must be in a format like ' +
'https://twitter.com/yourUsername';
this.get('user.errors').add('twitter', errMessage);
this.get('user.hasValidated').pushObject('twitter');
return;
}
},

View file

@ -18,7 +18,7 @@ export default Model.extend(ValidationEngine, {
availableThemes: attr(),
ghost_head: attr('string'),
ghost_foot: attr('string'),
facebook: attr('string'),
facebook: attr('facebook-url-user'),
twitter: attr('twitter-url-user'),
labs: attr('string'),
navigation: attr('navigation-settings'),

View file

@ -38,8 +38,8 @@ export default Model.extend(ValidationEngine, {
async: false
}),
count: attr('raw'),
facebook: attr('string'),
twitter: attr('string'),
facebook: attr('facebook-url-user'),
twitter: attr('twitter-url-user'),
ghostPaths: service(),
ajax: service(),

View file

@ -0,0 +1,21 @@
import Transform from 'ember-data/transform';
export default Transform.extend({
deserialize(serialized) {
if (serialized) {
let [ , user ] = serialized.match(/(\S+)/);
return `https://www.facebook.com/${user}`;
}
return serialized;
},
serialize(deserialized) {
if (deserialized) {
let [ , user] = deserialized.match(/(?:https:\/\/)(?:www\.)(?:facebook\.com)\/(?:#!\/)?(\w+\/?\S+)/mi);
return user;
}
return deserialized;
}
});

View file

@ -172,6 +172,15 @@ describe('Acceptance: Settings - General', function () {
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', 'facebook.com/pages/some-facebook-page/857469375913?ref=ts');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[facebook]"]').val()).to.be.equal('https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', '*(&*(%%))');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
@ -184,8 +193,18 @@ describe('Acceptance: Settings - General', function () {
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[facebook]"]').val()).to.be.equal('https://www.facebook.com/username');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('The URL must be in a format like https://www.facebook.com/yourPage');
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', 'http://github.com/pages/username');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[facebook]"]').val()).to.be.equal('https://www.facebook.com/pages/username');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', 'testuser');
@ -197,6 +216,32 @@ describe('Acceptance: Settings - General', function () {
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', 'ab99');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('Your Page name is not a valid Facebook Page name');
});
fillIn('#settings-general input[name="general[facebook]"]', 'page/ab99');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[facebook]"]').val()).to.be.equal('https://www.facebook.com/page/ab99');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('');
});
fillIn('#settings-general input[name="general[facebook]"]', 'page/*(&*(%%))');
triggerEvent('#settings-general input[name="general[facebook]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[facebook]"]').val()).to.be.equal('https://www.facebook.com/page/*(&*(%%))');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('');
});
// validates a twitter url correctly
fillIn('#settings-general input[name="general[twitter]"]', 'twitter.com/username');
triggerEvent('#settings-general input[name="general[twitter]"]', 'blur');
@ -219,8 +264,17 @@ describe('Acceptance: Settings - General', function () {
triggerEvent('#settings-general input[name="general[twitter]"]', 'blur');
andThen(() => {
expect(find('#settings-general input[name="general[twitter]"]').val()).to.be.equal('https://twitter.com/username');
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('The URL must be in a format like https://twitter.com/yourUsername');
.to.equal('');
});
fillIn('#settings-general input[name="general[twitter]"]', 'thisusernamehasmorethan15characters');
triggerEvent('#settings-general input[name="general[twitter]"]', 'blur');
andThen(() => {
expect(find('#settings-general .error .response').text().trim(), 'inline validation response')
.to.equal('Your Username is not a valid Twitter Username');
});
fillIn('#settings-general input[name="general[twitter]"]', 'testuser');

View file

@ -313,6 +313,7 @@ describe('Acceptance: Team', function () {
expect(find('.user-details-bottom .form-group:nth-of-type(4)').hasClass('error'), 'website input should be in error state').to.be.true;
});
// Testing Facebook input
fillIn('#user-facebook', '');
fillIn('#user-facebook', ')(*&%^%)');
triggerEvent('#user-facebook', 'blur');
@ -322,16 +323,34 @@ describe('Acceptance: Team', function () {
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'name');
fillIn('#user-facebook', 'pages/)(*&%^%)');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/name');
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/pages/)(*&%^%)');
expect(find('.user-details-bottom .form-group:nth-of-type(5)').hasClass('error'), 'facebook input should be in error state').to.be.false;
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'http://twitter.com/user');
fillIn('#user-facebook', 'testing');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testing');
expect(find('.user-details-bottom .form-group:nth-of-type(5)').hasClass('error'), 'facebook input should be in error state').to.be.false;
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'somewebsite.com/pages/some-facebook-page/857469375913?ref=ts');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
expect(find('.user-details-bottom .form-group:nth-of-type(5)').hasClass('error'), 'facebook input should be in error state').to.be.false;
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'test');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
@ -339,14 +358,24 @@ describe('Acceptance: Team', function () {
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'facebook.com/user');
fillIn('#user-facebook', 'http://twitter.com/testuser');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/user');
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testuser');
expect(find('.user-details-bottom .form-group:nth-of-type(5)').hasClass('error'), 'facebook input should be in error state').to.be.false;
});
fillIn('#user-facebook', '');
fillIn('#user-facebook', 'facebook.com/testing');
triggerEvent('#user-facebook', 'blur');
andThen(() => {
expect(find('#user-facebook').val()).to.be.equal('https://www.facebook.com/testing');
expect(find('.user-details-bottom .form-group:nth-of-type(5)').hasClass('error'), 'facebook input should be in error state').to.be.false;
});
// Testing Twitter input
fillIn('#user-twitter', '');
fillIn('#user-twitter', ')(*&%^%)');
triggerEvent('#user-twitter', 'blur');
@ -369,7 +398,8 @@ describe('Acceptance: Team', function () {
triggerEvent('#user-twitter', 'blur');
andThen(() => {
expect(find('.user-details-bottom .form-group:nth-of-type(6)').hasClass('error'), 'twitter input should be in error state').to.be.true;
expect(find('#user-twitter').val()).to.be.equal('https://twitter.com/user');
expect(find('.user-details-bottom .form-group:nth-of-type(6)').hasClass('error'), 'twitter input should be in error state').to.be.false;
});
fillIn('#user-twitter', '');

View file

@ -0,0 +1,32 @@
/* jshint expr:true */
import { expect } from 'chai';
import { describeModule, it } from 'ember-mocha';
import Ember from 'ember';
const emberA = Ember.A;
describeModule(
'transform:facebook-url-user',
'Unit: Transform: facebook-url-user',
{
// Specify the other units that are required for this test.
// needs: ['transform:foo']
},
function() {
it('deserializes facebook url', function () {
let transform = this.subject();
let serialized = 'testuser';
let result = transform.deserialize(serialized);
expect(result).to.equal('https://www.facebook.com/testuser');
});
it('serializes url to facebook username', function () {
let transform = this.subject();
let deserialized = 'https://www.facebook.com/testuser';
let result = transform.serialize(deserialized);
expect(result).to.equal('testuser');
});
}
);

View file

@ -35,8 +35,8 @@ module.exports = {
bio: {type: 'string', maxlength: 200, nullable: true},
website: {type: 'text', maxlength: 2000, nullable: true, validations: {isEmptyOrURL: true}},
location: {type: 'text', maxlength: 65535, nullable: true},
facebook: {type: 'text', maxlength: 2000, nullable: true, validations: {isEmptyOrURL: true}},
twitter: {type: 'text', maxlength: 2000, nullable: true, validations: {isEmptyOrURL: true}},
facebook: {type: 'text', maxlength: 2000, nullable: true},
twitter: {type: 'text', maxlength: 2000, nullable: true},
accessibility: {type: 'text', maxlength: 65535, nullable: true},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'active'},
language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'},