diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 32fdc6e..16aa6f8 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -3,6 +3,7 @@ [vipUsers](###vipUsers) [sponsorTimes](###sponsorTimes) [userNames](###userNames) +[userNameLogs](###userNameLogs) [categoryVotes](###categoryVotes) [lockCategories](###lockCategories) [warnings](###warnings) @@ -61,6 +62,16 @@ | -- | :--: | | userNames_userID | userID | +### userNameLogs + +| Name | Type | | +| -- | :--: | -- | +| userID | TEXT | not null | +| newUserName | TEXT | not null | +| oldUserName | TEXT | not null | +| updatedByAdmin | BOOLEAN | not null | +| updatedAt | INTEGER | not null | + ### categoryVotes | Name | Type | | diff --git a/databases/_upgrade_private_2.sql b/databases/_upgrade_private_2.sql new file mode 100644 index 0000000..1a70e67 --- /dev/null +++ b/databases/_upgrade_private_2.sql @@ -0,0 +1,13 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS "userNameLogs" ( + "userID" TEXT NOT NULL, + "newUserName" TEXT NOT NULL, + "oldUserName" TEXT NOT NULL, + "updatedByAdmin" BOOLEAN NOT NULL, + "updatedAt" INTEGER NOT NULL +); + +UPDATE "config" SET value = 2 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 71a85bd..e02953c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -32,6 +32,7 @@ import {endpoint as getSegmentInfo} from './routes/getSegmentInfo'; import {postClearCache} from './routes/postClearCache'; import { addUnlistedVideo } from './routes/addUnlistedVideo'; import {postPurgeAllSegments} from './routes/postPurgeAllSegments'; +import {getUserID} from './routes/getUserID'; export function createServer(callback: () => void) { // Create a service (the app object is just a callback). @@ -146,6 +147,9 @@ function setupRoutes(app: Express) { app.post('/api/purgeAllSegments', postPurgeAllSegments); app.post('/api/unlistedVideo', addUnlistedVideo); + + // get userID from username + app.get('/api/userID', getUserID); if (config.postgres) { app.get('/database', (req, res) => dumpDatabase(req, res, true)); diff --git a/src/routes/getUserID.ts b/src/routes/getUserID.ts new file mode 100644 index 0000000..6ecc1b7 --- /dev/null +++ b/src/routes/getUserID.ts @@ -0,0 +1,55 @@ +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; +import {UserID} from '../types/user.model'; + +function getFuzzyUserID(userName: String): Promise<{userName: String, userID: UserID }[]> { + // escape [_ % \] to avoid ReDOS + userName = userName.replace(/\\/g, '\\\\') + .replace(/_/g, '\\_') + .replace(/%/g, '\\%'); + userName = `%${userName}%`; // add wildcard to username + // LIMIT to reduce overhead | ESCAPE to escape LIKE wildcards + try { + return db.prepare('all', `SELECT "userName", "userID" FROM "userNames" WHERE "userName" + LIKE ? ESCAPE '\\' LIMIT 10`, [userName]) + } catch (err) { + return null; + } +} + +function getExactUserID(userName: String): Promise<{userName: String, userID: UserID }[]> { + try { + return db.prepare('all', `SELECT "userName", "userID" from "userNames" WHERE "userName" = ? LIMIT 10`, [userName]); + } catch (err) { + return null; + } +} + +export async function getUserID(req: Request, res: Response) { + let userName = req.query.username as string; + const exactSearch = req.query.exact + ? req.query.exact == "true" + : false as Boolean; + + // if not exact and length is 1, also skip + if (userName == undefined || userName.length > 64 || + (!exactSearch && userName.length < 3)) { + // invalid request + res.sendStatus(400); + return false; + } + const results = exactSearch + ? await getExactUserID(userName) + : await getFuzzyUserID(userName); + + if (results === undefined || results === null) { + res.sendStatus(500); + return false; + } else if (results.length === 0) { + res.sendStatus(404); + return false; + } else { + res.send(results); + return false; + } +} diff --git a/src/routes/setUsername.ts b/src/routes/setUsername.ts index dfc8108..daebc9f 100644 --- a/src/routes/setUsername.ts +++ b/src/routes/setUsername.ts @@ -1,9 +1,16 @@ import {config} from '../config'; import {Logger} from '../utils/logger'; -import {db} from '../databases/databases'; +import {db, privateDB} from '../databases/databases'; import {getHash} from '../utils/getHash'; import {Request, Response} from 'express'; +async function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise { + return privateDB.prepare('run', + `INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedByAdmin", "updatedAt") VALUES(?, ?, ?, ?, ?)`, + [userID, newUserName, oldUserName, + updatedByAdmin, new Date().getTime()] + ); +} + export async function setUsername(req: Request, res: Response) { let userID = req.query.userID as string; let userName = req.query.username as string; @@ -55,11 +62,13 @@ export async function setUsername(req: Request, res: Response) { try { //check if username is already set - const row = await db.prepare('get', `SELECT count(*) as count FROM "userNames" WHERE "userID" = ?`, [userID]); + const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [userID]); const locked = adminUserIDInput === undefined ? 0 : 1; + let oldUserName = ''; - if (row.count > 0) { + if (row.userName && row.userName.length !== 0) { //already exists, update this row + oldUserName = row.userName; await db.prepare('run', `UPDATE "userNames" SET "userName" = ? WHERE "userID" = ?`, [userName, userID]); await db.prepare('run', `UPDATE "userNames" SET "locked" = ? WHERE "userID" = ?`, [locked, userID]); } else { @@ -67,6 +76,8 @@ export async function setUsername(req: Request, res: Response) { await db.prepare('run', `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [userID, userName, locked]); } + await logUserNameChange(userID, userName, oldUserName, adminUserIDInput !== undefined); + res.sendStatus(200); } catch (err) { Logger.error(err); diff --git a/test/cases/getSegmentInfo.ts b/test/cases/getSegmentInfo.ts index 3f03027..a6c2f0d 100644 --- a/test/cases/getSegmentInfo.ts +++ b/test/cases/getSegmentInfo.ts @@ -3,20 +3,20 @@ import {db} from '../../src/databases/databases'; import {Done, getbaseURL} from '../utils'; import {getHash} from '../../src/utils/getHash'; -const ENOENTID = "0000000000000000000000000000000000000000000000000000000000000000" -const upvotedID = "a000000000000000000000000000000000000000000000000000000000000000" -const downvotedID = "b000000000000000000000000000000000000000000000000000000000000000" -const lockedupID = "c000000000000000000000000000000000000000000000000000000000000000" -const infvotesID = "d000000000000000000000000000000000000000000000000000000000000000" -const shadowhiddenID = "e000000000000000000000000000000000000000000000000000000000000000" -const lockeddownID = "f000000000000000000000000000000000000000000000000000000000000000" -const hiddenID = "1000000000000000000000000000000000000000000000000000000000000000" -const fillerID1 = "1100000000000000000000000000000000000000000000000000000000000000" -const fillerID2 = "1200000000000000000000000000000000000000000000000000000000000000" -const fillerID3 = "1300000000000000000000000000000000000000000000000000000000000000" -const fillerID4 = "1400000000000000000000000000000000000000000000000000000000000000" -const fillerID5 = "1500000000000000000000000000000000000000000000000000000000000000" -const oldID = "a0000000-0000-0000-0000-000000000000" +const ENOENTID = "0".repeat(64); +const upvotedID = "a"+"0".repeat(63); +const downvotedID = "b"+"0".repeat(63); +const lockedupID = "c"+"0".repeat(63); +const infvotesID = "d"+"0".repeat(63); +const shadowhiddenID = "e"+"0".repeat(63); +const lockeddownID = "f"+"0".repeat(63); +const hiddenID = "1"+"0".repeat(63); +const fillerID1 = "11"+"0".repeat(62); +const fillerID2 = "12"+"0".repeat(62); +const fillerID3 = "13"+"0".repeat(62); +const fillerID4 = "14"+"0".repeat(62); +const fillerID5 = "15"+"0".repeat(62); +const oldID = `${'0'.repeat(8)}-${'0000-'.repeat(3)}${'0'.repeat(12)}`; describe('getSegmentInfo', () => { before(async () => { diff --git a/test/cases/getUserID.ts b/test/cases/getUserID.ts new file mode 100644 index 0000000..7fe5d70 --- /dev/null +++ b/test/cases/getUserID.ts @@ -0,0 +1,403 @@ +import fetch from 'node-fetch'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; + +describe('getUserID', () => { + before(async () => { + const insertUserNameQuery = 'INSERT INTO "userNames" ("userID", "userName") VALUES(?, ?)'; + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_01"), 'fuzzy user 01']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_02"), 'fuzzy user 02']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_03"), 'specific user 03']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_04"), 'repeating']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_05"), 'repeating']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_06"), getHash("getuserid_user_06")]); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_07"), '0redos0']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_08"), '%redos%']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_09"), '_redos_']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_10"), 'redos\\%']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_11"), '\\\\\\']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_12"), 'a']); + }); + + it('Should be able to get a 200', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=fuzzy+user+01') + .then(async res => { + const text = await res.text() + if (res.status !== 200) done('non 200 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => done('couldn\'t call endpoint')); + }); + + it('Should be able to get a 400 (No username parameter)', (done: Done) => { + fetch(getbaseURL() + '/api/userID') + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => done('couldn\'t call endpoint')); + }); + + it('Should be able to get a 200 (username is public id)', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username='+getHash("getuserid_user_06")) + .then(async res => { + const text = await res.text() + if (res.status !== 200) done('non 200 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => done('couldn\'t call endpoint')); + }); + + it('Should be able to get a 400 (username longer than 64 chars)', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username='+getHash("getuserid_user_06")+'0') + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => done('couldn\'t call endpoint')); + }); + + it('Should be able to get single username', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=fuzzy+user+01') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "fuzzy user 01") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_01")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get multiple fuzzy user info from start', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=fuzzy+user') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 2) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "fuzzy user 01") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_01")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "fuzzy user 02") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_02")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get multiple fuzzy user info from middle', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=user') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 3) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "fuzzy user 01") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_01")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "fuzzy user 02") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_02")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[2].userName !== "specific user 03") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[2].userID !== getHash("getuserid_user_03")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get with public ID', (done: Done) => { + const userID = getHash("getuserid_user_06"); + fetch(getbaseURL() + '/api/userID?username='+userID) + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== userID) { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== userID) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get with fuzzy public ID', (done: Done) => { + const userID = getHash("getuserid_user_06"); + fetch(getbaseURL() + '/api/userID?username='+userID.substr(10,60)) + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== userID) { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== userID) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get repeating username', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=repeating') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 2) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_04")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_05")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get repeating fuzzy username', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=peat') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 2) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_04")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_05")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should avoid ReDOS with _', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=_redos_') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "_redos_") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_09")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should avoid ReDOS with %', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=%redos%') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "%redos%") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_08")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return 404 if escaped backslashes present', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=%redos\\\\_') + .then(res => { + if (res.status !== 404) done('non 404 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return 404 if backslashes present', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=\\%redos\\_') + .then(res => { + if (res.status !== 404) done('non 404 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return user if just backslashes', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=\\\\\\') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "\\\\\\") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_11")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should not allow usernames more than 64 characters', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username='+'0'.repeat(65)) + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should not allow usernames less than 3 characters', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=aa') + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should allow exact match', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=a&exact=true') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "a") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_12")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should be able to get repeating username with exact username', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=repeating&exact=true') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 2) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_04")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "repeating") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_05")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('Should not get exact unless explicitly set to true', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=user&exact=1') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 3) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "fuzzy user 01") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_01")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[1].userName !== "fuzzy user 02") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[1].userID !== getHash("getuserid_user_02")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else if (data[2].userName !== "specific user 03") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[2].userID !== getHash("getuserid_user_03")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); +}); diff --git a/test/cases/setUsername.ts b/test/cases/setUsername.ts index f96e1f4..7d7bb49 100644 --- a/test/cases/setUsername.ts +++ b/test/cases/setUsername.ts @@ -1,6 +1,6 @@ import fetch from 'node-fetch'; import { Done, getbaseURL } from '../utils'; -import { db } from '../../src/databases/databases'; +import { db, privateDB } from '../../src/databases/databases'; import { getHash } from '../../src/utils/getHash'; const adminPrivateUserID = 'testUserId'; @@ -21,6 +21,7 @@ const username07 = 'Username 07'; async function addUsername(userID: string, userName: string, locked = 0) { await db.prepare('run', 'INSERT INTO "userNames" ("userID", "userName", "locked") VALUES(?, ?, ?)', [userID, userName, locked]); + await addLogUserNameChange(userID, userName); } async function getUsernameInfo(userID: string): Promise<{ userName: string, locked: string }> { @@ -31,6 +32,40 @@ async function getUsernameInfo(userID: string): Promise<{ userName: string, lock return row; } +async function addLogUserNameChange(userID: string, newUserName: string, oldUserName: string = '') { + privateDB.prepare('run', + `INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedAt", "updatedByAdmin") VALUES(?, ?, ?, ?, ?)`, + [getHash(userID), newUserName, oldUserName, new Date().getTime(), + true] + ); +} + +async function getLastLogUserNameChange(userID: string) { + return privateDB.prepare('get', `SELECT * FROM "userNameLogs" WHERE "userID" = ? ORDER BY "updatedAt" DESC LIMIT 1`, [getHash(userID)]); +} + +function wellFormatUserName(userName: string) { + return userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, ''); +} + +async function testUserNameChangelog(userID: string, newUserName: string, oldUserName: string, byAdmin: boolean, done: Done) { + + const log = await getLastLogUserNameChange(userID); + + if (newUserName !== log.newUserName) { + return done(`UserID '${userID}' incorrect log on newUserName: ${newUserName} !== ${log.newUserName}`); + } + + if (oldUserName !== log.oldUserName) { + return done(`UserID '${userID}' incorrect log on oldUserName: ${oldUserName} !== ${log.oldUserName}`); + } + + if (byAdmin !== Boolean(log.updatedByAdmin)) { + return done(`UserID '${userID}' incorrect log on updatedByAdmin: ${byAdmin} !== ${log.updatedByAdmin}`); + } + + return done(); +} + describe('setUsername', () => { before(async () => { await addUsername(getHash(user01PrivateUserID), username01, 0); @@ -46,9 +81,11 @@ describe('setUsername', () => { fetch(`${getbaseURL()}/api/setUsername?userID=${user01PrivateUserID}&username=Changed%20Username`, { method: 'POST', }) - .then(res => { + .then(async res => { if (res.status !== 200) done(`Status code was ${res.status}`); - else done(); // pass + else { + testUserNameChangelog(user01PrivateUserID, decodeURIComponent('Changed%20Username'), username01, false, done); + } }) .catch(err => done(`couldn't call endpoint`)); }); @@ -114,7 +151,7 @@ describe('setUsername', () => { const usernameInfo = await getUsernameInfo(getHash(user03PrivateUserID)); if (usernameInfo.userName !== newUsername) done(`Username did not change`); if (usernameInfo.locked == "1") done(`Username was locked when it shouldn't have been`); - else done(); + testUserNameChangelog(user03PrivateUserID, newUsername, username03, false, done); }) .catch(err => done(`couldn't call endpoint`)); }); @@ -141,7 +178,7 @@ describe('setUsername', () => { .then(async res => { const usernameInfo = await getUsernameInfo(getHash(user05PrivateUserID)); if (usernameInfo.userName === newUsername) done(`Username contains unicode control characters`); - else done(); + testUserNameChangelog(user05PrivateUserID, wellFormatUserName(newUsername), username05, false, done); }) .catch(err => done(`couldn't call endpoint`)); }); @@ -167,7 +204,7 @@ describe('setUsername', () => { const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID)); if (usernameInfo.userName !== newUsername) done(`Failed to change username from '${username06}' to '${newUsername}'`); if (usernameInfo.locked == "0") done(`Username was not locked`); - else done(); + else testUserNameChangelog(user06PrivateUserID, newUsername, username06, true, done); }) .catch(err => done(`couldn't call endpoint`)); }); @@ -181,7 +218,7 @@ describe('setUsername', () => { const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID)); if (usernameInfo.userName !== newUsername) done(`Failed to change username from '${username06}' to '${newUsername}'`); if (usernameInfo.locked == "0") done(`Username was unlocked when it shouldn't have been`); - else done(); + else testUserNameChangelog(user07PrivateUserID, newUsername, username07, true, done); }) .catch(err => done(`couldn't call endpoint`)); });