diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index 3ab37025ca..4cea3af1da 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -265,16 +265,21 @@ Users.getEmail = async (req, res) => { }; Users.confirmEmail = async (req, res) => { - const [exists, canManage] = await Promise.all([ - db.isSortedSetMember('email:uid', req.params.email.toLowerCase()), + const [pending, current, canManage] = await Promise.all([ + user.email.isValidationPending(req.params.uid, req.params.email), + user.getUserField(req.params.uid, 'email'), privileges.admin.can('admin:users', req.uid), ]); if (!canManage) { - helpers.notAllowed(req, res); + return helpers.notAllowed(req, res); } - if (exists) { + if (pending) { // has active confirmation request + const code = await db.get(`confirm:byUid:${req.params.uid}`); + await user.email.confirmByCode(code, req.session.id); + helpers.formatApiResponse(200, res); + } else if (current && current === req.params.email) { // email in user hash (i.e. email passed into user.create) await user.email.confirmByUid(req.params.uid); helpers.formatApiResponse(200, res); } else { diff --git a/test/user.js b/test/user.js index c18b5bfc1f..63f200f198 100644 --- a/test/user.js +++ b/test/user.js @@ -20,6 +20,7 @@ const groups = require('../src/groups'); const messaging = require('../src/messaging'); const helpers = require('./helpers'); const meta = require('../src/meta'); +const file = require('../src/file'); const socketUser = require('../src/socket.io/user'); const apiUser = require('../src/api/users'); @@ -983,7 +984,7 @@ describe('User', () => { await User.email.expireValidation(uid); await apiUser.update({ uid: uid }, { uid: uid, email: 'updatedAgain@me.com', password: '123456' }); - assert.strictEqual(await User.email.isValidationPending(uid), true); + assert.strictEqual(await User.email.isValidationPending(uid, 'updatedAgain@me.com'.toLowerCase()), true); }); it('should update cover image', (done) => { @@ -2849,4 +2850,18 @@ describe('User', () => { }); }); }); + + describe('User\'s', async () => { + let files; + + before(async () => { + files = await file.walk(path.resolve(__dirname, './user')); + }); + + it('subfolder tests', () => { + files.forEach((filePath) => { + require(filePath); + }); + }); + }); }); diff --git a/test/user/emails.js b/test/user/emails.js new file mode 100644 index 0000000000..c99f47ff88 --- /dev/null +++ b/test/user/emails.js @@ -0,0 +1,107 @@ +'use strict'; + +const assert = require('assert'); +const nconf = require('nconf'); +const util = require('util'); + +const db = require('../mocks/databasemock'); + +const helpers = require('../helpers'); + +const user = require('../../src/user'); +const groups = require('../../src/groups'); + +describe('email confirmation (v3 api)', () => { + let userObj; + let jar; + const register = data => new Promise((resolve, reject) => { + helpers.registerUser(data, (err, jar, response, body) => { + if (err) { + return reject(err); + } + + resolve({ jar, response, body }); + }); + }); + const login = util.promisify(helpers.loginUser); + + before(async () => { + // If you're running this file directly, uncomment these lines + await register({ + username: 'fake-user', + password: 'derpioansdosa', + email: 'b@c.com', + gdpr_consent: true, + }); + + ({ body: userObj, jar } = await register({ + username: 'email-test', + password: 'abcdef', + email: 'test@example.org', + gdpr_consent: true, + })); + }); + + it('should have a pending validation', async () => { + const code = await db.get(`confirm:byUid:${userObj.uid}`); + assert.strictEqual(await user.email.isValidationPending(userObj.uid, 'test@example.org'), true); + }); + + it('should not list their email', async () => { + const { res, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, { + jar, + json: true, + }); + + assert.strictEqual(res.statusCode, 200); + assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{"emails":[]}}')); + }); + + it('should not allow confirmation if they are not an admin', async () => { + const { res } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, { + jar, + json: true, + }); + + assert.strictEqual(res.statusCode, 403); + }); + + it('should not confirm an email that is not pending or set', async () => { + await groups.join('administrators', userObj.uid); + const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, { + jar, + json: true, + }); + + assert.strictEqual(res.statusCode, 404); + await groups.leave('administrators', userObj.uid); + }); + + it('should confirm their email (using the pending validation)', async () => { + await groups.join('administrators', userObj.uid); + const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, { + jar, + json: true, + }); + + assert.strictEqual(res.statusCode, 200); + assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}')); + await groups.leave('administrators', userObj.uid); + }); + + it('should still confirm the email (as email is set in user hash)', async () => { + await user.email.remove(userObj.uid); + await user.setUserField(userObj.uid, 'email', 'test@example.org'); + ({ jar } = await login('email-test', 'abcdef')); // email removal logs out everybody + await groups.join('administrators', userObj.uid); + + const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, { + jar, + json: true, + }); + + assert.strictEqual(res.statusCode, 200); + assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}')); + await groups.leave('administrators', userObj.uid); + }); +});