From aac34b2d30ecc38a206fac22a00b1d2d3e7a19ff Mon Sep 17 00:00:00 2001 From: BAIZIKING Date: Thu, 12 Mar 2026 14:39:46 +0000 Subject: [PATCH] fix: reduce number of return statements in src/user/invite.js --- src/user/invite.js | 323 +++++++++++++++++++++++---------------------- 1 file changed, 167 insertions(+), 156 deletions(-) diff --git a/src/user/invite.js b/src/user/invite.js index a9a5368bb1..9804de41cc 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -15,176 +15,187 @@ const utils = require('../utils'); const plugins = require('../plugins'); module.exports = function (User) { - User.getInvites = async function (uid) { - const emails = await db.getSetMembers(`invitation:uid:${uid}`); - return emails.map(email => validator.escape(String(email))); - }; - - User.getInvitesNumber = async function (uid) { - return await db.setCount(`invitation:uid:${uid}`); - }; - - User.getInvitingUsers = async function () { - return await db.getSetMembers('invitation:uids'); - }; + User.getInvites = uid => getInvites(uid); + User.getInvitesNumber = uid => getInvitesNumber(uid); + User.getInvitingUsers = () => getInvitingUsers(); + User.getAllInvites = () => getAllInvites(); + User.sendInvitationEmail = (uid, email, groupsToJoin) => sendInvitationEmail(User, uid, email, groupsToJoin); + User.verifyInvitation = query => verifyInvitation(query); + User.confirmIfInviteEmailIsUsed = (token, enteredEmail, uid) => confirmIfInviteEmailIsUsed(User, token, enteredEmail, uid); + User.joinGroupsFromInvitation = (uid, token) => joinGroupsFromInvitation(uid, token); + User.deleteInvitation = (invitedBy, email) => deleteInvitation(User, invitedBy, email); + User.deleteInvitationKey = (registrationEmail, token) => deleteInvitationKey(registrationEmail, token); +}; - User.getAllInvites = async function () { - const uids = await User.getInvitingUsers(); - const invitations = await async.map(uids, User.getInvites); - return invitations.map((invites, index) => ({ - uid: uids[index], - invitations: invites, - })); - }; +async function getInvites(uid) { + const emails = await db.getSetMembers(`invitation:uid:${uid}`); + return emails.map(email => validator.escape(String(email))); +} + +async function getInvitesNumber(uid) { + return await db.setCount(`invitation:uid:${uid}`); +} + +async function getInvitingUsers() { + return await db.getSetMembers('invitation:uids'); +} + +async function getAllInvites() { + const uids = await getInvitingUsers(); + const invitations = await async.map(uids, getInvites); + return invitations.map((invites, index) => ({ + uid: uids[index], + invitations: invites, + })); +} + +async function sendInvitationEmail(User, uid, email, groupsToJoin) { + if (!uid) { + throw new Error('[[error:invalid-uid]]'); + } - User.sendInvitationEmail = async function (uid, email, groupsToJoin) { - if (!uid) { - throw new Error('[[error:invalid-uid]]'); - } + const email_exists = await User.getUidByEmail(email); + if (email_exists) { + // Silently drop the invitation if the invited email already exists locally + return true; + } - const email_exists = await User.getUidByEmail(email); - if (email_exists) { - // Silently drop the invitation if the invited email already exists locally - return true; - } + const invitation_exists = await db.exists(`invitation:uid:${uid}:invited:${email}`); + if (invitation_exists) { + throw new Error('[[error:email-invited]]'); + } - const invitation_exists = await db.exists(`invitation:uid:${uid}:invited:${email}`); - if (invitation_exists) { - throw new Error('[[error:email-invited]]'); + const data = await prepareInvitation(User, uid, email, groupsToJoin); + await emailer.sendToEmail('invitation', email, meta.config.defaultLang, data); + plugins.hooks.fire('action:user.invite', { uid, email, groupsToJoin }); +} + +async function verifyInvitation(query) { + if (!query.token) { + if (meta.config.registrationType.startsWith('admin-')) { + throw new Error('[[register:invite.error-admin-only]]'); + } else { + throw new Error('[[register:invite.error-invite-only]]'); } + } + const token = await db.getObjectField(`invitation:token:${query.token}`, 'token'); + if (!token || token !== query.token) { + throw new Error('[[register:invite.error-invalid-data]]'); + } +} - const data = await prepareInvitation(uid, email, groupsToJoin); - await emailer.sendToEmail('invitation', email, meta.config.defaultLang, data); - plugins.hooks.fire('action:user.invite', { uid, email, groupsToJoin }); - }; +async function confirmIfInviteEmailIsUsed(User, token, enteredEmail, uid) { + if (!enteredEmail) { + return; + } + const email = await db.getObjectField(`invitation:token:${token}`, 'email'); + // "Confirm" user's email if registration completed with invited address + if (email && email === enteredEmail) { + await User.setUserField(uid, 'email', email); + await User.email.confirmByUid(uid); + } +} - User.verifyInvitation = async function (query) { - if (!query.token) { - if (meta.config.registrationType.startsWith('admin-')) { - throw new Error('[[register:invite.error-admin-only]]'); - } else { - throw new Error('[[register:invite.error-invite-only]]'); - } - } - const token = await db.getObjectField(`invitation:token:${query.token}`, 'token'); - if (!token || token !== query.token) { - throw new Error('[[register:invite.error-invalid-data]]'); - } - }; +async function joinGroupsFromInvitation(uid, token) { + let groupsToJoin = await db.getObjectField(`invitation:token:${token}`, 'groupsToJoin'); - User.confirmIfInviteEmailIsUsed = async function (token, enteredEmail, uid) { - if (!enteredEmail) { - return; - } - const email = await db.getObjectField(`invitation:token:${token}`, 'email'); - // "Confirm" user's email if registration completed with invited address - if (email && email === enteredEmail) { - await User.setUserField(uid, 'email', email); - await User.email.confirmByUid(uid); - } - }; + try { + groupsToJoin = JSON.parse(groupsToJoin); + } catch (err) { + winston.error(`[User.joinGroupsFromInvitation] ${err.stack}`); + return; + } - User.joinGroupsFromInvitation = async function (uid, token) { - let groupsToJoin = await db.getObjectField(`invitation:token:${token}`, 'groupsToJoin'); + if (!groupsToJoin || groupsToJoin.length < 1) { + return; + } - try { - groupsToJoin = JSON.parse(groupsToJoin); - } catch (err) { - winston.error(`[User.joinGroupsFromInvitation] ${err.stack}`); - return; - } + await groups.join(groupsToJoin, uid); +} - if (!groupsToJoin || groupsToJoin.length < 1) { +async function deleteInvitation(User, invitedBy, email) { + const invitedByUid = await User.getUidByUsername(invitedBy); + if (!invitedByUid) { + throw new Error('[[error:invalid-username]]'); + } + const token = await db.get(`invitation:uid:${invitedByUid}:invited:${email}`); + await Promise.all([ + deleteFromReferenceList(invitedByUid, email), + db.setRemove(`invitation:invited:${email}`, token), + db.delete(`invitation:token:${token}`), + ]); +} + +async function deleteInvitationKey(registrationEmail, token) { + if (registrationEmail) { + const uids = await getInvitingUsers(); + await Promise.all(uids.map(uid => deleteFromReferenceList(uid, registrationEmail))); + // Delete all invites to an email address if it has joined + const tokens = await db.getSetMembers(`invitation:invited:${registrationEmail}`); + const keysToDelete = [`invitation:invited:${registrationEmail}`].concat(tokens.map(token => `invitation:token:${token}`)); + await db.deleteAll(keysToDelete); + } + if (token) { + const invite = await db.getObject(`invitation:token:${token}`); + if (!invite) { return; } - - await groups.join(groupsToJoin, uid); - }; - - User.deleteInvitation = async function (invitedBy, email) { - const invitedByUid = await User.getUidByUsername(invitedBy); - if (!invitedByUid) { - throw new Error('[[error:invalid-username]]'); - } - const token = await db.get(`invitation:uid:${invitedByUid}:invited:${email}`); - await Promise.all([ - deleteFromReferenceList(invitedByUid, email), - db.setRemove(`invitation:invited:${email}`, token), - db.delete(`invitation:token:${token}`), - ]); - }; - - User.deleteInvitationKey = async function (registrationEmail, token) { - if (registrationEmail) { - const uids = await User.getInvitingUsers(); - await Promise.all(uids.map(uid => deleteFromReferenceList(uid, registrationEmail))); - // Delete all invites to an email address if it has joined - const tokens = await db.getSetMembers(`invitation:invited:${registrationEmail}`); - const keysToDelete = [`invitation:invited:${registrationEmail}`].concat(tokens.map(token => `invitation:token:${token}`)); - await db.deleteAll(keysToDelete); - } - if (token) { - const invite = await db.getObject(`invitation:token:${token}`); - if (!invite) { - return; - } - await deleteFromReferenceList(invite.inviter, invite.email); - await db.deleteAll([ - `invitation:invited:${invite.email}`, - `invitation:token:${token}`, - ]); - } - }; - - async function deleteFromReferenceList(uid, email) { - await Promise.all([ - db.setRemove(`invitation:uid:${uid}`, email), - db.delete(`invitation:uid:${uid}:invited:${email}`), + await deleteFromReferenceList(invite.inviter, invite.email); + await db.deleteAll([ + `invitation:invited:${invite.email}`, + `invitation:token:${token}`, ]); - const count = await db.setCount(`invitation:uid:${uid}`); - if (count === 0) { - await db.setRemove('invitation:uids', uid); - } } +} + +async function deleteFromReferenceList(uid, email) { + await Promise.all([ + db.setRemove(`invitation:uid:${uid}`, email), + db.delete(`invitation:uid:${uid}:invited:${email}`), + ]); + const count = await db.setCount(`invitation:uid:${uid}`); + if (count === 0) { + await db.setRemove('invitation:uids', uid); + } +} - async function prepareInvitation(uid, email, groupsToJoin) { - const inviterExists = await User.exists(uid); - if (!inviterExists) { - throw new Error('[[error:invalid-uid]]'); - } - - const token = utils.generateUUID(); - const registerLink = `${nconf.get('url')}/register?token=${token}`; - - const expireDays = meta.config.inviteExpiration; - const expireIn = expireDays * 86400000; - - await db.setAdd(`invitation:uid:${uid}`, email); - await db.setAdd('invitation:uids', uid); - // Referencing from uid and email to token - await db.set(`invitation:uid:${uid}:invited:${email}`, token); - // Keeping references for all invites to this email address - await db.setAdd(`invitation:invited:${email}`, token); - await db.setObject(`invitation:token:${token}`, { - email, - token, - groupsToJoin: JSON.stringify(groupsToJoin), - inviter: uid, - }); - await db.pexpireAt(`invitation:token:${token}`, Date.now() + expireIn); - - const username = await User.getUserField(uid, 'username'); - const title = meta.config.title || meta.config.browserTitle || 'NodeBB'; - const subject = await translator.translate(`[[email:invite, ${title}]]`, meta.config.defaultLang); - - return { - ...emailer._defaultPayload, // Append default data to this email payload - site_title: title, - registerLink: registerLink, - subject: subject, - username: username, - template: 'invitation', - expireDays: expireDays, - }; +async function prepareInvitation(User, uid, email, groupsToJoin) { + const inviterExists = await User.exists(uid); + if (!inviterExists) { + throw new Error('[[error:invalid-uid]]'); } -}; + + const token = utils.generateUUID(); + const registerLink = `${nconf.get('url')}/register?token=${token}`; + + const expireDays = meta.config.inviteExpiration; + const expireIn = expireDays * 86400000; + + await db.setAdd(`invitation:uid:${uid}`, email); + await db.setAdd('invitation:uids', uid); + // Referencing from uid and email to token + await db.set(`invitation:uid:${uid}:invited:${email}`, token); + // Keeping references for all invites to this email address + await db.setAdd(`invitation:invited:${email}`, token); + await db.setObject(`invitation:token:${token}`, { + email, + token, + groupsToJoin: JSON.stringify(groupsToJoin), + inviter: uid, + }); + await db.pexpireAt(`invitation:token:${token}`, Date.now() + expireIn); + + const username = await User.getUserField(uid, 'username'); + const title = meta.config.title || meta.config.browserTitle || 'NodeBB'; + const subject = await translator.translate(`[[email:invite, ${title}]]`, meta.config.defaultLang); + + return { + ...emailer._defaultPayload, // Append default data to this email payload + site_title: title, + registerLink: registerLink, + subject: subject, + username: username, + template: 'invitation', + expireDays: expireDays, + }; +}