feat: invites regardless of registration type, invite privilege, groups to join on acceptance (#8786)

* feat: allow invites in normal registration mode + invite privilege

* feat: select groups to join from an invite

* test: check if groups from invitations have been joined

* fix: remove unused variable

* feat: write API versions of socket calls

* docs: openapi specs for the new routes

* test: iron out mongo redis difference

* refactor: move inviteGroups endpoint into write API

* refactor: use GET /api/v3/users/:uid/invites/groups

Instead of GET /api/v3/users/:uid/inviteGroups

* fix: no need for /api/v3 prefix when using api module

* fix: tests

* refactor: change POST /api/v3/users/invite

To POST /api/v3/users/:uid/invites

* refactor: make helpers.invite awaitable

* fix: restrict invite API to self-use only

* fix: move invite groups controller to write api, +tests

* fix: tests

Co-authored-by: Julian Lam <julian@nodebb.org>
This commit is contained in:
gasoved
2020-11-16 22:47:23 +03:00
committed by GitHub
parent dde9f1890f
commit 3ccebf112e
23 changed files with 725 additions and 220 deletions

View File

@@ -5,9 +5,10 @@ const nconf = require('nconf');
const db = require('../../database');
const api = require('../../api');
const user = require('../../user');
const groups = require('../../groups');
const meta = require('../../meta');
const privileges = require('../../privileges');
const user = require('../../user');
const utils = require('../../utils');
const helpers = require('../helpers');
@@ -153,3 +154,60 @@ Users.revokeSession = async (req, res) => {
await user.auth.revokeSession(_id, req.params.uid);
helpers.formatApiResponse(200, res);
};
Users.invite = async (req, res) => {
const { emails, groupsToJoin = [] } = req.body;
if (!emails || !Array.isArray(groupsToJoin)) {
return helpers.formatApiResponse(400, res, new Error('[[error:invalid-data]]'));
}
// For simplicity, this API route is restricted to self-use only. This can change if needed.
if (parseInt(req.user.uid, 10) !== parseInt(req.params.uid, 10)) {
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
}
const canInvite = await privileges.users.hasInvitePrivilege(req.uid);
if (!canInvite) {
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
}
const registrationType = meta.config.registrationType;
const isAdmin = await user.isAdministrator(req.uid);
if (registrationType === 'admin-invite-only' && !isAdmin) {
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
}
const inviteGroups = await groups.getUserInviteGroups(req.uid);
const cannotInvite = groupsToJoin.some(group => !inviteGroups.includes(group));
if (groupsToJoin.length > 0 && cannotInvite) {
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
}
const max = meta.config.maximumInvites;
const emailsArr = emails.split(',').map(email => email.trim()).filter(Boolean);
for (const email of emailsArr) {
/* eslint-disable no-await-in-loop */
let invites = 0;
if (max) {
invites = await user.getInvitesNumber(req.uid);
}
if (!isAdmin && max && invites >= max) {
return helpers.formatApiResponse(403, res, new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]'));
}
await user.sendInvitationEmail(req.uid, email, groupsToJoin);
}
return helpers.formatApiResponse(200, res);
};
Users.getInviteGroups = async function (req, res) {
if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10)) {
return helpers.formatApiResponse(401, res);
}
const userInviteGroups = await groups.getUserInviteGroups(req.params.uid);
return helpers.formatApiResponse(200, res, userInviteGroups);
};