You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/test/user.js

2948 lines
104 KiB
JavaScript

'use strict';
11 years ago
const assert = require('assert');
const async = require('async');
const fs = require('fs');
const path = require('path');
const nconf = require('nconf');
const validator = require('validator');
const request = require('request');
const requestAsync = require('request-promise-native');
const jwt = require('jsonwebtoken');
const db = require('./mocks/databasemock');
const User = require('../src/user');
const Topics = require('../src/topics');
const Categories = require('../src/categories');
const Posts = require('../src/posts');
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');
const utils = require('../src/utils');
const privileges = require('../src/privileges');
11 years ago
describe('User', () => {
let userData;
let testUid;
let testCid;
const plugins = require('../src/plugins');
async function dummyEmailerHook(data) {
// pretend to handle sending emails
}
before((done) => {
// Attach an emailer hook so related requests do not error
plugins.hooks.register('emailer-test', {
hook: 'filter:email.send',
method: dummyEmailerHook,
});
Categories.create({
name: 'Test Category',
description: 'A test',
order: 1,
}, (err, categoryObj) => {
if (err) {
return done(err);
}
testCid = categoryObj.cid;
done();
});
});
after(() => {
plugins.hooks.unregister('emailer-test', 'filter:email.send');
});
11 years ago
beforeEach(() => {
11 years ago
userData = {
11 years ago
username: 'John Smith',
fullname: 'John Smith McNamara',
11 years ago
password: 'swordfish',
email: '[email protected]',
callback: undefined,
11 years ago
};
});
const goodImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAAACXBIWXMAAC4jAAAuIwF4pT92AAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAACcJJREFUeNqMl9tvnNV6xn/f+s5z8DCeg88Zj+NYdhJH4KShFoJAIkzVphLVJnsDaiV6gUKaC2qQUFVATbnoValAakuQYKMqBKUUJCgI9XBBSmOROMqGoCStHbA9sWM7nrFn/I3n9B17kcwoabfarj9gvet53+d9nmdJAwMDAAgh8DyPtbU1XNfFMAwkScK2bTzPw/M8dF1/SAhxKAiCxxVF2aeqqqTr+q+Af+7o6Ch0d3f/69TU1KwkSRiGwbFjx3jmmWd47rnn+OGHH1BVFYX/5QRBkPQ87xeSJP22YRi/oapqStM0PM/D931kWSYIgnHf98cXFxepVqtomjZt2/Zf2bb990EQ4Pv+PXfeU1CSpGYhfN9/TgjxQTQaJQgCw
11 years ago
describe('.create(), when created', () => {
it('should be created properly', async () => {
testUid = await User.create({ username: userData.username, password: userData.password });
assert.ok(testUid);
await User.setUserField(testUid, 'email', userData.email);
await User.email.confirmByUid(testUid);
11 years ago
});
it('should be created properly', async () => {
const email = '<h1>test</h1>@gmail.com';
const uid = await User.create({ username: 'weirdemail', email: email });
const data = await User.getUserData(uid);
const validationPending = await User.email.isValidationPending(uid, email);
assert.strictEqual(validationPending, true);
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
assert.equal(data.email, '');
assert.strictEqual(data.profileviews, 0);
assert.strictEqual(data.reputation, 0);
assert.strictEqual(data.postcount, 0);
assert.strictEqual(data.topiccount, 0);
assert.strictEqual(data.lastposttime, 0);
assert.strictEqual(data.banned, false);
6 years ago
});
it('should have a valid email, if using an email', (done) => {
User.create({ username: userData.username, password: userData.password, email: 'fakeMail' }, (err) => {
9 years ago
assert(err);
assert.equal(err.message, '[[error:invalid-email]]');
done();
});
11 years ago
});
8 years ago
it('should error with invalid password', (done) => {
User.create({ username: 'test', password: '1' }, (err) => {
assert.equal(err.message, '[[reset_password:password_too_short]]');
8 years ago
done();
});
});
it('should error with invalid password', (done) => {
User.create({ username: 'test', password: {} }, (err) => {
8 years ago
assert.equal(err.message, '[[error:invalid-password]]');
done();
});
});
it('should error with a too long password', (done) => {
let toolong = '';
for (let i = 0; i < 5000; i++) {
8 years ago
toolong += 'a';
}
User.create({ username: 'test', password: toolong }, (err) => {
8 years ago
assert.equal(err.message, '[[error:password-too-long]]');
done();
});
});
it('should error if username is already taken or rename user', async () => {
let err;
async function tryCreate(data) {
try {
return await User.create(data);
} catch (_err) {
err = _err;
}
}
const [uid1, uid2] = await Promise.all([
tryCreate({ username: 'dupe1' }),
tryCreate({ username: 'dupe1' }),
]);
if (err) {
assert.strictEqual(err.message, '[[error:username-taken]]');
} else {
const userData = await User.getUsersFields([uid1, uid2], ['username']);
const userNames = userData.map(u => u.username);
// make sure only 1 dupe1 is created
assert.equal(userNames.filter(username => username === 'dupe1').length, 1);
assert.equal(userNames.filter(username => username === 'dupe1 0').length, 1);
}
});
it('should error if email is already taken', async () => {
let err;
async function tryCreate(data) {
try {
return await User.create(data);
} catch (_err) {
err = _err;
}
}
await Promise.all([
tryCreate({ username: 'notdupe1', email: '[email protected]' }),
tryCreate({ username: 'notdupe2', email: '[email protected]' }),
]);
assert.strictEqual(err.message, '[[error:email-taken]]');
});
11 years ago
});
describe('.uniqueUsername()', () => {
it('should deal with collisions', (done) => {
const users = [];
for (let i = 0; i < 10; i += 1) {
8 years ago
users.push({
username: 'Jane Doe',
email: `jane.doe${i}@example.com`,
8 years ago
});
}
async.series([
function (next) {
async.eachSeries(users, (user, next) => {
8 years ago
User.create(user, next);
}, next);
},
function (next) {
User.uniqueUsername({
8 years ago
username: 'Jane Doe',
userslug: 'jane-doe',
}, (err, username) => {
8 years ago
assert.ifError(err);
assert.strictEqual(username, 'Jane Doe 9');
8 years ago
next();
8 years ago
});
},
], done);
});
});
describe('.isModerator()', () => {
it('should return false', (done) => {
User.isModerator(testUid, testCid, (err, isModerator) => {
assert.equal(err, null);
assert.equal(isModerator, false);
done();
});
});
it('should return two false results', (done) => {
User.isModerator([testUid, testUid], testCid, (err, isModerator) => {
assert.equal(err, null);
assert.equal(isModerator[0], false);
assert.equal(isModerator[1], false);
done();
});
});
it('should return two false results', (done) => {
User.isModerator(testUid, [testCid, testCid], (err, isModerator) => {
assert.equal(err, null);
assert.equal(isModerator[0], false);
assert.equal(isModerator[1], false);
done();
});
});
});
describe('.getModeratorUids()', () => {
before((done) => {
8 years ago
groups.join('cid:1:privileges:moderate', 1, done);
});
it('should retrieve all users with moderator bit in category privilege', (done) => {
User.getModeratorUids((err, uids) => {
8 years ago
assert.ifError(err);
assert.strictEqual(1, uids.length);
8 years ago
assert.strictEqual(1, parseInt(uids[0], 10));
8 years ago
done();
});
});
after((done) => {
8 years ago
groups.leave('cid:1:privileges:moderate', 1, done);
});
});
describe('.getModeratorUids()', () => {
before((done) => {
8 years ago
async.series([
async.apply(groups.create, { name: 'testGroup' }),
async.apply(groups.join, 'cid:1:privileges:groups:moderate', 'testGroup'),
async.apply(groups.join, 'testGroup', 1),
], done);
});
it('should retrieve all users with moderator bit in category privilege', (done) => {
User.getModeratorUids((err, uids) => {
8 years ago
assert.ifError(err);
assert.strictEqual(1, uids.length);
8 years ago
assert.strictEqual(1, parseInt(uids[0], 10));
8 years ago
done();
});
});
after((done) => {
8 years ago
async.series([
async.apply(groups.leave, 'cid:1:privileges:groups:moderate', 'testGroup'),
async.apply(groups.destroy, 'testGroup'),
], done);
});
});
describe('.isReadyToPost()', () => {
it('should error when a user makes two posts in quick succession', (done) => {
7 years ago
meta.config = meta.config || {};
meta.config.postDelay = '10';
async.series([
async.apply(Topics.post, {
uid: testUid,
title: 'Topic 1',
content: 'lorem ipsum',
cid: testCid,
}),
async.apply(Topics.post, {
uid: testUid,
title: 'Topic 2',
content: 'lorem ipsum',
cid: testCid,
}),
], (err) => {
assert(err);
done();
});
});
it('should allow a post if the last post time is > 10 seconds', (done) => {
User.setUserField(testUid, 'lastposttime', +new Date() - (11 * 1000), () => {
Topics.post({
uid: testUid,
title: 'Topic 3',
content: 'lorem ipsum',
cid: testCid,
}, (err) => {
assert.ifError(err);
done();
});
});
});
it('should error when a new user posts if the last post time is 10 < 30 seconds', (done) => {
7 years ago
meta.config.newbiePostDelay = 30;
meta.config.newbiePostDelayThreshold = 3;
User.setUserField(testUid, 'lastposttime', +new Date() - (20 * 1000), () => {
Topics.post({
uid: testUid,
title: 'Topic 4',
content: 'lorem ipsum',
cid: testCid,
}, (err) => {
assert(err);
done();
});
});
});
it('should not error if a non-newbie user posts if the last post time is 10 < 30 seconds', (done) => {
User.setUserFields(testUid, {
lastposttime: +new Date() - (20 * 1000),
reputation: 10,
}, () => {
Topics.post({
uid: testUid,
title: 'Topic 5',
content: 'lorem ipsum',
cid: testCid,
}, (err) => {
assert.ifError(err);
done();
});
});
});
it('should only post 1 topic out of 10', async () => {
await User.create({ username: 'flooder', password: '123456' });
const { jar } = await helpers.loginUser('flooder', '123456');
const titles = new Array(10).fill('topic title');
const res = await Promise.allSettled(titles.map(async (title) => {
const { body } = await helpers.request('post', '/api/v3/topics', {
form: {
cid: testCid,
title: title,
content: 'the content',
},
jar: jar,
json: true,
});
return body.status;
}));
const failed = res.filter(res => res.value.code === 'bad-request');
const success = res.filter(res => res.value.code === 'ok');
assert.strictEqual(failed.length, 9);
assert.strictEqual(success.length, 1);
});
});
describe('.search()', () => {
let adminUid;
let uid;
before(async () => {
adminUid = await User.create({ username: 'noteadmin' });
await groups.join('administrators', adminUid);
});
it('should return an object containing an array of matching users', (done) => {
User.search({ query: 'john' }, (err, searchData) => {
assert.ifError(err);
uid = searchData.users[0].uid;
assert.equal(Array.isArray(searchData.users) && searchData.users.length > 0, true);
assert.equal(searchData.users[0].username, 'John Smith');
done();
});
});
it('should search user', async () => {
const searchData = await apiUser.search({ uid: testUid }, { query: 'john' });
assert.equal(searchData.users[0].username, 'John Smith');
});
it('should error for guest', async () => {
try {
await apiUser.search({ uid: 0 }, { query: 'john' });
assert(false);
} catch (err) {
7 years ago
assert.equal(err.message, '[[error:no-privileges]]');
}
});
it('should error with invalid data', async () => {
try {
await apiUser.search({ uid: testUid }, null);
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-data]]');
}
});
it('should error for unprivileged user', async () => {
try {
await apiUser.search({ uid: testUid }, { searchBy: 'ip', query: '123' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:no-privileges]]');
}
});
it('should error for unprivileged user', async () => {
try {
await apiUser.search({ uid: testUid }, { filters: ['banned'], query: '123' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:no-privileges]]');
}
});
it('should error for unprivileged user', async () => {
try {
await apiUser.search({ uid: testUid }, { filters: ['flagged'], query: '123' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:no-privileges]]');
}
});
it('should search users by ip', async () => {
const uid = await User.create({ username: 'ipsearch' });
await db.sortedSetAdd('ip:1.1.1.1:uid', [1, 1], [testUid, uid]);
const data = await apiUser.search({ uid: adminUid }, { query: '1.1.1.1', searchBy: 'ip' });
assert(Array.isArray(data.users));
assert.equal(data.users.length, 2);
});
it('should search users by uid', async () => {
const data = await apiUser.search({ uid: testUid }, { query: uid, searchBy: 'uid' });
assert(Array.isArray(data.users));
assert.equal(data.users[0].uid, uid);
});
it('should search users by fullname', async () => {
const uid = await User.create({ username: 'fullnamesearch1', fullname: 'Mr. Fullname' });
const data = await apiUser.search({ uid: adminUid }, { query: 'mr', searchBy: 'fullname' });
assert(Array.isArray(data.users));
assert.equal(data.users.length, 1);
assert.equal(uid, data.users[0].uid);
});
it('should search users by fullname', async () => {
const uid = await User.create({ username: 'fullnamesearch2', fullname: 'Baris:Usakli' });
const data = await apiUser.search({ uid: adminUid }, { query: 'baris:', searchBy: 'fullname' });
assert(Array.isArray(data.users));
assert.equal(data.users.length, 1);
assert.equal(uid, data.users[0].uid);
});
it('should return empty array if query is empty', async () => {
const data = await apiUser.search({ uid: testUid }, { query: '' });
assert.equal(data.users.length, 0);
});
it('should filter users', async () => {
const uid = await User.create({ username: 'ipsearch_filter' });
await User.bans.ban(uid, 0, '');
await User.setUserFields(uid, { flags: 10 });
const data = await apiUser.search({ uid: adminUid }, {
query: 'ipsearch',
filters: ['online', 'banned', 'flagged'],
});
assert.equal(data.users[0].username, 'ipsearch_filter');
});
it('should sort results by username', (done) => {
async.waterfall([
function (next) {
User.create({ username: 'brian' }, next);
},
function (uid, next) {
User.create({ username: 'baris' }, next);
},
function (uid, next) {
User.create({ username: 'bzari' }, next);
},
function (uid, next) {
User.search({
uid: testUid,
query: 'b',
sortBy: 'username',
paginate: false,
}, next);
},
], (err, data) => {
assert.ifError(err);
assert.equal(data.users[0].username, 'baris');
assert.equal(data.users[1].username, 'brian');
assert.equal(data.users[2].username, 'bzari');
done();
});
});
});
describe('.delete()', () => {
let uid;
before((done) => {
User.create({ username: 'usertodelete', password: '123456', email: '[email protected]' }, (err, newUid) => {
assert.ifError(err);
uid = newUid;
done();
});
});
it('should delete a user account', (done) => {
User.delete(1, uid, (err) => {
assert.ifError(err);
User.existsBySlug('usertodelete', (err, exists) => {
assert.ifError(err);
assert.equal(exists, false);
done();
});
});
});
it('should not re-add user to users:postcount if post is purged after user account deletion', async () => {
const uid = await User.create({ username: 'olduserwithposts' });
assert(await db.isSortedSetMember('users:postcount', uid));
const result = await Topics.post({
uid: uid,
title: 'old user topic',
content: 'old user topic post content',
cid: testCid,
});
assert.equal(await db.sortedSetScore('users:postcount', uid), 1);
await User.deleteAccount(uid);
assert(!await db.isSortedSetMember('users:postcount', uid));
await Posts.purge(result.postData.pid, 1);
assert(!await db.isSortedSetMember('users:postcount', uid));
});
it('should not re-add user to users:reputation if post is upvoted after user account deletion', async () => {
const uid = await User.create({ username: 'olduserwithpostsupvote' });
assert(await db.isSortedSetMember('users:reputation', uid));
const result = await Topics.post({
uid: uid,
title: 'old user topic',
content: 'old user topic post content',
cid: testCid,
});
assert.equal(await db.sortedSetScore('users:reputation', uid), 0);
await User.deleteAccount(uid);
assert(!await db.isSortedSetMember('users:reputation', uid));
await Posts.upvote(result.postData.pid, 1);
assert(!await db.isSortedSetMember('users:reputation', uid));
});
it('should delete user even if they started a chat', async () => {
const socketModules = require('../src/socket.io/modules');
const uid1 = await User.create({ username: 'chatuserdelete1' });
const uid2 = await User.create({ username: 'chatuserdelete2' });
Chat refactor (#11779) * first part of chat refactor remove per user chat zsets & store all mids in chat:room:<roomId>:mids reverse uids in getUidsInRoom * feat: create room button public groups wip * feat: public rooms create chats:room zset chat room deletion * join socket.io room * get rid of some calls that load all users in room * dont load all users when loadRoom is called * mange room users infinitescroll dont load all members in api call * IS for user list ability to change groups field for public rooms update groups field if group is renamed * test: test fixes * wip * keep 150 messages * fix extra awaits fix dupe code in chat toggleReadState * unread state for public rooms * feat: faster push unread * test: spec * change base to harmony * test: lint fixes * fix language of chat with message * add 2 methods for perf messaging.getTeasers and getUsers(roomIds) instead of loading one by one * refactor: cleaner conditional * test fix upgrade script fix save timestamp of room creation in room object * set progress.total * don't check for guests/spiders * public room unread fix * add public unread counts * mark read on send * ignore instead of throwing * doggy.gif * fix: restore delete * prevent entering chat rooms with meta.enter * fix self message causing mark unread * ability to sort public rooms * dont init sortable on mobile * move chat-loaded class to core * test: fix spec * add missing keys * use ajaxify * refactor: store some refs * fix: when user is deleted remove from public rooms as well * feat: change how unread count is calculated * get rid of cleaned content get rid of mid * add help text * test: fix tests, add back mid to prevent breaking change * ability to search members of chat rooms * remove * derp * perf: switch with partial data fix tests * more fixes if user leaves a group leave public rooms is he is no longer part of any of the groups that have access fix the cache key used to get all public room ids dont allow joining chat socket.io room if user is no longer part of group * fix: lint * fix: js error when trying to delete room after switching * add isRoomPublic
2 years ago
const roomId = await messaging.newRoom(uid1, { uids: [uid2] });
await messaging.addMessage({
uid: uid1,
content: 'hello',
roomId,
});
await messaging.leaveRoom([uid2], roomId);
await User.delete(1, uid1);
assert.strictEqual(await User.exists(uid1), false);
});
});
describe('hash methods', () => {
it('should return uid from email', (done) => {
User.getUidByEmail('[email protected]', (err, uid) => {
assert.ifError(err);
assert.equal(parseInt(uid, 10), parseInt(testUid, 10));
done();
});
});
it('should return uid from username', (done) => {
User.getUidByUsername('John Smith', (err, uid) => {
assert.ifError(err);
assert.equal(parseInt(uid, 10), parseInt(testUid, 10));
done();
});
});
it('should return uid from userslug', (done) => {
User.getUidByUserslug('john-smith', (err, uid) => {
assert.ifError(err);
assert.equal(parseInt(uid, 10), parseInt(testUid, 10));
done();
});
});
it('should get user data even if one uid is NaN', (done) => {
User.getUsersData([NaN, testUid], (err, data) => {
assert.ifError(err);
assert(data[0]);
assert.equal(data[0].username, '[[global:guest]]');
assert(data[1]);
assert.equal(data[1].username, userData.username);
done();
});
});
it('should not return private user data', (done) => {
User.setUserFields(testUid, {
fb_token: '123123123',
another_secret: 'abcde',
postcount: '123',
}, (err) => {
assert.ifError(err);
User.getUserData(testUid, (err, userData) => {
assert.ifError(err);
assert(!userData.hasOwnProperty('fb_token'));
assert(!userData.hasOwnProperty('another_secret'));
assert(!userData.hasOwnProperty('password'));
assert(!userData.hasOwnProperty('rss_token'));
assert.strictEqual(userData.postcount, 123);
assert.strictEqual(userData.uid, testUid);
done();
});
});
});
it('should not return password even if explicitly requested', (done) => {
User.getUserFields(testUid, ['password'], (err, payload) => {
assert.ifError(err);
assert(!payload.hasOwnProperty('password'));
done();
});
});
it('should not modify the fields array passed in', async () => {
const fields = ['username', 'email'];
await User.getUserFields(testUid, fields);
assert.deepStrictEqual(fields, ['username', 'email']);
});
it('should return an icon text and valid background if username and picture is explicitly requested', async () => {
const payload = await User.getUserFields(testUid, ['username', 'picture']);
const validBackgrounds = await User.getIconBackgrounds(testUid);
assert.strictEqual(payload['icon:text'], userData.username.slice(0, 1).toUpperCase());
assert(payload['icon:bgColor']);
assert(validBackgrounds.includes(payload['icon:bgColor']));
});
it('should return a valid background, even if an invalid background colour is set', async () => {
await User.setUserField(testUid, 'icon:bgColor', 'teal');
const payload = await User.getUserFields(testUid, ['username', 'picture']);
const validBackgrounds = await User.getIconBackgrounds(testUid);
assert(payload['icon:bgColor']);
assert(validBackgrounds.includes(payload['icon:bgColor']));
});
it('should return private data if field is whitelisted', (done) => {
function filterMethod(data, callback) {
data.whitelist.push('another_secret');
callback(null, data);
}
plugins.hooks.register('test-plugin', { hook: 'filter:user.whitelistFields', method: filterMethod });
User.getUserData(testUid, (err, userData) => {
assert.ifError(err);
assert(!userData.hasOwnProperty('fb_token'));
assert.equal(userData.another_secret, 'abcde');
plugins.hooks.unregister('test-plugin', 'filter:user.whitelistFields', filterMethod);
done();
});
});
7 years ago
it('should return 0 as uid if username is falsy', (done) => {
User.getUidByUsername('', (err, uid) => {
7 years ago
assert.ifError(err);
assert.strictEqual(uid, 0);
done();
});
});
it('should get username by userslug', (done) => {
User.getUsernameByUserslug('john-smith', (err, username) => {
7 years ago
assert.ifError(err);
assert.strictEqual('John Smith', username);
done();
});
});
it('should get uids by emails', (done) => {
User.getUidsByEmails(['[email protected]'], (err, uids) => {
7 years ago
assert.ifError(err);
assert.equal(uids[0], testUid);
done();
});
});
it('should not get groupTitle for guests', (done) => {
User.getUserData(0, (err, userData) => {
assert.ifError(err);
assert.strictEqual(userData.groupTitle, '');
assert.deepStrictEqual(userData.groupTitleArray, []);
done();
});
});
it('should load guest data', (done) => {
User.getUsersData([1, 0], (err, data) => {
assert.ifError(err);
assert.strictEqual(data[1].username, '[[global:guest]]');
assert.strictEqual(data[1].userslug, '');
assert.strictEqual(data[1].uid, 0);
done();
});
});
});
describe('profile methods', () => {
let uid;
let jar;
let csrf_token;
before(async () => {
const newUid = await User.create({ username: 'updateprofile', email: '[email protected]', password: '123456' });
uid = newUid;
await User.setUserField(uid, 'email', '[email protected]');
await User.email.confirmByUid(uid);
({ jar, csrf_token } = await helpers.loginUser('updateprofile', '123456'));
});
it('should return error if not logged in', async () => {
try {
await apiUser.update({ uid: 0 }, { uid: 1 });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-uid]]');
}
});
it('should return error if data is invalid', async () => {
try {
await apiUser.update({ uid: uid }, null);
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-data]]');
}
});
it('should return error if data is missing uid', async () => {
try {
await apiUser.update({ uid: uid }, { username: 'bip', email: 'bop' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-data]]');
}
});
describe('.updateProfile()', () => {
let uid;
it('should update a user\'s profile', async () => {
uid = await User.create({ username: 'justforupdate', email: '[email protected]', password: '123456' });
await User.setUserField(uid, 'email', '[email protected]');
await User.email.confirmByUid(uid);
const data = {
uid: uid,
username: 'updatedUserName',
email: '[email protected]',
fullname: 'updatedFullname',
website: 'http://nodebb.org',
location: 'izmir',
groupTitle: 'testGroup',
birthday: '01/01/1980',
signature: 'nodebb is good',
password: '123456',
};
const result = await apiUser.update({ uid: uid }, { ...data, password: '123456', invalid: 'field' });
assert.equal(result.username, 'updatedUserName');
assert.equal(result.userslug, 'updatedusername');
assert.equal(result.location, 'izmir');
const userData = await db.getObject(`user:${uid}`);
Object.keys(data).forEach((key) => {
if (key === 'email') {
assert.strictEqual(userData.email, '[email protected]'); // email remains the same until confirmed
} else if (key !== 'password') {
assert.equal(data[key], userData[key]);
} else {
assert(userData[key].startsWith('$2a$'));
}
});
// updateProfile only saves valid fields
assert.strictEqual(userData.invalid, undefined);
});
it('should also generate an email confirmation code for the changed email', async () => {
const confirmSent = await User.email.isValidationPending(uid, '[email protected]');
assert.strictEqual(confirmSent, true);
});
});
it('should change a user\'s password', async () => {
const uid = await User.create({ username: 'changepassword', password: '123456' });
await apiUser.changePassword({ uid: uid }, { uid: uid, newPassword: '654321', currentPassword: '123456' });
const correct = await User.isPasswordCorrect(uid, '654321', '127.0.0.1');
assert(correct);
});
it('should not let user change another user\'s password', async () => {
const regularUserUid = await User.create({ username: 'regularuserpwdchange', password: 'regularuser1234' });
const uid = await User.create({ username: 'changeadminpwd1', password: '123456' });
try {
await apiUser.changePassword({ uid: uid }, { uid: regularUserUid, newPassword: '654321', currentPassword: '123456' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[user:change_password_error_privileges]]');
}
});
it('should not let user change admin\'s password', async () => {
const adminUid = await User.create({ username: 'adminpwdchange', password: 'admin1234' });
await groups.join('administrators', adminUid);
const uid = await User.create({ username: 'changeadminpwd2', password: '123456' });
try {
await apiUser.changePassword({ uid: uid }, { uid: adminUid, newPassword: '654321', currentPassword: '123456' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[user:change_password_error_privileges]]');
}
});
it('should let admin change another users password', async () => {
const adminUid = await User.create({ username: 'adminpwdchange2', password: 'admin1234' });
await groups.join('administrators', adminUid);
const uid = await User.create({ username: 'forgotmypassword', password: '123456' });
await apiUser.changePassword({ uid: adminUid }, { uid: uid, newPassword: '654321' });
const correct = await User.isPasswordCorrect(uid, '654321', '127.0.0.1');
assert(correct);
});
it('should not let admin change their password if current password is incorrect', async () => {
const adminUid = await User.create({ username: 'adminforgotpwd', password: 'admin1234' });
await groups.join('administrators', adminUid);
try {
await apiUser.changePassword({ uid: adminUid }, { uid: adminUid, newPassword: '654321', currentPassword: 'wrongpwd' });
assert(false);
} catch (err) {
assert.equal(err.message, '[[user:change_password_error_wrong_current]]');
}
});
it('should change username', async () => {
await apiUser.update({ uid: uid }, { uid: uid, username: 'updatedAgain', password: '123456' });
const username = await db.getObjectField(`user:${uid}`, 'username');
assert.equal(username, 'updatedAgain');
});
it('should not let setting an empty username', async () => {
await apiUser.update({ uid: uid }, { uid: uid, username: '', password: '123456' });
const username = await db.getObjectField(`user:${uid}`, 'username');
assert.strictEqual(username, 'updatedAgain');
});
it('should let updating profile if current username is above max length and it is not being changed', async () => {
const maxLength = meta.config.maximumUsernameLength + 1;
const longName = new Array(maxLength).fill('a').join('');
const uid = await User.create({ username: longName });
await apiUser.update({ uid: uid }, { uid: uid, username: longName, email: '[email protected]' });
const userData = await db.getObject(`user:${uid}`);
const awaitingValidation = await User.email.isValidationPending(uid, '[email protected]');
assert.strictEqual(userData.username, longName);
assert.strictEqual(awaitingValidation, true);
});
it('should not update a user\'s username if it did not change', async () => {
await apiUser.update({ uid: uid }, { uid: uid, username: 'updatedAgain', password: '123456' });
const data = await db.getSortedSetRevRange(`user:${uid}:usernames`, 0, -1);
assert.equal(data.length, 2);
assert(data[0].startsWith('updatedAgain'));
});
it('should not update a user\'s username if a password is not supplied', async () => {
try {
await apiUser.update({ uid: uid }, { uid: uid, username: 'updatedAgain', password: '' });
assert(false);
} catch (err) {
assert.strictEqual(err.message, '[[error:invalid-password]]');
}
});
it('should properly change username and clean up old sorted sets', async () => {
const uid = await User.create({ username: 'DennyO', password: '123456' });
let usernames = await db.getSortedSetRevRangeWithScores('username:uid', 0, -1);
usernames = usernames.filter(d => d.score === uid);
assert.deepStrictEqual(usernames, [{ value: 'DennyO', score: uid }]);
await apiUser.update({ uid: uid }, { uid: uid, username: 'DennyO\'s', password: '123456' });
usernames = await db.getSortedSetRevRangeWithScores('username:uid', 0, -1);
usernames = usernames.filter(d => d.score === uid);
assert.deepStrictEqual(usernames, [{ value: 'DennyO\'s', score: uid }]);
await apiUser.update({ uid: uid }, { uid: uid, username: 'Denny O', password: '123456' });
usernames = await db.getSortedSetRevRangeWithScores('username:uid', 0, -1);
usernames = usernames.filter(d => d.score === uid);
assert.deepStrictEqual(usernames, [{ value: 'Denny O', score: uid }]);
});
it('should send validation email', async () => {
const uid = await User.create({ username: 'pooremailupdate', email: '[email protected]', password: '123456' });
await User.email.expireValidation(uid);
await apiUser.update({ uid: uid }, { uid: uid, email: '[email protected]', password: '123456' });
assert.strictEqual(await User.email.isValidationPending(uid, '[email protected]'.toLowerCase()), true);
});
it('should update cover image', (done) => {
const position = '50.0301% 19.2464%';
socketUser.updateCover({ uid: uid }, { uid: uid, imageData: goodImage, position: position }, (err, result) => {
assert.ifError(err);
assert(result.url);
db.getObjectFields(`user:${uid}`, ['cover:url', 'cover:position'], (err, data) => {
assert.ifError(err);
assert.equal(data['cover:url'], result.url);
assert.equal(data['cover:position'], position);
done();
});
});
});
it('should remove cover image', async () => {
const coverPath = await User.getLocalCoverPath(uid);
await socketUser.removeCover({ uid: uid }, { uid: uid });
const coverUrlNow = await db.getObjectField(`user:${uid}`, 'cover:url');
assert.strictEqual(coverUrlNow, null);
assert.strictEqual(fs.existsSync(coverPath), false);
});
it('should set user status', (done) => {
socketUser.setStatus({ uid: uid }, 'away', (err, data) => {
assert.ifError(err);
assert.equal(data.uid, uid);
assert.equal(data.status, 'away');
done();
});
});
it('should fail for invalid status', (done) => {
socketUser.setStatus({ uid: uid }, '12345', (err) => {
assert.equal(err.message, '[[error:invalid-user-status]]');
done();
});
});
it('should get user status', (done) => {
socketUser.checkStatus({ uid: uid }, uid, (err, status) => {
assert.ifError(err);
assert.equal(status, 'away');
done();
});
});
it('should change user picture', async () => {
await apiUser.changePicture({ uid: uid }, { type: 'default', uid: uid });
const picture = await User.getUserField(uid, 'picture');
assert.equal(picture, '');
});
it('should let you set an external image', async () => {
const token = await helpers.getCsrfToken(jar);
const body = await requestAsync(`${nconf.get('url')}/api/v3/users/${uid}/picture`, {
jar,
method: 'put',
json: true,
headers: {
'x-csrf-token': token,
},
body: {
type: 'external',
url: 'https://example.org/picture.jpg',
},
});
assert(body && body.status && body.response);
assert.strictEqual(body.status.code, 'ok');
const picture = await User.getUserField(uid, 'picture');
assert.strictEqual(picture, validator.escape('https://example.org/picture.jpg'));
});
it('should fail to change user picture with invalid data', async () => {
try {
await apiUser.changePicture({ uid: uid }, null);
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-data]]');
}
});
it('should fail to change user picture with invalid uid', async () => {
try {
await apiUser.changePicture({ uid: 0 }, { uid: 1 });
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:no-privileges]]');
}
});
it('should set user picture to uploaded', async () => {
await User.setUserField(uid, 'uploadedpicture', '/test');
await apiUser.changePicture({ uid: uid }, { type: 'uploaded', uid: uid });
const picture = await User.getUserField(uid, 'picture');
assert.equal(picture, `${nconf.get('relative_path')}/test`);
});
it('should return error if profile image uploads disabled', (done) => {
meta.config.allowProfileImageUploads = 0;
const picture = {
path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'),
size: 7189,
name: 'test.png',
type: 'image/png',
};
User.uploadCroppedPicture({
callerUid: uid,
uid: uid,
file: picture,
}, (err) => {
assert.equal(err.message, '[[error:profile-image-uploads-disabled]]');
meta.config.allowProfileImageUploads = 1;
done();
});
});
it('should return error if profile image has no mime type', (done) => {
User.uploadCroppedPicture({
callerUid: uid,
uid: uid,
imageData: 'data:image/invalid;base64,R0lGODlhPQBEAPeoAJosM/',
}, (err) => {
assert.equal(err.message, '[[error:invalid-image]]');
done();
});
});
describe('user.uploadCroppedPicture', () => {
const badImage = 'data:audio/mp3;base64,R0lGODlhPQBEAPeoAJosM//AwO/AwHVYZ/z595kzAP/s7P+goOXMv8+fhw/v739/f+8PD98fH/8mJl+fn/9ZWb8/PzWlwv///6wWGbImAPgTEMImIN9gUFCEm/gDALULDN8PAD6atYdCTX9gUNKlj8wZAKUsAOzZz+UMAOsJAP/Z2ccMDA8PD/95eX5NWvsJCOVNQPtfX/8zM8+QePLl38MGBr8JCP+zs9myn/8GBqwpAP/GxgwJCPny78lzYLgjAJ8vAP9fX/+MjMUcAN8zM/9wcM8ZGcATEL+QePdZWf/29uc/P9cmJu9MTDImIN+/r7+/vz8/P8VNQGNugV8AAF9fX8swMNgTAFlDOICAgPNSUnNWSMQ5MBAQEJE3QPIGAM9AQMqGcG9vb6MhJsEdGM8vLx8fH98AANIWAMuQeL8fABkTEPPQ0OM5OSYdGFl5jo+Pj/+pqcsTE78wMFNGQLYmID4dGPvd3UBAQJmTkP+8vH9QUK+vr8ZWSHpzcJMmILdwcLOGcHRQUHxwcK9PT9DQ0O/v70w5MLypoG8wKOuwsP/g4P/Q0IcwKEswKMl8aJ9fX2xjdOtGRs/Pz+Dg4GImIP8gIH0sKEAwKKmTiKZ8aB/f39Wsl+LFt8dgUE9PT5x5aHBwcP+AgP+WltdgYMyZfyywz78AAAAAAAD///8AAP9mZv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKgALAAAAAA9AEQAAAj/AFEJHEiwoMGDCBMqXMiwocAbBww4nEhxoYkUpzJGrMixogkfGUNqlNixJEIDB0SqHGmyJSojM1bKZOmyop0gM3Oe2liTISKMOoPy7GnwY9CjIYcSRYm0aVKSLmE6nfq05QycVLPuhDrxBlCtYJUqNAq2bNWEBj6ZXRuyxZyDRtqwnXvkhACDV+euTeJm1Ki7A73qNWtFiF+/gA95Gly2CJLDhwEHMOUAAuOpLYDEgBxZ4GRTlC1fDnpkM+fOqD6DDj1aZpITp0dtGCDhr+fVuCu3zlg49ijaokTZTo27uG7Gjn2P+hI8+PDPERoUB318bWbfAJ5sUNFcuGRTYUqV/3ogfXp1rWlMc6awJjiAAd2fm4ogXjz56aypOoIde4OE5u/F9x199dlXnnGiHZWEYbGpsAEA3QXYnHwEFliKAgswgJ8LPeiUXGwedCAKABACCN+EA1pYIIYaFlcDhytd51sGAJbo3onOpajiihlO92KHGaUXGwWjUBChjSPiWJuOO/LYIm4v1tXfE6J4gCSJEZ7YgRYUNrkji9P55sF/ogxw5ZkSqIDaZBV6aSGYq/lGZplndkckZ98xoICbTcIJGQAZcNmdmUc210hs35nCyJ58fgmIKX5RQGOZowxaZwYA+JaoKQwswGijBV4C6SiTUmpphMspJx9unX4KaimjDv9aaXOEBteBqmuuxgEHoLX6Kqx+yXqqBANsgCtit4FWQAEkrNbpq7HSOmtwag5w57GrmlJBASEU18ADjUYb3ADTinIttsgSB1oJFfA63bduimuqKB1keqwUhoCSK374wbujvOSu4QG6UvxBRydcpKsav++Ca6G8A6Pr1x2kVMyHwsVxUALDq/krnrhPSOzXG1lUTIoffqGR7Goi2MAxbv6O2kEG56I7CSlRsEFKFVyovDJoIRTg7sugNRDGqCJzJgcKE0ywc0ELm6KBCCJo8DIPFeCWNGcyqNFE06ToAfV0HBRgxsvLThHn1oddQMrXj5DyAQgjEHSAJMWZwS3HPxT/QMbabI/iBCliMLEJKX2EEkomBAUCxRi42VDADxyTYDVogV+wSChqmKxEKCDAYFDFj4OmwbY7bDGdBhtrnTQYOigeChUmc1K3QTnAUfEgGFgAWt88hKA6aCRIXhxnQ1yg3BCayK44EWdkUQcBByEQChFXfCB776aQsG0BIlQgQgE8qO26X1h8cEUep8ngRBnOy74E9QgRgEAC8SvOfQkh7FDBDmS43PmGoIiKUUEGkMEC/PJHgxw0xH74yx/3XnaYRJgMB8obxQW6kL9QYEJ0FIFgByfIL7/IQAlvQwEpnAC7DtLNJCKUoO/w45c44GwCXiAFB/OXAATQryUxdN4LfFiwgjCNYg+kYMIEFkCKDs6PKAIJouyGWMS1FSKJOMRB/BoIxYJIUXFUxNwoIkEKPAgCBZSQHQ1A2EWDfDEUVLyADj5AChSIQW6gu10bE/JG2VnCZGfo4R4d0sdQoBAHhPjhIB94v/wRoRKQWGRHgrhGSQJxCS+0pCZbEhAAOw==';
it('should upload cropped profile picture', async () => {
const result = await socketUser.uploadCroppedPicture({ uid: uid }, { uid: uid, imageData: goodImage });
assert(result.url);
const data = await db.getObjectFields(`user:${uid}`, ['uploadedpicture', 'picture']);
assert.strictEqual(result.url, data.uploadedpicture);
assert.strictEqual(result.url, data.picture);
});
it('should upload cropped profile picture in chunks', async () => {
const socketUploads = require('../src/socket.io/uploads');
const socketData = {
uid,
method: 'user.uploadCroppedPicture',
size: goodImage.length,
progress: 0,
};
const chunkSize = 1000;
let result;
do {
const chunk = goodImage.slice(socketData.progress, socketData.progress + chunkSize);
socketData.progress += chunk.length;
// eslint-disable-next-line
result = await socketUploads.upload({ uid: uid }, {
chunk: chunk,
params: socketData,
});
} while (socketData.progress < socketData.size);
assert(result.url);
const data = await db.getObjectFields(`user:${uid}`, ['uploadedpicture', 'picture']);
assert.strictEqual(result.url, data.uploadedpicture);
assert.strictEqual(result.url, data.picture);
});
it('should error if both file and imageData are missing', (done) => {
User.uploadCroppedPicture({}, (err) => {
assert.equal('[[error:invalid-data]]', err.message);
done();
});
});
it('should error if file size is too big', (done) => {
const temp = meta.config.maximumProfileImageSize;
meta.config.maximumProfileImageSize = 1;
User.uploadCroppedPicture({
callerUid: uid,
uid: 1,
imageData: goodImage,
}, (err) => {
assert.equal('[[error:file-too-big, 1]]', err.message);
// Restore old value
meta.config.maximumProfileImageSize = temp;
done();
});
});
it('should not allow image data with bad MIME type to be passed in', (done) => {
User.uploadCroppedPicture({
callerUid: uid,
uid: 1,
imageData: badImage,
}, (err) => {
assert.equal('[[error:invalid-image]]', err.message);
done();
});
});
it('should get profile pictures', (done) => {
socketUser.getProfilePictures({ uid: uid }, { uid: uid }, (err, data) => {
assert.ifError(err);
assert(data);
assert(Array.isArray(data));
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
assert.equal(data[0].type, 'default');
assert.equal(data[0].username, '[[user:default_picture]]');
assert.equal(data[1].type, 'uploaded');
assert.equal(data[1].username, '[[user:uploaded_picture]]');
done();
});
});
it('should get default profile avatar', (done) => {
assert.strictEqual(User.getDefaultAvatar(), '');
meta.config.defaultAvatar = 'https://path/to/default/avatar';
assert.strictEqual(User.getDefaultAvatar(), meta.config.defaultAvatar);
meta.config.defaultAvatar = '/path/to/default/avatar';
assert.strictEqual(User.getDefaultAvatar(), nconf.get('relative_path') + meta.config.defaultAvatar);
meta.config.defaultAvatar = '';
done();
});
it('should fail to get profile pictures with invalid data', (done) => {
socketUser.getProfilePictures({ uid: uid }, null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
socketUser.getProfilePictures({ uid: uid }, { uid: null }, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
});
it('should remove uploaded picture', async () => {
const avatarPath = await User.getLocalAvatarPath(uid);
assert.notStrictEqual(avatarPath, false);
await socketUser.removeUploadedPicture({ uid: uid }, { uid: uid });
const uploadedPicture = await User.getUserField(uid, 'uploadedpicture');
assert.strictEqual(uploadedPicture, '');
assert.strictEqual(fs.existsSync(avatarPath), false);
});
it('should fail to remove uploaded picture with invalid-data', (done) => {
socketUser.removeUploadedPicture({ uid: uid }, null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
socketUser.removeUploadedPicture({ uid: uid }, { }, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
socketUser.removeUploadedPicture({ uid: null }, { }, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
});
});
});
it('should load profile page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load settings page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain/settings`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.settings);
assert(body.languages);
assert(body.homePageRoutes);
done();
});
});
it('should load edit page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain/edit`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load edit/email page', async () => {
const res = await requestAsync(`${nconf.get('url')}/api/user/updatedagain/edit/email`, { jar: jar, json: true, resolveWithFullResponse: true });
assert.strictEqual(res.statusCode, 200);
assert(res.body);
// Accessing this page will mark the user's account as needing an updated email, below code undo's.
await requestAsync({
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
uri: `${nconf.get('url')}/register/abort`,
jar,
method: 'POST',
simple: false,
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
headers: {
'x-csrf-token': csrf_token,
},
});
});
it('should load user\'s groups page', async () => {
await groups.create({
name: 'Test',
description: 'Foobar!',
});
await groups.join('Test', uid);
const body = await requestAsync(`${nconf.get('url')}/api/user/updatedagain/groups`, { jar: jar, json: true });
assert(Array.isArray(body.groups));
assert.equal(body.groups[0].name, 'Test');
});
});
describe('user info', () => {
let testUserUid;
let verifiedTestUserUid;
before(async () => {
// Might be the first user thus a verified one if this test part is ran alone
verifiedTestUserUid = await User.create({ username: 'bannedUser', password: '123456', email: '[email protected]' });
await User.setUserField(verifiedTestUserUid, 'email:confirmed', 1);
testUserUid = await User.create({ username: 'bannedUser2', password: '123456', email: '[email protected]' });
});
it('should return error if there is no ban reason', (done) => {
User.getLatestBanInfo(123, (err) => {
assert.equal(err.message, 'no-ban-info');
done();
});
});
it('should get history from set', async () => {
const now = Date.now();
await db.sortedSetAdd(`user:${testUserUid}:usernames`, now, `derp:${now}`);
const data = await User.getHistory(`user:${testUserUid}:usernames`);
assert.equal(data[0].value, 'derp');
assert.equal(data[0].timestamp, now);
});
it('should return the correct ban reason', (done) => {
async.series([
function (next) {
User.bans.ban(testUserUid, 0, '', (err) => {
assert.ifError(err);
next(err);
});
},
function (next) {
User.getModerationHistory(testUserUid, (err, data) => {
assert.ifError(err);
assert.equal(data.bans.length, 1, 'one ban');
assert.equal(data.bans[0].reason, '[[user:info.banned-no-reason]]', 'no ban reason');
next(err);
});
},
], (err) => {
assert.ifError(err);
User.bans.unban(testUserUid, (err) => {
assert.ifError(err);
done();
});
});
});
it('should ban user permanently', (done) => {
User.bans.ban(testUserUid, (err) => {
assert.ifError(err);
User.bans.isBanned(testUserUid, (err, isBanned) => {
assert.ifError(err);
assert.equal(isBanned, true);
User.bans.unban(testUserUid, done);
});
});
});
it('should ban user temporarily', (done) => {
User.bans.ban(testUserUid, Date.now() + 2000, (err) => {
assert.ifError(err);
User.bans.isBanned(testUserUid, (err, isBanned) => {
assert.ifError(err);
assert.equal(isBanned, true);
setTimeout(() => {
User.bans.isBanned(testUserUid, (err, isBanned) => {
assert.ifError(err);
assert.equal(isBanned, false);
User.bans.unban(testUserUid, done);
});
}, 3000);
});
});
});
it('should error if until is NaN', (done) => {
User.bans.ban(testUserUid, 'asd', (err) => {
assert.equal(err.message, '[[error:ban-expiry-missing]]');
done();
});
});
it('should be member of "banned-users" system group only after a ban', async () => {
await User.bans.ban(testUserUid);
const systemGroups = groups.systemGroups.filter(group => group !== groups.BANNED_USERS);
const isMember = await groups.isMember(testUserUid, groups.BANNED_USERS);
const isMemberOfAny = await groups.isMemberOfAny(testUserUid, systemGroups);
assert.strictEqual(isMember, true);
assert.strictEqual(isMemberOfAny, false);
});
it('should restore system group memberships after an unban (for an unverified user)', async () => {
await User.bans.unban(testUserUid);
const isMemberOfGroups = await groups.isMemberOfGroups(testUserUid, groups.systemGroups);
const membership = new Map(groups.systemGroups.map((item, index) => [item, isMemberOfGroups[index]]));
assert.strictEqual(membership.get('registered-users'), true);
assert.strictEqual(membership.get('verified-users'), false);
assert.strictEqual(membership.get('unverified-users'), true);
assert.strictEqual(membership.get(groups.BANNED_USERS), false);
// administrators cannot be banned
assert.strictEqual(membership.get('administrators'), false);
// This will not restored
assert.strictEqual(membership.get('Global Moderators'), false);
});
it('should restore system group memberships after an unban (for a verified user)', async () => {
await User.bans.ban(verifiedTestUserUid);
await User.bans.unban(verifiedTestUserUid);
const isMemberOfGroups = await groups.isMemberOfGroups(verifiedTestUserUid, groups.systemGroups);
const membership = new Map(groups.systemGroups.map((item, index) => [item, isMemberOfGroups[index]]));
assert.strictEqual(membership.get('verified-users'), true);
assert.strictEqual(membership.get('unverified-users'), false);
});
it('should be able to post in category for banned users', async () => {
const { cid } = await Categories.create({
name: 'Test Category',
description: 'A test',
order: 1,
});
const testUid = await User.create({ username: userData.username });
await User.bans.ban(testUid);
let _err;
try {
await Topics.post({ title: 'banned topic', content: 'tttttttttttt', cid: cid, uid: testUid });
} catch (err) {
_err = err;
}
assert.strictEqual(_err && _err.message, '[[error:no-privileges]]');
await Promise.all([
privileges.categories.give(['groups:topics:create', 'groups:topics:reply'], cid, 'banned-users'),
privileges.categories.rescind(['groups:topics:create', 'groups:topics:reply'], cid, 'registered-users'),
]);
const result = await Topics.post({ title: 'banned topic', content: 'tttttttttttt', cid: cid, uid: testUid });
assert(result);
assert.strictEqual(result.topicData.title, 'banned topic');
});
});
3 years ago
describe('Digest.getSubscribers', () => {
const uidIndex = {};
before((done) => {
const testUsers = ['daysub', 'offsub', 'nullsub', 'weeksub'];
async.each(testUsers, (username, next) => {
async.waterfall([
async.apply(User.create, { username: username, email: `${username}@example.com` }),
function (uid, next) {
if (username === 'nullsub') {
return setImmediate(next);
}
uidIndex[username] = uid;
const sub = username.slice(0, -3);
async.parallel([
async.apply(User.updateDigestSetting, uid, sub),
async.apply(User.setSetting, uid, 'dailyDigestFreq', sub),
], next);
},
], next);
}, done);
});
it('should accurately build digest list given ACP default "null" (not set)', (done) => {
User.digest.getSubscribers('day', (err, subs) => {
assert.ifError(err);
assert.strictEqual(subs.length, 1);
done();
});
});
it('should accurately build digest list given ACP default "day"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'day'),
function (next) {
User.digest.getSubscribers('day', (err, subs) => {
assert.ifError(err);
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed
next();
});
},
], done);
});
it('should accurately build digest list given ACP default "week"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'week'),
function (next) {
User.digest.getSubscribers('week', (err, subs) => {
assert.ifError(err);
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed
next();
});
},
], done);
});
it('should accurately build digest list given ACP default "off"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'off'),
function (next) {
User.digest.getSubscribers('day', (err, subs) => {
assert.ifError(err);
assert.strictEqual(subs.length, 1);
next();
});
},
], done);
});
});
describe('digests', () => {
let uid;
before((done) => {
async.waterfall([
function (next) {
User.create({ username: 'digestuser', email: '[email protected]' }, next);
},
function (_uid, next) {
uid = _uid;
User.updateDigestSetting(uid, 'day', next);
},
function (next) {
User.setSetting(uid, 'dailyDigestFreq', 'day', next);
},
function (next) {
User.setSetting(uid, 'notificationType_test', 'notificationemail', next);
},
], done);
});
2 years ago
it('should send digests', async () => {
3 years ago
const oldValue = meta.config.includeUnverifiedEmails;
meta.config.includeUnverifiedEmails = true;
const uid = await User.create({ username: 'digest' });
await User.setUserField(uid, 'email', '[email protected]');
await User.email.confirmByUid(uid);
2 years ago
await User.digest.execute({
interval: 'day',
subscribers: [uid],
});
2 years ago
meta.config.includeUnverifiedEmails = oldValue;
});
it('should return 0', async () => {
const sent = await User.digest.send({ subscribers: [] });
assert.strictEqual(sent, 0);
});
it('should get users with single uid', async () => {
const res = await User.digest.getUsersInterval(1);
assert.strictEqual(res, false);
});
it('should not send digests', async () => {
const oldValue = meta.config.disableEmailSubsriptions;
meta.config.disableEmailSubsriptions = 1;
const res = await User.digest.execute({});
assert.strictEqual(res, false);
meta.config.disableEmailSubsriptions = oldValue;
});
2 years ago
it('should not send digests', async () => {
await User.digest.execute({ interval: 'month' });
});
3 years ago
it('should get delivery times', async () => {
const data = await User.digest.getDeliveryTimes(0, -1);
const users = data.users.filter(u => u.username === 'digestuser');
assert.strictEqual(users[0].setting, 'day');
});
describe('unsubscribe via POST', () => {
it('should unsubscribe from digest if one-click unsubscribe is POSTed', (done) => {
const token = jwt.sign({
template: 'digest',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
db.getObjectField(`user:${uid}:settings`, 'dailyDigestFreq', (err, value) => {
assert.ifError(err);
assert.strictEqual(value, 'off');
done();
});
});
});
it('should unsubscribe from notifications if one-click unsubscribe is POSTed', (done) => {
const token = jwt.sign({
template: 'notification',
type: 'test',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
db.getObjectField(`user:${uid}:settings`, 'notificationType_test', (err, value) => {
assert.ifError(err);
assert.strictEqual(value, 'notification');
done();
});
});
});
it('should return errors on missing template in token', (done) => {
const token = jwt.sign({
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
});
it('should return errors on wrong template in token', (done) => {
const token = jwt.sign({
template: 'user',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
});
it('should return errors on missing token', (done) => {
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
});
it('should return errors on token signed with wrong secret (verify-failure)', (done) => {
const token = jwt.sign({
template: 'notification',
type: 'test',
uid: uid,
}, `${nconf.get('secret')}aababacaba`);
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 403);
done();
});
});
});
});
describe('socket methods', () => {
const socketUser = require('../src/socket.io/user');
let delUid;
it('should fail with invalid data', (done) => {
meta.userOrGroupExists(null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should return true if user/group exists', (done) => {
meta.userOrGroupExists('registered-users', (err, exists) => {
assert.ifError(err);
assert(exists);
done();
});
});
it('should return true if user/group exists', (done) => {
meta.userOrGroupExists('John Smith', (err, exists) => {
assert.ifError(err);
assert(exists);
done();
});
});
it('should return false if user/group does not exists', (done) => {
meta.userOrGroupExists('doesnot exist', (err, exists) => {
assert.ifError(err);
assert(!exists);
done();
});
});
it('should delete user', async () => {
delUid = await User.create({ username: 'willbedeleted' });
// Upload some avatars and covers before deleting
meta.config['profile:keepAllUserImages'] = 1;
let result = await socketUser.uploadCroppedPicture({ uid: delUid }, { uid: delUid, imageData: goodImage });
assert(result.url);
result = await socketUser.uploadCroppedPicture({ uid: delUid }, { uid: delUid, imageData: goodImage });
assert(result.url);
const position = '50.0301% 19.2464%';
result = await socketUser.updateCover({ uid: delUid }, { uid: delUid, imageData: goodImage, position: position });
assert(result.url);
result = await socketUser.updateCover({ uid: delUid }, { uid: delUid, imageData: goodImage, position: position });
assert(result.url);
meta.config['profile:keepAllUserImages'] = 0;
await apiUser.deleteAccount({ uid: delUid }, { uid: delUid });
const exists = await meta.userOrGroupExists('willbedeleted');
4 years ago
assert(!exists);
});
it('should clean profile images after account deletion', () => {
const allProfileFiles = fs.readdirSync(path.join(nconf.get('upload_path'), 'profile'));
const deletedUserImages = allProfileFiles.filter(
f => f.startsWith(`${delUid}-profilecover`) || f.startsWith(`${delUid}-profileavatar`)
);
assert.strictEqual(deletedUserImages.length, 0);
});
it('should fail to delete user with wrong password', async () => {
4 years ago
const uid = await User.create({ username: 'willbedeletedpwd', password: '123456' });
try {
await apiUser.deleteAccount({ uid: uid }, { uid: uid, password: '654321' });
assert(false);
} catch (err) {
assert.strictEqual(err.message, '[[error:invalid-password]]');
4 years ago
}
});
it('should delete user with correct password', async () => {
4 years ago
const uid = await User.create({ username: 'willbedeletedcorrectpwd', password: '123456' });
await apiUser.deleteAccount({ uid: uid }, { uid: uid, password: '123456' });
4 years ago
const exists = await User.exists(uid);
assert(!exists);
});
it('should fail to delete user if account deletion is not allowed', async () => {
const oldValue = meta.config.allowAccountDelete;
meta.config.allowAccountDelete = 0;
const uid = await User.create({ username: 'tobedeleted' });
try {
await apiUser.deleteAccount({ uid: uid }, { uid: uid });
assert(false);
} catch (err) {
assert.strictEqual(err.message, '[[error:account-deletion-disabled]]');
}
meta.config.allowAccountDelete = oldValue;
});
it('should send reset email', (done) => {
socketUser.reset.send({ uid: 0 }, '[email protected]', (err) => {
assert.ifError(err);
done();
});
});
it('should return invalid-data error', (done) => {
socketUser.reset.send({ uid: 0 }, null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should not error', (done) => {
socketUser.reset.send({ uid: 0 }, '[email protected]', (err) => {
assert.ifError(err);
done();
});
});
it('should commit reset', (done) => {
db.getObject('reset:uid', (err, data) => {
assert.ifError(err);
const code = Object.keys(data).find(code => parseInt(data[code], 10) === parseInt(testUid, 10));
socketUser.reset.commit({ uid: 0 }, { code: code, password: 'pwdchange' }, (err) => {
assert.ifError(err);
done();
});
});
});
it('should save user settings', async () => {
const data = {
uid: testUid,
settings: {
bootswatchSkin: 'default',
homePageRoute: 'none',
homePageCustom: '',
openOutgoingLinksInNewTab: 0,
scrollToMyPost: 1,
userLang: 'en-GB',
usePagination: 1,
topicsPerPage: '10',
postsPerPage: '5',
showemail: 1,
showfullname: 1,
restrictChat: 0,
followTopicsOnCreate: 1,
followTopicsOnReply: 1,
},
};
await apiUser.updateSettings({ uid: testUid }, data);
const userSettings = await User.getSettings(testUid);
assert.strictEqual(userSettings.usePagination, true);
});
it('should properly escape homePageRoute', async () => {
const data = {
uid: testUid,
settings: {
bootswatchSkin: 'default',
homePageRoute: 'category/6/testing-ground',
homePageCustom: '',
openOutgoingLinksInNewTab: 0,
scrollToMyPost: 1,
userLang: 'en-GB',
usePagination: 1,
topicsPerPage: '10',
postsPerPage: '5',
showemail: 1,
showfullname: 1,
restrictChat: 0,
followTopicsOnCreate: 1,
followTopicsOnReply: 1,
},
};
await apiUser.updateSettings({ uid: testUid }, data);
const userSettings = await User.getSettings(testUid);
assert.strictEqual(userSettings.homePageRoute, 'category/6/testing-ground');
});
it('should error if language is invalid', async () => {
const data = {
uid: testUid,
settings: {
userLang: '<invalid-string>',
topicsPerPage: '10',
postsPerPage: '5',
},
};
try {
await apiUser.updateSettings({ uid: testUid }, data);
assert(false);
} catch (err) {
assert.equal(err.message, '[[error:invalid-language]]');
}
});
it('should set moderation note', (done) => {
let adminUid;
async.waterfall([
function (next) {
User.create({ username: 'noteadmin' }, next);
},
function (_adminUid, next) {
adminUid = _adminUid;
groups.join('administrators', adminUid, next);
},
function (next) {
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' }, next);
},
function (next) {
setTimeout(next, 50);
},
function (next) {
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' }, next);
},
function (next) {
User.getModerationNotes(testUid, 0, -1, next);
},
], (err, notes) => {
assert.ifError(err);
assert.equal(notes[0].note, '&lt;svg&#x2F;onload=alert(document.location);&#x2F;&#x2F;');
assert.equal(notes[0].uid, adminUid);
assert.equal(notes[1].note, 'this is a test user');
assert(notes[0].timestamp);
done();
});
});
it('should get unread count 0 for guest', async () => {
const count = await socketUser.getUnreadCount({ uid: 0 });
assert.strictEqual(count, 0);
});
it('should get unread count for user', async () => {
const count = await socketUser.getUnreadCount({ uid: testUid });
assert.strictEqual(count, 4);
});
it('should get unread chat count 0 for guest', async () => {
const count = await socketUser.getUnreadChatCount({ uid: 0 });
assert.strictEqual(count, 0);
});
it('should get unread chat count for user', async () => {
const count = await socketUser.getUnreadChatCount({ uid: testUid });
assert.strictEqual(count, 0);
});
it('should get unread counts 0 for guest', async () => {
const counts = await socketUser.getUnreadCounts({ uid: 0 });
assert.deepStrictEqual(counts, {});
});
it('should get unread counts for user', async () => {
const counts = await socketUser.getUnreadCounts({ uid: testUid });
assert.deepStrictEqual(counts, {
unreadChatCount: 0,
unreadCounts: {
'': 4,
new: 4,
unreplied: 4,
watched: 0,
},
unreadNewTopicCount: 4,
unreadNotificationCount: 0,
unreadTopicCount: 4,
unreadUnrepliedTopicCount: 4,
unreadWatchedTopicCount: 0,
});
});
it('should get user data by uid', async () => {
const userData = await socketUser.getUserByUID({ uid: testUid }, testUid);
assert.strictEqual(userData.uid, testUid);
});
it('should get user data by username', async () => {
const userData = await socketUser.getUserByUsername({ uid: testUid }, 'John Smith');
assert.strictEqual(userData.uid, testUid);
});
it('should get user data by email', async () => {
const userData = await socketUser.getUserByEmail({ uid: testUid }, '[email protected]');
assert.strictEqual(userData.uid, testUid);
});
it('should check/consent gdpr status', async () => {
const consent = await socketUser.gdpr.check({ uid: testUid }, { uid: testUid });
assert(!consent);
await socketUser.gdpr.consent({ uid: testUid });
const consentAfter = await socketUser.gdpr.check({ uid: testUid }, { uid: testUid });
assert(consentAfter);
});
});
describe('approval queue', () => {
let oldRegistrationApprovalType;
let adminUid;
before((done) => {
oldRegistrationApprovalType = meta.config.registrationApprovalType;
meta.config.registrationApprovalType = 'admin-approval';
User.create({ username: 'admin', password: '123456' }, (err, uid) => {
assert.ifError(err);
adminUid = uid;
groups.join('administrators', uid, done);
});
});
after((done) => {
meta.config.registrationApprovalType = oldRegistrationApprovalType;
done();
});
it('should add user to approval queue', async () => {
await helpers.registerUser({
username: 'rejectme',
password: '123456',
'password-confirm': '123456',
email: '<script>alert("ok")<script>[email protected]',
gdpr_consent: true,
});
const { jar } = await helpers.loginUser('admin', '123456');
const { users } = await requestAsync(`${nconf.get('url')}/api/admin/manage/registration`, { jar, json: true });
assert.equal(users[0].username, 'rejectme');
assert.equal(users[0].email, '&lt;script&gt;alert(&quot;ok&quot;)&lt;script&gt;[email protected]');
});
it('should fail to add user to queue if username is taken', (done) => {
helpers.registerUser({
username: 'rejectme',
password: '123456',
'password-confirm': '123456',
email: '<script>alert("ok")<script>[email protected]',
gdpr_consent: true,
}, (err, jar, res, body) => {
assert.ifError(err);
assert.equal(body, '[[error:username-taken]]');
done();
});
});
it('should fail to add user to queue if email is taken', (done) => {
helpers.registerUser({
username: 'rejectmenew',
password: '123456',
'password-confirm': '123456',
email: '<script>alert("ok")<script>[email protected]',
gdpr_consent: true,
}, (err, jar, res, body) => {
assert.ifError(err);
assert.equal(body, '[[error:email-taken]]');
done();
});
});
it('should reject user registration', (done) => {
socketUser.rejectRegistration({ uid: adminUid }, { username: 'rejectme' }, (err) => {
assert.ifError(err);
User.getRegistrationQueue(0, -1, (err, users) => {
assert.ifError(err);
assert.equal(users.length, 0);
done();
});
});
});
it('should accept user registration', (done) => {
helpers.registerUser({
username: 'acceptme',
password: '123456',
'password-confirm': '123456',
email: '[email protected]',
gdpr_consent: true,
}, (err) => {
assert.ifError(err);
socketUser.acceptRegistration({ uid: adminUid }, { username: 'acceptme' }, (err, uid) => {
assert.ifError(err);
User.exists(uid, (err, exists) => {
assert.ifError(err);
assert(exists);
User.getRegistrationQueue(0, -1, (err, users) => {
assert.ifError(err);
assert.equal(users.length, 0);
done();
});
});
});
});
});
4 years ago
it('should trim username and add user to registration queue', (done) => {
4 years ago
helpers.registerUser({
username: 'invalidname\r\n',
password: '123456',
'password-confirm': '123456',
email: '[email protected]',
gdpr_consent: true,
}, (err) => {
4 years ago
assert.ifError(err);
db.getSortedSetRange('registration:queue', 0, -1, (err, data) => {
4 years ago
assert.ifError(err);
assert.equal(data[0], 'invalidname');
done();
});
});
});
});
describe('invites', () => {
let notAnInviterUid;
let inviterUid;
let adminUid;
const PUBLIC_GROUP = 'publicGroup';
const PRIVATE_GROUP = 'privateGroup';
const OWN_PRIVATE_GROUP = 'ownPrivateGroup';
const HIDDEN_GROUP = 'hiddenGroup';
const COMMON_PW = '123456';
before(async () => {
const results = await utils.promiseParallel({
publicGroup: groups.create({ name: PUBLIC_GROUP, private: 0 }),
privateGroup: groups.create({ name: PRIVATE_GROUP, private: 1 }),
hiddenGroup: groups.create({ name: HIDDEN_GROUP, hidden: 1 }),
notAnInviter: User.create({ username: 'notAnInviter', password: COMMON_PW }),
inviter: User.create({ username: 'inviter', password: COMMON_PW }),
admin: User.create({ username: 'adminInvite', password: COMMON_PW }),
});
notAnInviterUid = results.notAnInviter;
inviterUid = results.inviter;
adminUid = results.admin;
await User.setUserField(inviterUid, 'email', '[email protected]');
await Promise.all([
groups.create({ name: OWN_PRIVATE_GROUP, ownerUid: inviterUid, private: 1 }),
groups.join('administrators', adminUid),
groups.join('cid:0:privileges:invite', inviterUid),
User.email.confirmByUid(inviterUid),
]);
});
describe('when inviter is not an admin and does not have invite privilege', () => {
let csrf_token;
let jar;
before(async () => {
({ jar, csrf_token } = await helpers.loginUser('notAnInviter', COMMON_PW));
});
it('should error if user does not have invite privilege', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, notAnInviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
});
it('should error out if user tries to use an inviter\'s uid via the API', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(inviterUid);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(numInvites, 0);
});
});
describe('when inviter has invite privilege', () => {
let csrf_token;
let jar;
before(async () => {
({ jar, csrf_token } = await helpers.loginUser('inviter', COMMON_PW));
});
it('should error with invalid data', async () => {
const { res } = await helpers.invite({}, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 400);
4 years ago
assert.strictEqual(res.body.status.message, 'Invalid Data');
});
it('should error if user is not admin and type is admin-invite-only', async () => {
meta.config.registrationType = 'admin-invite-only';
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
});
it('should send invitation email (without groups to be joined)', async () => {
meta.config.registrationType = 'normal';
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
it('should send multiple invitation emails (with a public group to be joined)', async () => {
const { res } = await helpers.invite({ emails: '[email protected],[email protected]', groupsToJoin: [PUBLIC_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
it('should error if the user has not permission to invite to the group', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
});
it('should error if a non-admin tries to invite to the administrators group', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: ['administrators'] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
});
it('should to invite to own private group', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
it('should to invite to multiple groups', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [PUBLIC_GROUP, OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
it('should error if tries to invite to hidden group', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [HIDDEN_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
});
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
it('should error if out of invitations', async () => {
meta.config.maximumInvites = 1;
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, `You have invited the maximum amount of people (${5} out of ${1}).`);
meta.config.maximumInvites = 10;
});
it('should send invitation email after maximumInvites increased', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
it('should error if invite is sent via API with a different UID', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, adminUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(adminUid);
assert.strictEqual(res.statusCode, 403);
4 years ago
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(numInvites, 0);
});
it('should succeed if email exists but not actually send an invite', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(adminUid);
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(numInvites, 0);
});
});
describe('when inviter is an admin', () => {
let csrf_token;
let jar;
before(async () => {
({ jar, csrf_token } = await helpers.loginUser('adminInvite', COMMON_PW));
});
it('should escape email', async () => {
await helpers.invite({ emails: '<script>alert("ok");</script>', groupsToJoin: [] }, adminUid, jar, csrf_token);
const data = await User.getInvites(adminUid);
assert.strictEqual(data[0], '&lt;script&gt;alert(&quot;ok&quot;);&lt;&#x2F;script&gt;');
await User.deleteInvitationKey('<script>alert("ok");</script>');
});
it('should invite to the administrators group if inviter is an admin', async () => {
const { res } = await helpers.invite({ emails: '[email protected]', groupsToJoin: ['administrators'] }, adminUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
});
});
describe('after invites checks', () => {
it('should get user\'s invites', (done) => {
User.getInvites(inviterUid, (err, data) => {
assert.ifError(err);
Array.from(Array(6)).forEach((_, i) => {
assert.notEqual(data.indexOf(`invite${i + 1}@test.com`), -1);
});
done();
});
});
it('should get all invites', (done) => {
User.getAllInvites((err, data) => {
assert.ifError(err);
const adminData = data.filter(d => parseInt(d.uid, 10) === adminUid)[0];
assert.notEqual(adminData.invitations.indexOf('[email protected]'), -1);
const inviterData = data.filter(d => parseInt(d.uid, 10) === inviterUid)[0];
Array.from(Array(6)).forEach((_, i) => {
assert.notEqual(inviterData.invitations.indexOf(`invite${i + 1}@test.com`), -1);
});
done();
});
});
it('should fail to verify invitation with invalid data', (done) => {
User.verifyInvitation({ token: '', email: '' }, (err) => {
assert.strictEqual(err.message, '[[register:invite.error-invite-only]]');
done();
});
});
it('should fail to verify invitation with invalid email', (done) => {
User.verifyInvitation({ token: 'test', email: '[email protected]' }, (err) => {
assert.strictEqual(err.message, '[[register:invite.error-invalid-data]]');
done();
});
});
it('should verify installation with no errors', (done) => {
const email = '[email protected]';
db.get(`invitation:uid:${inviterUid}:invited:${email}`, 'token', (err, token) => {
assert.ifError(err);
User.verifyInvitation({ token: token, email: '[email protected]' }, (err) => {
assert.ifError(err);
done();
});
});
});
it('should error with invalid username', (done) => {
User.deleteInvitation('doesnotexist', '[email protected]', (err) => {
assert.equal(err.message, '[[error:invalid-username]]');
done();
});
});
it('should delete invitation', (done) => {
const socketUser = require('../src/socket.io/user');
socketUser.deleteInvitation({ uid: adminUid }, { invitedBy: 'inviter', email: '[email protected]' }, (err) => {
assert.ifError(err);
db.isSetMember(`invitation:uid:${inviterUid}`, '[email protected]', (err, isMember) => {
assert.ifError(err);
assert.equal(isMember, false);
done();
});
});
});
it('should delete invitation key', (done) => {
User.deleteInvitationKey('[email protected]', (err) => {
assert.ifError(err);
db.isSetMember(`invitation:uid:${adminUid}`, '[email protected]', (err, isMember) => {
assert.ifError(err);
assert.equal(isMember, false);
db.isSetMember('invitation:uids', adminUid, (err, isMember) => {
assert.ifError(err);
assert.equal(isMember, false);
done();
});
});
});
});
it('should joined the groups from invitation after registration', async () => {
const email = '[email protected]';
const groupsToJoin = [PUBLIC_GROUP, OWN_PRIVATE_GROUP];
const token = await db.get(`invitation:uid:${inviterUid}:invited:${email}`);
await new Promise((resolve, reject) => {
helpers.registerUser({
username: 'invite5',
password: '123456',
'password-confirm': '123456',
email: email,
gdpr_consent: true,
token: token,
}, async (err, jar, response, body) => {
if (err) {
reject(err);
}
const memberships = await groups.isMemberOfGroups(body.uid, groupsToJoin);
const joinedToAll = memberships.filter(Boolean);
if (joinedToAll.length !== groupsToJoin.length) {
reject(new Error('Not joined to the groups'));
}
resolve();
});
});
});
});
describe('invite groups', () => {
let csrf_token;
let jar;
before(async () => {
({ jar, csrf_token } = await helpers.loginUser('inviter', COMMON_PW));
});
it('should show a list of groups for adding to an invite', async () => {
const body = await requestAsync({
url: `${nconf.get('url')}/api/v3/users/${inviterUid}/invites/groups`,
json: true,
jar,
});
assert(Array.isArray(body.response));
assert.strictEqual(2, body.response.length);
assert.deepStrictEqual(body.response, ['ownPrivateGroup', 'publicGroup']);
});
it('should error out if you request invite groups for another uid', async () => {
const res = await requestAsync({
url: `${nconf.get('url')}/api/v3/users/${adminUid}/invites/groups`,
json: true,
jar,
simple: false,
resolveWithFullResponse: true,
});
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
assert.strictEqual(res.statusCode, 403);
});
});
});
describe('email confirm', () => {
it('should error with invalid code', (done) => {
User.email.confirmByCode('asdasda', (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should confirm email of user', async () => {
const email = '[email protected]';
const uid = await User.create({
username: 'confirme',
email: email,
});
const code = await User.email.sendValidationEmail(uid, { email, force: 1 });
const unverified = await groups.isMember(uid, 'unverified-users');
assert.strictEqual(unverified, true);
await User.email.confirmByCode(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);
});
it('should confirm email of user by uid', async () => {
const email = '[email protected]';
const uid = await User.create({
username: 'confirme2',
email,
});
await User.setUserField(uid, 'email', email);
const unverified = await groups.isMember(uid, 'unverified-users');
assert.strictEqual(unverified, true);
await User.email.confirmByUid(uid);
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);
});
it('should remove the email from a different account if the email is already in use', async () => {
const email = '[email protected]';
const uid = await User.create({
username: 'confirme3',
});
const oldUid = await db.sortedSetScore('email:uid', email);
const code = await User.email.sendValidationEmail(uid, email);
await User.email.confirmByCode(code);
const oldUserData = await User.getUserData(oldUid);
assert.strictEqual((await db.sortedSetScore('email:uid', email)), uid);
assert.strictEqual(oldUserData.email, '');
});
});
describe('user jobs', () => {
it('should start user jobs', (done) => {
User.startJobs();
done();
});
it('should stop user jobs', (done) => {
User.stopJobs();
done();
});
it('should send digest', (done) => {
db.sortedSetAdd('digest:day:uids', [Date.now(), Date.now()], [1, 2], (err) => {
assert.ifError(err);
User.digest.execute({ interval: 'day' }, (err) => {
assert.ifError(err);
done();
});
});
});
});
describe('hideEmail/hideFullname', () => {
const COMMON_PW = '123456';
const hidingUser = {
username: 'hiddenemail',
email: '[email protected]',
fullname: 'baris soner usakli',
password: COMMON_PW,
};
const regularUser = {
username: 'regularUser',
email: '[email protected]',
fullname: 'regular user',
password: COMMON_PW,
};
let hidingUserJar;
let adminUid;
let adminJar;
let globalModJar;
let regularUserJar;
before(async () => {
adminUid = await User.create({
username: 'adminhideemail',
password: COMMON_PW,
});
await groups.join('administrators', adminUid);
({ jar: adminJar } = await helpers.loginUser('adminhideemail', COMMON_PW));
// Edge case: In a grepped test, this user should not be created as the first user to have its email not confirmed
hidingUser.uid = await User.create(hidingUser);
({ jar: hidingUserJar } = await helpers.loginUser(hidingUser.username, COMMON_PW));
const globalModUid = await User.create({
username: 'globalmodhideemail',
password: COMMON_PW,
});
await groups.join('Global Moderators', globalModUid);
({ jar: globalModJar } = await helpers.loginUser('globalmodhideemail', COMMON_PW));
regularUser.uid = await User.create(regularUser);
({ jar: regularUserJar } = await helpers.loginUser(regularUser.username, COMMON_PW));
});
after((done) => {
meta.config.hideEmail = 0;
meta.config.hideFullname = 0;
done();
});
async function assertPrivacy({ expectVisible, jar, v3Api, emailOnly }) {
const path = v3Api ? `v3/users/${hidingUser.uid}` : `user/${hidingUser.username}`;
const response = await requestAsync(`${nconf.get('url')}/api/${path}`, { json: true, jar });
const { response: userData } = v3Api ? response : { response };
assert.strictEqual(userData.email, expectVisible ? hidingUser.email : '');
if (!emailOnly) {
assert.strictEqual(userData.fullname, expectVisible ? hidingUser.fullname : '');
}
}
it('should hide unconfirmed emails on profile pages', async () => {
await assertPrivacy({ v3Api: false, emailOnly: true });
await assertPrivacy({ v3Api: false, jar: hidingUserJar, emailOnly: true });
await assertPrivacy({ v3Api: false, jar: adminJar, emailOnly: true });
await assertPrivacy({ v3Api: false, jar: globalModJar, emailOnly: true });
await assertPrivacy({ v3Api: false, jar: regularUserJar, emailOnly: true });
// Let's confirm for afterwards
Bootstrap5 (#10894) * chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future :hourglass: * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for 179faa2270f2ad955dcc4a7b04755acce59e6ffd and c3920ccb10d8ead2dcd9914bb1784bed3f6adfd4 * fix: schema changes from 488f0978a4aa1ca1e4d2a1f2e8c7ef7a681f2f27 * fix: schema changes for f4cf482a874701ce80c0f306c49d8788cec66f87 * fix: schema update for be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 69c96078ea78ee2c45885a90a6f6a59f9042a33c * fix: schema changes for d1364c313021e48a879a818b24947e1457c062f7 * fix: schema changes for 84ff1152f7552dd866e25a90972d970b9861107e * fix: schema changes for b860c2605c209e0650ef98f4c80d842ea23a51ce * fix: schema changes for 23cb67a1126481848fac39aafd1e253441e76d7f * fix: schema changes for b916e42f400dac8aa51670b15e439f87f0eb8939 * fix: schema change for a9bbb586fcb3a1c61b5fb69052236e78cdf7d743 * fix: schema changes for 4b738c8cd36c936a1dbe2bb900c694bf6c5520ec * fix: schema changes for 58b5781cea9acb129e6604a82ab5a5bfc0d8394d * fix: schema changes for 794bf01b21709c4be06584d576d706b3d6342057 * fix: schema changes for 80ea12c1c1963f5b39fb64841e4f3c8da3c87af2, e368feef51e0766f119c9710fb4db8f64724725c, and 52ead114bec961c62fa2eb0786540e229f6e4873 * fix: composer-default object in config? * fix: schema changes for 9acdc6808c070555352951c651921df181b10993 and 093093420027999df3c67bf0ea6024f6dbf81d2d * fix: schema changes for c0a52924f1f7ef8caeaacda67363ac269b56042c * fix: schema change for aba420a3f3b774e949c2539c73f3dc0e1ae79a38, move loggedInUser to optional props * fix: schema changes for 8c67031609da30d788561459f8bb76e9a69253de * fix: schema changes for 27e53b42f3ce48fa61d3754375715cd41ffe808d * fix: schema changes for 28359665187b0a3b9ec6226dca1234ebdbd725a5 * fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for 9f531f957e08eabb4bae844ddd67bde14d9b59f0 * fix: schema changes for c4042c70decd628e5b880bd109515b47e4e16164 and 23175110a29640e6fa052db1079bfedb34a61055 * fix: schema changes for 9b3616b10392e247974eb0c1e6225a1582bf6c69 * fix: schema changes for 5afd5de07d42fd33f039a6f85ded3b4992200e5a * fix: schema change for 1d7baf12171cffbd3af8914bef4e6297d1160d49 * fix: schema changes for 57bfb37c55a839662144e684875003ab52315ecc and be6bbabd0e2551fbe9571dcf3ee40ad721764543 * fix: schema changes for 6e86b4afa20d662af8b9f1c07518df2d8c258105 and 3efad2e13b7319eb9a1f4fda7af047be43ebc11f and 68f66223e73a72f378f193c83a9b5546bede2cda * fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes for c926358d734a2fa410de87f4e4a91744215fc14a * fix: schema changes for 388a8270c9882892bad5c8141f65da8d59eac0fd * fix: schema change for 2658bcc821c22e137a6eeb9bb74098856a642eaf * fix: no need to call account middlewares for chats routes * fix: schema changes for 71743affc3e58dc85d4ffa15ce043d4d9ddd3d67 * fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit 9bcd85c2c6848c3d325d32027261809da6e11c9e. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, :train: * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit 737973cca9e94b6cb3867492a09e1e0b1af391d5. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commit cf6cc2c454dc35c330393c62ee8ce67b42d8eefb. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <[email protected]> Co-authored-by: Opliko <[email protected]>
2 years ago
await User.setUserField(hidingUser.uid, 'email', '[email protected]');
await User.email.confirmByUid(hidingUser.uid);
});
it('should hide from guests by default', async () => {
await assertPrivacy({ v3Api: false });
});
it('should hide from unprivileged users by default', async () => {
await assertPrivacy({ v3Api: false, jar: regularUserJar });
await assertPrivacy({ v3Api: true, jar: regularUserJar });
});
it('should be visible to self by default', async () => {
await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true });
});
it('should be visible to privileged users by default', async () => {
await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true });
});
it('should hide from guests (system-wide: hide, by-user: hide)', async () => {
meta.config.hideEmail = 1;
meta.config.hideFullname = 1;
// Explicitly set user's privacy settings to hide its email and fullname
const data = { uid: hidingUser.uid, settings: { showemail: 0, showfullname: 0 } };
await apiUser.updateSettings({ uid: hidingUser.uid }, data);
await assertPrivacy({ v3Api: false });
});
it('should hide from unprivileged users (system-wide: hide, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: regularUserJar });
await assertPrivacy({ v3Api: true, jar: regularUserJar });
});
it('should be visible to self (system-wide: hide, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true });
});
it('should be visible to privileged users (system-wide: hide, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true });
});
it('should hide from guests (system-wide: show, by-user: hide)', async () => {
meta.config.hideEmail = 0;
meta.config.hideFullname = 0;
await assertPrivacy({ v3Api: false });
});
it('should hide from unprivileged users (system-wide: show, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: regularUserJar });
await assertPrivacy({ v3Api: true, jar: regularUserJar });
});
it('should be visible to self (system-wide: show, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true });
});
it('should be visible to privileged users (system-wide: show, by-user: hide)', async () => {
await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true });
});
it('should be visible to guests (system-wide: show, by-user: show)', async () => {
meta.config.hideEmail = 0;
meta.config.hideFullname = 0;
// Set user's individual privacy settings to show its email and fullname
const data = { uid: hidingUser.uid, settings: { showemail: 1, showfullname: 1 } };
await apiUser.updateSettings({ uid: hidingUser.uid }, data);
await assertPrivacy({ v3Api: false, expectVisible: true });
});
it('should be visible to unprivileged users (system-wide: show, by-user: show)', async () => {
await assertPrivacy({ v3Api: false, jar: regularUserJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: regularUserJar, expectVisible: true });
});
// System-wide "hide" prioritized over individual users' settings
it('should hide from guests (system-wide: hide, by-user: show)', async () => {
meta.config.hideEmail = 1;
meta.config.hideFullname = 1;
await assertPrivacy({ v3Api: false });
});
it('should hide from unprivileged users (system-wide: hide, by-user: show)', async () => {
await assertPrivacy({ v3Api: false, jar: regularUserJar });
await assertPrivacy({ v3Api: true, jar: regularUserJar });
});
it('should be visible to self (system-wide: hide, by-user: show)', async () => {
await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true });
});
it('should be visible to privileged users (system-wide: hide, by-user: show)', async () => {
await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true });
await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true });
await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true });
});
it('should handle array of user data (system-wide: hide)', async () => {
const userData = await User.hidePrivateData([hidingUser, regularUser], hidingUser.uid);
assert.strictEqual(userData[0].fullname, hidingUser.fullname);
assert.strictEqual(userData[0].email, hidingUser.email);
assert.strictEqual(userData[1].fullname, '');
assert.strictEqual(userData[1].email, '');
});
it('should hide fullname in topic list and topic', (done) => {
Topics.post({
uid: hidingUser.uid,
title: 'Topic hidden',
content: 'lorem ipsum',
cid: testCid,
}, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/recent`, { json: true }, (err, res, body) => {
assert.ifError(err);
assert(!body.topics[0].user.hasOwnProperty('fullname'));
request(`${nconf.get('url')}/api/topic/${body.topics[0].slug}`, { json: true }, (err, res, body) => {
assert.ifError(err);
assert(!body.posts[0].user.hasOwnProperty('fullname'));
done();
});
});
});
});
});
describe('user blocking methods', (done) => {
let blockeeUid;
before((done) => {
User.create({
username: 'blockee',
email: '[email protected]',
fullname: 'Block me',
}, (err, uid) => {
blockeeUid = uid;
done(err);
});
});
describe('.toggle()', () => {
it('should toggle block', (done) => {
socketUser.toggleBlock({ uid: 1 }, { blockerUid: 1, blockeeUid: blockeeUid }, (err) => {
7 years ago
assert.ifError(err);
User.blocks.is(blockeeUid, 1, (err, blocked) => {
7 years ago
assert.ifError(err);
assert(blocked);
done();
});
});
});
it('should toggle block', (done) => {
socketUser.toggleBlock({ uid: 1 }, { blockerUid: 1, blockeeUid: blockeeUid }, (err) => {
7 years ago
assert.ifError(err);
User.blocks.is(blockeeUid, 1, (err, blocked) => {
7 years ago
assert.ifError(err);
assert(!blocked);
done();
});
});
});
});
describe('.add()', () => {
it('should block a uid', (done) => {
User.blocks.add(blockeeUid, 1, (err) => {
assert.ifError(err);
User.blocks.list(1, (err, blocked_uids) => {
7 years ago
assert.ifError(err);
assert.strictEqual(Array.isArray(blocked_uids), true);
assert.strictEqual(blocked_uids.length, 1);
assert.strictEqual(blocked_uids.includes(blockeeUid), true);
done();
});
});
});
it('should automatically increment corresponding user field', (done) => {
db.getObjectField('user:1', 'blocksCount', (err, count) => {
assert.ifError(err);
assert.strictEqual(parseInt(count, 10), 1);
done();
});
});
8 years ago
it('should error if you try to block the same uid again', (done) => {
User.blocks.add(blockeeUid, 1, (err) => {
assert.equal(err.message, '[[error:already-blocked]]');
8 years ago
done();
});
});
});
describe('.remove()', () => {
it('should unblock a uid', (done) => {
User.blocks.remove(blockeeUid, 1, (err) => {
assert.ifError(err);
User.blocks.list(1, (err, blocked_uids) => {
7 years ago
assert.ifError(err);
assert.strictEqual(Array.isArray(blocked_uids), true);
assert.strictEqual(blocked_uids.length, 0);
done();
});
});
});
it('should automatically decrement corresponding user field', (done) => {
db.getObjectField('user:1', 'blocksCount', (err, count) => {
assert.ifError(err);
assert.strictEqual(parseInt(count, 10), 0);
done();
});
});
it('should error if you try to unblock the same uid again', (done) => {
User.blocks.remove(blockeeUid, 1, (err) => {
assert.equal(err.message, '[[error:already-unblocked]]');
done();
});
});
});
describe('.is()', () => {
before((done) => {
User.blocks.add(blockeeUid, 1, done);
});
it('should return a Boolean with blocked status for the queried uid', (done) => {
User.blocks.is(blockeeUid, 1, (err, blocked) => {
assert.ifError(err);
assert.strictEqual(blocked, true);
done();
});
});
});
describe('.list()', () => {
it('should return a list of blocked uids', (done) => {
User.blocks.list(1, (err, blocked_uids) => {
assert.ifError(err);
assert.strictEqual(Array.isArray(blocked_uids), true);
assert.strictEqual(blocked_uids.length, 1);
assert.strictEqual(blocked_uids.includes(blockeeUid), true);
done();
});
});
});
describe('.filter()', () => {
it('should remove entries by blocked uids and return filtered set', (done) => {
User.blocks.filter(1, [{
foo: 'foo',
uid: blockeeUid,
}, {
foo: 'bar',
uid: 1,
}, {
foo: 'baz',
uid: blockeeUid,
}], (err, filtered) => {
assert.ifError(err);
assert.strictEqual(Array.isArray(filtered), true);
assert.strictEqual(filtered.length, 1);
assert.equal(filtered[0].uid, 1);
done();
});
});
it('should allow property argument to be passed in to customise checked property', (done) => {
User.blocks.filter(1, 'fromuid', [{
foo: 'foo',
fromuid: blockeeUid,
}, {
foo: 'bar',
fromuid: 1,
}, {
foo: 'baz',
fromuid: blockeeUid,
}], (err, filtered) => {
assert.ifError(err);
assert.strictEqual(Array.isArray(filtered), true);
assert.strictEqual(filtered.length, 1);
assert.equal(filtered[0].fromuid, 1);
done();
});
});
it('should not process invalid sets', (done) => {
User.blocks.filter(1, [{ foo: 'foo' }, { foo: 'bar' }, { foo: 'baz' }], (err, filtered) => {
assert.ifError(err);
assert.strictEqual(Array.isArray(filtered), true);
assert.strictEqual(filtered.length, 3);
filtered.forEach((obj) => {
assert.strictEqual(obj.hasOwnProperty('foo'), true);
});
done();
});
});
it('should process plain sets that just contain uids', (done) => {
User.blocks.filter(1, [1, blockeeUid], (err, filtered) => {
assert.ifError(err);
assert.strictEqual(filtered.length, 1);
assert.strictEqual(filtered[0], 1);
done();
});
});
it('should filter uids that are blocking targetUid', (done) => {
User.blocks.filterUids(blockeeUid, [1, 2], (err, filtered) => {
assert.ifError(err);
assert.deepEqual(filtered, [2]);
done();
});
});
});
});
7 years ago
describe('status/online', () => {
it('should return offline if user is guest', (done) => {
const status = User.getStatus({ uid: 0 });
assert.strictEqual(status, 'offline');
done();
});
it('should return offline if user is guest', async () => {
assert.strictEqual(await User.isOnline(0), false);
});
it('should return true', async () => {
assert.strictEqual(await User.isOnline(testUid), true);
});
7 years ago
});
describe('isPrivilegedOrSelf', () => {
it('should return not error if self', (done) => {
User.isPrivilegedOrSelf(1, 1, (err) => {
7 years ago
assert.ifError(err);
done();
});
});
it('should not error if privileged', (done) => {
User.create({ username: 'theadmin' }, (err, uid) => {
7 years ago
assert.ifError(err);
groups.join('administrators', uid, (err) => {
7 years ago
assert.ifError(err);
User.isPrivilegedOrSelf(uid, 2, (err) => {
7 years ago
assert.ifError(err);
done();
});
});
});
});
it('should error if not privileged', (done) => {
User.isPrivilegedOrSelf(0, 1, (err) => {
7 years ago
assert.equal(err.message, '[[error:no-privileges]]');
done();
});
});
});
it('should get admins and mods', (done) => {
User.getAdminsandGlobalMods((err, data) => {
7 years ago
assert.ifError(err);
assert(Array.isArray(data));
done();
});
});
it('should allow user to login even if password is weak', async () => {
await User.create({ username: 'weakpwd', password: '123456' });
const oldValue = meta.config.minimumPasswordStrength;
meta.config.minimumPasswordStrength = 3;
await helpers.loginUser('weakpwd', '123456');
meta.config.minimumPasswordStrength = oldValue;
});
describe('User\'s', async () => {
let files;
before(async () => {
files = await file.walk(path.resolve(__dirname, './user'));
});
it('subfolder tests', () => {
files.forEach((filePath) => {
require(filePath);
});
});
});
});