From 325d00ea942a156374eab60ca532360351289664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 29 Apr 2024 10:32:07 -0400 Subject: [PATCH] feat: track uid for email/username changes, closes #12454 --- install/package.json | 4 ++-- src/api/users.js | 4 ++-- src/socket.io/admin/user.js | 2 +- src/user/email.js | 5 +++-- src/user/info.js | 17 ++++++++++++++--- src/user/profile.js | 6 +++--- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/install/package.json b/install/package.json index ba493698ad32..7112a6273306 100644 --- a/install/package.json +++ b/install/package.json @@ -103,10 +103,10 @@ "nodebb-plugin-ntfy": "1.7.4", "nodebb-plugin-spam-be-gone": "2.2.2", "nodebb-rewards-essentials": "1.0.0", - "nodebb-theme-harmony": "1.2.53", + "nodebb-theme-harmony": "1.2.54", "nodebb-theme-lavender": "7.1.8", "nodebb-theme-peace": "2.2.4", - "nodebb-theme-persona": "13.3.18", + "nodebb-theme-persona": "13.3.19", "nodebb-widget-essentials": "7.0.16", "nodemailer": "6.9.13", "nprogress": "0.2.0", diff --git a/src/api/users.js b/src/api/users.js index 1f44bb4372db..931e75b36b1a 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -454,7 +454,7 @@ usersAPI.addEmail = async (caller, { email, skipConfirmation, uid }) => { throw new Error('[[error:email-taken]]'); } await user.setUserField(uid, 'email', email); - await user.email.confirmByUid(uid); + await user.email.confirmByUid(uid, caller.uid); } } else { await usersAPI.update(caller, { uid, email }); @@ -504,7 +504,7 @@ usersAPI.confirmEmail = async (caller, { uid, email, sessionId }) => { await user.email.confirmByCode(code, sessionId); return true; } else if (current && current === email) { // i.e. old account w/ unconf. email in user hash - await user.email.confirmByUid(uid); + await user.email.confirmByUid(uid, caller.uid); return true; } diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 45e91caaa6ef..db9a49ac1f2d 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -70,7 +70,7 @@ User.validateEmail = async function (socket, uids) { if (email) { await user.setUserField(uid, 'email', email); } - await user.email.confirmByUid(uid); + await user.email.confirmByUid(uid, socket.uid); } }; diff --git a/src/user/email.js b/src/user/email.js index c54a98682889..aec9379f413e 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -214,10 +214,11 @@ UserEmail.confirmByCode = async function (code, sessionId) { }; // confirm uid's email via ACP -UserEmail.confirmByUid = async function (uid) { +UserEmail.confirmByUid = async function (uid, callerUid = 0) { if (!(parseInt(uid, 10) > 0)) { throw new Error('[[error:invalid-uid]]'); } + callerUid = callerUid || uid; const currentEmail = await user.getUserField(uid, 'email'); if (!currentEmail) { throw new Error('[[error:invalid-email]]'); @@ -241,7 +242,7 @@ UserEmail.confirmByUid = async function (uid) { db.sortedSetAddBulk([ ['email:uid', uid, currentEmail.toLowerCase()], ['email:sorted', 0, `${currentEmail.toLowerCase()}:${uid}`], - [`user:${uid}:emails`, Date.now(), `${currentEmail}:${Date.now()}`], + [`user:${uid}:emails`, Date.now(), `${currentEmail}:${Date.now()}:${callerUid}`], ]), user.setUserField(uid, 'email:confirmed', 1), groups.join('verified-users', uid), diff --git a/src/user/info.js b/src/user/info.js index 47de1154fbea..6b488fe41ecf 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -60,13 +60,24 @@ module.exports = function (User) { User.getHistory = async function (set) { const data = await db.getSortedSetRevRangeWithScores(set, 0, -1); - return data.map((set) => { + data.forEach((set) => { set.timestamp = set.score; set.timestampISO = utils.toISOString(set.score); - set.value = validator.escape(String(set.value.split(':')[0])); + const parts = set.value.split(':'); + set.value = validator.escape(String(parts[0])); + set.byUid = validator.escape(String(parts[2] || '')); delete set.score; - return set; }); + + const uids = _.uniq(data.map(d => d && d.byUid).filter(Boolean)); + const usersData = await User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); + const uidToUser = _.zipObject(uids, usersData); + data.forEach((d) => { + if (d.byUid) { + d.byUser = uidToUser[d.byUid]; + } + }); + return data; }; async function getFlagMetadata(flags) { diff --git a/src/user/profile.js b/src/user/profile.js index 9c1792f0f6f5..9d65037bbed9 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -48,7 +48,7 @@ module.exports = function (User) { if (field === 'email') { return await updateEmail(updateUid, data.email); } else if (field === 'username') { - return await updateUsername(updateUid, data.username); + return await updateUsername(updateUid, data.username, uid); } else if (field === 'fullname') { return await updateFullname(updateUid, data.fullname); } @@ -247,7 +247,7 @@ module.exports = function (User) { } } - async function updateUsername(uid, newUsername) { + async function updateUsername(uid, newUsername, callerUid) { if (!newUsername) { return; } @@ -260,7 +260,7 @@ module.exports = function (User) { await Promise.all([ updateUidMapping('username', uid, newUsername, userData.username), updateUidMapping('userslug', uid, newUserslug, userData.userslug), - db.sortedSetAdd(`user:${uid}:usernames`, now, `${newUsername}:${now}`), + db.sortedSetAdd(`user:${uid}:usernames`, now, `${newUsername}:${now}:${callerUid}`), ]); await db.sortedSetRemove('username:sorted', `${userData.username.toLowerCase()}:${uid}`); await db.sortedSetAdd('username:sorted', 0, `${newUsername.toLowerCase()}:${uid}`);