'use strict'; const nconf = require('nconf'); const winston = require('winston'); const user = require('./index'); const utils = require('../utils'); const plugins = require('../plugins'); const db = require('../database'); const meta = require('../meta'); const emailer = require('../emailer'); const groups = require('../groups'); const UserEmail = module.exports; UserEmail.exists = async function (email) { const uid = await user.getUidByEmail(email.toLowerCase()); return !!uid; }; UserEmail.available = async function (email) { const exists = await db.isSortedSetMember('email:uid', email.toLowerCase()); return !exists; }; UserEmail.sendValidationEmail = async function (uid, options) { /* * Options: * - email, overrides email retrieval * - force, sends email even if it is too soon to send another */ options = options || {}; // Fallback behaviour (email passed in as second argument) if (typeof options === 'string') { options = { email: options, }; } let confirm_code = utils.generateUUID(); const confirm_link = nconf.get('url') + '/confirm/' + confirm_code; const emailInterval = meta.config.emailConfirmInterval; // If no email passed in (default), retrieve email from uid if (!options.email || !options.email.length) { options.email = await user.getUserField(uid, 'email'); } if (!options.email) { return; } let sent = false; if (!options.force) { sent = await db.get('uid:' + uid + ':confirm:email:sent'); } if (sent) { throw new Error('[[error:confirm-email-already-sent, ' + emailInterval + ']]'); } await db.set('uid:' + uid + ':confirm:email:sent', 1); await db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000)); confirm_code = await plugins.hooks.fire('filter:user.verify.code', confirm_code); await db.setObject('confirm:' + confirm_code, { email: options.email.toLowerCase(), uid: uid, }); await db.expireAt('confirm:' + confirm_code, Math.floor((Date.now() / 1000) + (60 * 60 * 24))); const username = await user.getUserField(uid, 'username'); const data = { username: username, confirm_link: confirm_link, confirm_code: confirm_code, subject: options.subject || '[[email:welcome-to, ' + (meta.config.title || meta.config.browserTitle || 'NodeBB') + ']]', template: options.template || 'welcome', uid: uid, }; if (plugins.hooks.hasListeners('action:user.verify')) { plugins.hooks.fire('action:user.verify', { uid: uid, data: data }); } else { await emailer.send(data.template, uid, data); } return confirm_code; }; UserEmail.confirm = async function (code) { // TODO: remove in 1.17.0 winston.warn('[deprecated] User.email.confirm deprecated use User.email.confirmByCode'); await UserEmail.confirmByCode(code); }; // confirm email by code sent by confirmation email UserEmail.confirmByCode = async function (code) { const confirmObj = await db.getObject('confirm:' + code); if (!confirmObj || !confirmObj.uid || !confirmObj.email) { throw new Error('[[error:invalid-data]]'); } const currentEmail = await user.getUserField(confirmObj.uid, 'email'); if (!currentEmail || currentEmail.toLowerCase() !== confirmObj.email) { throw new Error('[[error:invalid-email]]'); } await UserEmail.confirmByUid(confirmObj.uid); await db.delete('confirm:' + code); }; // confirm uid's email UserEmail.confirmByUid = async function (uid) { if (!(parseInt(uid, 10) > 0)) { throw new Error('[[error:invalid-uid]]'); } const currentEmail = await user.getUserField(uid, 'email'); if (!currentEmail) { throw new Error('[[error:invalid-email]]'); } await Promise.all([ user.setUserField(uid, 'email:confirmed', 1), groups.join('verified-users', uid), groups.leave('unverified-users', uid), db.delete('uid:' + uid + ':confirm:email:sent'), ]); await plugins.hooks.fire('action:user.email.confirmed', { uid: uid, email: currentEmail }); };