Add name_dedupe column instead of using LIKE kludge for similar-looking names

This commit is contained in:
Calvin Montgomery 2017-03-11 17:09:50 -08:00
parent 4701e767b6
commit f8183bea1b
5 changed files with 80 additions and 17 deletions

View File

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.31.0", "version": "3.32.0",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View File

@ -48,6 +48,7 @@ module.exports.init = function () {
global_ipbans = {}; global_ipbans = {};
module.exports.users = require("./database/accounts"); module.exports.users = require("./database/accounts");
module.exports.channels = require("./database/channels"); module.exports.channels = require("./database/channels");
module.exports.pool = pool;
}; };
/** /**

View File

@ -7,15 +7,6 @@ var Logger = require("../logger");
var registrationLock = {}; var registrationLock = {};
var blackHole = function () { }; var blackHole = function () { };
/**
* Replaces look-alike characters with "_" (single character wildcard) for
* use in LIKE queries. This prevents guests from taking names that look
* visually identical to existing names in certain fonts.
*/
function wildcardSimilarChars(name) {
return name.replace(/_/g, "\\_").replace(/[Il1oO0]/g, "_");
}
function parseProfile(data) { function parseProfile(data) {
try { try {
var profile = JSON.parse(data.profile); var profile = JSON.parse(data.profile);
@ -31,12 +22,23 @@ module.exports = {
init: function () { init: function () {
}, },
/**
* Convert a username for deduplication purposes.
* Collapses visibily similar characters into a single character.
* @param name
*/
dedupeUsername: function dedupeUsername(name) {
return name.replace(/[Il1]/ig, '1')
.replace(/[o0]/ig, '0')
.replace(/[_-]/g, '_');
},
/** /**
* Check if a username is taken * Check if a username is taken
*/ */
isUsernameTaken: function (name, callback) { isUsernameTaken: function (name, callback) {
db.query("SELECT name FROM `users` WHERE name LIKE ? ESCAPE '\\\\'", db.query("SELECT name FROM `users` WHERE name = ? or name_dedupe = ?",
[wildcardSimilarChars(name)], [name, module.exports.dedupeUsername(name)],
function (err, rows) { function (err, rows) {
if (err) { if (err) {
callback(err, true); callback(err, true);
@ -175,10 +177,10 @@ module.exports = {
} }
db.query("INSERT INTO `users` " + db.query("INSERT INTO `users` " +
"(`name`, `password`, `global_rank`, `email`, `profile`, `ip`, `time`)" + "(`name`, `password`, `global_rank`, `email`, `profile`, `ip`, `time`, `name_dedupe`)" +
" VALUES " + " VALUES " +
"(?, ?, ?, ?, '', ?, ?)", "(?, ?, ?, ?, '', ?, ?, ?)",
[name, hash, 1, email, ip, Date.now()], [name, hash, 1, email, ip, Date.now(), module.exports.dedupeUsername(name)],
function (err, res) { function (err, res) {
delete registrationLock[lname]; delete registrationLock[lname];
if (err) { if (err) {

View File

@ -8,8 +8,10 @@ const TBL_USERS = "" +
"`profile` TEXT CHARACTER SET utf8mb4 NOT NULL," + "`profile` TEXT CHARACTER SET utf8mb4 NOT NULL," +
"`ip` VARCHAR(39) NOT NULL," + "`ip` VARCHAR(39) NOT NULL," +
"`time` BIGINT NOT NULL," + "`time` BIGINT NOT NULL," +
"`name_dedupe` VARCHAR(20) DEFAULT NULL," +
"PRIMARY KEY(`id`)," + "PRIMARY KEY(`id`)," +
"UNIQUE(`name`)) " + "UNIQUE(`name`)," +
"UNIQUE(`name_dedupe`)) " +
"CHARACTER SET utf8"; "CHARACTER SET utf8";
const TBL_CHANNELS = "" + const TBL_CHANNELS = "" +

View File

@ -1,8 +1,9 @@
var db = require("../database"); var db = require("../database");
var Logger = require("../logger"); var Logger = require("../logger");
var Q = require("q"); var Q = require("q");
import Promise from 'bluebird';
const DB_VERSION = 7; const DB_VERSION = 9;
var hasUpdates = []; var hasUpdates = [];
module.exports.checkVersion = function () { module.exports.checkVersion = function () {
@ -58,6 +59,10 @@ function update(version, cb) {
fixCustomEmbeds(cb); fixCustomEmbeds(cb);
} else if (version < 7) { } else if (version < 7) {
fixCustomEmbedsInUserPlaylists(cb); fixCustomEmbedsInUserPlaylists(cb);
} else if (version < 8) {
addUsernameDedupeColumn(cb);
} else if (version < 9) {
populateUsernameDedupeColumn(cb);
} }
} }
@ -330,3 +335,56 @@ function fixCustomEmbedsInUserPlaylists(cb) {
Logger.errlog.log(err.stack); Logger.errlog.log(err.stack);
}); });
} }
function addUsernameDedupeColumn(cb) {
Logger.syslog.log("Adding name_dedupe column on the users table");
db.query("ALTER TABLE users ADD COLUMN name_dedupe VARCHAR(20) UNIQUE DEFAULT NULL", (error) => {
if (error) {
Logger.errlog.log(`Unable to add name_dedupe column: ${error}`);
} else {
cb();
}
});
}
function populateUsernameDedupeColumn(cb) {
const dbUsers = require("./accounts");
Logger.syslog.log("Populating name_dedupe column on the users table");
db.query("SELECT id, name FROM users WHERE name_dedupe IS NULL", (err, rows) => {
if (err) {
Logger.errlog.log("Unable to perform database upgrade to add dedupe column: " + err);
return;
}
Promise.map(rows, row => {
return new Promise((resolve, reject) => {
db.pool.getConnection((error, conn) => {
if (error) {
reject(error);
return;
}
const dedupedName = dbUsers.dedupeUsername(row.name);
Logger.syslog.log(`Deduping [${row.name}] as [${dedupedName}]`);
conn.query("UPDATE users SET name_dedupe = ? WHERE id = ?", [dedupedName, row.id], (error, res) => {
conn.release();
if (error) {
if (error.errno === 1062) {
Logger.syslog.log(`WARNING: could not set name_dedupe for [${row.name}] due to an existing row for [${dedupedName}]`);
resolve();
} else {
reject(error);
}
} else {
resolve();
}
});
});
});
}, { concurrency: 10 }).then(() => {
cb();
}).catch(error => {
Logger.errlog.log("Unable to perform database upgrade to add dedupe column: " + (error.stack ? error.stack : error));
})
});
}