diff --git a/models/setting.js b/models/setting.js index 25882b3f8..f6eff613a 100644 --- a/models/setting.js +++ b/models/setting.js @@ -62,6 +62,16 @@ const SettingSchema = new Schema({ requireEmailConfirmation: { type: Boolean, default: false + }, + domains: { + whitelist: { + type: Array, + default: ['localhost'] + }, + enable: { + type: Boolean, + default: true + } } }, { timestamps: { diff --git a/services/assets.js b/services/assets.js index 24a161865..e6d2e32d4 100644 --- a/services/assets.js +++ b/services/assets.js @@ -1,5 +1,7 @@ const AssetModel = require('../models/asset'); const SettingsService = require('./settings'); +const domainlist = require('./domainlist'); +const errors = require('../errors'); module.exports = class AssetsService { @@ -53,6 +55,12 @@ module.exports = class AssetsService { * @return {Promise} */ static findOrCreateByUrl(url) { + + // Check the URL to confirm that is in the domain whitelist + if (!domainlist.urlCheck(url)) { + return Promise.reject(errors.ErrInvalidAssetURL); + } + return AssetModel.findOneAndUpdate({url}, {url}, { // Ensure that if it's new, we return the new object created. diff --git a/services/domainlist.js b/services/domainlist.js new file mode 100644 index 000000000..3d5568fda --- /dev/null +++ b/services/domainlist.js @@ -0,0 +1,112 @@ +const debug = require('debug')('talk:services:domainlist'); +const _ = require('lodash'); +const SettingsService = require('./settings'); + +/** + * The root domainlist object. + * @type {Object} + */ +class Domainlist { + + constructor() { + this.lists = { + whitelist: [], + }; + } + + /** + * Loads domains white list in from the database + */ + load() { + return SettingsService + .retrieve() + .then((settings) => { + + // Insert the settings domains whitelist. + this.upsert(settings.domains); + }); + } + + /** + * Inserts the domains whitelist data + * @param {Array} list list of domains to be set to the whitelist + */ + upsert(lists) { + + // Add the domains to this array and also be sure are all unique domains + if (!('whitelist' in lists)) { + return; + } + + this.lists['whitelist'] = Domainlist.parseList(lists['whitelist']); + debug(`Added ${lists['whitelist'].length} domains to the whitelist.`); + + return Promise.resolve(this); + } + + /** + * Tests the url to see if it contains any of the whitelisted domains. + * @param {String} url value to match. + * @return {Boolean} true if the url contains any of the domains, false otherwise. + */ + match(list, url) { + + const domainToMatch = Domainlist.parseURL(url); + + // This will return true in the event that at least one blockword is found + // in the phrase. + return list.every((domain) => { + + // Not considering wildcards for now. + if (domain === domainToMatch) { + return true; + } + + // We've walked over all the whitelisted domains, and haven't had a + // mismatch... It is not an allowed domain! + return false; + }); + } + + /** + * Parses the list content. + * @param {Array} list array of domains to parse for a list. + * @return {Array} the parsed list + */ + static parseList(list) { + return _.uniq(list.map((domain) => Domainlist.parseURL(domain))); + } + + /** + * Parses the URL. + * @param {String} url url to parse for a domain. + * @return {String} the domain + */ + static parseURL(url){ + let domain; + + // removes protocol and get domain + if (url.indexOf('://') > -1) { + domain = url.split('/')[2]; + } else { + domain = url.split('/')[0]; + } + + // remove port number + domain = domain.split(':')[0]; + + return domain.toLowerCase(); + } + + static urlCheck(url) { + const dl = new Domainlist(); + + return dl.load() + .then(() => { + return dl.match(dl.lists['whitelist'], url); + }); + } + +} + +module.exports = Domainlist;