mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 03:26:04 +01:00
Admin/users (#8762)
* feat: wip admin/users * feat: more work * feat: more fixes * feat: #8662, verified/unverified user groups * feat: add filter * feat: change user search to use filters array * refactor: remove unused search call * fix: tests * fix: cant join system groups * fix: upgrade script
This commit is contained in:
committed by
GitHub
parent
bfaeb27c11
commit
872bacf1c4
@@ -20,16 +20,10 @@
|
||||
"add-group": "Add Group",
|
||||
"invite": "Invite",
|
||||
"new": "New User",
|
||||
|
||||
"pills.latest": "Latest Users",
|
||||
"filter-by": "Filter by",
|
||||
"pills.unvalidated": "Not Validated",
|
||||
"pills.no-posts": "No Posts",
|
||||
"pills.top-posters": "Top Posters",
|
||||
"pills.top-rep": "Most Reputation",
|
||||
"pills.inactive": "Inactive",
|
||||
"pills.flagged": "Most Flagged",
|
||||
"pills.validated": "Validated",
|
||||
"pills.banned": "Banned",
|
||||
"pills.search": "User Search",
|
||||
|
||||
"50-per-page": "50 per page",
|
||||
"100-per-page": "100 per page",
|
||||
|
||||
@@ -79,24 +79,6 @@ paths:
|
||||
$ref: 'read/admin/manage/tags.yaml'
|
||||
/api/admin/manage/users:
|
||||
$ref: 'read/admin/manage/users.yaml'
|
||||
/api/admin/manage/users/search:
|
||||
$ref: 'read/admin/manage/users/search.yaml'
|
||||
/api/admin/manage/users/latest:
|
||||
$ref: 'read/admin/manage/users/latest.yaml'
|
||||
/api/admin/manage/users/not-validated:
|
||||
$ref: 'read/admin/manage/users/not-validated.yaml'
|
||||
/api/admin/manage/users/no-posts:
|
||||
$ref: 'read/admin/manage/users/no-posts.yaml'
|
||||
/api/admin/manage/users/top-posters:
|
||||
$ref: 'read/admin/manage/users/top-posters.yaml'
|
||||
/api/admin/manage/users/most-reputation:
|
||||
$ref: 'read/admin/manage/users/most-reputation.yaml'
|
||||
/api/admin/manage/users/inactive:
|
||||
$ref: 'read/admin/manage/users/inactive.yaml'
|
||||
/api/admin/manage/users/flagged:
|
||||
$ref: 'read/admin/manage/users/flagged.yaml'
|
||||
/api/admin/manage/users/banned:
|
||||
$ref: 'read/admin/manage/users/banned.yaml'
|
||||
/api/admin/manage/registration:
|
||||
$ref: 'read/admin/manage/registration.yaml'
|
||||
/api/admin/manage/admins-mods:
|
||||
|
||||
@@ -79,6 +79,8 @@ get:
|
||||
description: Each privilege will have a key in this object
|
||||
isPrivate:
|
||||
type: boolean
|
||||
isSystem:
|
||||
type: boolean
|
||||
columnCountUser:
|
||||
type: number
|
||||
columnCountUserOther:
|
||||
|
||||
@@ -21,12 +21,12 @@ get:
|
||||
type: number
|
||||
resultsPerPage:
|
||||
type: number
|
||||
latest:
|
||||
reverse:
|
||||
type: boolean
|
||||
search_display:
|
||||
sortBy:
|
||||
type: string
|
||||
requireEmailConfirmation:
|
||||
type: number
|
||||
sort_online:
|
||||
type: boolean
|
||||
inviteOnly:
|
||||
type: boolean
|
||||
adminInviteOnly:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get banned users
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get flagged users
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get inactive users
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get latest users
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get users with the most reputation
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get users with no posts
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get non-verified users
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -1,39 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get users via search term
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
search_display:
|
||||
type: string
|
||||
matchCount:
|
||||
type: number
|
||||
query:
|
||||
type: string
|
||||
uidQuery:
|
||||
type: string
|
||||
usernameQuery:
|
||||
type: string
|
||||
emailQuery:
|
||||
type: string
|
||||
ipQuery:
|
||||
type: string
|
||||
pageCount:
|
||||
type: number
|
||||
resultsPerPage:
|
||||
type: number
|
||||
timing:
|
||||
type: number
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../../../../components/schemas/UserObject.yaml#/UserObjectACP
|
||||
- $ref: ../../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||
- $ref: ../../../../components/schemas/Pagination.yaml#/Pagination
|
||||
@@ -1,7 +0,0 @@
|
||||
get:
|
||||
tags:
|
||||
- admin
|
||||
summary: Get users with the most posts
|
||||
responses:
|
||||
"418":
|
||||
description: "TODO: A proper response needs to be added. It is not really a teapot | Replace this responses block with the block from /manage/users/latest"
|
||||
@@ -6,17 +6,11 @@ define('admin/manage/users', [
|
||||
var Users = {};
|
||||
|
||||
Users.init = function () {
|
||||
var navPills = $('.nav-pills li');
|
||||
var pathname = window.location.pathname;
|
||||
if (!navPills.find('a[href^="' + pathname + '"]').length || pathname === config.relative_path + '/admin/manage/users') {
|
||||
pathname = config.relative_path + '/admin/manage/users/latest';
|
||||
}
|
||||
navPills.removeClass('active').find('a[href^="' + pathname + '"]').parent().addClass('active');
|
||||
|
||||
$('#results-per-page').val(ajaxify.data.resultsPerPage).on('change', function () {
|
||||
var query = utils.params();
|
||||
query.resultsPerPage = $('#results-per-page').val();
|
||||
ajaxify.go(window.location.pathname + '?' + $.param(query));
|
||||
var qs = buildSearchQuery(query);
|
||||
ajaxify.go(window.location.pathname + '?' + qs);
|
||||
});
|
||||
|
||||
function getSelectedUids() {
|
||||
@@ -346,7 +340,7 @@ define('admin/manage/users', [
|
||||
});
|
||||
|
||||
function handleUserCreate() {
|
||||
$('#createUser').on('click', function () {
|
||||
$('[data-action="create"]').on('click', function () {
|
||||
Benchpress.parse('admin/partials/create_user_modal', {}, function (html) {
|
||||
var modal = bootbox.dialog({
|
||||
message: html,
|
||||
@@ -403,35 +397,45 @@ define('admin/manage/users', [
|
||||
}, err => errorEl.translateHtml('[[admin/manage/users:alerts.error-x, ' + err.status.message + ']]').removeClass('hidden'));
|
||||
}
|
||||
|
||||
var timeoutId = 0;
|
||||
|
||||
$('#search-user-uid, #search-user-name, #search-user-email, #search-user-ip').on('keyup', function () {
|
||||
if (timeoutId !== 0) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = 0;
|
||||
}
|
||||
|
||||
var $this = $(this);
|
||||
var type = $this.attr('data-search-type');
|
||||
|
||||
timeoutId = setTimeout(function () {
|
||||
$('.fa-spinner').removeClass('hidden');
|
||||
loadSearchPage({
|
||||
searchBy: type,
|
||||
query: $this.val(),
|
||||
page: 1,
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
handleSearch();
|
||||
|
||||
handleUserCreate();
|
||||
|
||||
handleInvite();
|
||||
|
||||
handleSort();
|
||||
handleFilter();
|
||||
};
|
||||
|
||||
function handleSearch() {
|
||||
var timeoutId = 0;
|
||||
function doSearch() {
|
||||
$('.fa-spinner').removeClass('hidden');
|
||||
loadSearchPage({
|
||||
searchBy: $('#user-search-by').val(),
|
||||
query: $('#user-search').val(),
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
$('#user-search').on('keyup', function () {
|
||||
if (timeoutId !== 0) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = 0;
|
||||
}
|
||||
timeoutId = setTimeout(doSearch, 250);
|
||||
});
|
||||
$('#user-search-by').on('change', function () {
|
||||
doSearch();
|
||||
});
|
||||
}
|
||||
|
||||
function loadSearchPage(query) {
|
||||
var qs = decodeURIComponent($.param(query));
|
||||
$.get(config.relative_path + '/api/admin/manage/users/search?' + qs, renderSearchResults).fail(function (xhrErr) {
|
||||
var params = utils.params();
|
||||
params.searchBy = query.searchBy;
|
||||
params.query = query.query;
|
||||
params.page = query.page;
|
||||
var qs = decodeURIComponent($.param(params));
|
||||
$.get(config.relative_path + '/api/admin/manage/users?' + qs, renderSearchResults).fail(function (xhrErr) {
|
||||
if (xhrErr && xhrErr.responseJSON && xhrErr.responseJSON.error) {
|
||||
app.alertError(xhrErr.responseJSON.error);
|
||||
}
|
||||
@@ -448,14 +452,19 @@ define('admin/manage/users', [
|
||||
$('.users-table tbody').append(html);
|
||||
html.find('.timeago').timeago();
|
||||
$('.fa-spinner').addClass('hidden');
|
||||
|
||||
if (!$('#user-search').val()) {
|
||||
$('#user-found-notify').addClass('hidden');
|
||||
$('#user-notfound-notify').addClass('hidden');
|
||||
return;
|
||||
}
|
||||
if (data && data.users.length === 0) {
|
||||
$('#user-notfound-notify').translateHtml('[[admin/manage/users:search.not-found]]')
|
||||
.removeClass('hidden');
|
||||
$('#user-found-notify').addClass('hidden');
|
||||
} else {
|
||||
$('#user-found-notify').translateHtml(translator.compile('admin/manage/users:alerts.x-users-found', data.matchCount, data.timing))
|
||||
.removeClass('hidden');
|
||||
$('#user-found-notify').translateHtml(
|
||||
translator.compile('admin/manage/users:alerts.x-users-found', data.matchCount, data.timing)
|
||||
).removeClass('hidden');
|
||||
$('#user-notfound-notify').addClass('hidden');
|
||||
}
|
||||
});
|
||||
@@ -479,6 +488,75 @@ define('admin/manage/users', [
|
||||
});
|
||||
}
|
||||
|
||||
function buildSearchQuery(params) {
|
||||
if ($('#user-search').val()) {
|
||||
params.query = $('#user-search').val();
|
||||
params.searchBy = $('#user-search-by').val();
|
||||
} else {
|
||||
delete params.query;
|
||||
delete params.searchBy;
|
||||
}
|
||||
|
||||
return decodeURIComponent($.param(params));
|
||||
}
|
||||
|
||||
function handleSort() {
|
||||
$('.users-table thead th').on('click', function () {
|
||||
var $this = $(this);
|
||||
var sortBy = $this.attr('data-sort');
|
||||
if (!sortBy) {
|
||||
return;
|
||||
}
|
||||
var params = utils.params();
|
||||
params.sortBy = sortBy;
|
||||
if (ajaxify.data.sortBy === sortBy) {
|
||||
params.sortDirection = ajaxify.data.reverse ? 'asc' : 'desc';
|
||||
} else {
|
||||
params.sortDirection = 'desc';
|
||||
}
|
||||
|
||||
var qs = buildSearchQuery(params);
|
||||
ajaxify.go('admin/manage/users?' + qs);
|
||||
});
|
||||
}
|
||||
|
||||
function getFilters() {
|
||||
var filters = [];
|
||||
$('#filter-by').find('[data-filter-by]').each(function () {
|
||||
if ($(this).find('.fa-check').length) {
|
||||
filters.push($(this).attr('data-filter-by'));
|
||||
}
|
||||
});
|
||||
return filters;
|
||||
}
|
||||
|
||||
function handleFilter() {
|
||||
var currentFilters = getFilters();
|
||||
$('#filter-by').on('click', 'li', function () {
|
||||
var $this = $(this);
|
||||
$this.find('i').toggleClass('fa-check', !$this.find('i').hasClass('fa-check'));
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#filter-by').on('hidden.bs.dropdown', function () {
|
||||
var filters = getFilters();
|
||||
var changed = filters.length !== currentFilters.length;
|
||||
if (filters.length === currentFilters.length) {
|
||||
filters.forEach(function (filter, i) {
|
||||
if (filter !== currentFilters[i]) {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
currentFilters = getFilters();
|
||||
if (changed) {
|
||||
var params = utils.params();
|
||||
params.filters = filters;
|
||||
var qs = buildSearchQuery(params);
|
||||
ajaxify.go('admin/manage/users?' + qs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Users;
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ define('forum/topic/change-owner', [
|
||||
changeOwner();
|
||||
});
|
||||
|
||||
autocomplete.user(modal.find('#username'), { notBanned: true }, function (ev, ui) {
|
||||
autocomplete.user(modal.find('#username'), { filters: ['notbanned'] }, function (ev, ui) {
|
||||
toUid = ui.item.user.uid;
|
||||
checkButtonEnable();
|
||||
});
|
||||
|
||||
@@ -61,17 +61,20 @@ define('forum/users', ['translator', 'benchpress'], function (translator, Benchp
|
||||
return loadPage(query);
|
||||
}
|
||||
|
||||
query.term = username;
|
||||
query.query = username;
|
||||
query.sortBy = getSortBy();
|
||||
|
||||
var filters = [];
|
||||
if ($('.search .online-only').is(':checked') || (activeSection === 'online')) {
|
||||
query.onlineOnly = true;
|
||||
filters.push('online');
|
||||
}
|
||||
if (activeSection === 'banned') {
|
||||
query.bannedOnly = true;
|
||||
filters.push('banned');
|
||||
}
|
||||
if (activeSection === 'flagged') {
|
||||
query.flaggedOnly = true;
|
||||
filters.push('flagged');
|
||||
}
|
||||
if (filters.length) {
|
||||
query.filters = filters;
|
||||
}
|
||||
|
||||
loadPage(query);
|
||||
|
||||
@@ -67,7 +67,11 @@ groupsController.get = async function (req, res, next) {
|
||||
|
||||
async function getGroupNames() {
|
||||
const groupNames = await db.getSortedSetRange('groups:createtime', 0, -1);
|
||||
return groupNames.filter(name => name !== 'registered-users' && !groups.isPrivilegeGroup(name));
|
||||
return groupNames.filter(name => name !== 'registered-users' &&
|
||||
name !== 'verified-users' &&
|
||||
name !== 'unverified-users' &&
|
||||
!groups.isPrivilegeGroup(name)
|
||||
);
|
||||
}
|
||||
|
||||
groupsController.getCSV = async function (req, res) {
|
||||
|
||||
@@ -13,19 +13,130 @@ const utils = require('../../utils');
|
||||
|
||||
const usersController = module.exports;
|
||||
|
||||
const userFields = ['uid', 'username', 'userslug', 'email', 'postcount', 'joindate', 'banned',
|
||||
'reputation', 'picture', 'flags', 'lastonline', 'email:confirmed'];
|
||||
const userFields = [
|
||||
'uid', 'username', 'userslug', 'email', 'postcount', 'joindate', 'banned',
|
||||
'reputation', 'picture', 'flags', 'lastonline', 'email:confirmed',
|
||||
];
|
||||
|
||||
usersController.index = async function (req, res) {
|
||||
if (req.query.query) {
|
||||
await usersController.search(req, res);
|
||||
} else {
|
||||
await getUsers(req, res);
|
||||
}
|
||||
};
|
||||
|
||||
async function getUsers(req, res) {
|
||||
const sortDirection = req.query.sortDirection || 'desc';
|
||||
const reverse = sortDirection === 'desc';
|
||||
|
||||
usersController.search = async function (req, res) {
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
let resultsPerPage = parseInt(req.query.resultsPerPage, 10) || 50;
|
||||
if (![50, 100, 250, 500].includes(resultsPerPage)) {
|
||||
resultsPerPage = 50;
|
||||
}
|
||||
let sortBy = validator.escape(req.query.sortBy || '');
|
||||
const filterBy = Array.isArray(req.query.filters || []) ? (req.query.filters || []) : [req.query.filters];
|
||||
const start = Math.max(0, page - 1) * resultsPerPage;
|
||||
const stop = start + resultsPerPage - 1;
|
||||
|
||||
function buildSet() {
|
||||
const sortToSet = {
|
||||
postcount: 'users:postcount',
|
||||
reputation: 'users:reputation',
|
||||
joindate: 'users:joindate',
|
||||
online: 'users:online',
|
||||
flags: 'users:flags',
|
||||
};
|
||||
|
||||
const set = [];
|
||||
if (sortBy) {
|
||||
set.push(sortToSet[sortBy]);
|
||||
}
|
||||
if (filterBy.includes('unverified')) {
|
||||
set.push('group:unverified-users:members');
|
||||
}
|
||||
if (filterBy.includes('verified')) {
|
||||
set.push('group:verified-users:members');
|
||||
}
|
||||
if (filterBy.includes('banned')) {
|
||||
set.push('users:banned');
|
||||
}
|
||||
if (!set.length) {
|
||||
set.push('users:online');
|
||||
sortBy = 'online';
|
||||
}
|
||||
return set.length > 1 ? set : set[0];
|
||||
}
|
||||
|
||||
async function getCount(set) {
|
||||
if (Array.isArray(set)) {
|
||||
return await db.sortedSetIntersectCard(set);
|
||||
}
|
||||
return await db.sortedSetCard(set);
|
||||
}
|
||||
|
||||
async function getUids(set) {
|
||||
let uids = [];
|
||||
if (Array.isArray(set)) {
|
||||
const weights = set.map((s, index) => (index ? 0 : 1));
|
||||
uids = await db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({
|
||||
sets: set,
|
||||
start: start,
|
||||
stop: stop,
|
||||
weights: weights,
|
||||
});
|
||||
} else {
|
||||
uids = await db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop);
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
|
||||
async function getUsersWithFields(set) {
|
||||
const uids = await getUids(set);
|
||||
const [isAdmin, userData] = await Promise.all([
|
||||
user.isAdministrator(uids),
|
||||
user.getUsersWithFields(uids, userFields, req.uid),
|
||||
]);
|
||||
userData.forEach((user, index) => {
|
||||
if (user) {
|
||||
user.administrator = isAdmin[index];
|
||||
}
|
||||
});
|
||||
return userData;
|
||||
}
|
||||
const set = buildSet();
|
||||
const [count, users] = await Promise.all([
|
||||
getCount(set),
|
||||
getUsersWithFields(set),
|
||||
]);
|
||||
|
||||
render(req, res, {
|
||||
users: users.filter(user => user && parseInt(user.uid, 10)),
|
||||
page: page,
|
||||
pageCount: Math.max(1, Math.ceil(count / resultsPerPage)),
|
||||
resultsPerPage: resultsPerPage,
|
||||
reverse: reverse,
|
||||
sortBy: sortBy,
|
||||
});
|
||||
}
|
||||
|
||||
usersController.search = async function (req, res) {
|
||||
const sortDirection = req.query.sortDirection || 'desc';
|
||||
const reverse = sortDirection === 'desc';
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
let resultsPerPage = parseInt(req.query.resultsPerPage, 10) || 50;
|
||||
if (![50, 100, 250, 500].includes(resultsPerPage)) {
|
||||
resultsPerPage = 50;
|
||||
}
|
||||
|
||||
const searchData = await user.search({
|
||||
uid: req.uid,
|
||||
query: req.query.query,
|
||||
searchBy: req.query.searchBy,
|
||||
sortBy: req.query.sortBy,
|
||||
sortDirection: sortDirection,
|
||||
filters: req.query.filters,
|
||||
page: page,
|
||||
resultsPerPage: resultsPerPage,
|
||||
findUids: async function (query, searchBy, hardCap) {
|
||||
@@ -58,48 +169,10 @@ usersController.search = async function (req, res) {
|
||||
}
|
||||
});
|
||||
searchData.query = validator.escape(String(req.query.query || ''));
|
||||
searchData.uidQuery = req.query.searchBy === 'uid' ? searchData.query : '';
|
||||
searchData.usernameQuery = req.query.searchBy === 'username' ? searchData.query : '';
|
||||
searchData.emailQuery = req.query.searchBy === 'email' ? searchData.query : '';
|
||||
searchData.ipQuery = req.query.searchBy === 'uid' ? searchData.query : '';
|
||||
searchData.resultsPerPage = resultsPerPage;
|
||||
searchData.pagination = pagination.create(page, searchData.pageCount, req.query);
|
||||
searchData.search_display = '';
|
||||
res.render('admin/manage/users', searchData);
|
||||
};
|
||||
|
||||
usersController.sortByJoinDate = async function (req, res) {
|
||||
await getUsers('users:joindate', 'latest', undefined, undefined, req, res);
|
||||
};
|
||||
|
||||
usersController.notValidated = async function (req, res) {
|
||||
await getUsers('users:notvalidated', 'notvalidated', undefined, undefined, req, res);
|
||||
};
|
||||
|
||||
usersController.noPosts = async function (req, res) {
|
||||
await getUsers('users:postcount', 'noposts', '-inf', 0, req, res);
|
||||
};
|
||||
|
||||
usersController.topPosters = async function (req, res) {
|
||||
await getUsers('users:postcount', 'topposts', 0, '+inf', req, res);
|
||||
};
|
||||
|
||||
usersController.mostReputaion = async function (req, res) {
|
||||
await getUsers('users:reputation', 'mostreputation', 0, '+inf', req, res);
|
||||
};
|
||||
|
||||
usersController.flagged = async function (req, res) {
|
||||
await getUsers('users:flags', 'mostflags', 1, '+inf', req, res);
|
||||
};
|
||||
|
||||
usersController.inactive = async function (req, res) {
|
||||
const timeRange = 1000 * 60 * 60 * 24 * 30 * (parseInt(req.query.months, 10) || 3);
|
||||
const cutoff = Date.now() - timeRange;
|
||||
await getUsers('users:online', 'inactive', '-inf', cutoff, req, res);
|
||||
};
|
||||
|
||||
usersController.banned = async function (req, res) {
|
||||
await getUsers('users:banned', 'banned', undefined, undefined, req, res);
|
||||
searchData.sortBy = req.query.sortBy;
|
||||
searchData.reverse = reverse;
|
||||
render(req, res, searchData);
|
||||
};
|
||||
|
||||
usersController.registrationQueue = async function (req, res) {
|
||||
@@ -149,69 +222,21 @@ async function getInvites() {
|
||||
return invitations;
|
||||
}
|
||||
|
||||
async function getUsers(set, section, min, max, req, res) {
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
let resultsPerPage = parseInt(req.query.resultsPerPage, 10) || 50;
|
||||
if (![50, 100, 250, 500].includes(resultsPerPage)) {
|
||||
resultsPerPage = 50;
|
||||
}
|
||||
const start = Math.max(0, page - 1) * resultsPerPage;
|
||||
const stop = start + resultsPerPage - 1;
|
||||
const byScore = min !== undefined && max !== undefined;
|
||||
|
||||
async function getCount() {
|
||||
if (byScore) {
|
||||
return await db.sortedSetCount(set, min, max);
|
||||
} else if (set === 'users:banned' || set === 'users:notvalidated') {
|
||||
return await db.sortedSetCard(set);
|
||||
}
|
||||
return await db.getObjectField('global', 'userCount');
|
||||
}
|
||||
|
||||
async function getUsersWithFields() {
|
||||
let uids;
|
||||
if (byScore) {
|
||||
uids = await db.getSortedSetRevRangeByScore(set, start, resultsPerPage, max, min);
|
||||
} else {
|
||||
uids = await user.getUidsFromSet(set, start, stop);
|
||||
}
|
||||
const [isAdmin, userData] = await Promise.all([
|
||||
user.isAdministrator(uids),
|
||||
user.getUsersWithFields(uids, userFields, req.uid),
|
||||
]);
|
||||
userData.forEach((user, index) => {
|
||||
if (user) {
|
||||
user.administrator = isAdmin[index];
|
||||
}
|
||||
});
|
||||
return userData;
|
||||
}
|
||||
|
||||
const [count, users] = await Promise.all([
|
||||
getCount(),
|
||||
getUsersWithFields(),
|
||||
]);
|
||||
|
||||
const data = {
|
||||
users: users.filter(user => user && parseInt(user.uid, 10)),
|
||||
page: page,
|
||||
pageCount: Math.max(1, Math.ceil(count / resultsPerPage)),
|
||||
resultsPerPage: resultsPerPage,
|
||||
};
|
||||
data[section] = true;
|
||||
render(req, res, data);
|
||||
}
|
||||
|
||||
function render(req, res, data) {
|
||||
data.search_display = 'hidden';
|
||||
data.pagination = pagination.create(data.page, data.pageCount, req.query);
|
||||
data.requireEmailConfirmation = meta.config.requireEmailConfirmation;
|
||||
|
||||
var registrationType = meta.config.registrationType;
|
||||
const registrationType = meta.config.registrationType;
|
||||
|
||||
data.inviteOnly = registrationType === 'invite-only' || registrationType === 'admin-invite-only';
|
||||
data.adminInviteOnly = registrationType === 'admin-invite-only';
|
||||
|
||||
data['sort_' + data.sortBy] = true;
|
||||
if (req.query.searchBy) {
|
||||
data['searchBy_' + validator.escape(String(req.query.searchBy))] = true;
|
||||
}
|
||||
const filterBy = Array.isArray(req.query.filters || []) ? (req.query.filters || []) : [req.query.filters];
|
||||
filterBy.forEach(function (filter) {
|
||||
data['filterBy_' + validator.escape(String(filter))] = true;
|
||||
});
|
||||
res.render('admin/manage/users', data);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ usersController.index = async function (req, res, next) {
|
||||
flagged: usersController.getFlaggedUsers,
|
||||
};
|
||||
|
||||
if (req.query.term) {
|
||||
if (req.query.query) {
|
||||
await usersController.search(req, res, next);
|
||||
} else if (sectionToController[section]) {
|
||||
await sectionToController[section](req, res, next);
|
||||
@@ -35,19 +35,25 @@ usersController.search = async function (req, res) {
|
||||
privileges.global.can('search:users', req.uid),
|
||||
user.isPrivileged(req.uid),
|
||||
]);
|
||||
|
||||
if (!allowed || ((req.query.searchBy === 'ip' || req.query.searchBy === 'email' || req.query.bannedOnly === 'true' || req.query.flaggedOnly === 'true') && !isPrivileged)) {
|
||||
let filters = req.query.filters || [];
|
||||
filters = Array.isArray(filters) ? filters : [filters];
|
||||
if (!allowed ||
|
||||
((
|
||||
req.query.searchBy === 'ip' ||
|
||||
req.query.searchBy === 'email' ||
|
||||
filters.includes('banned') ||
|
||||
filters.includes('flagged')
|
||||
) && !isPrivileged)
|
||||
) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
const [searchData, isAdminOrGlobalMod] = await Promise.all([
|
||||
user.search({
|
||||
query: req.query.term,
|
||||
query: req.query.query,
|
||||
searchBy: req.query.searchBy || 'username',
|
||||
page: req.query.page || 1,
|
||||
sortBy: req.query.sortBy || 'joindate',
|
||||
onlineOnly: req.query.onlineOnly === 'true',
|
||||
bannedOnly: req.query.bannedOnly === 'true',
|
||||
flaggedOnly: req.query.flaggedOnly === 'true',
|
||||
filters: filters,
|
||||
}),
|
||||
user.isAdminOrGlobalMod(req.uid),
|
||||
]);
|
||||
|
||||
@@ -70,7 +70,7 @@ Groups.join = async (req, res) => {
|
||||
|
||||
if (!res.locals.privileges.isAdmin) {
|
||||
// Admin and privilege groups unjoinable client-side
|
||||
if (group.name === 'administrators' || groups.isPrivilegeGroup(group.name)) {
|
||||
if (groups.systemGroups.includes(group.name) || groups.isPrivilegeGroup(group.name)) {
|
||||
throw new Error('[[error:not-allowed]]');
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ module.exports = function (Groups) {
|
||||
|
||||
function isSystemGroup(data) {
|
||||
return data.system === true || parseInt(data.system, 10) === 1 ||
|
||||
data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' ||
|
||||
Groups.systemGroups.includes(data.name) ||
|
||||
Groups.isPrivilegeGroup(data.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,22 @@ require('./cache')(Groups);
|
||||
|
||||
Groups.ephemeralGroups = ['guests', 'spiders'];
|
||||
|
||||
Groups.systemGroups = [
|
||||
'registered-users',
|
||||
'verified-users',
|
||||
'unverified-users',
|
||||
'administrators',
|
||||
'Global Moderators',
|
||||
];
|
||||
|
||||
Groups.getEphemeralGroup = function (groupName) {
|
||||
return {
|
||||
name: groupName,
|
||||
slug: slugify(groupName),
|
||||
description: '',
|
||||
deleted: '0',
|
||||
hidden: '0',
|
||||
system: '1',
|
||||
deleted: 0,
|
||||
hidden: 0,
|
||||
system: 1,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ module.exports = function (Groups) {
|
||||
});
|
||||
} catch (err) {
|
||||
if (err && err.message !== '[[error:group-already-exists]]') {
|
||||
winston.error('[groups.join] Could not create new hidden group', err.stack);
|
||||
winston.error('[groups.join] Could not create new hidden group (' + groupName + ')\n' + err.stack);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,10 @@ module.exports = function (Groups) {
|
||||
}
|
||||
|
||||
async function setGroupTitleIfNotSet(groupNames, uid) {
|
||||
groupNames = groupNames.filter(groupName => groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName));
|
||||
const ignore = ['registered-users', 'verified-users', 'unverified-users'];
|
||||
groupNames = groupNames.filter(
|
||||
groupName => !ignore.includes(groupName) && !Groups.isPrivilegeGroup(groupName)
|
||||
);
|
||||
if (!groupNames.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const meta = require('../meta');
|
||||
const user = require('../user');
|
||||
const plugins = require('../plugins');
|
||||
const privileges = require('../privileges');
|
||||
|
||||
const sockets = require('../socket.io');
|
||||
|
||||
@@ -52,12 +53,13 @@ module.exports = function (Messaging) {
|
||||
throw new Error('[[error:chat-message-editing-disabled]]');
|
||||
}
|
||||
|
||||
const userData = await user.getUserFields(uid, ['banned', 'email:confirmed']);
|
||||
const userData = await user.getUserFields(uid, ['banned']);
|
||||
if (userData.banned) {
|
||||
throw new Error('[[error:user-banned]]');
|
||||
}
|
||||
if (meta.config.requireEmailConfirmation && !userData['email:confirmed']) {
|
||||
throw new Error('[[error:email-not-confirmed]]');
|
||||
const canChat = await privileges.global.can('chat', uid);
|
||||
if (!canChat) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
const [isAdmin, messageData] = await Promise.all([
|
||||
|
||||
@@ -5,6 +5,7 @@ const validator = require('validator');
|
||||
|
||||
const db = require('../database');
|
||||
const user = require('../user');
|
||||
const privileges = require('../privileges');
|
||||
const plugins = require('../plugins');
|
||||
const meta = require('../meta');
|
||||
const utils = require('../utils');
|
||||
@@ -201,13 +202,13 @@ Messaging.canMessageUser = async (uid, toUid) => {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
|
||||
const userData = await user.getUserFields(uid, ['banned', 'email:confirmed']);
|
||||
const userData = await user.getUserFields(uid, ['banned']);
|
||||
if (userData.banned) {
|
||||
throw new Error('[[error:user-banned]]');
|
||||
}
|
||||
|
||||
if (meta.config.requireEmailConfirmation && !userData['email:confirmed']) {
|
||||
throw new Error('[[error:email-not-confirmed-chat]]');
|
||||
const canChat = await privileges.global.can('chat', uid);
|
||||
if (!canChat) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
const results = await utils.promiseParallel({
|
||||
@@ -237,13 +238,13 @@ Messaging.canMessageRoom = async (uid, roomId) => {
|
||||
throw new Error('[[error:not-in-room]]');
|
||||
}
|
||||
|
||||
const userData = await user.getUserFields(uid, ['banned', 'email:confirmed']);
|
||||
const userData = await user.getUserFields(uid, ['banned']);
|
||||
if (userData.banned) {
|
||||
throw new Error('[[error:user-banned]]');
|
||||
}
|
||||
|
||||
if (meta.config.requireEmailConfirmation && !userData['email:confirmed']) {
|
||||
throw new Error('[[error:email-not-confirmed-chat]]');
|
||||
const canChat = await privileges.global.can('chat', uid);
|
||||
if (!canChat) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
await plugins.fireHook('static:messaging.canMessageRoom', {
|
||||
|
||||
@@ -79,7 +79,6 @@ module.exports = function (privileges) {
|
||||
'admin.user.deleteUsers': 'admin:users',
|
||||
'admin.user.deleteUsersAndContent': 'admin:users',
|
||||
'admin.user.createUser': 'admin:users',
|
||||
'admin.user.search': 'admin:users',
|
||||
'admin.user.invite': 'admin:users',
|
||||
|
||||
'admin.getSearchDict': 'admin:settings',
|
||||
|
||||
@@ -119,13 +119,15 @@ helpers.getGroupPrivileges = async function (cid, groupPrivileges) {
|
||||
|
||||
groupNames = groups.ephemeralGroups.concat(groupNames);
|
||||
moveToFront(groupNames, 'Global Moderators');
|
||||
moveToFront(groupNames, 'unverified-users');
|
||||
moveToFront(groupNames, 'verified-users');
|
||||
moveToFront(groupNames, 'registered-users');
|
||||
|
||||
const adminIndex = groupNames.indexOf('administrators');
|
||||
if (adminIndex !== -1) {
|
||||
groupNames.splice(adminIndex, 1);
|
||||
}
|
||||
const groupData = await groups.getGroupsFields(groupNames, ['private']);
|
||||
const groupData = await groups.getGroupsFields(groupNames, ['private', 'system']);
|
||||
const memberData = groupNames.map(function (member, index) {
|
||||
const memberPrivs = {};
|
||||
|
||||
@@ -137,6 +139,7 @@ helpers.getGroupPrivileges = async function (cid, groupPrivileges) {
|
||||
nameEscaped: translator.escape(validator.escape(member)),
|
||||
privileges: memberPrivs,
|
||||
isPrivate: groupData[index] && !!groupData[index].private,
|
||||
isSystem: groupData[index] && !!groupData[index].system,
|
||||
};
|
||||
});
|
||||
return memberData;
|
||||
|
||||
@@ -16,16 +16,7 @@ module.exports = function (app, middleware, controllers) {
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/privileges/:cid?', middleware, middlewares, controllers.admin.privileges.get);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/tags', middleware, middlewares, controllers.admin.tags.get);
|
||||
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users', middleware, middlewares, controllers.admin.users.sortByJoinDate);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/search', middleware, middlewares, controllers.admin.users.search);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/latest', middleware, middlewares, controllers.admin.users.sortByJoinDate);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/not-validated', middleware, middlewares, controllers.admin.users.notValidated);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/no-posts', middleware, middlewares, controllers.admin.users.noPosts);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/top-posters', middleware, middlewares, controllers.admin.users.topPosters);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/most-reputation', middleware, middlewares, controllers.admin.users.mostReputaion);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/inactive', middleware, middlewares, controllers.admin.users.inactive);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/flagged', middleware, middlewares, controllers.admin.users.flagged);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users/banned', middleware, middlewares, controllers.admin.users.banned);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/users', middleware, middlewares, controllers.admin.users.index);
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/registration', middleware, middlewares, controllers.admin.users.registrationQueue);
|
||||
|
||||
helpers.setupAdminPageRoute(app, '/admin/manage/admins-mods', middleware, middlewares, controllers.admin.adminsMods.get);
|
||||
|
||||
@@ -80,7 +80,10 @@ User.validateEmail = async function (socket, uids) {
|
||||
|
||||
uids = uids.filter(uid => parseInt(uid, 10));
|
||||
await db.setObjectField(uids.map(uid => 'user:' + uid), 'email:confirmed', 1);
|
||||
await db.sortedSetRemove('users:notvalidated', uids);
|
||||
for (const uid of uids) {
|
||||
await groups.join('verified-users', uid);
|
||||
await groups.leave('unverified-users', uid);
|
||||
}
|
||||
};
|
||||
|
||||
User.sendValidationEmail = async function (socket, uids) {
|
||||
@@ -183,32 +186,6 @@ async function deleteUsers(socket, uids, method) {
|
||||
}
|
||||
}
|
||||
|
||||
User.search = async function (socket, data) {
|
||||
// TODO: deprecate
|
||||
const searchData = await user.search({
|
||||
query: data.query,
|
||||
searchBy: data.searchBy,
|
||||
uid: socket.uid,
|
||||
});
|
||||
|
||||
if (!searchData.users.length) {
|
||||
return searchData;
|
||||
}
|
||||
|
||||
const uids = searchData.users.map(user => user && user.uid);
|
||||
const userInfo = await user.getUsersFields(uids, ['email', 'flags', 'lastonline', 'joindate']);
|
||||
|
||||
searchData.users.forEach(function (user, index) {
|
||||
if (user && userInfo[index]) {
|
||||
user.email = userInfo[index].email;
|
||||
user.flags = userInfo[index].flags || 0;
|
||||
user.lastonlineISO = userInfo[index].lastonlineISO;
|
||||
user.joindateISO = userInfo[index].joindateISO;
|
||||
}
|
||||
});
|
||||
return searchData;
|
||||
};
|
||||
|
||||
User.restartJobs = async function () {
|
||||
user.startJobs();
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ SocketGroups.join = async (socket, data) => {
|
||||
throw new Error('[[error:invalid-group-name]]');
|
||||
}
|
||||
|
||||
if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) {
|
||||
if (groups.systemGroups.includes(data.groupName) || groups.isPrivilegeGroup(data.groupName)) {
|
||||
throw new Error('[[error:not-allowed]]');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const privileges = require('../../privileges');
|
||||
|
||||
module.exports = function (SocketUser) {
|
||||
SocketUser.search = async function (socket, data) {
|
||||
// TODO: depracate and use usersController.search
|
||||
if (!data) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
@@ -14,7 +15,16 @@ module.exports = function (SocketUser) {
|
||||
user.isPrivileged(socket.uid),
|
||||
]);
|
||||
|
||||
if (!allowed || ((data.searchBy === 'ip' || data.searchBy === 'email' || data.bannedOnly || data.flaggedOnly) && !isPrivileged)) {
|
||||
let filters = data.filters || [];
|
||||
filters = Array.isArray(filters) ? filters : [filters];
|
||||
if (!allowed ||
|
||||
((
|
||||
data.searchBy === 'ip' ||
|
||||
data.searchBy === 'email' ||
|
||||
filters.includes('banned') ||
|
||||
filters.includes('flagged')
|
||||
) && !isPrivileged)
|
||||
) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
const result = await user.search({
|
||||
@@ -22,10 +32,7 @@ module.exports = function (SocketUser) {
|
||||
page: data.page,
|
||||
searchBy: data.searchBy,
|
||||
sortBy: data.sortBy,
|
||||
onlineOnly: data.onlineOnly,
|
||||
bannedOnly: data.bannedOnly,
|
||||
notBanned: data.notBanned,
|
||||
flaggedOnly: data.flaggedOnly,
|
||||
filters: data.filters,
|
||||
paginate: data.paginate,
|
||||
uid: socket.uid,
|
||||
});
|
||||
|
||||
@@ -129,6 +129,7 @@ Upgrade.process = async function (files, skipCount) {
|
||||
const version = path.dirname(file).split('/').pop();
|
||||
const progress = {
|
||||
current: 0,
|
||||
counter: 0,
|
||||
total: 0,
|
||||
incr: Upgrade.incrementProgress,
|
||||
script: scriptExport,
|
||||
@@ -177,9 +178,11 @@ Upgrade.incrementProgress = function (value) {
|
||||
}
|
||||
|
||||
this.current += value || 1;
|
||||
this.counter += value || 1;
|
||||
const step = (this.total ? Math.floor(this.total / 100) : 100);
|
||||
|
||||
// Redraw the progress bar every 100 units
|
||||
if (this.current % (this.total ? Math.floor(this.total / 100) : 100) === 0 || this.current === this.total) {
|
||||
if (this.counter > step || this.current >= this.total) {
|
||||
this.counter -= step;
|
||||
var percentage = 0;
|
||||
var filled = 0;
|
||||
var unfilled = 15;
|
||||
|
||||
95
src/upgrades/1.15.0/verified_users_group.js
Normal file
95
src/upgrades/1.15.0/verified_users_group.js
Normal file
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../../database');
|
||||
|
||||
const batch = require('../../batch');
|
||||
const user = require('../../user');
|
||||
const groups = require('../../groups');
|
||||
const meta = require('../../meta');
|
||||
const privileges = require('../../privileges');
|
||||
|
||||
module.exports = {
|
||||
name: 'Create verified/unverified user groups',
|
||||
timestamp: Date.UTC(2020, 9, 13),
|
||||
method: async function () {
|
||||
const progress = this.progress;
|
||||
const timestamp = await db.getObjectField('group:administrators', 'timestamp');
|
||||
const verifiedExists = await groups.exists('verified-users');
|
||||
if (!verifiedExists) {
|
||||
await groups.create({
|
||||
name: 'verified-users',
|
||||
hidden: 1,
|
||||
private: 1,
|
||||
system: 1,
|
||||
disableLeave: 1,
|
||||
disableJoinRequests: 1,
|
||||
timestamp: timestamp + 1,
|
||||
});
|
||||
}
|
||||
const unverifiedExists = await groups.exists('unverified-users');
|
||||
if (!unverifiedExists) {
|
||||
await groups.create({
|
||||
name: 'unverified-users',
|
||||
hidden: 1,
|
||||
private: 1,
|
||||
system: 1,
|
||||
disableLeave: 1,
|
||||
disableJoinRequests: 1,
|
||||
timestamp: timestamp + 1,
|
||||
});
|
||||
}
|
||||
|
||||
await batch.processSortedSet('users:joindate', async function (uids) {
|
||||
progress.incr(uids.length);
|
||||
const userData = await user.getUsersFields(uids, ['uid', 'email:confirmed']);
|
||||
|
||||
const verified = userData.filter(u => parseInt(u['email:confirmed'], 10) === 1);
|
||||
const unverified = userData.filter(u => parseInt(u['email:confirmed'], 10) !== 1);
|
||||
|
||||
for (const user of verified) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await groups.join('verified-users', user.uid);
|
||||
}
|
||||
for (const user of unverified) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await groups.join('unverified-users', user.uid);
|
||||
}
|
||||
}, {
|
||||
batch: 500,
|
||||
progress: this.progress,
|
||||
});
|
||||
|
||||
await db.delete('users:notvalidated');
|
||||
await updatePrivilges();
|
||||
},
|
||||
};
|
||||
|
||||
async function updatePrivilges() {
|
||||
// if email confirmation is required
|
||||
// give chat, posting privs to "verified-users" group
|
||||
// remove chat, posting privs from "registered-users" group
|
||||
if (meta.config.requireEmailConfirmation) {
|
||||
const cids = await db.getSortedSetRevRange('categories:cid', 0, -1);
|
||||
const canChat = await privileges.global.canGroup('chat', 'registered-users');
|
||||
if (canChat) {
|
||||
await privileges.global.give(['groups:chat'], 'verified-users');
|
||||
await privileges.global.rescind(['groups:chat'], 'registered-users');
|
||||
}
|
||||
for (const cid of cids) {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const data = await privileges.categories.list(cid);
|
||||
|
||||
const registeredUsersPrivs = data.groups.find(d => d.name === 'registered-users').privileges;
|
||||
|
||||
if (registeredUsersPrivs['groups:topics:create']) {
|
||||
await privileges.categories.give(['groups:topics:create'], cid, 'verified-users');
|
||||
await privileges.categories.rescind(['groups:topics:create'], cid, 'registered-users');
|
||||
}
|
||||
|
||||
if (registeredUsersPrivs['groups:topics:reply']) {
|
||||
await privileges.categories.give(['groups:topics:reply'], cid, 'verified-users');
|
||||
await privileges.categories.rescind(['groups:topics:reply'], cid, 'registered-users');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,6 @@ module.exports = function (User) {
|
||||
['users:reputation', 0, userData.uid],
|
||||
];
|
||||
|
||||
if (parseInt(userData.uid, 10) !== 1) {
|
||||
bulkAdd.push(['users:notvalidated', timestamp, userData.uid]);
|
||||
}
|
||||
if (userData.email) {
|
||||
bulkAdd.push(['email:uid', userData.uid, userData.email.toLowerCase()]);
|
||||
bulkAdd.push(['email:sorted', 0, userData.email.toLowerCase() + ':' + userData.uid]);
|
||||
@@ -99,10 +96,15 @@ module.exports = function (User) {
|
||||
bulkAdd.push(['fullname:sorted', 0, userData.fullname.toLowerCase() + ':' + userData.uid]);
|
||||
}
|
||||
|
||||
const groupsToJoin = ['registered-users'].concat(
|
||||
parseInt(userData.uid, 10) !== 1 ?
|
||||
'unverified-users' : 'verified-users'
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
db.incrObjectField('global', 'userCount'),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
groups.join('registered-users', userData.uid),
|
||||
groups.join(groupsToJoin, userData.uid),
|
||||
User.notifications.sendWelcomeNotification(userData.uid),
|
||||
storePassword(userData.uid, data.password),
|
||||
User.updateDigestSetting(userData.uid, meta.config.dailyDigestFreq),
|
||||
|
||||
@@ -81,7 +81,6 @@ module.exports = function (User) {
|
||||
'users:banned:expire',
|
||||
'users:flags',
|
||||
'users:online',
|
||||
'users:notvalidated',
|
||||
'digest:day:uids',
|
||||
'digest:week:uids',
|
||||
'digest:month:uids',
|
||||
|
||||
@@ -9,6 +9,7 @@ var plugins = require('../plugins');
|
||||
var db = require('../database');
|
||||
var meta = require('../meta');
|
||||
var emailer = require('../emailer');
|
||||
const groups = require('../groups');
|
||||
|
||||
var UserEmail = module.exports;
|
||||
|
||||
@@ -96,8 +97,9 @@ UserEmail.confirm = async function (code) {
|
||||
throw new Error('[[error:invalid-email]]');
|
||||
}
|
||||
await user.setUserField(confirmObj.uid, 'email:confirmed', 1);
|
||||
await groups.join('verified-users', confirmObj.uid);
|
||||
await groups.leave('unverified-users', confirmObj.uid);
|
||||
await db.delete('confirm:' + code);
|
||||
await db.delete('uid:' + confirmObj.uid + ':confirm:email:sent');
|
||||
await db.sortedSetRemove('users:notvalidated', confirmObj.uid);
|
||||
await plugins.fireHook('action:user.email.confirmed', { uid: confirmObj.uid, email: confirmObj.email });
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = function (User) {
|
||||
return;
|
||||
}
|
||||
const [userData, isAdminOrMod] = await Promise.all([
|
||||
User.getUserFields(uid, ['uid', 'banned', 'joindate', 'email', 'email:confirmed', 'reputation'].concat([field])),
|
||||
User.getUserFields(uid, ['uid', 'banned', 'joindate', 'email', 'reputation'].concat([field])),
|
||||
privileges.categories.isAdminOrMod(cid, uid),
|
||||
]);
|
||||
|
||||
@@ -34,16 +34,12 @@ module.exports = function (User) {
|
||||
throw new Error('[[error:user-banned]]');
|
||||
}
|
||||
|
||||
if (meta.config.requireEmailConfirmation && !userData['email:confirmed']) {
|
||||
throw new Error('[[error:email-not-confirmed]]');
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
const now = Date.now();
|
||||
if (now - userData.joindate < meta.config.initialPostDelay * 1000) {
|
||||
throw new Error('[[error:user-too-new, ' + meta.config.initialPostDelay + ']]');
|
||||
}
|
||||
|
||||
var lasttime = userData[field] || 0;
|
||||
const lasttime = userData[field] || 0;
|
||||
|
||||
if (meta.config.newbiePostDelay > 0 && meta.config.newbiePostDelayThreshold > userData.reputation && now - lasttime < meta.config.newbiePostDelay * 1000) {
|
||||
throw new Error('[[error:too-many-posts-newbie, ' + meta.config.newbiePostDelay + ', ' + meta.config.newbiePostDelayThreshold + ']]');
|
||||
|
||||
@@ -234,9 +234,10 @@ module.exports = function (User) {
|
||||
['email:uid', uid, newEmail.toLowerCase()],
|
||||
['email:sorted', 0, newEmail.toLowerCase() + ':' + uid],
|
||||
['user:' + uid + ':emails', Date.now(), newEmail + ':' + Date.now()],
|
||||
['users:notvalidated', Date.now(), uid],
|
||||
]),
|
||||
User.setUserFields(uid, { email: newEmail, 'email:confirmed': 0 }),
|
||||
groups.leave('verified-users', uid),
|
||||
groups.join('unverified-users', uid),
|
||||
User.reset.cleanByUid(uid),
|
||||
]);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ var nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
|
||||
var user = require('./index');
|
||||
const groups = require('../groups');
|
||||
var utils = require('../utils');
|
||||
var batch = require('../batch');
|
||||
|
||||
@@ -70,11 +71,12 @@ UserReset.commit = async function (code, password) {
|
||||
const hash = await user.hashPassword(password);
|
||||
|
||||
await user.setUserFields(uid, { password: hash, 'email:confirmed': 1 });
|
||||
await groups.join('verified-users', uid);
|
||||
await groups.leave('unverified-users', uid);
|
||||
await db.deleteObjectField('reset:uid', code);
|
||||
await db.sortedSetRemoveBulk([
|
||||
['reset:issueDate', code],
|
||||
['reset:issueDate:uid', uid],
|
||||
['users:notvalidated', uid],
|
||||
]);
|
||||
await user.reset.updateExpiry(uid);
|
||||
await user.auth.resetLockout(uid);
|
||||
|
||||
@@ -10,6 +10,25 @@ const groups = require('../groups');
|
||||
const utils = require('../utils');
|
||||
|
||||
module.exports = function (User) {
|
||||
const filterFnMap = {
|
||||
online: user => user.status !== 'offline' && (Date.now() - user.lastonline < 300000),
|
||||
banned: user => user.banned,
|
||||
notbanned: user => !user.banned,
|
||||
flagged: user => parseInt(user.flags, 10) > 0,
|
||||
verified: user => !!user['email:confirmed'],
|
||||
unverified: user => !user['email:confirmed'],
|
||||
};
|
||||
|
||||
const filterFieldMap = {
|
||||
online: ['status', 'lastonline'],
|
||||
banned: ['banned'],
|
||||
notbanned: ['banned'],
|
||||
flagged: ['flags'],
|
||||
verified: ['email:confirmed'],
|
||||
unverified: ['email:confirmed'],
|
||||
};
|
||||
|
||||
|
||||
User.search = async function (data) {
|
||||
const query = data.query || '';
|
||||
const searchBy = data.searchBy || 'username';
|
||||
@@ -69,21 +88,19 @@ module.exports = function (User) {
|
||||
|
||||
async function filterAndSortUids(uids, data) {
|
||||
uids = uids.filter(uid => parseInt(uid, 10));
|
||||
|
||||
let filters = data.filters || [];
|
||||
filters = Array.isArray(filters) ? filters : [data.filters];
|
||||
const fields = [];
|
||||
|
||||
if (data.sortBy) {
|
||||
fields.push(data.sortBy);
|
||||
}
|
||||
if (data.onlineOnly) {
|
||||
fields.push('status', 'lastonline');
|
||||
}
|
||||
if (data.bannedOnly || data.notBanned) {
|
||||
fields.push('banned');
|
||||
}
|
||||
if (data.flaggedOnly) {
|
||||
fields.push('flags');
|
||||
|
||||
filters.forEach(function (filter) {
|
||||
if (filterFieldMap[filter]) {
|
||||
fields.push(...filterFieldMap[filter]);
|
||||
}
|
||||
});
|
||||
|
||||
if (data.groupName) {
|
||||
const isMembers = await groups.isMembers(uids, data.groupName);
|
||||
@@ -96,42 +113,36 @@ module.exports = function (User) {
|
||||
|
||||
fields.push('uid');
|
||||
let userData = await User.getUsersFields(uids, fields);
|
||||
if (data.onlineOnly) {
|
||||
userData = userData.filter(user => user.status !== 'offline' && (Date.now() - user.lastonline < 300000));
|
||||
}
|
||||
|
||||
if (data.bannedOnly) {
|
||||
userData = userData.filter(user => user.banned);
|
||||
}
|
||||
|
||||
if (data.notBanned) {
|
||||
userData = userData.filter(user => !user.banned);
|
||||
}
|
||||
|
||||
if (data.flaggedOnly) {
|
||||
userData = userData.filter(user => parseInt(user.flags, 10) > 0);
|
||||
filters.forEach(function (filter) {
|
||||
if (filterFnMap[filter]) {
|
||||
userData = userData.filter(filterFnMap[filter]);
|
||||
}
|
||||
});
|
||||
|
||||
if (data.sortBy) {
|
||||
sortUsers(userData, data.sortBy);
|
||||
sortUsers(userData, data.sortBy, data.sortDirection);
|
||||
}
|
||||
|
||||
return userData.map(user => user.uid);
|
||||
}
|
||||
|
||||
function sortUsers(userData, sortBy) {
|
||||
function sortUsers(userData, sortBy, sortDirection) {
|
||||
if (!userData || !userData.length) {
|
||||
return;
|
||||
}
|
||||
sortDirection = sortDirection || 'desc';
|
||||
const direction = sortDirection === 'desc' ? 1 : -1;
|
||||
|
||||
const isNumeric = utils.isNumber(userData[0][sortBy]);
|
||||
if (isNumeric) {
|
||||
userData.sort((u1, u2) => u2[sortBy] - u1[sortBy]);
|
||||
userData.sort((u1, u2) => direction * (u2[sortBy] - u1[sortBy]));
|
||||
} else {
|
||||
userData.sort(function (u1, u2) {
|
||||
if (u1[sortBy] < u2[sortBy]) {
|
||||
return -1;
|
||||
return direction * -1;
|
||||
} else if (u1[sortBy] > u2[sortBy]) {
|
||||
return 1;
|
||||
return direction * 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -2,7 +2,47 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="clearfix">
|
||||
<form class="form-inline pull-right">
|
||||
<button id="createUser" class="btn btn-primary">[[admin/manage/users:new]]</button>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="[[global:search]]" id="user-search" value="{query}">
|
||||
<span class="input-group-addon search-button"><i class="fa fa-search"></i></span>
|
||||
</div>
|
||||
<select id="user-search-by" class="form-control">
|
||||
<option value="username" {{{if searchBy_username}}}selected{{{end}}}>[[admin/manage/users:search.username]]</option>
|
||||
<option value="email" {{{if searchBy_email}}}selected{{{end}}}>[[admin/manage/users:search.email]]</option>
|
||||
<option value="uid" {{{if searchBy_uid}}}selected{{{end}}}>[[admin/manage/users:search.uid]]</option>
|
||||
<option value="ip" {{{if searchBy_ip}}}selected{{{end}}}>[[admin/manage/users:search.ip]]</option>
|
||||
</select>
|
||||
<select id="results-per-page" class="form-control">
|
||||
<option value="50">[[admin/manage/users:50-per-page]]</option>
|
||||
<option value="100">[[admin/manage/users:100-per-page]]</option>
|
||||
<option value="250">[[admin/manage/users:250-per-page]]</option>
|
||||
<option value="500">[[admin/manage/users:500-per-page]]</option>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<hr/>
|
||||
<ul class="nav nav-pills">
|
||||
<li class="pull-right">
|
||||
<div class="btn-group" id="filter-by">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
[[admin/manage/users:filter-by]]<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li data-filter-by="unverified" role="presentation">
|
||||
<a role="menu-item" href="#"><i class="fa fa-fw {{{ if filterBy_unverified }}}fa-check{{{end}}}"></i>[[admin/manage/users:pills.unvalidated]]</a>
|
||||
</li>
|
||||
<li data-filter-by="verified" role="presentation">
|
||||
<a role="menu-item" href="#"><i class="fa fa-fw {{{ if filterBy_verified }}}fa-check{{{end}}}"></i>[[admin/manage/users:pills.validated]]</a>
|
||||
</li>
|
||||
<li data-filter-by="banned" role="presentation">
|
||||
<a role="menu-item" href="#"><i class="fa fa-fw {{{ if filterBy_banned }}}fa-check{{{end}}}"></i>[[admin/manage/users:pills.banned]]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<form class="form-inline">
|
||||
<!-- IF inviteOnly -->
|
||||
<button component="user/invite" class="btn btn-success"><i class="fa fa-users"></i> [[admin/manage/users:invite]]</button>
|
||||
<!-- ENDIF inviteOnly -->
|
||||
@@ -27,51 +67,12 @@
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr/>
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href='{config.relative_path}/admin/manage/users/latest?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.latest]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/not-validated?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.unvalidated]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/no-posts?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.no-posts]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/top-posters?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.top-posters]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/most-reputation?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.top-rep]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/inactive?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.inactive]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/flagged?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.flagged]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/banned?resultsPerPage={resultsPerPage}'>[[admin/manage/users:pills.banned]]</a></li>
|
||||
<li><a href='{config.relative_path}/admin/manage/users/search'>[[admin/manage/users:pills.search]]</a></li>
|
||||
<li class="pull-right">
|
||||
<form class="form-inline">
|
||||
<select id="results-per-page" class="form-control">
|
||||
<option value="50">[[admin/manage/users:50-per-page]]</option>
|
||||
<option value="100">[[admin/manage/users:100-per-page]]</option>
|
||||
<option value="250">[[admin/manage/users:250-per-page]]</option>
|
||||
<option value="500">[[admin/manage/users:500-per-page]]</option>
|
||||
</select>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="search {search_display}">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label>[[admin/manage/users:search.uid]]</label>
|
||||
<input class="form-control" id="search-user-uid" data-search-type="uid" type="number" placeholder="[[admin/manage/users:search.uid-placeholder]]" value="{uidQuery}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>[[admin/manage/users:search.username]]</label>
|
||||
<input class="form-control" id="search-user-name" data-search-type="username" type="text" placeholder="[[admin/manage/users:search.username-placeholder]]" value="{usernameQuery}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>[[admin/manage/users:search.email]]</label>
|
||||
<input class="form-control" id="search-user-email" data-search-type="email" type="text" placeholder="[[admin/manage/users:search.email-placeholder]]" value="{emailQuery}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>[[admin/manage/users:search.ip]]</label>
|
||||
<input class="form-control" id="search-user-ip" data-search-type="ip" type="text" placeholder="[[admin/manage/users:search.ip-placeholder]]" value="{ipQuery}"/>
|
||||
</div>
|
||||
</form>
|
||||
<i class="fa fa-spinner fa-spin hidden"></i>
|
||||
|
||||
<div id="user-found-notify" class="label label-info {{{if !matchCount}}}hidden{{{end}}}">[[admin/manage/users:alerts.x-users-found, {matchCount}, {timing}]]</div>
|
||||
@@ -90,15 +91,14 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input component="user/select/all" type="checkbox"/></th>
|
||||
<th class="text-right">[[admin/manage/users:users.uid]]</th>
|
||||
<th>[[admin/manage/users:users.username]]</th>
|
||||
<th>[[admin/manage/users:users.email]]</th>
|
||||
<th class="text-right">[[admin/manage/users:users.postcount]]</th>
|
||||
<th class="text-right">[[admin/manage/users:users.reputation]]</th>
|
||||
<th class="text-right">[[admin/manage/users:users.flags]]</th>
|
||||
<th>[[admin/manage/users:users.joined]]</th>
|
||||
<th>[[admin/manage/users:users.last-online]]</th>
|
||||
<th>[[admin/manage/users:users.banned]]</th>
|
||||
<th class="text-right text-muted">[[admin/manage/users:users.uid]]</th>
|
||||
<th class="text-muted">[[admin/manage/users:users.username]]</th>
|
||||
<th class="text-muted">[[admin/manage/users:users.email]]</th>
|
||||
<th data-sort="postcount" class="text-right pointer">[[admin/manage/users:users.postcount]] {{{if sort_postcount}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="reputation" class="text-right pointer">[[admin/manage/users:users.reputation]] {{{if sort_reputation}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="flags" class="text-right pointer">[[admin/manage/users:users.flags]] {{{if sort_flags}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="joindate" class="pointer">[[admin/manage/users:users.joined]] {{{if sort_joindate}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
<th data-sort="online" class="pointer">[[admin/manage/users:users.last-online]] {{{if sort_online}}}<i class="fa fa-sort-{{{if reverse}}}down{{{else}}}up{{{end}}}">{{{end}}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -106,19 +106,17 @@
|
||||
<tr class="user-row">
|
||||
<th><input component="user/select/single" data-uid="{users.uid}" type="checkbox"/></th>
|
||||
<td class="text-right">{users.uid}</td>
|
||||
<td><i class="administrator fa fa-shield text-success<!-- IF !users.administrator --> hidden<!-- ENDIF !users.administrator -->"></i><a href="{config.relative_path}/user/{users.userslug}"> {users.username}</a></td>
|
||||
<td><i title="[[admin/manage/users:users.banned]]" class="ban fa fa-gavel text-danger<!-- IF !users.banned --> hidden<!-- ENDIF !users.banned -->"></i><i class="administrator fa fa-shield text-success<!-- IF !users.administrator --> hidden<!-- ENDIF !users.administrator -->"></i><a href="{config.relative_path}/user/{users.userslug}"> {users.username}</a></td>
|
||||
|
||||
<td>
|
||||
<!-- IF config.requireEmailConfirmation -->
|
||||
<i class="validated fa fa-check text-success<!-- IF !users.email:confirmed --> hidden<!-- ENDIF !users.email:confirmed -->" title="validated"></i>
|
||||
<i class="notvalidated fa fa-times text-danger<!-- IF users.email:confirmed --> hidden<!-- ENDIF users.email:confirmed -->" title="not validated"></i>
|
||||
<!-- ENDIF config.requireEmailConfirmation --> {users.email}</td>
|
||||
<i class="notvalidated fa fa-check text-muted<!-- IF users.email:confirmed --> hidden<!-- ENDIF users.email:confirmed -->" title="not validated"></i>
|
||||
{users.email}</td>
|
||||
<td class="text-right">{users.postcount}</td>
|
||||
<td class="text-right">{users.reputation}</td>
|
||||
<td class="text-right"><!-- IF users.flags -->{users.flags}<!-- ELSE -->0<!-- ENDIF users.flags --></td>
|
||||
<td><span class="timeago" title="{users.joindateISO}"></span></td>
|
||||
<td><span class="timeago" title="{users.lastonlineISO}"></span></td>
|
||||
<td class="text-center"><i class="ban fa fa-gavel text-danger<!-- IF !users.banned --> hidden<!-- ENDIF !users.banned -->"></i></td>
|
||||
</tr>
|
||||
<!-- END users -->
|
||||
</tbody>
|
||||
@@ -129,3 +127,7 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button data-action="create" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="alert alert-danger d-none" id="create-modal-error"></div>
|
||||
<div class="alert alert-danger hidden" id="create-modal-error"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="group-name">[[admin/manage/users:create.username]]</label>
|
||||
|
||||
@@ -219,78 +219,6 @@ describe('Admin Controllers', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/search', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/search', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body.users);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/not-validated', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/not-validated', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/no-posts', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/no-posts', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/top-posters', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/top-posters', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/most-reputation', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/most-reputation', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/inactive', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/inactive', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/flagged', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/flagged', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/users/banned', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/users/banned', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load /admin/manage/registration', function (done) {
|
||||
request(nconf.get('url') + '/api/admin/manage/registration', { jar: jar, json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
|
||||
@@ -661,7 +661,7 @@ describe('Controllers', function () {
|
||||
});
|
||||
|
||||
it('should error if guests do not have search privilege', function (done) {
|
||||
request(nconf.get('url') + '/api/users?term=bar§ion=sort-posts', { json: true }, function (err, res, body) {
|
||||
request(nconf.get('url') + '/api/users?query=bar§ion=sort-posts', { json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 500);
|
||||
assert(body);
|
||||
@@ -673,7 +673,7 @@ describe('Controllers', function () {
|
||||
it('should load users search page', function (done) {
|
||||
privileges.global.give(['groups:search:users'], 'guests', function (err) {
|
||||
assert.ifError(err);
|
||||
request(nconf.get('url') + '/users?term=bar§ion=sort-posts', function (err, res, body) {
|
||||
request(nconf.get('url') + '/users?query=bar§ion=sort-posts', function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(body);
|
||||
@@ -1509,6 +1509,7 @@ describe('Controllers', function () {
|
||||
assert.ifError(err);
|
||||
request(nconf.get('url') + '/api/user/groupie', { json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
console.log(body);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert(Array.isArray(body.selectedGroup));
|
||||
assert.equal(body.selectedGroup[0].name, 'selectedGroup');
|
||||
|
||||
@@ -48,6 +48,16 @@ describe('Groups', function () {
|
||||
disableLeave: 1,
|
||||
});
|
||||
},
|
||||
async () => {
|
||||
await Groups.create({
|
||||
name: 'Global Moderators',
|
||||
userTitle: 'Global Moderator',
|
||||
description: 'Forum wide moderators',
|
||||
hidden: 0,
|
||||
private: 1,
|
||||
disableJoinRequests: 1,
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
// Create a new user
|
||||
User.create({
|
||||
@@ -72,8 +82,8 @@ describe('Groups', function () {
|
||||
},
|
||||
], function (err, results) {
|
||||
assert.ifError(err);
|
||||
testUid = results[4];
|
||||
adminUid = results[5];
|
||||
testUid = results[5];
|
||||
adminUid = results[6];
|
||||
Groups.join('administrators', adminUid, done);
|
||||
});
|
||||
});
|
||||
@@ -699,6 +709,29 @@ describe('Groups', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to add user to system group', async function () {
|
||||
const uid = await User.create({ username: 'eviluser' });
|
||||
const oldValue = meta.config.allowPrivateGroups;
|
||||
meta.config.allowPrivateGroups = 0;
|
||||
async function test(groupName) {
|
||||
let err;
|
||||
try {
|
||||
await socketGroups.join({ uid: uid }, { groupName: groupName });
|
||||
const isMember = await Groups.isMember(uid, groupName);
|
||||
assert.strictEqual(isMember, false);
|
||||
} catch (_err) {
|
||||
err = _err;
|
||||
}
|
||||
assert.strictEqual(err.message, '[[error:not-allowed]]');
|
||||
}
|
||||
const groups = ['Global Moderators', 'verified-users', 'unverified-users'];
|
||||
for (const g of groups) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await test(g);
|
||||
}
|
||||
meta.config.allowPrivateGroups = oldValue;
|
||||
});
|
||||
});
|
||||
|
||||
describe('.leave()', function () {
|
||||
|
||||
@@ -277,15 +277,6 @@ describe('socket.io', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should search users', function (done) {
|
||||
socketAdmin.user.search({ uid: adminUid }, { query: 'reg', searchBy: 'username' }, function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.matchCount, 1);
|
||||
assert.equal(data.users[0].username, 'regular');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should push unread notifications on reconnect', function (done) {
|
||||
var socketMeta = require('../src/socket.io/meta');
|
||||
socketMeta.reconnected({ uid: 1 }, {}, function (err) {
|
||||
|
||||
47
test/user.js
47
test/user.js
@@ -369,14 +369,14 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('should error for unprivileged user', function (done) {
|
||||
socketUser.search({ uid: testUid }, { bannedOnly: true, query: '123' }, function (err) {
|
||||
socketUser.search({ uid: testUid }, { filters: ['banned'], query: '123' }, function (err) {
|
||||
assert.equal(err.message, '[[error:no-privileges]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should error for unprivileged user', function (done) {
|
||||
socketUser.search({ uid: testUid }, { flaggedOnly: true, query: '123' }, function (err) {
|
||||
socketUser.search({ uid: testUid }, { filters: ['flagged'], query: '123' }, function (err) {
|
||||
assert.equal(err.message, '[[error:no-privileges]]');
|
||||
done();
|
||||
});
|
||||
@@ -430,9 +430,7 @@ describe('User', function () {
|
||||
assert.ifError(err);
|
||||
socketUser.search({ uid: adminUid }, {
|
||||
query: 'ipsearch',
|
||||
onlineOnly: true,
|
||||
bannedOnly: true,
|
||||
flaggedOnly: true,
|
||||
filters: ['online', 'banned', 'flagged'],
|
||||
}, function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.users[0].username, 'ipsearch_filter');
|
||||
@@ -2073,34 +2071,23 @@ describe('User', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm email of user', function (done) {
|
||||
var email = 'confirm@me.com';
|
||||
User.create({
|
||||
it('should confirm email of user', async function () {
|
||||
const email = 'confirm@me.com';
|
||||
const uid = await User.create({
|
||||
username: 'confirme',
|
||||
email: email,
|
||||
}, function (err, uid) {
|
||||
assert.ifError(err);
|
||||
User.email.sendValidationEmail(uid, email, function (err, code) {
|
||||
assert.ifError(err);
|
||||
User.email.confirm(code, function (err) {
|
||||
assert.ifError(err);
|
||||
});
|
||||
|
||||
async.parallel({
|
||||
confirmed: function (next) {
|
||||
db.getObjectField('user:' + uid, 'email:confirmed', next);
|
||||
},
|
||||
isMember: function (next) {
|
||||
db.isSortedSetMember('users:notvalidated', uid, next);
|
||||
},
|
||||
}, function (err, results) {
|
||||
assert.ifError(err);
|
||||
assert.equal(results.confirmed, 1);
|
||||
assert.equal(results.isMember, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
const code = await User.email.sendValidationEmail(uid, email);
|
||||
const unverified = await groups.isMember(uid, 'unverified-users');
|
||||
assert.strictEqual(unverified, true);
|
||||
await User.email.confirm(code);
|
||||
const [confirmed, isVerified] = await Promise.all([
|
||||
db.getObjectField('user:' + uid, 'email:confirmed'),
|
||||
groups.isMember(uid, 'verified-users', uid),
|
||||
]);
|
||||
assert.strictEqual(parseInt(confirmed, 10), 1);
|
||||
assert.strictEqual(isVerified, true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user