mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-17 05:50:25 +01:00
Compare commits
19 Commits
normalize-
...
v1.17.2-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4344d31d9 | ||
|
|
1fad6f6804 | ||
|
|
a07b898864 | ||
|
|
d1a7a71003 | ||
|
|
e92f5fc561 | ||
|
|
3675d343ef | ||
|
|
e6c9585ae6 | ||
|
|
f57291ef5f | ||
|
|
9556da11d4 | ||
|
|
fdf8231800 | ||
|
|
a8748e137f | ||
|
|
89af582951 | ||
|
|
9410a73aaa | ||
|
|
b2652ae697 | ||
|
|
9f90bdcd59 | ||
|
|
b002d25421 | ||
|
|
902da54029 | ||
|
|
7c0da8fcbb | ||
|
|
6b4ef46ead |
52
.github/workflows/docker.yml
vendored
Normal file
52
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: Run Docker
|
||||||
|
|
||||||
|
# Controls when the workflow will run
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v*.x'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: nodebb/docker
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=raw,value=latest
|
||||||
|
|
||||||
|
- name: Build and push Docker images
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "nodebb",
|
"name": "nodebb",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"description": "NodeBB Forum",
|
"description": "NodeBB Forum",
|
||||||
"version": "1.17.1",
|
"version": "1.17.2",
|
||||||
"homepage": "http://www.nodebb.org",
|
"homepage": "http://www.nodebb.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"nodebb-plugin-dbsearch": "5.0.2",
|
"nodebb-plugin-dbsearch": "5.0.2",
|
||||||
"nodebb-plugin-emoji": "^3.5.0",
|
"nodebb-plugin-emoji": "^3.5.0",
|
||||||
"nodebb-plugin-emoji-android": "2.0.5",
|
"nodebb-plugin-emoji-android": "2.0.5",
|
||||||
"nodebb-plugin-markdown": "8.14.0",
|
"nodebb-plugin-markdown": "8.14.1",
|
||||||
"nodebb-plugin-mentions": "2.13.11",
|
"nodebb-plugin-mentions": "2.13.11",
|
||||||
"nodebb-plugin-spam-be-gone": "0.7.9",
|
"nodebb-plugin-spam-be-gone": "0.7.9",
|
||||||
"nodebb-rewards-essentials": "0.1.5",
|
"nodebb-rewards-essentials": "0.1.5",
|
||||||
@@ -182,4 +182,4 @@
|
|||||||
"url": "https://github.com/barisusakli"
|
"url": "https://github.com/barisusakli"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ define('forum/account/info', ['forum/account/header', 'components', 'forum/accou
|
|||||||
app.alertSuccess('[[user:info.moderation-note.success]]');
|
app.alertSuccess('[[user:info.moderation-note.success]]');
|
||||||
var timestamp = Date.now();
|
var timestamp = Date.now();
|
||||||
var data = [{
|
var data = [{
|
||||||
note: note,
|
note: utils.escapeHTML(note),
|
||||||
user: app.user,
|
user: app.user,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
timestampISO: utils.toISOString(timestamp),
|
timestampISO: utils.toISOString(timestamp),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
define('forum/login', ['jquery-form'], function () {
|
define('forum/login', ['translator', 'jquery-form'], function (translator) {
|
||||||
var Login = {
|
var Login = {
|
||||||
_capsState: false,
|
_capsState: false,
|
||||||
};
|
};
|
||||||
@@ -44,17 +44,22 @@ define('forum/login', ['jquery-form'], function () {
|
|||||||
window.location.href = pathname + '?' + qs;
|
window.location.href = pathname + '?' + qs;
|
||||||
},
|
},
|
||||||
error: function (data) {
|
error: function (data) {
|
||||||
|
var message = data.responseText;
|
||||||
|
var errInfo = data.responseJSON;
|
||||||
if (data.status === 403 && data.responseText === 'Forbidden') {
|
if (data.status === 403 && data.responseText === 'Forbidden') {
|
||||||
window.location.href = config.relative_path + '/login?error=csrf-invalid';
|
window.location.href = config.relative_path + '/login?error=csrf-invalid';
|
||||||
} else {
|
} else if (errInfo && errInfo.hasOwnProperty('banned_until')) {
|
||||||
errorEl.find('p').translateText(data.responseText);
|
message = errInfo.banned_until ?
|
||||||
errorEl.show();
|
translator.compile('error:user-banned-reason-until', (new Date(errInfo.banned_until).toLocaleString()), errInfo.reason) :
|
||||||
submitEl.removeClass('disabled');
|
'[[error:user-banned-reason, ' + errInfo.reason + ']]';
|
||||||
|
}
|
||||||
|
errorEl.find('p').translateText(message);
|
||||||
|
errorEl.show();
|
||||||
|
submitEl.removeClass('disabled');
|
||||||
|
|
||||||
// Select the entire password if that field has focus
|
// Select the entire password if that field has focus
|
||||||
if ($('#password:focus').length) {
|
if ($('#password:focus').length) {
|
||||||
$('#password').select();
|
$('#password').select();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -208,15 +208,20 @@ socket = window.socket;
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onEventBanned(data) {
|
function onEventBanned(data) {
|
||||||
var message = data.until ? '[[error:user-banned-reason-until, ' + utils.toISOString(data.until) + ', ' + data.reason + ']]' : '[[error:user-banned-reason, ' + data.reason + ']]';
|
require(['translator'], function (translator) {
|
||||||
|
var message = data.until ?
|
||||||
bootbox.alert({
|
translator.compile('error:user-banned-reason-until', (new Date(data.until).toLocaleString()), data.reason) :
|
||||||
title: '[[error:user-banned]]',
|
'[[error:user-banned-reason, ' + data.reason + ']]';
|
||||||
message: message,
|
translator.translate(message, function (message) {
|
||||||
closeButton: false,
|
bootbox.alert({
|
||||||
callback: function () {
|
title: '[[error:user-banned]]',
|
||||||
window.location.href = config.relative_path + '/';
|
message: message,
|
||||||
},
|
closeButton: false,
|
||||||
|
callback: function () {
|
||||||
|
window.location.href = config.relative_path + '/';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
src/als.js
Normal file
7
src/als.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { AsyncLocalStorage } = require('async_hooks');
|
||||||
|
|
||||||
|
const asyncLocalStorage = new AsyncLocalStorage();
|
||||||
|
|
||||||
|
module.exports = asyncLocalStorage;
|
||||||
@@ -15,7 +15,6 @@ const user = require('../user');
|
|||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
const slugify = require('../slugify');
|
const slugify = require('../slugify');
|
||||||
const translator = require('../translator');
|
|
||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
const privileges = require('../privileges');
|
const privileges = require('../privileges');
|
||||||
const sockets = require('../socket.io');
|
const sockets = require('../socket.io');
|
||||||
@@ -263,7 +262,7 @@ function continueLogin(strategy, req, res, next) {
|
|||||||
passport.authenticate(strategy, async (err, userData, info) => {
|
passport.authenticate(strategy, async (err, userData, info) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
plugins.hooks.fire('action:login.continue', { req, strategy, userData, error: err });
|
plugins.hooks.fire('action:login.continue', { req, strategy, userData, error: err });
|
||||||
return helpers.noScriptErrors(req, res, err.message, 403);
|
return helpers.noScriptErrors(req, res, err.data || err.message, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userData) {
|
if (!userData) {
|
||||||
@@ -423,8 +422,7 @@ authenticationController.localLogin = async function (req, username, password, n
|
|||||||
userData.isAdminOrGlobalMod = isAdminOrGlobalMod;
|
userData.isAdminOrGlobalMod = isAdminOrGlobalMod;
|
||||||
|
|
||||||
if (!canLoginIfBanned) {
|
if (!canLoginIfBanned) {
|
||||||
const banMesage = await getBanInfo(uid);
|
return next(await getBanError(uid));
|
||||||
return next(new Error(banMesage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doing this after the ban check, because user's privileges might change after a ban expires
|
// Doing this after the ban check, because user's privileges might change after a ban expires
|
||||||
@@ -483,19 +481,19 @@ authenticationController.logout = async function (req, res, next) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getBanInfo(uid) {
|
async function getBanError(uid) {
|
||||||
try {
|
try {
|
||||||
const banInfo = await user.getLatestBanInfo(uid);
|
const banInfo = await user.getLatestBanInfo(uid);
|
||||||
|
|
||||||
if (!banInfo.reason) {
|
if (!banInfo.reason) {
|
||||||
banInfo.reason = await translator.translate('[[user:info.banned-no-reason]]');
|
banInfo.reason = '[[user:info.banned-no-reason]]';
|
||||||
}
|
}
|
||||||
return banInfo.banned_until ?
|
const err = new Error(banInfo.reason);
|
||||||
`[[error:user-banned-reason-until, ${banInfo.banned_until_readable}, ${banInfo.reason}]]` :
|
err.data = banInfo;
|
||||||
`[[error:user-banned-reason, ${banInfo.reason}]]`;
|
return err;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === 'no-ban-info') {
|
if (err.message === 'no-ban-info') {
|
||||||
return '[[error:user-banned]]';
|
return new Error('[[error:user-banned]]');
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const privileges = require('../privileges');
|
|||||||
const categories = require('../categories');
|
const categories = require('../categories');
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
|
const middlewareHelpers = require('../middleware/helpers');
|
||||||
|
|
||||||
const helpers = module.exports;
|
const helpers = module.exports;
|
||||||
|
|
||||||
@@ -19,7 +20,10 @@ const url = nconf.get('url');
|
|||||||
|
|
||||||
helpers.noScriptErrors = async function (req, res, error, httpStatus) {
|
helpers.noScriptErrors = async function (req, res, error, httpStatus) {
|
||||||
if (req.body.noscript !== 'true') {
|
if (req.body.noscript !== 'true') {
|
||||||
return res.status(httpStatus).send(error);
|
if (typeof error === 'string') {
|
||||||
|
return res.status(httpStatus).send(error);
|
||||||
|
}
|
||||||
|
return res.status(httpStatus).json(error);
|
||||||
}
|
}
|
||||||
const middleware = require('../middleware');
|
const middleware = require('../middleware');
|
||||||
const httpStatusString = httpStatus.toString();
|
const httpStatusString = httpStatus.toString();
|
||||||
@@ -124,7 +128,17 @@ helpers.notAllowed = async function (req, res, error) {
|
|||||||
|
|
||||||
if (req.loggedIn || req.uid === -1) {
|
if (req.loggedIn || req.uid === -1) {
|
||||||
if (res.locals.isAPI) {
|
if (res.locals.isAPI) {
|
||||||
helpers.formatApiResponse(403, res, error);
|
if (req.originalUrl.startsWith(`${relative_path}/api/v3`)) {
|
||||||
|
helpers.formatApiResponse(403, res, error);
|
||||||
|
} else {
|
||||||
|
res.status(403).json({
|
||||||
|
path: req.path.replace(/^\/api/, ''),
|
||||||
|
loggedIn: req.loggedIn,
|
||||||
|
error: error,
|
||||||
|
title: '[[global:403.title]]',
|
||||||
|
bodyClass: middlewareHelpers.buildBodyClass(req, res),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const middleware = require('../middleware');
|
const middleware = require('../middleware');
|
||||||
await middleware.buildHeaderAsync(req, res);
|
await middleware.buildHeaderAsync(req, res);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ connection.getConnectionString = function (mongo) {
|
|||||||
let usernamePassword = '';
|
let usernamePassword = '';
|
||||||
const uri = mongo.uri || '';
|
const uri = mongo.uri || '';
|
||||||
if (mongo.username && mongo.password) {
|
if (mongo.username && mongo.password) {
|
||||||
usernamePassword = `${nconf.get('mongo:username')}:${encodeURIComponent(nconf.get('mongo:password'))}@`;
|
usernamePassword = `${mongo.username}:${encodeURIComponent(mongo.password)}@`;
|
||||||
} else if (!uri.includes('@') || !uri.slice(uri.indexOf('://') + 3, uri.indexOf('@'))) {
|
} else if (!uri.includes('@') || !uri.slice(uri.indexOf('://') + 3, uri.indexOf('@'))) {
|
||||||
winston.warn('You have no mongo username/password setup!');
|
winston.warn('You have no mongo username/password setup!');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ module.exports = function (module) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
|
const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
|
||||||
data.forEach(item => bulk.find({ _key: item[0], value: String(item[1]) }).remove());
|
data.forEach(item => bulk.find({ _key: item[0], value: String(item[1]) }).delete());
|
||||||
await bulk.execute();
|
await bulk.execute();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -776,7 +776,7 @@ async function mergeBanHistory(history, targetUid, uids) {
|
|||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
key: '[[user:banned]]',
|
key: '[[user:banned]]',
|
||||||
value: cur.reason,
|
value: validator.escape(String(cur.reason)),
|
||||||
labelClass: 'danger',
|
labelClass: 'danger',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ Hooks.fire = async function (hook, params) {
|
|||||||
winston.warn(`[plugins] Unknown hookType: ${hookType}, hook : ${hook}`);
|
winston.warn(`[plugins] Unknown hookType: ${hookType}, hook : ${hook}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (params && typeof params === 'object' && !params.hasOwnProperty('caller')) {
|
||||||
|
const als = require('../als');
|
||||||
|
params.caller = als.getStore();
|
||||||
|
}
|
||||||
const result = await hookTypeToMethod[hookType](hook, hookList, params);
|
const result = await hookTypeToMethod[hookType](hook, hookList, params);
|
||||||
|
|
||||||
if (hook !== 'action:plugins.firehook' && hook !== 'filter:plugins.firehook') {
|
if (hook !== 'action:plugins.firehook' && hook !== 'filter:plugins.firehook') {
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ function onConnection(socket) {
|
|||||||
onConnect(socket);
|
onConnect(socket);
|
||||||
socket.onAny((event, ...args) => {
|
socket.onAny((event, ...args) => {
|
||||||
const payload = { data: [event].concat(args) };
|
const payload = { data: [event].concat(args) };
|
||||||
onMessage(socket, payload);
|
const als = require('../als');
|
||||||
|
als.run({ uid: socket.uid }, onMessage, socket, payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ module.exports = function (User) {
|
|||||||
throw new Error('[[error:no-user]]');
|
throw new Error('[[error:no-user]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
await plugins.hooks.fire('static:user.delete', { uid: uid });
|
await plugins.hooks.fire('static:user.delete', { uid: uid, userData: userData });
|
||||||
await deleteVotes(uid);
|
await deleteVotes(uid);
|
||||||
await deleteChats(uid);
|
await deleteChats(uid);
|
||||||
await User.auth.revokeAllSessions(uid);
|
await User.auth.revokeAllSessions(uid);
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ function setupExpressApp(app) {
|
|||||||
app.use(middleware.addHeaders);
|
app.use(middleware.addHeaders);
|
||||||
app.use(middleware.processRender);
|
app.use(middleware.processRender);
|
||||||
auth.initialize(app, middleware);
|
auth.initialize(app, middleware);
|
||||||
|
const als = require('./als');
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
als.run({ uid: req.uid }, next);
|
||||||
|
});
|
||||||
app.use(middleware.autoLocale); // must be added after auth middlewares are added
|
app.use(middleware.autoLocale); // must be added after auth middlewares are added
|
||||||
|
|
||||||
const toobusy = require('toobusy-js');
|
const toobusy = require('toobusy-js');
|
||||||
|
|||||||
@@ -487,7 +487,15 @@ describe('authentication', () => {
|
|||||||
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(res.statusCode, 403);
|
assert.equal(res.statusCode, 403);
|
||||||
assert.equal(body, '[[error:user-banned-reason, spammer]]');
|
delete body.timestamp;
|
||||||
|
assert.deepStrictEqual(body, {
|
||||||
|
banned_until: 0,
|
||||||
|
banned_until_readable: '',
|
||||||
|
expiry: 0,
|
||||||
|
expiry_readable: '',
|
||||||
|
reason: 'spammer',
|
||||||
|
uid: 6,
|
||||||
|
});
|
||||||
user.bans.unban(bannedUser.uid, (err) => {
|
user.bans.unban(bannedUser.uid, (err) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
const expiry = Date.now() + 10000;
|
const expiry = Date.now() + 10000;
|
||||||
@@ -496,7 +504,8 @@ describe('authentication', () => {
|
|||||||
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(res.statusCode, 403);
|
assert.equal(res.statusCode, 403);
|
||||||
assert.equal(body, `[[error:user-banned-reason-until, ${utils.toISOString(expiry)}, No reason given.]]`);
|
assert(body.banned_until);
|
||||||
|
assert(body.reason, '[[user:info.banned-no-reason]]');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ describe('Sorted Set methods', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work with big arrays (length > 100) ', async function () {
|
it('should work with big arrays (length > 100) ', async function () {
|
||||||
this.timeout(50000);
|
this.timeout(100000);
|
||||||
const keys = [];
|
const keys = [];
|
||||||
for (let i = 0; i < 400; i++) {
|
for (let i = 0; i < 400; i++) {
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe('emailer', () => {
|
|||||||
assert.equal(output, text);
|
assert.equal(output, text);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 2000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ describe('Notifications', () => {
|
|||||||
assert(nids.includes(nid));
|
assert(nids.includes(nid));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, 1500);
|
}, 3000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user