mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-04 10:18:32 +02:00
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:
@@ -9,6 +9,7 @@ const db = require('../../database');
|
||||
const pagination = require('../../pagination');
|
||||
const events = require('../../events');
|
||||
const plugins = require('../../plugins');
|
||||
const privileges = require('../../privileges');
|
||||
const utils = require('../../utils');
|
||||
|
||||
const usersController = module.exports;
|
||||
@@ -115,7 +116,7 @@ async function getUsers(req, res) {
|
||||
getUsersWithFields(set),
|
||||
]);
|
||||
|
||||
render(req, res, {
|
||||
await render(req, res, {
|
||||
users: users.filter(user => user && parseInt(user.uid, 10)),
|
||||
page: page,
|
||||
pageCount: Math.max(1, Math.ceil(count / resultsPerPage)),
|
||||
@@ -176,7 +177,7 @@ usersController.search = async function (req, res) {
|
||||
searchData.resultsPerPage = resultsPerPage;
|
||||
searchData.sortBy = req.query.sortBy;
|
||||
searchData.reverse = reverse;
|
||||
render(req, res, searchData);
|
||||
await render(req, res, searchData);
|
||||
};
|
||||
|
||||
usersController.registrationQueue = async function (req, res) {
|
||||
@@ -226,7 +227,7 @@ async function getInvites() {
|
||||
return invitations;
|
||||
}
|
||||
|
||||
function render(req, res, data) {
|
||||
async function render(req, res, data) {
|
||||
data.pagination = pagination.create(data.page, data.pageCount, req.query);
|
||||
|
||||
const registrationType = meta.config.registrationType;
|
||||
@@ -241,6 +242,12 @@ function render(req, res, data) {
|
||||
filterBy.forEach(function (filter) {
|
||||
data['filterBy_' + validator.escape(String(filter))] = true;
|
||||
});
|
||||
|
||||
data.showInviteButton = await privileges.users.hasInvitePrivilege(req.uid);
|
||||
if (data.adminInviteOnly) {
|
||||
data.showInviteButton = await privileges.users.isAdministrator(req.uid);
|
||||
}
|
||||
|
||||
res.render('admin/manage/users', data);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,11 @@ async function registerAndLoginUser(req, res, userData) {
|
||||
await authenticationController.doLogin(req, uid);
|
||||
}
|
||||
|
||||
user.deleteInvitationKey(userData.email);
|
||||
// Distinguish registrations through invites from direct ones
|
||||
if (userData.token) {
|
||||
await user.joinGroupsFromInvitation(uid, userData.email);
|
||||
}
|
||||
await user.deleteInvitationKey(userData.email);
|
||||
const referrer = req.body.referrer || req.session.referrer || nconf.get('relative_path') + '/';
|
||||
const complete = await plugins.fireHook('filter:register.complete', { uid: uid, referrer: referrer });
|
||||
req.session.returnTo = complete.referrer;
|
||||
@@ -74,7 +78,7 @@ authenticationController.register = async function (req, res) {
|
||||
|
||||
const userData = req.body;
|
||||
try {
|
||||
if (registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
|
||||
if (userData.token || registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
|
||||
await user.verifyInvitation(userData);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ async function renderIfAdminOrGlobalMod(set, req, res) {
|
||||
|
||||
usersController.renderUsersPage = async function (set, req, res) {
|
||||
const userData = await usersController.getUsers(set, req.uid, req.query);
|
||||
render(req, res, userData);
|
||||
await render(req, res, userData);
|
||||
};
|
||||
|
||||
usersController.getUsers = async function (set, uid, query) {
|
||||
@@ -171,10 +171,15 @@ async function render(req, res, data) {
|
||||
data.inviteOnly = registrationType === 'invite-only' || registrationType === 'admin-invite-only';
|
||||
data.adminInviteOnly = registrationType === 'admin-invite-only';
|
||||
data.invites = await user.getInvitesNumber(req.uid);
|
||||
data.showInviteButton = req.loggedIn && (
|
||||
(registrationType === 'invite-only' && (data.isAdmin || !data.maximumInvites || data.invites < data.maximumInvites)) ||
|
||||
(registrationType === 'admin-invite-only' && data.isAdmin)
|
||||
);
|
||||
|
||||
data.showInviteButton = false;
|
||||
if (data.adminInviteOnly) {
|
||||
data.showInviteButton = await privileges.users.isAdministrator(req.uid);
|
||||
} else if (req.loggedIn) {
|
||||
const canInvite = await privileges.users.hasInvitePrivilege(req.uid);
|
||||
data.showInviteButton = canInvite && (!data.maximumInvites || data.invites < data.maximumInvites);
|
||||
}
|
||||
|
||||
data['reputation:disabled'] = meta.config['reputation:disabled'];
|
||||
|
||||
res.append('X-Total-Count', data.userCount);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user