Merge remote-tracking branch 'origin/develop' into activitypub

This commit is contained in:
Julian Lam
2023-12-19 14:02:33 -05:00
39 changed files with 3102 additions and 5453 deletions

View File

@@ -34,7 +34,7 @@
"@fortawesome/fontawesome-free": "6.5.1",
"@isaacs/ttlcache": "1.4.1",
"@popperjs/core": "2.11.8",
"ace-builds": "1.32.1",
"ace-builds": "1.32.2",
"archiver": "6.0.1",
"async": "3.2.5",
"autoprefixer": "10.4.16",
@@ -60,13 +60,14 @@
"cookie-parser": "1.4.6",
"cron": "3.1.6",
"cropperjs": "1.6.1",
"csrf-sync": "4.0.1",
"csrf-sync": "4.0.3",
"daemon": "1.1.0",
"diff": "5.1.0",
"esbuild": "0.19.9",
"express": "4.18.2",
"express-session": "1.17.3",
"express-useragent": "1.0.15",
"fetch-cookie": "2.1.0",
"file-loader": "6.2.0",
"fs-extra": "11.2.0",
"graceful-fs": "4.2.11",
@@ -102,7 +103,7 @@
"nodebb-plugin-ntfy": "1.7.3",
"nodebb-plugin-spam-be-gone": "2.2.0",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "1.1.103",
"nodebb-theme-harmony": "1.1.104",
"nodebb-theme-lavender": "7.1.5",
"nodebb-theme-peace": "2.1.25",
"nodebb-theme-persona": "13.2.49",
@@ -119,8 +120,6 @@
"progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0",
"ioredis": "5.3.2",
"request": "2.88.2",
"request-promise-native": "1.0.9",
"rimraf": "5.0.5",
"rss": "1.2.2",
"rtlcss": "4.1.1",
@@ -128,7 +127,7 @@
"sass": "1.69.5",
"semver": "7.5.4",
"serve-favicon": "2.5.0",
"sharp": "0.33.0",
"sharp": "0.33.1",
"sitemap": "7.1.1",
"socket.io": "4.7.2",
"socket.io-client": "4.7.2",
@@ -142,11 +141,12 @@
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
"tough-cookie": "4.1.3",
"validator": "13.11.0",
"webpack": "5.89.0",
"webpack-merge": "5.10.0",
"winston": "3.11.0",
"workerpool": "8.0.0",
"workerpool": "9.0.1",
"xml": "1.0.1",
"xregexp": "5.1.1",
"yargs": "17.7.2",
@@ -157,9 +157,9 @@
"@commitlint/cli": "18.4.3",
"@commitlint/config-angular": "18.4.3",
"coveralls": "3.1.1",
"eslint": "8.55.0",
"eslint": "8.56.0",
"eslint-config-nodebb": "0.2.1",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-import": "2.29.1",
"grunt": "1.6.1",
"grunt-contrib-watch": "1.1.0",
"husky": "8.0.3",
@@ -181,7 +181,7 @@
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"maintainers": [
{

41
public/500.html Normal file
View File

@@ -0,0 +1,41 @@
<html>
<head>
<title>Internal Server Error</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
<script type="text/javascript">
window.onload = function() {
let count = 0;
const bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';
setTimeout(function() {
bounce.className = 'animated bounce';
}, 50);
if (count > 5) {
document.getElementById('hide').className = '';
}
};
}
</script>
</head>
<body>
<div class="wrapper">
<div class="center">
<h1 id="click-me" class="animated bounce">500</h1>
<p>
<strong>Internal server error. </strong>
</p>
<p>
{message}
</p>
<p>
&nbsp;<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
</p>
</div>
</div>
</body>
</html>

View File

@@ -2,147 +2,12 @@
<head>
<title>Excessive Load Warning</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
body {
background: #00A9EA;
color: white;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}
</style>
<script type="text/javascript">
window.onload = function() {
var count = 0,
bounce = document.getElementById('click-me');
let count = 0;
const bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';

135
public/5xx.css Normal file
View File

@@ -0,0 +1,135 @@
body {
background: #00A9EA;
color: white;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}

View File

@@ -150,7 +150,7 @@ ajaxify.widgets = { render: render };
if (data) {
let status = parseInt(data.status, 10);
if ([400, 403, 404, 500, 502, 504].includes(status)) {
if ([400, 403, 404, 500, 502, 503].includes(status)) {
if (status === 502 && retry) {
retry = false;
ajaxifyTimer = undefined;

View File

@@ -62,9 +62,6 @@ define('forum/flags/detail', [
Detail.reloadHistory(payload.history);
}).catch(alerts.error);
},
onShown: (e) => {
console.log(e);
},
});
break;
}

View File

@@ -1,7 +1,7 @@
'use strict';
define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts'], function (posts, hooks, alerts) {
define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts', 'api'], function (posts, hooks, alerts, api) {
const Replies = {};
Replies.init = function (button) {
@@ -14,8 +14,8 @@ define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts'], function
if (open.is(':not(.hidden)') && loading.is('.hidden')) {
open.addClass('hidden');
loading.removeClass('hidden');
socket.emit('posts.getReplies', pid, function (err, postData) {
api.get(`/posts/${pid}/replies`, {}, function (err, { replies }) {
const postData = replies;
loading.addClass('hidden');
if (err) {
open.removeClass('hidden');

View File

@@ -1,15 +1,15 @@
'use strict';
const request = require('request');
const request = require('../request');
const meta = require('../meta');
let versionCache = '';
let versionCacheLastModified = '';
const isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
const latestReleaseUrl = 'https://api.github.com/repos/NodeBB/NodeBB/releases/latest';
function getLatestVersion(callback) {
async function getLatestVersion() {
const headers = {
Accept: 'application/vnd.github.v3+json',
'User-Agent': encodeURIComponent(`NodeBB Admin Control Panel/${meta.config.title}`),
@@ -19,31 +19,23 @@ function getLatestVersion(callback) {
headers['If-Modified-Since'] = versionCacheLastModified;
}
request('https://api.github.com/repos/NodeBB/NodeBB/releases/latest', {
json: true,
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
headers: headers,
timeout: 2000,
}, (err, res, latestRelease) => {
if (err) {
return callback(err);
}
if (res.statusCode === 304) {
return callback(null, versionCache);
}
if (res.statusCode !== 200) {
return callback(new Error(res.statusMessage));
}
if (!latestRelease || !latestRelease.tag_name) {
return callback(new Error('[[error:cant-get-latest-release]]'));
}
const tagName = latestRelease.tag_name.replace(/^v/, '');
versionCache = tagName;
versionCacheLastModified = res.headers['last-modified'];
callback(null, versionCache);
});
if (response.statusCode === 304) {
return versionCache;
}
if (response.statusCode !== 200) {
throw new Error(response.statusText);
}
if (!latestRelease || !latestRelease.tag_name) {
throw new Error('[[error:cant-get-latest-release]]');
}
const tagName = latestRelease.tag_name.replace(/^v/, '');
versionCache = tagName;
versionCacheLastModified = response.headers['last-modified'];
return versionCache;
}
exports.getLatestVersion = getLatestVersion;

View File

@@ -1,13 +1,13 @@
'use strict';
const prompt = require('prompt');
const request = require('request-promise-native');
const cproc = require('child_process');
const semver = require('semver');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const { paths, pluginNamePattern } = require('../constants');
const pkgInstall = require('./package-install');
@@ -74,11 +74,11 @@ async function getCurrentVersion() {
}
async function getSuggestedModules(nbbVersion, toCheck) {
let body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`,
json: true,
});
const request = require('../request');
let { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
if (!response.ok) {
throw new Error(`Unable to get suggested module for NodeBB(${nbbVersion}) ${toCheck.join(',')}`);
}
if (!Array.isArray(body) && toCheck.length === 1) {
body = [body];
}

View File

@@ -1,8 +1,10 @@
'use strict';
const fs = require('fs');
const nconf = require('nconf');
const winston = require('winston');
const validator = require('validator');
const path = require('path');
const translator = require('../translator');
const plugins = require('../plugins');
const middleware = require('../middleware');
@@ -54,6 +56,12 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
controllers['404'].handle404(req, res);
};
const notBuiltHandler = async () => {
let file = await fs.promises.readFile(path.join(__dirname, '../../public/500.html'), { encoding: 'utf-8' });
file = file.replace('{message}', 'Failed to lookup view! Did you run `./nodebb build`?');
return res.type('text/html').send(file);
};
const defaultHandler = async function () {
if (res.headersSent) {
return;
@@ -95,6 +103,8 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
data.cases[err.code](err, req, res, defaultHandler);
} else if (err.message.startsWith('[[error:no-') && err.message !== '[[error:no-privileges]]') {
notFoundHandler();
} else if (err.message.startsWith('Failed to lookup view')) {
notBuiltHandler();
} else {
await defaultHandler();
}

View File

@@ -6,8 +6,8 @@ const winston = require('winston');
const semver = require('semver');
const nconf = require('nconf');
const chalk = require('chalk');
const request = require('request-promise-native');
const request = require('../request');
const user = require('../user');
const posts = require('../posts');
@@ -153,10 +153,10 @@ Plugins.reloadRoutes = async function (params) {
Plugins.get = async function (id) {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins/${id}`;
const body = await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugin, ${id}]]`);
}
let normalised = await Plugins.normalise([body ? body.payload : {}]);
normalised = normalised.filter(plugin => plugin.id === id);
return normalised.length ? normalised[0] : undefined;
@@ -169,9 +169,10 @@ Plugins.list = async function (matching) {
const { version } = require(paths.currentPackage);
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins${matching !== false ? `?version=${version}` : ''}`;
try {
const body = await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugins-from-nbbpm]]`);
}
return await Plugins.normalise(body);
} catch (err) {
winston.error(`Error loading ${url}`, err);
@@ -181,9 +182,11 @@ Plugins.list = async function (matching) {
Plugins.listTrending = async () => {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/analytics/top/week`;
return await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-trending-plugins]]`);
}
return body;
};
Plugins.normalise = async function (apiReturn) {

View File

@@ -7,8 +7,8 @@ const nconf = require('nconf');
const os = require('os');
const cproc = require('child_process');
const util = require('util');
const request = require('request-promise-native');
const request = require('../request');
const db = require('../database');
const meta = require('../meta');
const pubsub = require('../pubsub');
@@ -74,12 +74,10 @@ module.exports = function (Plugins) {
};
Plugins.checkWhitelist = async function (id, version) {
const body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`,
json: true,
});
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
if (body && body.code === 'ok' && (version === 'latest' || body.payload.valid.includes(version))) {
return;
}
@@ -88,11 +86,10 @@ module.exports = function (Plugins) {
};
Plugins.suggest = async function (pluginId, nbbVersion) {
const body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`,
json: true,
});
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
return body;
};

View File

@@ -1,48 +1,45 @@
'use strict';
const nconf = require('nconf');
const request = require('request');
const winston = require('winston');
const crypto = require('crypto');
const cronJob = require('cron').CronJob;
const request = require('../request');
const pkg = require('../../package.json');
const meta = require('../meta');
module.exports = function (Plugins) {
Plugins.startJobs = function () {
new cronJob('0 0 0 * * *', (() => {
Plugins.submitUsageData();
new cronJob('0 0 0 * * *', (async () => {
await Plugins.submitUsageData();
}), null, true);
};
Plugins.submitUsageData = function (callback) {
callback = callback || function () {};
Plugins.submitUsageData = async function () {
if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || global.env !== 'production') {
return callback();
return;
}
const hash = crypto.createHash('sha256');
hash.update(nconf.get('url'));
request.post(`${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`, {
form: {
id: hash.digest('hex'),
version: pkg.version,
plugins: Plugins.loadedPlugins,
},
timeout: 5000,
}, (err, res, body) => {
if (err) {
winston.error(err.stack);
return callback(err);
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`;
try {
const { response, body } = await request.post(url, {
body: {
id: hash.digest('hex'),
version: pkg.version,
plugins: Plugins.loadedPlugins,
},
timeout: 5000,
});
if (!response.ok) {
winston.error(`[plugins.submitUsageData] received ${response.status} ${body}`);
}
if (res.statusCode !== 200) {
winston.error(`[plugins.submitUsageData] received ${res.statusCode} ${body}`);
callback(new Error(`[[error:nbbpm-${res.statusCode}]]`));
} else {
callback();
}
});
} catch (err) {
winston.error(err.stack);
}
};
};

79
src/request.js Normal file
View File

@@ -0,0 +1,79 @@
'use strict';
const { CookieJar } = require('tough-cookie');
const fetchCookie = require('fetch-cookie');
exports.jar = function () {
return new CookieJar();
};
async function call(url, method, { body, timeout, jar, ...config } = {}) {
let fetchImpl = fetch;
if (jar) {
fetchImpl = fetchCookie(fetch, jar);
}
const opts = {
...config,
method,
headers: {
'content-type': 'application/json',
...config.headers,
},
};
if (timeout > 0) {
opts.signal = AbortSignal.timeout(timeout);
}
if (body && ['POST', 'PUT', 'PATCH', 'DEL', 'DELETE'].includes(method)) {
if (opts.headers['content-type'] && opts.headers['content-type'].startsWith('application/json')) {
opts.body = JSON.stringify(body);
} else {
opts.body = body;
}
}
const response = await fetchImpl(url, opts);
const { headers } = response;
const contentType = headers.get('content-type');
const isJSON = contentType && contentType.indexOf('application/json') !== -1;
let respBody = await response.text();
if (isJSON && respBody) {
try {
respBody = JSON.parse(respBody);
} catch (err) {
throw new Error('invalid json in response body', url);
}
}
return {
body: respBody,
response: {
ok: response.ok,
status: response.status,
statusCode: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
},
};
}
/*
const { body, response } = await request.get('someurl?foo=1&baz=2')
*/
exports.get = async (url, config) => call(url, 'GET', config);
exports.head = async (url, config) => call(url, 'HEAD', config);
exports.del = async (url, config) => call(url, 'DELETE', config);
exports.delete = exports.del;
exports.options = async (url, config) => call(url, 'OPTIONS', config);
/*
const { body, response } = await request.post('someurl', { body: { foo: 1, baz: 2}})
*/
exports.post = async (url, config) => call(url, 'POST', config);
exports.put = async (url, config) => call(url, 'PUT', config);
exports.patch = async (url, config) => call(url, 'PATCH', config);

View File

@@ -13,6 +13,9 @@ module.exports = function (app, middleware, controllers) {
app.get('/css/previews/:theme', controllers.admin.themes.get);
app.get('/osd.xml', controllers.osd.handle);
app.get('/service-worker.js', (req, res) => {
res.status(200).type('application/javascript').set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`).sendFile(path.join(__dirname, '../../public/src/service-worker.js'));
res.status(200)
.type('application/javascript')
.set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`)
.sendFile(path.join(__dirname, '../../build/public/src/service-worker.js'));
});
};

View File

@@ -100,8 +100,8 @@ SocketAdmin.getSearchDict = async function (socket) {
return await getAdminSearchDict(lang);
};
SocketAdmin.deleteAllSessions = function (socket, data, callback) {
user.auth.deleteAllSessions(callback);
SocketAdmin.deleteAllSessions = async function () {
await user.auth.deleteAllSessions();
};
SocketAdmin.reloadAllSessions = function (socket, data, callback) {

View File

@@ -36,7 +36,7 @@
{{{ if files.isFile }}}
<td class="align-middle">
<i class="fa fa-fw fa-file-text-o"></i> <a href="{config.relative_path}{files.url}" target="_blank">{files.name}</a>
<i class="fa fa-fw fa-file-text-o"></i> <a class="text-break" href="{config.relative_path}{files.url}" target="_blank">{files.name}</a>
</td>
{{{ end }}}
@@ -53,7 +53,7 @@
<td class="text-end align-middle">{{{ if files.isFile }}}{files.sizeHumanReadable}{{{ else }}}[[admin/manage/uploads:filecount, {files.fileCount}]]{{{ end }}}</td>
<td role="button" class=" text-end">
<td role="button" class="align-middle text-end">
<button class="delete btn btn-sm btn-light {{{ if !files.isFile }}} hidden{{{ end }}}">
<i class="fa fa-fw fa-trash-o text-danger"></i>
</button>

View File

@@ -5,13 +5,13 @@ const assert = require('assert');
const path = require('path');
const fs = require('fs');
const SwaggerParser = require('@apidevtools/swagger-parser');
const request = require('request-promise-native');
const nconf = require('nconf');
const jwt = require('jsonwebtoken');
const util = require('util');
const wait = util.promisify(setTimeout);
const request = require('../src/request');
const db = require('./mocks/databasemock');
const helpers = require('./helpers');
const meta = require('../src/meta');
@@ -314,12 +314,7 @@ describe('API', async () => {
({ jar } = await helpers.loginUser('admin', '123456'));
// Retrieve CSRF token using cookie, to test Write API
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
});
csrfToken = config.csrf_token;
csrfToken = await helpers.getCsrfToken(jar);
setup = true;
}
@@ -409,7 +404,7 @@ describe('API', async () => {
paths.forEach((path) => {
const context = api.paths[path];
let schema;
let response;
let result;
let url;
let method;
const headers = {};
@@ -498,26 +493,16 @@ describe('API', async () => {
try {
if (type === 'json') {
response = await request(url, {
method: method,
const searchParams = new URLSearchParams(qs);
result = await request[method](`${url}?${searchParams}`, {
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
json: true,
followRedirect: false, // all responses are significant (e.g. 302)
simple: false, // don't throw on non-200 (e.g. 302)
resolveWithFullResponse: true, // send full request back (to check statusCode)
maxRedirect: 0,
redirect: 'manual',
headers: headers,
qs: qs,
body: body,
});
} else if (type === 'form') {
response = await new Promise((resolve, reject) => {
helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken, (err, res) => {
if (err) {
return reject(err);
}
resolve(res);
});
});
result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken);
}
} catch (e) {
assert(!e, `${method.toUpperCase()} ${path} errored with: ${e.message}`);
@@ -526,13 +511,18 @@ describe('API', async () => {
it('response status code should match one of the schema defined responses', () => {
// HACK: allow HTTP 418 I am a teapot, for now 👇
assert(context[method].responses.hasOwnProperty('418') || Object.keys(context[method].responses).includes(String(response.statusCode)), `${method.toUpperCase()} ${path} sent back unexpected HTTP status code: ${response.statusCode}`);
const { responses } = context[method];
assert(
responses.hasOwnProperty('418') ||
Object.keys(responses).includes(String(result.response.statusCode)),
`${method.toUpperCase()} ${path} sent back unexpected HTTP status code: ${result.response.statusCode}`
);
});
// Recursively iterate through schema properties, comparing type
it('response body should match schema definition', () => {
const http302 = context[method].responses['302'];
if (http302 && response.statusCode === 302) {
if (http302 && result.response.statusCode === 302) {
// Compare headers instead
const expectedHeaders = Object.keys(http302.headers).reduce((memo, name) => {
const value = http302.headers[name].schema.example;
@@ -541,13 +531,13 @@ describe('API', async () => {
}, {});
for (const header of Object.keys(expectedHeaders)) {
assert(response.headers[header.toLowerCase()]);
assert.strictEqual(response.headers[header.toLowerCase()], expectedHeaders[header]);
assert(result.response.headers[header.toLowerCase()]);
assert.strictEqual(result.response.headers[header.toLowerCase()], expectedHeaders[header]);
}
return;
}
if (response.statusCode === 400 && context[method].responses['400']) {
if (result.response.statusCode === 400 && context[method].responses['400']) {
// TODO: check 400 schema to response.body?
return;
}
@@ -557,12 +547,12 @@ describe('API', async () => {
return;
}
assert.strictEqual(response.statusCode, 200, `HTTP 200 expected (path: ${method} ${path}`);
assert.strictEqual(result.response.statusCode, 200, `HTTP 200 expected (path: ${method} ${path}`);
const hasJSON = http200.content && http200.content['application/json'];
if (hasJSON) {
schema = context[method].responses['200'].content['application/json'].schema;
compare(schema, response.body, method.toUpperCase(), path, 'root');
compare(schema, result.body, method.toUpperCase(), path, 'root');
}
// TODO someday: text/csv, binary file type checking?
@@ -576,12 +566,7 @@ describe('API', async () => {
mocks.delete['/users/{uid}/sessions/{uuid}'][1].example = Object.keys(sessionUUIDs).pop();
// Retrieve CSRF token using cookie, to test Write API
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
});
csrfToken = config.csrf_token;
csrfToken = await helpers.getCsrfToken(jar);
}
});
});

View File

@@ -3,12 +3,9 @@
const assert = require('assert');
const url = require('url');
const async = require('async');
const nconf = require('nconf');
const request = require('request');
const requestAsync = require('request-promise-native');
const util = require('util');
const request = require('../src/request');
const db = require('./mocks/databasemock');
const user = require('../src/user');
const utils = require('../src/utils');
@@ -45,8 +42,8 @@ describe('authentication', () => {
it('should allow login with email for uid 1', async () => {
const oldValue = meta.config.allowLoginWith;
meta.config.allowLoginWith = 'username-email';
const { res } = await helpers.loginUser('regular@nodebb.org', 'regularpwd');
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser('regular@nodebb.org', 'regularpwd');
assert.strictEqual(response.statusCode, 200);
meta.config.allowLoginWith = oldValue;
});
@@ -54,150 +51,112 @@ describe('authentication', () => {
const oldValue = meta.config.allowLoginWith;
meta.config.allowLoginWith = 'username-email';
const uid = await user.create({ username: '2nduser', password: '2ndpassword', email: '2nduser@nodebb.org' });
const { res, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword');
assert.strictEqual(res.statusCode, 403);
const { response, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword');
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body, '[[error:invalid-login-credentials]]');
meta.config.allowLoginWith = oldValue;
});
it('should fail to create user if username is too short', (done) => {
helpers.registerUser({
it('should fail to create user if username is too short', async () => {
const { response, body } = await helpers.registerUser({
username: 'a',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: '----a-----',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: ' a',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: 'a ',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
});
it('should register and login a user', (done) => {
request({
url: `${nconf.get('url')}/api/config`,
json: true,
it('should register and login a user', async () => {
const jar = request.jar();
const csrf_token = await helpers.getCsrfToken(jar);
const { body } = await request.post(`${nconf.get('url')}/register`, {
jar,
body: {
email: 'admin@nodebb.org',
username: 'admin',
password: 'adminpwd',
'password-confirm': 'adminpwd',
userLang: 'it',
gdpr_consent: true,
},
headers: {
'x-csrf-token': csrf_token,
},
});
const validationPending = await user.email.isValidationPending(body.uid, 'admin@nodebb.org');
assert.strictEqual(validationPending, true);
assert(body);
assert(body.hasOwnProperty('uid') && body.uid > 0);
const newUid = body.uid;
const { body: self } = await request.get(`${nconf.get('url')}/api/self`, {
jar,
});
assert(self);
assert.equal(self.username, 'admin');
assert.equal(self.uid, newUid);
const settings = await user.getSettings(body.uid);
assert.equal(settings.userLang, 'it');
});
it('should logout a user', async () => {
await helpers.logoutUser(jar);
const { response, body } = await request.get(`${nconf.get('url')}/api/me`, {
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
request.post(`${nconf.get('url')}/register`, {
form: {
email: 'admin@nodebb.org',
username: 'admin',
password: 'adminpwd',
'password-confirm': 'adminpwd',
userLang: 'it',
gdpr_consent: true,
},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
},
}, async (err, response, body) => {
const validationPending = await user.email.isValidationPending(body.uid, 'admin@nodebb.org');
assert.strictEqual(validationPending, true);
assert.ifError(err);
assert(body);
assert(body.hasOwnProperty('uid') && body.uid > 0);
const newUid = body.uid;
request({
url: `${nconf.get('url')}/api/self`,
json: true,
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
assert(body);
assert.equal(body.username, 'admin');
assert.equal(body.uid, newUid);
user.getSettings(body.uid, (err, settings) => {
assert.ifError(err);
assert.equal(settings.userLang, 'it');
done();
});
});
});
});
});
it('should logout a user', (done) => {
helpers.logoutUser(jar, (err) => {
assert.ifError(err);
request({
url: `${nconf.get('url')}/api/me`,
json: true,
jar: jar,
}, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 401);
assert.strictEqual(body.status.code, 'not-authorised');
done();
});
});
assert.equal(response.statusCode, 401);
assert.strictEqual(body.status.code, 'not-authorised');
});
it('should regenerate the session identifier on successful login', async () => {
const matchRegexp = /express\.sid=s%3A(.+?);/;
const { hostname, path } = url.parse(nconf.get('url'));
const sid = String(jar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
const sid = String(jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
await helpers.logoutUser(jar);
const newJar = (await helpers.loginUser('regular', 'regularpwd')).jar;
const newSid = String(newJar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
const newSid = String(newJar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
assert.notStrictEqual(newSid, sid);
});
it('should revoke all sessions', (done) => {
it('should revoke all sessions', async () => {
const socketAdmin = require('../src/socket.io/admin');
db.sortedSetCard(`uid:${regularUid}:sessions`, (err, count) => {
assert.ifError(err);
assert(count);
socketAdmin.deleteAllSessions({ uid: 1 }, {}, (err) => {
assert.ifError(err);
db.sortedSetCard(`uid:${regularUid}:sessions`, (err, count) => {
assert.ifError(err);
assert(!count);
done();
});
});
});
let sessionCount = await db.sortedSetCard(`uid:${regularUid}:sessions`);
assert(sessionCount);
await socketAdmin.deleteAllSessions({ uid: 1 }, {});
sessionCount = await db.sortedSetCard(`uid:${regularUid}:sessions`);
assert(!sessionCount);
});
describe('login', () => {
@@ -205,11 +164,12 @@ describe('authentication', () => {
let password;
let uid;
function getCookieExpiry(res) {
assert(res.headers['set-cookie']);
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), true);
function getCookieExpiry(response) {
const { headers } = response;
assert(headers['set-cookie']);
assert.strictEqual(headers['set-cookie'].includes('Expires'), true);
const values = res.headers['set-cookie'][0].split(';');
const values = headers['set-cookie'].split(';');
return values.reduce((memo, cur) => {
if (!memo) {
const [name, value] = cur.split('=');
@@ -230,9 +190,7 @@ describe('authentication', () => {
it('should login a user', async () => {
const { jar, body: loginBody } = await helpers.loginUser(username, password);
assert(loginBody);
const body = await requestAsync({
url: `${nconf.get('url')}/api/self`,
json: true,
const { body } = await request.get(`${nconf.get('url')}/api/self`, {
jar,
});
assert(body);
@@ -243,11 +201,11 @@ describe('authentication', () => {
});
it('should set a cookie that only lasts for the life of the browser session', async () => {
const { res } = await helpers.loginUser(username, password);
const { response } = await helpers.loginUser(username, password);
assert(res.headers);
assert(res.headers['set-cookie']);
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), false);
assert(response.headers);
assert(response.headers['set-cookie']);
assert.strictEqual(response.headers['set-cookie'].includes('Expires'), false);
});
it('should set a different expiry if sessionDuration is set', async () => {
@@ -255,9 +213,9 @@ describe('authentication', () => {
const days = 1;
meta.config.sessionDuration = days * 24 * 60 * 60;
const { res } = await helpers.loginUser(username, password);
const { response } = await helpers.loginUser(username, password);
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + days);
@@ -267,9 +225,9 @@ describe('authentication', () => {
});
it('should set a cookie that lasts for x days where x is loginDays setting, if asked to remember', async () => {
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
@@ -280,9 +238,9 @@ describe('authentication', () => {
const _loginDays = meta.config.loginDays;
meta.config.loginDays = 5;
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
@@ -295,9 +253,9 @@ describe('authentication', () => {
const _loginSeconds = meta.config.loginSeconds;
meta.config.loginSeconds = 60;
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCSeconds(expected.getUTCSeconds() + meta.config.loginSeconds);
@@ -308,158 +266,128 @@ describe('authentication', () => {
});
});
it('should fail to login if ip address is invalid', (done) => {
it('should fail to login if ip address is invalid', async () => {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return done(err);
}
const csrf_token = await helpers.getCsrfToken(jar);
request.post(`${nconf.get('url')}/login`, {
form: {
username: 'regular',
password: 'regularpwd',
},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-forwarded-for': '<script>alert("xss")</script>',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 500);
done();
});
const { response } = await request.post(`${nconf.get('url')}/login`, {
body: {
username: 'regular',
password: 'regularpwd',
},
jar: jar,
headers: {
'x-csrf-token': csrf_token,
'x-forwarded-for': '<script>alert("xss")</script>',
},
});
assert.equal(response.status, 500);
});
it('should fail to login if user does not exist', async () => {
const { res, body } = await helpers.loginUser('doesnotexist', 'nopassword');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('doesnotexist', 'nopassword');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-login-credentials]]');
});
it('should fail to login if username is empty', async () => {
const { res, body } = await helpers.loginUser('', 'some password');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('', 'some password');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if password is empty', async () => {
const { res, body } = await helpers.loginUser('someuser', '');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('someuser', '');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if username and password are empty', async () => {
const { res, body } = await helpers.loginUser('', '');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('', '');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if user does not have password field in db', async () => {
await user.create({ username: 'hasnopassword', email: 'no@pass.org' });
const { res, body } = await helpers.loginUser('hasnopassword', 'doesntmatter');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('hasnopassword', 'doesntmatter');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-login-credentials]]');
});
it('should fail to login if password is longer than 4096', async () => {
let longPassword;
let longPassword = '';
for (let i = 0; i < 5000; i++) {
longPassword += 'a';
}
const { res, body } = await helpers.loginUser('someuser', longPassword);
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('someuser', longPassword);
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:password-too-long]]');
});
it('should fail to login if local login is disabled', async () => {
await privileges.global.rescind(['groups:local:login'], 'registered-users');
const { res, body } = await helpers.loginUser('regular', 'regularpwd');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('regular', 'regularpwd');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:local-login-disabled]]');
await privileges.global.give(['groups:local:login'], 'registered-users');
});
it('should fail to register if registraton is disabled', (done) => {
it('should fail to register if registraton is disabled', async () => {
meta.config.registrationType = 'disabled';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
username: 'someuser',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 403);
assert.equal(body, 'Forbidden');
done();
});
assert.equal(response.statusCode, 403);
assert.equal(body, 'Forbidden');
});
it('should return error if invitation is not valid', (done) => {
it('should return error if invitation is not valid', async () => {
meta.config.registrationType = 'invite-only';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
username: 'someuser',
password: 'somepassword',
}, (err, jar, response, body) => {
meta.config.registrationType = 'normal';
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[register:invite.error-invite-only]]');
done();
});
meta.config.registrationType = 'normal';
assert.equal(response.statusCode, 400);
assert.equal(body, '[[register:invite.error-invite-only]]');
});
it('should fail to register if username is falsy or too short', (done) => {
helpers.registerUser({
username: '',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
it('should fail to register if username is falsy or too short', async () => {
const userData = [
{ username: '', password: 'somepassword' },
{ username: 'a', password: 'somepassword' },
];
for (const user of userData) {
// eslint-disable-next-line no-await-in-loop
const { response, body } = await helpers.registerUser(user);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
helpers.registerUser({
username: 'a',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
}
});
it('should fail to register if username is too long', (done) => {
helpers.registerUser({
it('should fail to register if username is too long', async () => {
const { response, body } = await helpers.registerUser({
username: 'thisisareallylongusername',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-long]]');
done();
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-long]]');
});
it('should queue user if ip is used before', (done) => {
it('should queue user if ip is used before', async () => {
meta.config.registrationApprovalType = 'admin-approval-ip';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
email: 'another@user.com',
username: 'anotheruser',
password: 'anotherpwd',
gdpr_consent: 1,
}, (err, jar, response, body) => {
meta.config.registrationApprovalType = 'normal';
assert.ifError(err);
assert.equal(response.statusCode, 200);
assert.equal(body.message, '[[register:registration-added-to-queue]]');
done();
});
meta.config.registrationApprovalType = 'normal';
assert.equal(response.statusCode, 200);
assert.equal(body.message, '[[register:registration-added-to-queue]]');
});
@@ -468,41 +396,32 @@ describe('authentication', () => {
const uid = await user.create({ username: 'ginger', password: '123456', email });
await user.setUserField(uid, 'email', email);
await user.email.confirmByUid(uid);
const { res } = await helpers.loginUser('ginger@nodebb.org', '123456');
assert.equal(res.statusCode, 200);
const { response } = await helpers.loginUser('ginger@nodebb.org', '123456');
assert.equal(response.statusCode, 200);
});
it('should fail to login if login type is username and an email is sent', async () => {
meta.config.allowLoginWith = 'username';
const { res, body } = await helpers.loginUser('ginger@nodebb.org', '123456');
const { response, body } = await helpers.loginUser('ginger@nodebb.org', '123456');
meta.config.allowLoginWith = 'username-email';
assert.equal(res.statusCode, 400);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:wrong-login-type-username]]');
});
it('should send 200 if not logged in', (done) => {
it('should send 200 if not logged in', async () => {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
const csrf_token = await helpers.getCsrfToken(jar);
request.post(`${nconf.get('url')}/logout`, {
form: {},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
},
}, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(body, 'not-logged-in');
done();
});
const { response, body } = await request.post(`${nconf.get('url')}/logout`, {
data: {},
jar: jar,
headers: {
'x-csrf-token': csrf_token,
},
});
assert.equal(response.statusCode, 200);
assert.equal(body, 'not-logged-in');
});
describe('banned user authentication', () => {
@@ -518,7 +437,7 @@ describe('authentication', () => {
it('should prevent banned user from logging in', async () => {
await user.bans.ban(bannedUser.uid, 0, 'spammer');
const { res: res1, body: body1 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
const { response: res1, body: body1 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.equal(res1.statusCode, 403);
delete body1.timestamp;
assert.deepStrictEqual(body1, {
@@ -532,7 +451,7 @@ describe('authentication', () => {
await user.bans.unban(bannedUser.uid);
const expiry = Date.now() + 10000;
await user.bans.ban(bannedUser.uid, expiry, '');
const { res: res2, body: body2 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
const { response: res2, body: body2 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.equal(res2.statusCode, 403);
assert(body2.banned_until);
assert(body2.reason, '[[user:info.banned-no-reason]]');
@@ -540,15 +459,15 @@ describe('authentication', () => {
it('should allow banned user to log in if the "banned-users" group has "local-login" privilege', async () => {
await privileges.global.give(['groups:local:login'], 'banned-users');
const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(response.statusCode, 200);
});
it('should allow banned user to log in if the user herself has "local-login" privilege', async () => {
await privileges.global.rescind(['groups:local:login'], 'banned-users');
await privileges.categories.give(['local:login'], 0, bannedUser.uid);
const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(response.statusCode, 200);
});
});
@@ -561,10 +480,10 @@ describe('authentication', () => {
let data = await helpers.loginUser('lockme', 'abcdef');
meta.config.loginAttempts = 5;
assert.equal(data.res.statusCode, 403);
assert.equal(data.response.statusCode, 403);
assert.equal(data.body, '[[error:account-locked]]');
data = await helpers.loginUser('lockme', 'abcdef');
assert.equal(data.res.statusCode, 403);
assert.equal(data.response.statusCode, 403);
assert.equal(data.body, '[[error:account-locked]]');
const locked = await db.exists(`lockout:${uid}`);
assert(locked);
@@ -594,57 +513,46 @@ describe('authentication', () => {
});
it('should fail with invalid token', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {
_uid: newUid,
},
json: true,
const { response, body } = await helpers.request('get', `/api/self?_uid${newUid}`, {
jar: jar,
headers: {
Authorization: `Bearer sdfhaskfdja-jahfdaksdf`,
},
});
assert.strictEqual(res.statusCode, 401);
assert.strictEqual(response.statusCode, 401);
assert.strictEqual(body, 'not-authorized');
});
it('should use a token tied to an uid', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
json: true,
const { response, body } = await helpers.request('get', `/api/self`, {
headers: {
Authorization: `Bearer ${userToken}`,
},
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.username, 'apiUserTarget');
});
it('should fail if _uid is not passed in with master token', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {},
json: true,
const { response, body } = await helpers.request('get', `/api/self`, {
headers: {
Authorization: `Bearer ${masterToken}`,
},
});
assert.strictEqual(res.statusCode, 500);
assert.strictEqual(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:api.master-token-no-uid]]');
});
it('should use master api token and _uid', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {
_uid: newUid,
},
json: true,
const { response, body } = await helpers.request('get', `/api/self?_uid=${newUid}`, {
headers: {
Authorization: `Bearer ${masterToken}`,
},
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.username, 'apiUserTarget');
});
});

View File

@@ -2,8 +2,8 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request');
const request = require('../src/request');
const db = require('./mocks/databasemock');
const Categories = require('../src/categories');
const Topics = require('../src/topics');
@@ -76,14 +76,11 @@ describe('Categories', () => {
});
});
it('should load a category route', (done) => {
request(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`, { json: true }, (err, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 200);
assert.equal(body.name, 'Test Category &amp; NodeBB');
assert(body);
done();
});
it('should load a category route', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`);
assert.equal(response.statusCode, 200);
assert.equal(body.name, 'Test Category &amp; NodeBB');
assert(body);
});
describe('Categories.getRecentTopicReplies', () => {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,12 @@
'use strict';
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
const request = require('../src/request');
const topics = require('../src/topics');
const categories = require('../src/categories');
const groups = require('../src/groups');
const user = require('../src/user');
const meta = require('../src/meta');
const privileges = require('../src/privileges');
@@ -16,38 +14,27 @@ const helpers = require('./helpers');
describe('feeds', () => {
let tid;
let pid;
let fooUid;
let cid;
before((done) => {
before(async () => {
meta.config['feeds:disableRSS'] = 1;
async.series({
category: function (next) {
categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
user: function (next) {
user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' }, next);
},
}, (err, results) => {
if (err) {
return done(err);
}
cid = results.category.cid;
fooUid = results.user;
topics.post({ uid: results.user, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, (err, result) => {
tid = result.topicData.tid;
pid = result.postData.pid;
done(err);
});
const category = await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
});
cid = category.cid;
fooUid = await user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' });
const result = await topics.post({
cid: cid,
uid: fooUid,
title: 'test topic title',
content: 'test topic content',
});
tid = result.topicData.tid;
});
it('should 404', (done) => {
it('should 404', async () => {
const feedUrls = [
`${nconf.get('url')}/topic/${tid}.rss`,
`${nconf.get('url')}/category/${cid}.rss`,
@@ -61,67 +48,45 @@ describe('feeds', () => {
`${nconf.get('url')}/user/foo/topics.rss`,
`${nconf.get('url')}/tags/nodebb.rss`,
];
async.eachSeries(feedUrls, (url, next) => {
request(url, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
next();
});
}, (err) => {
assert.ifError(err);
meta.config['feeds:disableRSS'] = 0;
done();
});
for (const url of feedUrls) {
// eslint-disable-next-line no-await-in-loop
const { response } = await request.get(url);
assert.equal(response.statusCode, 404);
}
meta.config['feeds:disableRSS'] = 0;
});
it('should 404 if topic does not exist', (done) => {
request(`${nconf.get('url')}/topic/${1000}.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if topic does not exist', async () => {
const { response } = await request.get(`${nconf.get('url')}/topic/${1000}.rss`);
assert.equal(response.statusCode, 404);
});
it('should 404 if category id is not a number', (done) => {
request(`${nconf.get('url')}/category/invalid.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if category id is not a number', async () => {
const { response } = await request.get(`${nconf.get('url')}/category/invalid.rss`);
assert.equal(response.statusCode, 404);
});
it('should redirect if we do not have read privilege', (done) => {
privileges.categories.rescind(['groups:topics:read'], cid, 'guests', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/topic/${tid}.rss`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
privileges.categories.give(['groups:topics:read'], cid, 'guests', done);
});
});
it('should redirect if we do not have read privilege', async () => {
await privileges.categories.rescind(['groups:topics:read'], cid, 'guests');
const { response, body } = await request.get(`${nconf.get('url')}/topic/${tid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
await privileges.categories.give(['groups:topics:read'], cid, 'guests');
});
it('should 404 if user is not found', (done) => {
request(`${nconf.get('url')}/user/doesnotexist/topics.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if user is not found', async () => {
const { response } = await request.get(`${nconf.get('url')}/user/doesnotexist/topics.rss`);
assert.equal(response.statusCode, 404);
});
it('should redirect if we do not have read privilege', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'guests', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/category/${cid}.rss`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
privileges.categories.give(['groups:read'], cid, 'guests', done);
});
});
it('should redirect if we do not have read privilege', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'guests');
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
await privileges.categories.give(['groups:read'], cid, 'guests');
});
describe('private feeds and tokens', () => {
@@ -131,69 +96,45 @@ describe('feeds', () => {
({ jar } = await helpers.loginUser('foo', 'barbar'));
});
it('should load feed if its not private', (done) => {
request(`${nconf.get('url')}/category/${cid}.rss`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
it('should load feed if its not private', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
});
it('should not allow access if uid or token is missing', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'guests', (err) => {
assert.ifError(err);
async.parallel({
test1: function (next) {
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}`, { }, next);
},
test2: function (next) {
request(`${nconf.get('url')}/category/${cid}.rss?token=sometoken`, { }, next);
},
}, (err, results) => {
assert.ifError(err);
assert.equal(results.test1[0].statusCode, 200);
assert.equal(results.test2[0].statusCode, 200);
assert(results.test1[0].body.includes('Login to your account'));
assert(results.test2[0].body.includes('Login to your account'));
done();
});
});
it('should not allow access if uid or token is missing', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'guests');
const [test1, test2] = await Promise.all([
request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}`, { }),
request.get(`${nconf.get('url')}/category/${cid}.rss?token=sometoken`, { }),
]);
assert.equal(test1.response.statusCode, 200);
assert.equal(test2.response.statusCode, 200);
assert(test1.body.includes('Login to your account'));
assert(test2.body.includes('Login to your account'));
});
it('should not allow access if token is wrong', (done) => {
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=sometoken`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.includes('Login to your account'));
done();
});
it('should not allow access if token is wrong', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=sometoken`);
assert.equal(response.statusCode, 200);
assert(body.includes('Login to your account'));
});
it('should allow access if token is correct', (done) => {
request(`${nconf.get('url')}/api/category/${cid}`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
rssToken = body.rssFeedUrl.split('token')[1].slice(1);
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.startsWith('<?xml version="1.0"'));
done();
});
});
it('should allow access if token is correct', async () => {
const { body: body1 } = await request.get(`${nconf.get('url')}/api/category/${cid}`, { jar });
rssToken = body1.rssFeedUrl.split('token')[1].slice(1);
const { response, body: body2 } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`);
assert.equal(response.statusCode, 200);
assert(body2.startsWith('<?xml version="1.0"'));
});
it('should not allow access if token is correct but has no privilege', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'registered-users', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.includes('Login to your account'));
done();
});
});
it('should not allow access if token is correct but has no privilege', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'registered-users');
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`);
assert.equal(response.statusCode, 200);
assert(body.includes('Login to your account'));
});
});
});

View File

@@ -2,15 +2,13 @@
const assert = require('assert');
const nconf = require('nconf');
const async = require('async');
const request = require('request-promise-native');
const util = require('util');
const sleep = util.promisify(setTimeout);
const db = require('./mocks/databasemock');
const helpers = require('./helpers');
const request = require('../src/request');
const Flags = require('../src/flags');
const Categories = require('../src/categories');
const Topics = require('../src/topics');
@@ -243,13 +241,11 @@ describe('Flags', () => {
it('should show user history for admins', async () => {
await Groups.join('administrators', moderatorUid);
const flagData = await request({
uri: `${nconf.get('url')}/api/flags/1`,
const { body: flagData } = await request.get(`${nconf.get('url')}/api/flags/1`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
assert(flagData.history);
@@ -260,13 +256,11 @@ describe('Flags', () => {
it('should show user history for global moderators', async () => {
await Groups.join('Global Moderators', moderatorUid);
const flagData = await request({
uri: `${nconf.get('url')}/api/flags/1`,
const { body: flagData } = await request.get(`${nconf.get('url')}/api/flags/1`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
assert(flagData.history);
@@ -895,9 +889,7 @@ describe('Flags', () => {
describe('.create()', () => {
it('should create a flag with no errors', async () => {
await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -907,7 +899,6 @@ describe('Flags', () => {
id: pid,
reason: 'foobar',
},
json: true,
});
const exists = await Flags.exists('post', pid, 2);
@@ -921,9 +912,7 @@ describe('Flags', () => {
content: 'This is flaggable content',
});
const { response } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
const { body } = await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -933,10 +922,9 @@ describe('Flags', () => {
id: postData.pid,
reason: '"<script>alert(\'ok\');</script>',
},
json: true,
});
const flagData = await Flags.get(response.flagId);
const flagData = await Flags.get(body.response.flagId);
assert.strictEqual(flagData.reports[0].value, '&quot;&lt;script&gt;alert(&#x27;ok&#x27;);&lt;&#x2F;script&gt;');
});
@@ -953,15 +941,9 @@ describe('Flags', () => {
});
const login = await helpers.loginUser('unprivileged', 'abcdef');
const jar3 = login.jar;
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar3,
});
const csrfToken = config.csrf_token;
const { statusCode, body } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
const csrfToken = await helpers.getCsrfToken(jar3);
const { response, body } = await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar: jar3,
headers: {
'x-csrf-token': csrfToken,
@@ -971,11 +953,8 @@ describe('Flags', () => {
id: result.postData.pid,
reason: 'foobar',
},
json: true,
simple: false,
resolveWithFullResponse: true,
});
assert.strictEqual(statusCode, 403);
assert.strictEqual(response.statusCode, 403);
// Handle dev mode test
delete body.stack;
@@ -992,9 +971,7 @@ describe('Flags', () => {
describe('.update()', () => {
it('should update a flag\'s properties', async () => {
const { response } = await request({
method: 'put',
uri: `${nconf.get('url')}/api/v3/flags/4`,
const { body } = await request.put(`${nconf.get('url')}/api/v3/flags/4`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -1002,10 +979,9 @@ describe('Flags', () => {
body: {
state: 'wip',
},
json: true,
});
const { history } = response;
const { history } = body.response;
assert(Array.isArray(history));
assert(history[0].fields.hasOwnProperty('state'));
assert.strictEqual('[[flags:state-wip]]', history[0].fields.state);
@@ -1014,14 +990,11 @@ describe('Flags', () => {
describe('.rescind()', () => {
it('should remove a flag\'s report', async () => {
const response = await request({
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/4/report`,
const { response } = await request.del(`${nconf.get('url')}/api/v3/flags/4/report`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
resolveWithFullResponse: true,
});
assert.strictEqual(response.statusCode, 200);
@@ -1030,9 +1003,7 @@ describe('Flags', () => {
describe('.appendNote()', () => {
it('should append a note to the flag', async () => {
const { response } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags/4/notes`,
const { body } = await request.post(`${nconf.get('url')}/api/v3/flags/4/notes`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -1041,9 +1012,8 @@ describe('Flags', () => {
note: 'lorem ipsum dolor sit amet',
datetime: 1626446956652,
},
json: true,
});
const { response } = body;
assert(response.hasOwnProperty('notes'));
assert(Array.isArray(response.notes));
assert.strictEqual('lorem ipsum dolor sit amet', response.notes[0].content);
@@ -1058,16 +1028,13 @@ describe('Flags', () => {
describe('.deleteNote()', () => {
it('should delete a note from a flag', async () => {
const { response } = await request({
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/4/notes/1626446956652`,
const { body } = await request.del(`${nconf.get('url')}/api/v3/flags/4/notes/1626446956652`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
const { response } = body;
assert(Array.isArray(response.history));
assert(Array.isArray(response.notes));
assert.strictEqual(response.notes.length, 0);
@@ -1088,7 +1055,7 @@ describe('Flags', () => {
before(async () => {
uid = await User.create({ username: 'flags-access-control', password: 'abcdef' });
({ jar, csrf_token } = await helpers.loginUser('flags-access-control', 'abcdef'));
console.log('cs', csrfToken);
flaggerUid = await User.create({ username: 'flags-access-control-flagger', password: 'abcdef' });
});
@@ -1106,68 +1073,44 @@ describe('Flags', () => {
});
({ flagId } = await Flags.create('post', postData.pid, flaggerUid, 'spam'));
const commonOpts = {
jar,
headers: {
'x-csrf-token': csrf_token,
},
};
requests = new Set([
{
...commonOpts,
method: 'get',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'put',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
body: {
state: 'wip',
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}/notes`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
body: {
note: 'test note',
datetime: noteTime,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}/notes/${noteTime}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
]);
});
@@ -1179,7 +1122,8 @@ describe('Flags', () => {
delete opts.headers;
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1187,7 +1131,8 @@ describe('Flags', () => {
it('should not allow access to privileged flag endpoints to regular users', async () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1197,7 +1142,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1207,7 +1153,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1217,7 +1164,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1231,7 +1179,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});

View File

@@ -1,26 +1,22 @@
'use strict';
const request = require('request');
const requestAsync = require('request-promise-native');
const nconf = require('nconf');
const fs = require('fs');
const path = require('path');
const winston = require('winston');
const utils = require('../../src/utils');
const request = require('../../src/request');
const helpers = module.exports;
helpers.getCsrfToken = async (jar) => {
const { csrf_token: token } = await requestAsync({
url: `${nconf.get('url')}/api/config`,
json: true,
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
jar,
});
return token;
return body.csrf_token;
};
helpers.request = async function (method, uri, options) {
helpers.request = async function (method, uri, options = {}) {
const ignoreMethods = ['GET', 'HEAD', 'OPTIONS'];
const lowercaseMethod = String(method).toLowerCase();
let csrf_token;
@@ -28,79 +24,44 @@ helpers.request = async function (method, uri, options) {
csrf_token = await helpers.getCsrfToken(options.jar);
}
return new Promise((resolve, reject) => {
options.headers = options.headers || {};
if (csrf_token) {
options.headers['x-csrf-token'] = csrf_token;
}
request[lowercaseMethod](`${nconf.get('url')}${uri}`, options, (err, res, body) => {
if (err) reject(err);
else resolve({ res, body });
});
});
options.headers = options.headers || {};
if (csrf_token) {
options.headers['x-csrf-token'] = csrf_token;
}
return await request[lowercaseMethod](`${nconf.get('url')}${uri}`, options);
};
helpers.loginUser = async (username, password, payload = {}) => {
const jar = request.jar();
const form = { username, password, ...payload };
const data = { username, password, ...payload };
const { statusCode, body: configBody } = await requestAsync({
url: `${nconf.get('url')}/api/config`,
json: true,
const csrf_token = await helpers.getCsrfToken(jar);
const { response, body } = await request.post(`${nconf.get('url')}/login`, {
body: data,
jar: jar,
followRedirect: false,
simple: false,
resolveWithFullResponse: true,
});
if (statusCode !== 200) {
throw new Error('[[error:invalid-response]]');
}
const { csrf_token } = configBody;
const res = await requestAsync.post(`${nconf.get('url')}/login`, {
form,
json: true,
jar: jar,
followRedirect: false,
simple: false,
resolveWithFullResponse: true,
headers: {
'x-csrf-token': csrf_token,
},
});
return { jar, res, body: res.body, csrf_token: csrf_token };
return { jar, response, body, csrf_token };
};
helpers.logoutUser = function (jar, callback) {
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return callback(err, response, body);
}
request.post(`${nconf.get('url')}/logout`, {
form: {},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
},
}, (err, response, body) => {
callback(err, response, body);
});
helpers.logoutUser = async function (jar) {
const csrf_token = await helpers.getCsrfToken(jar);
const { response, body } = await request.post(`${nconf.get('url')}/logout`, {
body: {},
jar,
headers: {
'x-csrf-token': csrf_token,
},
});
return { response, body };
};
helpers.connectSocketIO = function (res, csrf_token, callback) {
helpers.connectSocketIO = function (res, csrf_token) {
const io = require('socket.io-client');
let cookies = res.headers['set-cookie'];
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
const cookie = cookies[0];
const cookie = res.headers['set-cookie'];
const socket = io(nconf.get('base_url'), {
path: `${nconf.get('relative_path')}/socket.io`,
extraHeaders: {
@@ -111,73 +72,71 @@ helpers.connectSocketIO = function (res, csrf_token, callback) {
_csrf: csrf_token,
},
});
let error;
socket.on('connect', () => {
if (error) {
return;
}
callback(null, socket);
});
return new Promise((resolve, reject) => {
let error;
socket.on('connect', () => {
if (error) {
return;
}
resolve(socket);
});
socket.on('error', (err) => {
error = err;
console.log('socket.io error', err.stack);
callback(err);
socket.on('error', (err) => {
error = err;
console.log('socket.io error', err.stack);
reject(err);
});
});
};
helpers.uploadFile = function (uploadEndPoint, filePath, body, jar, csrf_token, callback) {
let formData = {
files: [
fs.createReadStream(filePath),
],
helpers.uploadFile = async function (uploadEndPoint, filePath, data, jar, csrf_token) {
const mime = require('mime');
const form = new FormData();
const file = await fs.promises.readFile(filePath);
const blob = new Blob([file], { type: mime.getType(filePath) });
form.append('files', blob, path.basename(filePath));
if (data && data.params) {
form.append('params', data.params);
}
const response = await fetch(uploadEndPoint, {
method: 'post',
body: form,
headers: {
'x-csrf-token': csrf_token,
cookie: await jar.getCookieString(uploadEndPoint),
},
});
const body = await response.json();
return {
body,
response: {
status: response.status,
statusCode: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
},
};
formData = utils.merge(formData, body);
request.post({
url: uploadEndPoint,
formData: formData,
json: true,
jar: jar,
};
helpers.registerUser = async function (data) {
const jar = request.jar();
const csrf_token = await helpers.getCsrfToken(jar);
if (!data.hasOwnProperty('password-confirm')) {
data['password-confirm'] = data.password;
}
const { response, body } = await request.post(`${nconf.get('url')}/register`, {
body: data,
jar,
headers: {
'x-csrf-token': csrf_token,
},
}, (err, res, body) => {
if (err) {
return callback(err);
}
if (res.statusCode !== 200) {
winston.error(JSON.stringify(body));
}
callback(null, res, body);
});
};
helpers.registerUser = function (data, callback) {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return callback(err);
}
if (!data.hasOwnProperty('password-confirm')) {
data['password-confirm'] = data.password;
}
request.post(`${nconf.get('url')}/register`, {
form: data,
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
},
}, (err, response, body) => {
callback(err, jar, response, body);
});
});
return { jar, response, body };
};
// http://stackoverflow.com/a/14387791/583363
@@ -205,37 +164,26 @@ helpers.copyFile = function (source, target, callback) {
}
};
helpers.invite = async function (body, uid, jar, csrf_token) {
console.log('making call');
const res = await requestAsync.post(`${nconf.get('url')}/api/v3/users/${uid}/invites`, {
helpers.invite = async function (data, uid, jar, csrf_token) {
return await request.post(`${nconf.get('url')}/api/v3/users/${uid}/invites`, {
jar: jar,
// using "form" since client "api" module make requests with "application/x-www-form-urlencoded" content-type
form: body,
body: data,
headers: {
'x-csrf-token': csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
console.log(res.statusCode, res.body);
res.body = JSON.parse(res.body);
return { res, body };
};
helpers.createFolder = function (path, folderName, jar, csrf_token) {
return requestAsync.put(`${nconf.get('url')}/api/v3/files/folder`, {
helpers.createFolder = async function (path, folderName, jar, csrf_token) {
return await request.put(`${nconf.get('url')}/api/v3/files/folder`, {
jar,
body: {
path,
folderName,
},
json: true,
headers: {
'x-csrf-token': csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
};

View File

@@ -2,45 +2,34 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request');
const db = require('./mocks/databasemock');
const meta = require('../src/meta');
const request = require('../src/request');
describe('Language detection', () => {
it('should detect the language for a guest', (done) => {
meta.configs.set('autoDetectLang', 1, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
json: true,
}, (err, res, body) => {
assert.ifError(err);
assert.ok(body);
it('should detect the language for a guest', async () => {
await meta.configs.set('autoDetectLang', 1);
assert.strictEqual(body.userLang, 'de');
done();
});
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
});
assert.ok(body);
assert.strictEqual(body.userLang, 'de');
});
it('should do nothing when disabled', (done) => {
meta.configs.set('autoDetectLang', 0, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
json: true,
}, (err, res, body) => {
assert.ifError(err);
assert.ok(body);
it('should do nothing when disabled', async () => {
await meta.configs.set('autoDetectLang', 0);
assert.strictEqual(body.userLang, 'en-GB');
done();
});
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
});
assert.ok(body);
assert.strictEqual(body.userLang, 'en-GB');
});
});

View File

@@ -1,7 +1,7 @@
'use strict';
const assert = require('assert');
const request = require('request-promise-native');
const nconf = require('nconf');
const util = require('util');
@@ -14,7 +14,7 @@ const Groups = require('../src/groups');
const Messaging = require('../src/messaging');
const api = require('../src/api');
const helpers = require('./helpers');
const socketModules = require('../src/socket.io/modules');
const request = require('../src/request');
const utils = require('../src/utils');
const translator = require('../src/translator');
@@ -33,12 +33,8 @@ describe('Messaging Library', () => {
const callv3API = async (method, path, body, user) => {
const options = {
method,
body,
json: true,
jar: mocks.users[user].jar,
resolveWithFullResponse: true,
simple: false,
};
if (method !== 'get') {
@@ -47,7 +43,7 @@ describe('Messaging Library', () => {
};
}
return request(`${nconf.get('url')}/api/v3${path}`, options);
return request[method](`${nconf.get('url')}/api/v3${path}`, options);
};
before(async () => {
@@ -162,11 +158,11 @@ describe('Messaging Library', () => {
uids: [mocks.users.baz.uid],
}, 'foo');
const { statusCode, body } = await callv3API('post', `/chats`, {
const { response, body } = await callv3API('post', `/chats`, {
uids: [mocks.users.baz.uid],
}, 'foo');
assert.equal(statusCode, 400);
assert.equal(response.statusCode, 400);
assert.equal(body.status.code, 'bad-request');
assert.equal(body.status.message, await translator.translate('[[error:too-many-messages]]'));
meta.config.chatMessageDelay = oldValue;
@@ -190,20 +186,20 @@ describe('Messaging Library', () => {
assert.strictEqual(messages[0].system, 1);
assert.strictEqual(messages[0].content, 'user-join');
const { statusCode, body: body2 } = await callv3API('put', `/chats/${roomId}/messages/${messages[0].messageId}`, {
const { response, body: body2 } = await callv3API('put', `/chats/${roomId}/messages/${messages[0].messageId}`, {
message: 'test',
}, 'foo');
assert.strictEqual(statusCode, 400);
assert.strictEqual(response.statusCode, 400);
assert.equal(body2.status.message, await translator.translate('[[error:cant-edit-chat-message]]'));
});
it('should fail to add user to room with invalid data', async () => {
let { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('post', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
@@ -220,38 +216,38 @@ describe('Messaging Library', () => {
});
it('should throw error if user is not in room', async () => {
const { statusCode, body } = await callv3API('get', `/chats/${roomId}/users`, {}, 'bar');
assert.strictEqual(statusCode, 403);
const { response, body } = await callv3API('get', `/chats/${roomId}/users`, {}, 'bar');
assert.strictEqual(response.statusCode, 403);
assert.equal(body.status.message, await translator.translate('[[error:no-privileges]]'));
});
it('should fail to add users to room if max is reached', async () => {
meta.config.maximumUsersInChatRoom = 2;
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.bar.uid] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.bar.uid] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.equal(body.status.message, await translator.translate('[[error:cant-add-more-users-to-chat-room]]'));
meta.config.maximumUsersInChatRoom = 0;
});
it('should fail to add users to room if user does not exist', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [98237498234] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [98237498234] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should fail to add self to room', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.foo.uid] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.foo.uid] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:cant-chat-with-yourself]]'));
});
it('should fail to leave room with invalid data', async () => {
let { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [98237423] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [98237423] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
@@ -303,9 +299,7 @@ describe('Messaging Library', () => {
const { jar: senderJar, csrf_token: senderCsrf } = await helpers.loginUser('deleted_chat_user', 'barbar');
const receiver = await User.create({ username: 'receiver' });
const { response } = await request(`${nconf.get('url')}/api/v3/chats`, {
method: 'post',
json: true,
const { body } = await request.post(`${nconf.get('url')}/api/v3/chats`, {
jar: senderJar,
body: {
uids: [receiver],
@@ -315,31 +309,31 @@ describe('Messaging Library', () => {
},
});
await User.deleteAccount(sender);
assert(await Messaging.isRoomOwner(receiver, response.roomId));
assert(await Messaging.isRoomOwner(receiver, body.response.roomId));
});
it('should fail to remove user from room', async () => {
let { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should fail to remove user from room if user does not exist', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [99] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [99] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should remove user from room', async () => {
const { statusCode, body } = await callv3API('post', `/chats`, {
const { response, body } = await callv3API('post', `/chats`, {
uids: [mocks.users.herp.uid],
}, 'foo');
const { roomId } = body.response;
assert.strictEqual(statusCode, 200);
assert.strictEqual(response.statusCode, 200);
let isInRoom = await Messaging.isUserInRoom(mocks.users.herp.uid, roomId);
assert(isInRoom);
@@ -488,8 +482,8 @@ describe('Messaging Library', () => {
});
it('should rename room', async () => {
const { statusCode } = await callv3API('put', `/chats/${roomId}`, { name: 'new room name' }, 'foo');
assert.strictEqual(statusCode, 200);
const { response } = await callv3API('put', `/chats/${roomId}`, { name: 'new room name' }, 'foo');
assert.strictEqual(response.statusCode, 200);
});
it('should send a room-rename system message when a room is renamed', async () => {
@@ -638,46 +632,46 @@ describe('Messaging Library', () => {
});
it('should fail to edit message with invalid data', async () => {
let { statusCode, body } = await callv3API('put', `/chats/1/messages/10000`, { message: 'foo' }, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('put', `/chats/1/messages/10000`, { message: 'foo' }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-mid]]'));
({ statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-chat-message]]'));
});
it('should fail to edit message if new content is empty string', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: ' ' }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: ' ' }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-chat-message]]'));
});
it('should fail to edit message if not own message', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:cant-edit-chat-message]]'));
});
it('should fail to edit message if message not in room', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/1014`, { message: 'message edited' }, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/1014`, { message: 'message edited' }, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-mid]]'));
});
it('should edit message', async () => {
let { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'foo');
assert.strictEqual(statusCode, 200);
let { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'foo');
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.response.content, 'message edited');
({ statusCode, body } = await callv3API('get', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(statusCode, 200);
({ response, body } = await callv3API('get', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.response.content, 'message edited');
});
it('should fail to delete message if not owner', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'You are not allowed to delete this message');
});
@@ -716,8 +710,8 @@ describe('Messaging Library', () => {
});
it('should error out if a message is deleted again', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'This chat message has already been deleted.');
});
@@ -728,8 +722,8 @@ describe('Messaging Library', () => {
});
it('should error out if a message is restored again', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'This chat message has already been restored.');
});
@@ -743,8 +737,8 @@ describe('Messaging Library', () => {
});
it('should error out for regular users', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid2}`, {}, 'baz');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid2}`, {}, 'baz');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'chat-message-editing-disabled');
});
@@ -767,33 +761,21 @@ describe('Messaging Library', () => {
describe('controller', () => {
it('should 404 if chat is disabled', async () => {
meta.config.disableChat = 1;
const response = await request(`${nconf.get('url')}/user/baz/chats`, {
resolveWithFullResponse: true,
simple: false,
});
const { response } = await request.get(`${nconf.get('url')}/user/baz/chats`);
assert.equal(response.statusCode, 404);
});
it('should 401 for guest with not-authorised status code', async () => {
meta.config.disableChat = 0;
const response = await request(`${nconf.get('url')}/api/user/baz/chats`, {
resolveWithFullResponse: true,
simple: false,
json: true,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/baz/chats`);
assert.equal(response.statusCode, 401);
assert.equal(body.status.code, 'not-authorised');
});
it('should 404 for non-existent user', async () => {
const response = await request(`${nconf.get('url')}/user/doesntexist/chats`, {
resolveWithFullResponse: true,
simple: false,
});
const { response } = await request.get(`${nconf.get('url')}/user/doesntexist/chats`);
assert.equal(response.statusCode, 404);
});
});
@@ -805,13 +787,7 @@ describe('Messaging Library', () => {
});
it('should return chats page data', async () => {
const response = await request(`${nconf.get('url')}/api/user/herp/chats`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats`, { jar });
assert.equal(response.statusCode, 200);
assert(Array.isArray(body.rooms));
@@ -820,13 +796,7 @@ describe('Messaging Library', () => {
});
it('should return room data', async () => {
const response = await request(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, { jar });
assert.equal(response.statusCode, 200);
assert.equal(body.roomId, roomId);
@@ -834,27 +804,16 @@ describe('Messaging Library', () => {
});
it('should redirect to chats page', async () => {
const res = await request(`${nconf.get('url')}/api/chats`, {
resolveWithFullResponse: true,
simple: false,
jar,
json: true,
});
const { body } = res;
const { response, body } = await request.get(`${nconf.get('url')}/api/chats`, { jar });
assert.equal(res.statusCode, 200);
assert.equal(res.headers['x-redirect'], '/user/herp/chats');
assert.equal(response.statusCode, 200);
assert.equal(response.headers['x-redirect'], '/user/herp/chats');
assert.equal(body, '/user/herp/chats');
});
it('should return 404 if user is not in room', async () => {
const data = await helpers.loginUser('baz', 'quuxquux');
const response = await request(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar: data.jar,
});
const { response } = await request.get(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, { jar: data.jar });
assert.equal(response.statusCode, 404);
});

View File

@@ -2,13 +2,14 @@
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
const meta = require('../src/meta');
const User = require('../src/user');
const Groups = require('../src/groups');
const request = require('../src/request');
describe('meta', () => {
let fooUid;
@@ -489,117 +490,86 @@ describe('meta', () => {
});
describe('Access-Control-Allow-Origin', () => {
it('Access-Control-Allow-Origin header should be empty', (done) => {
it('Access-Control-Allow-Origin header should be empty', async () => {
const jar = request.jar();
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], undefined);
done();
});
assert.equal(response.headers['access-control-allow-origin'], undefined);
});
it('should set proper Access-Control-Allow-Origin header', (done) => {
it('should set proper Access-Control-Allow-Origin header', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin'];
meta.config['access-control-allow-origin'] = 'test.com, mydomain.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'mydomain.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin'] = oldValue;
done(err);
});
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin'] = oldValue;
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', (done) => {
it('Access-Control-Allow-Origin header should be empty if origin does not match', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin'];
meta.config['access-control-allow-origin'] = 'test.com, mydomain.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
data: {},
jar: jar,
headers: {
origin: 'notallowed.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin'] = oldValue;
done(err);
});
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin'] = oldValue;
});
it('should set proper Access-Control-Allow-Origin header', (done) => {
it('should set proper Access-Control-Allow-Origin header', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'match.this.anything123.domain.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], 'match.this.anything123.domain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
assert.equal(response.headers['access-control-allow-origin'], 'match.this.anything123.domain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', (done) => {
it('Access-Control-Allow-Origin header should be empty if origin does not match', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'notallowed.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin-regex'] = oldValue;
});
it('should not error with invalid regexp', (done) => {
it('should not error with invalid regexp', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = '[match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'mydomain.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
});
});

View File

@@ -2,13 +2,13 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request-promise-native');
const db = require('./mocks/databasemock');
const user = require('../src/user');
const groups = require('../src/groups');
const utils = require('../src/utils');
const request = require('../src/request');
const helpers = require('./helpers');
describe('Middlewares', () => {
@@ -116,81 +116,61 @@ describe('Middlewares', () => {
});
it('should be absent on non-existent routes, for guests', async () => {
const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`);
assert.strictEqual(res.statusCode, 404);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 404);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be set to "private" on non-existent routes, for logged in users', async () => {
const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, {
simple: false,
resolveWithFullResponse: true,
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`, {
jar,
headers: {
accept: 'text/html',
},
});
assert.strictEqual(res.statusCode, 404);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 404);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be absent on regular routes, for guests', async () => {
const res = await request(nconf.get('url'), {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(nconf.get('url'));
assert.strictEqual(res.statusCode, 200);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 200);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be absent on api routes, for guests', async () => {
const res = await request(`${nconf.get('url')}/api`, {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(`${nconf.get('url')}/api`);
assert.strictEqual(res.statusCode, 200);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 200);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be set to "private" on regular routes, for logged-in users', async () => {
const res = await request(nconf.get('url'), {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(nconf.get('url'), { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be set to "private" on api routes, for logged-in users', async () => {
const res = await request(`${nconf.get('url')}/api`, {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(`${nconf.get('url')}/api`, { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be set to "private" on apiv3 routes, for logged-in users', async () => {
const res = await request(`${nconf.get('url')}/api/v3/users/${uid}`, {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(`${nconf.get('url')}/api/v3/users/${uid}`, { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
});
});

View File

@@ -3,11 +3,12 @@
const assert = require('assert');
const path = require('path');
const nconf = require('nconf');
const request = require('request');
const fs = require('fs');
const db = require('./mocks/databasemock');
const plugins = require('../src/plugins');
const request = require('../src/request');
describe('Plugins', () => {
it('should load plugin data', (done) => {
@@ -290,33 +291,24 @@ describe('Plugins', () => {
});
describe('static assets', () => {
it('should 404 if resource does not exist', (done) => {
request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
assert(body);
done();
});
it('should 404 if resource does not exist', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`);
assert.equal(response.statusCode, 404);
assert(body);
});
it('should 404 if resource does not exist', (done) => {
it('should 404 if resource does not exist', async () => {
const url = `${nconf.get('url')}/plugins/nodebb-plugin-dbsearch/dbsearch/templates/admin/plugins/should404.tpl`;
request.get(url, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
assert(body);
done();
});
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 404);
assert(body);
});
it('should get resource', (done) => {
it('should get resource', async () => {
const url = `${nconf.get('url')}/assets/templates/admin/plugins/dbsearch.tpl`;
request.get(url, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 200);
assert(body);
});
});
@@ -371,7 +363,6 @@ describe('Plugins', () => {
assert.ifError(err);
assert(Array.isArray(data));
data.forEach((pluginData) => {
console.log(pluginData);
assert(activePlugins.includes(pluginData));
});
done();

View File

@@ -2,8 +2,7 @@
const assert = require('assert');
const async = require('async');
const request = require('request-promise-native');
const nconf = require('nconf');
const path = require('path');
const util = require('util');
@@ -24,6 +23,7 @@ const meta = require('../src/meta');
const file = require('../src/file');
const helpers = require('./helpers');
const utils = require('../src/utils');
const request = require('../src/request');
describe('Post\'s', () => {
let voterUid;
@@ -33,52 +33,26 @@ describe('Post\'s', () => {
let topicData;
let cid;
before((done) => {
async.series({
voterUid: function (next) {
user.create({ username: 'upvoter' }, next);
},
voteeUid: function (next) {
user.create({ username: 'upvotee' }, next);
},
globalModUid: function (next) {
user.create({ username: 'globalmod', password: 'globalmodpwd' }, next);
},
category: function (next) {
categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
}, (err, results) => {
if (err) {
return done(err);
}
before(async () => {
voterUid = await user.create({ username: 'upvoter' });
voteeUid = await user.create({ username: 'upvotee' });
globalModUid = await user.create({ username: 'globalmod', password: 'globalmodpwd' });
({ cid } = await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}));
voterUid = results.voterUid;
voteeUid = results.voteeUid;
globalModUid = results.globalModUid;
cid = results.category.cid;
topics.post({
uid: results.voteeUid,
cid: results.category.cid,
title: 'Test Topic Title',
content: 'The content of test topic',
}, (err, data) => {
if (err) {
return done(err);
}
postData = data.postData;
topicData = data.topicData;
groups.join('Global Moderators', globalModUid, done);
});
});
({ topicData, postData } = await topics.post({
uid: voteeUid,
cid: cid,
title: 'Test Topic Title',
content: 'The content of test topic',
}));
await groups.join('Global Moderators', globalModUid);
});
it('should update category teaser properly', async () => {
const getCategoriesAsync = async () => await request(`${nconf.get('url')}/api/categories`, { json: true });
const getCategoriesAsync = async () => (await request.get(`${nconf.get('url')}/api/categories`, { })).body;
const postResult = await topics.post({ uid: globalModUid, cid: cid, title: 'topic title', content: '123456789' });
let data = await getCategoriesAsync();
@@ -372,24 +346,14 @@ describe('Post\'s', () => {
assert.strictEqual(isDeleted, 1);
});
it('should not see post content if global mod does not have posts:view_deleted privilege', (done) => {
async.waterfall([
function (next) {
user.create({ username: 'global mod', password: '123456' }, next);
},
function (uid, next) {
groups.join('Global Moderators', uid, next);
},
function (next) {
privileges.categories.rescind(['groups:posts:view_deleted'], cid, 'Global Moderators', next);
},
async () => {
const { jar } = await helpers.loginUser('global mod', '123456');
const { posts } = await request(`${nconf.get('url')}/api/topic/${tid}`, { jar, json: true });
assert.equal(posts[1].content, '[[topic:post-is-deleted]]');
await privileges.categories.give(['groups:posts:view_deleted'], cid, 'Global Moderators');
},
], done);
it('should not see post content if global mod does not have posts:view_deleted privilege', async () => {
const uid = await user.create({ username: 'global mod', password: '123456' });
await groups.join('Global Moderators', uid);
await privileges.categories.rescind(['groups:posts:view_deleted'], cid, 'Global Moderators');
const { jar } = await helpers.loginUser('global mod', '123456');
const { body } = await request.get(`${nconf.get('url')}/api/topic/${tid}`, { jar });
assert.equal(body.posts[1].content, '[[topic:post-is-deleted]]');
await privileges.categories.give(['groups:posts:view_deleted'], cid, 'Global Moderators');
});
it('should restore a post', async () => {
@@ -1013,7 +977,8 @@ describe('Post\'s', () => {
it('should load queued posts', async () => {
({ jar } = await helpers.loginUser('globalmod', 'globalmodpwd'));
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.content, 'queued topic content');
assert.equal(posts[1].type, 'reply');
@@ -1029,21 +994,24 @@ describe('Post\'s', () => {
it('should edit post in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: queueId, content: 'newContent' });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[1].type, 'reply');
assert.equal(posts[1].data.content, 'newContent');
});
it('should edit topic title in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, title: 'new topic title' });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.title, 'new topic title');
});
it('should edit topic category in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, cid: 2 });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.cid, 2);
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, cid: cid });
@@ -1063,20 +1031,10 @@ describe('Post\'s', () => {
});
});
it('should accept queued posts and submit', (done) => {
let ids;
async.waterfall([
function (next) {
db.getSortedSetRange('post:queue', 0, -1, next);
},
function (_ids, next) {
ids = _ids;
socketPosts.accept({ uid: globalModUid }, { id: ids[0] }, next);
},
function (next) {
socketPosts.accept({ uid: globalModUid }, { id: ids[1] }, next);
},
], done);
it('should accept queued posts and submit', async () => {
const ids = await db.getSortedSetRange('post:queue', 0, -1);
await socketPosts.accept({ uid: globalModUid }, { id: ids[0] });
await socketPosts.accept({ uid: globalModUid }, { id: ids[1] });
});
it('should not crash if id does not exist', (done) => {

View File

@@ -6,7 +6,6 @@ const path = require('path');
const os = require('os');
const nconf = require('nconf');
const async = require('async');
const crypto = require('crypto');
const db = require('../mocks/databasemock');
@@ -75,24 +74,15 @@ describe('upload methods', () => {
});
});
it('should remove an image if it is edited out of the post', (done) => {
async.series([
function (next) {
posts.edit({
pid: pid,
uid,
content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!',
}, next);
},
async.apply(posts.uploads.sync, pid),
], (err) => {
assert.ifError(err);
db.sortedSetCard(`post:${pid}:uploads`, (err, length) => {
assert.ifError(err);
assert.strictEqual(1, length);
done();
});
it('should remove an image if it is edited out of the post', async () => {
await posts.edit({
pid: pid,
uid,
content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!',
});
await posts.uploads.sync(pid);
const length = await db.sortedSetCard(`post:${pid}:uploads`);
assert.strictEqual(1, length);
});
});
@@ -127,85 +117,52 @@ describe('upload methods', () => {
});
describe('.associate()', () => {
it('should add an image to the post\'s maintained list of uploads', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, 'files/whoa.gif'),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(2, uploads.length);
assert.strictEqual(true, uploads.includes('files/whoa.gif'));
done();
});
it('should add an image to the post\'s maintained list of uploads', async () => {
await posts.uploads.associate(pid, 'files/whoa.gif');
const uploads = await posts.uploads.list(pid);
assert.strictEqual(2, uploads.length);
assert.strictEqual(true, uploads.includes('files/whoa.gif'));
});
it('should allow arrays to be passed in', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(4, uploads.length);
assert.strictEqual(true, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(true, uploads.includes('files/wut.txt'));
done();
});
it('should allow arrays to be passed in', async () => {
await posts.uploads.associate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(4, uploads.length);
assert.strictEqual(true, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(true, uploads.includes('files/wut.txt'));
});
it('should save a reverse association of md5sum to pid', (done) => {
it('should save a reverse association of md5sum to pid', async () => {
const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/test.bmp']),
function (next) {
db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1, next);
},
], (err, pids) => {
assert.ifError(err);
assert.strictEqual(true, Array.isArray(pids));
assert.strictEqual(true, pids.length > 0);
assert.equal(pid, pids[0]);
done();
});
await posts.uploads.associate(pid, ['files/test.bmp']);
const pids = await db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1);
assert.strictEqual(true, Array.isArray(pids));
assert.strictEqual(true, pids.length > 0);
assert.equal(pid, pids[0]);
});
it('should not associate a file that does not exist on the local disk', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/nonexistant.xls']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(uploads.length, 5);
assert.strictEqual(false, uploads.includes('files/nonexistant.xls'));
done();
});
it('should not associate a file that does not exist on the local disk', async () => {
await posts.uploads.associate(pid, ['files/nonexistant.xls']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(uploads.length, 5);
assert.strictEqual(false, uploads.includes('files/nonexistant.xls'));
});
});
describe('.dissociate()', () => {
it('should remove an image from the post\'s maintained list of uploads', (done) => {
async.waterfall([
async.apply(posts.uploads.dissociate, pid, 'files/whoa.gif'),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(4, uploads.length);
assert.strictEqual(false, uploads.includes('files/whoa.gif'));
done();
});
it('should remove an image from the post\'s maintained list of uploads', async () => {
await posts.uploads.dissociate(pid, 'files/whoa.gif');
const uploads = await posts.uploads.list(pid);
assert.strictEqual(4, uploads.length);
assert.strictEqual(false, uploads.includes('files/whoa.gif'));
});
it('should allow arrays to be passed in', (done) => {
async.waterfall([
async.apply(posts.uploads.dissociate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(2, uploads.length);
assert.strictEqual(false, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(false, uploads.includes('files/wut.txt'));
done();
});
it('should allow arrays to be passed in', async () => {
await posts.uploads.dissociate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(2, uploads.length);
assert.strictEqual(false, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(false, uploads.includes('files/wut.txt'));
});
it('should remove the image\'s user association, if present', async () => {
@@ -397,21 +354,14 @@ describe('post uploads management', () => {
});
});
it('should automatically sync uploads on post edit', (done) => {
async.waterfall([
async.apply(posts.edit, {
pid: reply.pid,
uid,
content: 'no uploads',
}),
function (postData, next) {
posts.uploads.list(reply.pid, next);
},
], (err, uploads) => {
assert.ifError(err);
assert.strictEqual(true, Array.isArray(uploads));
assert.strictEqual(0, uploads.length);
done();
it('should automatically sync uploads on post edit', async () => {
await posts.edit({
pid: reply.pid,
uid,
content: 'no uploads',
});
const uploads = await posts.uploads.list(reply.pid);
assert.strictEqual(true, Array.isArray(uploads));
assert.strictEqual(0, uploads.length);
});
});

View File

@@ -2,8 +2,6 @@
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
@@ -12,6 +10,7 @@ const categories = require('../src/categories');
const user = require('../src/user');
const search = require('../src/search');
const privileges = require('../src/privileges');
const request = require('../src/request');
describe('Search', () => {
let phoebeUid;
@@ -26,103 +25,60 @@ describe('Search', () => {
let cid2;
let cid3;
before((done) => {
async.waterfall([
function (next) {
async.series({
phoebe: function (next) {
user.create({ username: 'phoebe' }, next);
},
ginger: function (next) {
user.create({ username: 'ginger' }, next);
},
category1: function (next) {
categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
category2: function (next) {
categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
}, next);
},
function (results, next) {
phoebeUid = results.phoebe;
gingerUid = results.ginger;
cid1 = results.category1.cid;
cid2 = results.category2.cid;
before(async () => {
phoebeUid = await user.create({ username: 'phoebe' });
gingerUid = await user.create({ username: 'ginger' });
cid1 = (await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
})).cid;
async.waterfall([
function (next) {
categories.create({
name: 'Child Test Category',
description: 'Test category created by testing script',
parentCid: cid2,
}, next);
},
function (category, next) {
cid3 = category.cid;
topics.post({
uid: phoebeUid,
cid: cid1,
title: 'nodebb mongodb bugs',
content: 'avocado cucumber apple orange fox',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'jquery'],
}, next);
},
function (results, next) {
topic1Data = results.topicData;
post1Data = results.postData;
cid2 = (await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
})).cid;
topics.post({
uid: gingerUid,
cid: cid2,
title: 'java mongodb redis',
content: 'avocado cucumber carrot armadillo',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'javascript'],
}, next);
},
function (results, next) {
topic2Data = results.topicData;
post2Data = results.postData;
topics.reply({
uid: phoebeUid,
content: 'reply post apple',
tid: topic2Data.tid,
}, next);
},
function (_post3Data, next) {
post3Data = _post3Data;
setTimeout(next, 500);
},
], next);
},
], done);
cid3 = (await categories.create({
name: 'Child Test Category',
description: 'Test category created by testing script',
parentCid: cid2,
})).cid;
({ topicData: topic1Data, postData: post1Data } = await topics.post({
uid: phoebeUid,
cid: cid1,
title: 'nodebb mongodb bugs',
content: 'avocado cucumber apple orange fox',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'jquery'],
}));
({ topicData: topic2Data, postData: post2Data } = await topics.post({
uid: gingerUid,
cid: cid2,
title: 'java mongodb redis',
content: 'avocado cucumber carrot armadillo',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'javascript'],
}));
post3Data = await topics.reply({
uid: phoebeUid,
content: 'reply post apple',
tid: topic2Data.tid,
});
});
it('should search term in titles and posts', (done) => {
it('should search term in titles and posts', async () => {
const meta = require('../src/meta');
const qs = `/api/search?term=cucumber&in=titlesposts&categories[]=${cid1}&by=phoebe&replies=1&repliesFilter=atleast&sortBy=timestamp&sortDirection=desc&showAs=posts`;
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
assert(body);
assert.equal(body.matchCount, 1);
assert.equal(body.posts.length, 1);
assert.equal(body.posts[0].pid, post1Data.pid);
assert.equal(body.posts[0].uid, phoebeUid);
await privileges.global.give(['groups:search:content'], 'guests');
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
const { body } = await request.get(nconf.get('url') + qs);
assert(body);
assert.equal(body.matchCount, 1);
assert.equal(body.posts.length, 1);
assert.equal(body.posts[0].pid, post1Data.pid);
assert.equal(body.posts[0].uid, phoebeUid);
await privileges.global.rescind(['groups:search:content'], 'guests');
});
it('should search for a user', (done) => {
@@ -225,69 +181,47 @@ describe('Search', () => {
});
});
it('should search child categories', (done) => {
async.waterfall([
function (next) {
topics.post({
uid: gingerUid,
cid: cid3,
title: 'child category topic',
content: 'avocado cucumber carrot armadillo',
}, next);
},
function (result, next) {
search.search({
query: 'avocado',
searchIn: 'titlesposts',
categories: [cid2],
searchChildren: true,
sortBy: 'topic.timestamp',
sortDirection: 'desc',
}, next);
},
function (result, next) {
assert(result.posts.length, 2);
assert(result.posts[0].topic.title === 'child category topic');
assert(result.posts[1].topic.title === 'java mongodb redis');
next();
},
], done);
it('should search child categories', async () => {
await topics.post({
uid: gingerUid,
cid: cid3,
title: 'child category topic',
content: 'avocado cucumber carrot armadillo',
});
const result = await search.search({
query: 'avocado',
searchIn: 'titlesposts',
categories: [cid2],
searchChildren: true,
sortBy: 'topic.timestamp',
sortDirection: 'desc',
});
assert(result.posts.length, 2);
assert(result.posts[0].topic.title === 'child category topic');
assert(result.posts[1].topic.title === 'java mongodb redis');
});
it('should return json search data with no categories', (done) => {
it('should return json search data with no categories', async () => {
const qs = '/api/search?term=cucumber&in=titlesposts&searchOnly=1';
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
assert(body);
assert(body.hasOwnProperty('matchCount'));
assert(body.hasOwnProperty('pagination'));
assert(body.hasOwnProperty('pageCount'));
assert(body.hasOwnProperty('posts'));
assert(!body.hasOwnProperty('categories'));
await privileges.global.give(['groups:search:content'], 'guests');
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
const { body } = await request.get(nconf.get('url') + qs);
assert(body);
assert(body.hasOwnProperty('matchCount'));
assert(body.hasOwnProperty('pagination'));
assert(body.hasOwnProperty('pageCount'));
assert(body.hasOwnProperty('posts'));
assert(!body.hasOwnProperty('categories'));
await privileges.global.rescind(['groups:search:content'], 'guests');
});
it('should not crash without a search term', (done) => {
it('should not crash without a search term', async () => {
const qs = '/api/search';
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
assert(body);
assert.strictEqual(response.statusCode, 200);
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
await privileges.global.give(['groups:search:content'], 'guests');
const { response, body } = await request.get(nconf.get('url') + qs);
assert(body);
assert.strictEqual(response.statusCode, 200);
await privileges.global.rescind(['groups:search:content'], 'guests');
});
});

View File

@@ -9,11 +9,7 @@ const util = require('util');
const sleep = util.promisify(setTimeout);
const assert = require('assert');
const async = require('async');
const nconf = require('nconf');
const request = require('request');
const cookies = request.jar();
const db = require('./mocks/databasemock');
const user = require('../src/user');
@@ -52,35 +48,11 @@ describe('socket.io', () => {
});
it('should connect and auth properly', (done) => {
request.get({
url: `${nconf.get('url')}/api/config`,
jar: cookies,
json: true,
}, (err, res, body) => {
assert.ifError(err);
request.post(`${nconf.get('url')}/login`, {
jar: cookies,
form: {
username: 'admin',
password: 'adminpwd',
},
headers: {
'x-csrf-token': body.csrf_token,
},
json: true,
}, (err, res) => {
assert.ifError(err);
helpers.connectSocketIO(res, body.csrf_token, (err, _io) => {
io = _io;
assert.ifError(err);
done();
});
});
});
it('should connect and auth properly', async () => {
const { response, csrf_token } = await helpers.loginUser('admin', 'adminpwd');
io = await helpers.connectSocketIO(response, csrf_token);
assert(io);
assert(io.emit);
});
it('should return error for unknown event', (done) => {
@@ -459,20 +431,38 @@ describe('socket.io', () => {
});
});
it('should toggle plugin install', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.toggleInstall({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err, data) => {
assert.ifError(err);
assert.equal(data.name, 'nodebb-plugin-location-to-map');
process.env.NODE_ENV = oldValue;
done();
describe('install/upgrade plugin', () => {
it('should toggle plugin install', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.toggleInstall({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err, data) => {
assert.ifError(err);
assert.equal(data.name, 'nodebb-plugin-location-to-map');
process.env.NODE_ENV = oldValue;
done();
});
});
it('should upgrade plugin', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.upgrade({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err) => {
assert.ifError(err);
process.env.NODE_ENV = oldValue;
done();
});
});
});
@@ -501,22 +491,6 @@ describe('socket.io', () => {
});
});
it('should upgrade plugin', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.upgrade({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err) => {
assert.ifError(err);
process.env.NODE_ENV = oldValue;
done();
});
});
it('should error with invalid data', (done) => {
socketAdmin.widgets.set({ uid: adminUid }, null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
@@ -709,60 +683,43 @@ describe('socket.io', () => {
assert(pwExpiry > then && pwExpiry < Date.now());
});
it('should not error on valid email', (done) => {
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
assert.ifError(err);
it('should not error on valid email', async () => {
await socketUser.reset.send({ uid: 0 }, 'regular@test.com');
const [count, eventsData] = await Promise.all([
db.sortedSetCount('reset:issueDate', 0, Date.now()),
events.getEvents('', 0, 0),
]);
assert.strictEqual(count, 2);
async.parallel({
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
event: async.apply(events.getEvents, '', 0, 0),
}, (err, data) => {
assert.ifError(err);
assert.strictEqual(data.count, 2);
// Event validity
assert.strictEqual(data.event.length, 1);
const event = data.event[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[success:success]]');
done();
});
});
// Event validity
assert.strictEqual(eventsData.length, 1);
const event = eventsData[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[success:success]]');
});
it('should not generate code if rate limited', (done) => {
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
assert(err);
it('should not generate code if rate limited', async () => {
await assert.rejects(
socketUser.reset.send({ uid: 0 }, 'regular@test.com'),
{ message: '[[error:reset-rate-limited]]' },
);
const [count, eventsData] = await Promise.all([
db.sortedSetCount('reset:issueDate', 0, Date.now()),
events.getEvents('', 0, 0),
]);
assert.strictEqual(count, 2);
async.parallel({
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
event: async.apply(events.getEvents, '', 0, 0),
}, (err, data) => {
assert.ifError(err);
assert.strictEqual(data.count, 2);
// Event validity
assert.strictEqual(data.event.length, 1);
const event = data.event[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[error:reset-rate-limited]]');
done();
});
});
// Event validity
assert.strictEqual(eventsData.length, 1);
const event = eventsData[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[error:reset-rate-limited]]');
});
it('should not error on invalid email (but not generate reset code)', (done) => {
socketUser.reset.send({ uid: 0 }, 'irregular@test.com', (err) => {
assert.ifError(err);
db.sortedSetCount('reset:issueDate', 0, Date.now(), (err, count) => {
assert.ifError(err);
assert.strictEqual(count, 2);
done();
});
});
it('should not error on invalid email (but not generate reset code)', async () => {
await socketUser.reset.send({ uid: 0 }, 'irregular@test.com');
const count = await db.sortedSetCount('reset:issueDate', 0, Date.now());
assert.strictEqual(count, 2);
});
it('should error on no email', (done) => {

File diff suppressed because it is too large Load Diff

View File

@@ -324,20 +324,14 @@ describe('Topic thumbs', () => {
createFiles();
});
it('should succeed with a valid tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
done();
});
it('should succeed with a valid tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 200);
});
it('should succeed with a uuid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
done();
});
it('should succeed with a uuid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 200);
});
it('should succeed with uploader plugins', async () => {
@@ -350,63 +344,49 @@ describe('Topic thumbs', () => {
method: hookMethod,
});
await new Promise((resolve) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
resolve();
});
});
const { response } = await helpers.uploadFile(
`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`,
path.join(__dirname, '../files/test.png'),
{},
adminJar,
adminCSRF
);
assert.strictEqual(response.statusCode, 200);
await plugins.hooks.unregister('test', 'filter:uploadFile', hookMethod);
});
it('should fail with a non-existant tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/4/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
it('should fail with a non-existant tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/4/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 404);
});
it('should fail when garbage is passed in', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/abracadabra/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
it('should fail when garbage is passed in', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/abracadabra/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 404);
});
it('should fail when calling user cannot edit the tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/2/thumbs`, path.join(__dirname, '../files/test.png'), {}, fooJar, fooCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 403);
done();
});
it('should fail when calling user cannot edit the tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/2/thumbs`, path.join(__dirname, '../files/test.png'), {}, fooJar, fooCSRF);
assert.strictEqual(response.statusCode, 403);
});
it('should fail if thumbnails are not enabled', (done) => {
it('should fail if thumbnails are not enabled', async () => {
meta.config.allowTopicsThumbnail = 0;
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 503);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Topic thumbnails are disabled.');
done();
});
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 503);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Topic thumbnails are disabled.');
});
it('should fail if file is not image', (done) => {
it('should fail if file is not image', async () => {
meta.config.allowTopicsThumbnail = 1;
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Invalid File');
done();
});
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Invalid File');
});
});

View File

@@ -5,9 +5,6 @@ const assert = require('assert');
const nconf = require('nconf');
const path = require('path');
const fs = require('fs').promises;
const request = require('request');
const requestAsync = require('request-promise-native');
const util = require('util');
const db = require('./mocks/databasemock');
const categories = require('../src/categories');
@@ -21,6 +18,7 @@ const socketUser = require('../src/socket.io/user');
const helpers = require('./helpers');
const file = require('../src/file');
const image = require('../src/image');
const request = require('../src/request');
const emptyUploadsFolder = async () => {
const files = await fs.readdir(`${nconf.get('upload_path')}/files`);
@@ -83,31 +81,25 @@ describe('Upload Controllers', () => {
await privileges.global.give(['groups:upload:post:file'], 'registered-users');
});
it('should fail if the user exceeds the upload rate limit threshold', (done) => {
it('should fail if the user exceeds the upload rate limit threshold', async () => {
const oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
require('../src/middleware/uploads').clearCache();
const times = meta.config.uploadRateLimitThreshold + 1;
async.timesSeries(times, (i, next) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, (err, res, body) => {
if (i + 1 >= times) {
assert.strictEqual(res.statusCode, 500);
assert.strictEqual(body.error, '[[error:upload-ratelimit-reached]]');
} else {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
}
next(err);
});
}, (err) => {
meta.config.allowedFileExtensions = oldValue;
assert.ifError(err);
done();
});
for (let i = 0; i < times; i++) {
// eslint-disable-next-line no-await-in-loop
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token);
if (i + 1 >= times) {
assert.strictEqual(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:upload-ratelimit-reached]]');
} else {
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
}
}
meta.config.allowedFileExtensions = oldValue;
});
});
@@ -121,34 +113,26 @@ describe('Upload Controllers', () => {
await privileges.global.give(['groups:upload:post:file'], 'registered-users');
});
it('should upload an image to a post', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
done();
});
it('should upload an image to a post', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
});
it('should upload an image to a post and then delete the upload', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
const name = body.response.images[0].url.replace(`${nconf.get('relative_path') + nconf.get('upload_url')}/`, '');
socketUser.deleteUpload({ uid: regularUid }, { uid: regularUid, name: name }, (err) => {
assert.ifError(err);
db.getSortedSetRange(`uid:${regularUid}:uploads`, 0, -1, (err, uploads) => {
assert.ifError(err);
assert.equal(uploads.includes(name), false);
done();
});
});
});
it('should upload an image to a post and then delete the upload', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
const name = body.response.images[0].url.replace(`${nconf.get('relative_path') + nconf.get('upload_url')}/`, '');
await socketUser.deleteUpload({ uid: regularUid }, { uid: regularUid, name: name });
const uploads = await db.getSortedSetRange(`uid:${regularUid}:uploads`, 0, -1);
assert.equal(uploads.includes(name), false);
});
it('should not allow deleting if path is not correct', (done) => {
@@ -165,55 +149,45 @@ describe('Upload Controllers', () => {
});
});
it('should resize and upload an image to a post', (done) => {
it('should resize and upload an image to a post', async () => {
const oldValue = meta.config.resizeImageWidth;
meta.config.resizeImageWidth = 10;
meta.config.resizeImageWidthThreshold = 10;
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
assert(body.response.images[0].url.match(/\/assets\/uploads\/files\/\d+-test-resized\.png/));
meta.config.resizeImageWidth = oldValue;
meta.config.resizeImageWidthThreshold = 1520;
done();
});
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
assert(body.response.images[0].url.match(/\/assets\/uploads\/files\/\d+-test-resized\.png/));
meta.config.resizeImageWidth = oldValue;
meta.config.resizeImageWidthThreshold = 1520;
});
it('should upload a file to a post', (done) => {
it('should upload a file to a post', async () => {
const oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, (err, res, body) => {
meta.config.allowedFileExtensions = oldValue;
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
done();
});
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token);
meta.config.allowedFileExtensions = oldValue;
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
});
it('should fail to upload image to post if image dimensions are too big', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Image dimensions are too big');
done();
});
it('should fail to upload image to post if image dimensions are too big', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Image dimensions are too big');
});
it('should fail to upload image to post if image is broken', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/brokenimage.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Input file contains unsupported image format');
done();
});
it('should fail to upload image to post if image is broken', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/brokenimage.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Input file contains unsupported image format');
});
it('should fail if file is not an image', (done) => {
@@ -286,39 +260,22 @@ describe('Upload Controllers', () => {
});
});
it('should delete users uploads if account is deleted', (done) => {
let uid;
let url;
it('should delete users uploads if account is deleted', async () => {
const uid = await user.create({ username: 'uploader', password: 'barbar' });
const file = require('../src/file');
const data = await helpers.loginUser('uploader', 'barbar');
const { body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, data.jar, data.csrf_token);
async.waterfall([
function (next) {
user.create({ username: 'uploader', password: 'barbar' }, next);
},
function (_uid, next) {
uid = _uid;
helpers.loginUser('uploader', 'barbar', next);
},
function (data, next) {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, data.jar, data.csrf_token, next);
},
function (res, body, next) {
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
url = body.response.images[0].url;
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
const { url } = body.response.images[0];
user.delete(1, uid, next);
},
function (userData, next) {
const filePath = path.join(nconf.get('upload_path'), url.replace('/assets/uploads', ''));
file.exists(filePath, next);
},
function (exists, next) {
assert(!exists);
done();
},
], done);
await user.delete(1, uid);
const filePath = path.join(nconf.get('upload_path'), url.replace('/assets/uploads', ''));
const exists = await file.exists(filePath);
assert(!exists);
});
after(emptyUploadsFolder);
@@ -337,173 +294,147 @@ describe('Upload Controllers', () => {
regular_csrf_token = regularLogin.csrf_token;
});
it('should upload site logo', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadlogo`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/site-logo.png`);
done();
});
it('should upload site logo', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadlogo`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/site-logo.png`);
});
it('should fail to upload invalid file type', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/503.html'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
assert.equal(body.error, '[[error:invalid-image-type, image&#x2F;png&amp;#44; image&#x2F;jpeg&amp;#44; image&#x2F;pjpeg&amp;#44; image&#x2F;jpg&amp;#44; image&#x2F;gif&amp;#44; image&#x2F;svg+xml]]');
done();
});
it('should fail to upload invalid file type', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/503.html'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert.equal(body.error, '[[error:invalid-image-type, image&#x2F;png&amp;#44; image&#x2F;jpeg&amp;#44; image&#x2F;pjpeg&amp;#44; image&#x2F;jpg&amp;#44; image&#x2F;gif&amp;#44; image&#x2F;svg+xml]]');
});
it('should fail to upload category image with invalid json params', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: 'invalid json' }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
assert.equal(body.error, '[[error:invalid-json]]');
done();
});
it('should fail to upload category image with invalid json params', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: 'invalid json' }, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert.equal(body.error, '[[error:invalid-json]]');
});
it('should upload category image', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.png`);
done();
});
it('should upload category image', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.png`);
});
it('should upload default avatar', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/avatar-default.png`);
done();
});
it('should upload default avatar', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/avatar-default.png`);
});
it('should upload og image', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadOgImage`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/og-image.png`);
done();
});
it('should upload og image', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadOgImage`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/og-image.png`);
});
it('should upload favicon', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadfavicon`, path.join(__dirname, '../test/files/favicon.ico'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/favicon.ico');
done();
});
it('should upload favicon', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadfavicon`, path.join(__dirname, '../test/files/favicon.ico'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/favicon.ico');
});
it('should upload touch icon', (done) => {
it('should upload touch icon', async () => {
const touchiconAssetPath = '/assets/uploads/system/touchicon-orig.png';
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadTouchIcon`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, touchiconAssetPath);
meta.config['brand:touchIcon'] = touchiconAssetPath;
request(`${nconf.get('url')}/apple-touch-icon`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
const { response, body } = await helpers.uploadFile(
`${nconf.get('url')}/api/admin/uploadTouchIcon`,
path.join(__dirname, '../test/files/test.png'),
{},
jar,
csrf_token
);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, touchiconAssetPath);
meta.config['brand:touchIcon'] = touchiconAssetPath;
const { response: res1, body: body1 } = await request.get(`${nconf.get('url')}/apple-touch-icon`);
assert.equal(res1.statusCode, 200);
assert(body1);
});
it('should upload regular file', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
it('should upload regular file', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
params: JSON.stringify({
folder: 'system',
}),
}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/test.png');
assert(file.existsSync(path.join(nconf.get('upload_path'), 'system', 'test.png')));
done();
});
}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/test.png');
assert(file.existsSync(path.join(nconf.get('upload_path'), 'system', 'test.png')));
});
it('should fail to upload regular file in wrong directory', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
it('should fail to upload regular file in wrong directory', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
params: JSON.stringify({
folder: '../../system',
}),
}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 500);
assert.strictEqual(body.error, '[[error:invalid-path]]');
done();
});
}, jar, csrf_token);
assert.equal(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:invalid-path]]');
});
describe('ACP uploads screen', () => {
it('should create a folder', async () => {
const res = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(file.existsSync(path.join(nconf.get('upload_path'), 'myfolder')));
});
it('should fail to create a folder if it already exists', async () => {
const res = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'Folder exists',
});
});
it('should fail to create a folder as a non-admin', async () => {
const res = await helpers.createFolder('', 'hisfolder', regularJar, regular_csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('', 'hisfolder', regularJar, regular_csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'You are not authorised to make this call',
});
});
it('should fail to create a folder in wrong directory', async () => {
const res = await helpers.createFolder('../traversing', 'unexpectedfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('../traversing', 'unexpectedfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'Invalid path',
});
});
it('should use basename of given folderName to create new folder', async () => {
const res = await helpers.createFolder('/myfolder', '../another folder', jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.createFolder('/myfolder', '../another folder', jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
const slugifiedName = 'another-folder';
assert(file.existsSync(path.join(nconf.get('upload_path'), 'myfolder', slugifiedName)));
});
it('should fail to delete a file as a non-admin', async () => {
const res = await requestAsync.delete(`${nconf.get('url')}/api/v3/files`, {
const { response, body } = await request.delete(`${nconf.get('url')}/api/v3/files`, {
body: {
path: '/system/test.png',
},
jar: regularJar,
json: true,
headers: {
'x-csrf-token': regular_csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'You are not authorised to make this call',
});

File diff suppressed because it is too large Load Diff

View File

@@ -141,26 +141,16 @@ describe('email confirmation (library methods)', () => {
describe('email confirmation (v3 api)', () => {
let userObj;
let jar;
const register = data => new Promise((resolve, reject) => {
helpers.registerUser(data, (err, jar, response, body) => {
if (err) {
return reject(err);
}
resolve({ jar, response, body });
});
});
before(async () => {
// If you're running this file directly, uncomment these lines
await register({
await helpers.registerUser({
username: 'fake-user',
password: 'derpioansdosa',
email: 'b@c.com',
gdpr_consent: true,
});
({ body: userObj, jar } = await register({
({ body: userObj, jar } = await helpers.registerUser({
username: 'email-test',
password: 'abcdef',
email: 'test@example.org',
@@ -174,43 +164,40 @@ describe('email confirmation (v3 api)', () => {
});
it('should not list their email', async () => {
const { res, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, {
const { response, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{"emails":[]}}'));
});
it('should not allow confirmation if they are not an admin', async () => {
const { res } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(response.statusCode, 403);
});
it('should not confirm an email that is not pending or set', async () => {
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, {
const { response } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 404);
assert.strictEqual(response.statusCode, 404);
await groups.leave('administrators', userObj.uid);
});
it('should confirm their email (using the pending validation)', async () => {
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
await groups.leave('administrators', userObj.uid);
});
@@ -221,12 +208,12 @@ describe('email confirmation (v3 api)', () => {
({ jar } = await helpers.loginUser('email-test', 'abcdef')); // email removal logs out everybody
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
await groups.leave('administrators', userObj.uid);
});