Compare commits

...

29 Commits

Author SHA1 Message Date
Barış Soner Uşaklı
bba9e7c0f7 refactor: show invalid uri 2022-04-22 12:14:34 -04:00
Barış Soner Uşaklı
8047900170 refactor: skip content length check if submitting from post-queue 2022-04-21 12:32:09 -04:00
Barış Soner Uşaklı
32b38643f8 feat: add response:helpers.notAllowed 2022-04-04 17:36:45 -04:00
Barış Soner Uşaklı
37ba8a2c8e fix: use asset_base_url 2022-03-31 11:15:58 -04:00
Barış Soner Uşaklı
35f0c559c0 fix: dont overwrite asset_base_url 2022-03-31 11:15:02 -04:00
Barış Soner Uşaklı
d19a273ce9 fix: #10360, only take top level posts 2022-03-02 15:25:27 -05:00
Julian Lam
7624af5769 feat: add feature flag to disable verification emails, closes #9996 2022-01-19 22:22:23 -05:00
Opliko
00a6b05f89 Stop colors.js vandalism (#10131)
* fix: pin colors to 1.4.0

* fix: exclude colors from renovate updates
2022-01-10 09:20:54 -05:00
Barış Soner Uşaklı
b46dcd9243 fix: use component instead of class name 2021-12-23 11:07:57 -05:00
Barış Soner Uşaklı
1a375000a7 refactor: only pass qs 2021-12-16 13:47:12 -05:00
Barış Soner Uşaklı
1f8f2e9168 feat: pass in all query params to category search filter 2021-12-16 13:33:37 -05:00
Barış Soner Uşaklı
edae9522b5 feat: add data to filter:categories.search 2021-12-16 09:54:09 -05:00
Barış Soner Uşaklı
7ff2b7fbb1 chore: up mentions to fix crash
https://github.com/julianlam/nodebb-plugin-mentions/issues/156
2021-12-16 09:53:11 -05:00
Misty (Bot)
22e74dc0bb chore: incrementing version number - v1.18.6 2021-11-10 20:45:24 +00:00
Misty (Bot)
27acf19325 Merge commit 'a0f0dd021b6b275d377aec83956a8fc0a0e7aa52' into v1.18.x 2021-11-10 20:45:19 +00:00
Barış Soner Uşaklı
0f29433baf Merge branch 'v1.18.x' of https://github.com/NodeBB/NodeBB into v1.18.x 2021-10-27 14:28:28 -04:00
Barış Soner Uşaklı
ebe7f11d0b merge 2021-10-27 14:25:37 -04:00
Misty (Bot)
c248805165 chore: incrementing version number - v1.18.5 2021-10-27 16:47:58 +00:00
Misty (Bot)
830cddfb40 Merge commit 'bf20965f0bd68a46de4de4e3f274a6fbffa28073' into v1.18.x 2021-10-27 16:47:54 +00:00
Barış Soner Uşaklı
abbbc3d7c2 chore: up persona 2021-10-07 09:24:44 -04:00
Misty (Bot)
8593ea87e9 chore: incrementing version number - v1.18.4 2021-10-06 17:59:38 +00:00
Misty (Bot)
1d401329ee Merge commit 'f4e62fb1cdbeade062ef3172dbdfaaedacb14925' into v1.18.x 2021-10-06 17:59:33 +00:00
Misty (Bot)
9e52236973 chore: incrementing version number - v1.18.3 2021-09-22 17:01:44 +00:00
Misty (Bot)
eff03e4b57 Merge commit '9855429445d1856a28b3f845e4bad788f5860914' into v1.18.x 2021-09-22 17:01:34 +00:00
Misty (Bot)
854c078b73 chore: incrementing version number - v1.18.2 2021-09-08 16:27:00 +00:00
Misty (Bot)
36653525bd Merge commit '507517fce5248ad37f6a239fce16ae92d0e0f5b0' into v1.18.x 2021-09-08 16:26:58 +00:00
Misty (Bot)
0409403f5b chore: incrementing version number - v1.18.1 2021-09-03 15:04:06 +00:00
Misty (Bot)
839673d321 Merge commit 'b73d8849ab768fb665271a1b3cffe8211e6083f0' into v1.18.x 2021-09-03 15:03:59 +00:00
Misty (Bot)
d220d1d461 chore: incrementing version number - v1.18.0 2021-08-25 20:29:55 +00:00
18 changed files with 57 additions and 25 deletions

View File

@@ -138,6 +138,7 @@
"disableEmailSubscriptions": 0, "disableEmailSubscriptions": 0,
"emailConfirmInterval": 10, "emailConfirmInterval": 10,
"removeEmailNotificationImages": 0, "removeEmailNotificationImages": 0,
"sendValidationEmail": 1,
"includeUnverifiedEmails": 0, "includeUnverifiedEmails": 0,
"emailPrompt": 1, "emailPrompt": 1,
"inviteExpiration": 7, "inviteExpiration": 7,

View File

@@ -2,7 +2,7 @@
"name": "nodebb", "name": "nodebb",
"license": "GPL-3.0", "license": "GPL-3.0",
"description": "NodeBB Forum", "description": "NodeBB Forum",
"version": "1.18.5", "version": "1.18.6",
"homepage": "http://www.nodebb.org", "homepage": "http://www.nodebb.org",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -41,7 +41,7 @@
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"cli-graph": "^3.2.2", "cli-graph": "^3.2.2",
"clipboard": "^2.0.6", "clipboard": "^2.0.6",
"colors": "^1.4.0", "colors": "1.4.0",
"commander": "^7.1.0", "commander": "^7.1.0",
"compare-versions": "3.6.0", "compare-versions": "3.6.0",
"compression": "^1.7.4", "compression": "^1.7.4",
@@ -89,7 +89,7 @@
"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.4", "nodebb-plugin-markdown": "8.14.4",
"nodebb-plugin-mentions": "3.0.2", "nodebb-plugin-mentions": "3.0.3",
"nodebb-plugin-spam-be-gone": "0.7.11", "nodebb-plugin-spam-be-gone": "0.7.11",
"nodebb-rewards-essentials": "0.2.0", "nodebb-rewards-essentials": "0.2.0",
"nodebb-theme-lavender": "5.3.1", "nodebb-theme-lavender": "5.3.1",
@@ -182,4 +182,4 @@
"url": "https://github.com/barisusakli" "url": "https://github.com/barisusakli"
} }
] ]
} }

View File

@@ -38,7 +38,8 @@
"subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. <code>0</code> for midnight, <code>17</code> for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.<br /> The approximate server time is: <span id=\"serverTime\"></span><br /> The next daily digest is scheduled to be sent <span id=\"nextDigestTime\"></span>", "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. <code>0</code> for midnight, <code>17</code> for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.<br /> The approximate server time is: <span id=\"serverTime\"></span><br /> The next daily digest is scheduled to be sent <span id=\"nextDigestTime\"></span>",
"notifications.remove-images": "Remove images from email notifications", "notifications.remove-images": "Remove images from email notifications",
"require-email-address": "Require new users to specify an email address", "require-email-address": "Require new users to specify an email address",
"require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. <strong>It does not ensure user will enter a real email address, nor even an address they own.</strong>", "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. <strong>It does not ensure user will enter a real email address, nor even an address they own.</strong>",
"send-validation-email": "Send validation emails when an email is added or changed",
"include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails",
"include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). <strong>Enable this setting at your own risk</strong> &ndash; sending emails to unverified addresses may be a violation of regional anti-spam laws.", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). <strong>Enable this setting at your own risk</strong> &ndash; sending emails to unverified addresses may be a violation of regional anti-spam laws.",
"prompt": "Prompt users to enter or confirm their emails", "prompt": "Prompt users to enter or confirm their emails",

View File

@@ -424,7 +424,7 @@ ajaxify = window.ajaxify || {};
}; };
ajaxify.loadTemplate = function (template, callback) { ajaxify.loadTemplate = function (template, callback) {
require([config.assetBaseUrl + '/templates/' + template + '.js'], callback, function (err) { require([config.asset_base_url + '/templates/' + template + '.js'], callback, function (err) {
console.error('Unable to load template: ' + template); console.error('Unable to load template: ' + template);
throw err; throw err;
}); });

View File

@@ -132,7 +132,7 @@ define('forum/topic/posts', [
if (!isPreviousPostAdded && data.posts[0].selfPost) { if (!isPreviousPostAdded && data.posts[0].selfPost) {
return ajaxify.go('post/' + data.posts[0].pid); return ajaxify.go('post/' + data.posts[0].pid);
} }
const repliesSelector = $('[component="post"]:not([data-index=0]), [component="topic/event"]'); const repliesSelector = $('[component="topic"]>[component="post"]:not([data-index=0]), [component="topic"]>[component="topic/event"]');
createNewPosts(data, repliesSelector, direction, false, function (html) { createNewPosts(data, repliesSelector, direction, false, function (html) {
if (html) { if (html) {
html.addClass('new'); html.addClass('new');

View File

@@ -285,7 +285,7 @@ define('forum/topic/threadTools', [
threadEl.find('[component="post"][data-uid="' + app.user.uid + '"].deleted [component="post/tools"]').toggleClass('hidden', isLocked); threadEl.find('[component="post"][data-uid="' + app.user.uid + '"].deleted [component="post/tools"]').toggleClass('hidden', isLocked);
$('.topic-header [component="topic/locked"]').toggleClass('hidden', !data.isLocked); $('[component="topic/labels"] [component="topic/locked"]').toggleClass('hidden', !data.isLocked);
$('[component="post/tools"] .dropdown-menu').html(''); $('[component="post/tools"] .dropdown-menu').html('');
ajaxify.data.locked = data.isLocked; ajaxify.data.locked = data.isLocked;
@@ -334,7 +334,7 @@ define('forum/topic/threadTools', [
components.get('topic/pin').toggleClass('hidden', data.pinned).parent().attr('hidden', data.pinned ? '' : null); components.get('topic/pin').toggleClass('hidden', data.pinned).parent().attr('hidden', data.pinned ? '' : null);
components.get('topic/unpin').toggleClass('hidden', !data.pinned).parent().attr('hidden', !data.pinned ? '' : null); components.get('topic/unpin').toggleClass('hidden', !data.pinned).parent().attr('hidden', !data.pinned ? '' : null);
const icon = $('.topic-header [component="topic/pinned"]'); const icon = $('[component="topic/labels"] [component="topic/pinned"]');
icon.toggleClass('hidden', !data.pinned); icon.toggleClass('hidden', !data.pinned);
if (data.pinned) { if (data.pinned) {
icon.translateAttr('title', ( icon.translateAttr('title', (

View File

@@ -3,7 +3,7 @@
(function (factory) { (function (factory) {
function loadClient(language, namespace) { function loadClient(language, namespace) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
jQuery.getJSON([config.assetBaseUrl, 'language', language, namespace].join('/') + '.json?' + config['cache-buster'], function (data) { jQuery.getJSON([config.asset_base_url, 'language', language, namespace].join('/') + '.json?' + config['cache-buster'], function (data) {
const payload = { const payload = {
language: language, language: language,
namespace: namespace, namespace: namespace,

View File

@@ -6,7 +6,8 @@
"packageRules": [ "packageRules": [
{ {
"updateTypes": ["minor", "patch", "pin", "digest"], "updateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true "automerge": true,
"excludePackageNames": ["colors"]
} }
] ]
} }

View File

@@ -18,6 +18,7 @@ module.exports = function (Categories) {
let cids = await findCids(query, data.hardCap); let cids = await findCids(query, data.hardCap);
const result = await plugins.hooks.fire('filter:categories.search', { const result = await plugins.hooks.fire('filter:categories.search', {
data: data,
cids: cids, cids: cids,
uid: uid, uid: uid,
}); });

View File

@@ -14,6 +14,7 @@ const apiController = module.exports;
const relative_path = nconf.get('relative_path'); const relative_path = nconf.get('relative_path');
const upload_url = nconf.get('upload_url'); const upload_url = nconf.get('upload_url');
const asset_base_url = nconf.get('asset_base_url');
const socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket']; const socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket'];
const socketioOrigins = nconf.get('socket.io:origins'); const socketioOrigins = nconf.get('socket.io:origins');
const websocketAddress = nconf.get('socket.io:address') || ''; const websocketAddress = nconf.get('socket.io:address') || '';
@@ -22,7 +23,8 @@ apiController.loadConfig = async function (req) {
const config = { const config = {
relative_path, relative_path,
upload_url, upload_url,
assetBaseUrl: `${relative_path}/assets`, asset_base_url,
assetBaseUrl: asset_base_url, // deprecate in 1.20.x
siteTitle: validator.escape(String(meta.config.title || meta.config.browserTitle || 'NodeBB')), siteTitle: validator.escape(String(meta.config.title || meta.config.browserTitle || 'NodeBB')),
browserTitle: validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB')), browserTitle: validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB')),
titleLayout: (meta.config.titleLayout || '{pageTitle} | {browserTitle}').replace(/{/g, '&#123;').replace(/}/g, '&#125;'), titleLayout: (meta.config.titleLayout || '{pageTitle} | {browserTitle}').replace(/{/g, '&#123;').replace(/}/g, '&#125;'),

View File

@@ -123,6 +123,11 @@ helpers.buildTerms = function (url, term, query) {
helpers.notAllowed = async function (req, res, error) { helpers.notAllowed = async function (req, res, error) {
({ error } = await plugins.hooks.fire('filter:helpers.notAllowed', { req, res, error })); ({ error } = await plugins.hooks.fire('filter:helpers.notAllowed', { req, res, error }));
await plugins.hooks.fire('response:helpers.notAllowed', { req, res, error });
if (res.headersSent) {
return;
}
if (req.loggedIn || req.uid === -1) { if (req.loggedIn || req.uid === -1) {
if (res.locals.isAPI) { if (res.locals.isAPI) {
if (req.originalUrl.startsWith(`${relative_path}/api/v3`)) { if (req.originalUrl.startsWith(`${relative_path}/api/v3`)) {

View File

@@ -32,6 +32,7 @@ helpers.buildBodyClass = function (req, res, templateData = {}) {
try { try {
p = slugify(decodeURIComponent(p)); p = slugify(decodeURIComponent(p));
} catch (err) { } catch (err) {
winston.error(`Error decoding URI: ${p}`);
winston.error(err.stack); winston.error(err.stack);
p = ''; p = '';
} }

View File

@@ -94,6 +94,9 @@ function loadConfig(configFile) {
nconf.set('secure', urlObject.protocol === 'https:'); nconf.set('secure', urlObject.protocol === 'https:');
nconf.set('use_port', !!urlObject.port); nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath); nconf.set('relative_path', relativePath);
if (!nconf.get('asset_base_url')) {
nconf.set('asset_base_url', `${relativePath}/assets`);
}
nconf.set('port', nconf.get('PORT') || nconf.get('port') || urlObject.port || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); nconf.set('port', nconf.get('PORT') || nconf.get('port') || urlObject.port || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
// cookies don't provide isolation by port: http://stackoverflow.com/a/16328399/122353 // cookies don't provide isolation by port: http://stackoverflow.com/a/16328399/122353

View File

@@ -53,10 +53,10 @@ module.exports = function (SocketCategories) {
const result = await categories.search({ const result = await categories.search({
uid: uid, uid: uid,
query: data.search, query: data.search,
qs: data.query,
paginate: false, paginate: false,
}); });
let matchedCids = result.categories.map(c => c.cid); let matchedCids = result.categories.map(c => c.cid);
// no need to filter if all 3 states are used // no need to filter if all 3 states are used
const filterByWatchState = !Object.values(categories.watchStates) const filterByWatchState = !Object.values(categories.watchStates)

View File

@@ -88,7 +88,9 @@ module.exports = function (Topics) {
Topics.checkTitle(data.title); Topics.checkTitle(data.title);
await Topics.validateTags(data.tags, data.cid, uid); await Topics.validateTags(data.tags, data.cid, uid);
data.tags = await Topics.filterTags(data.tags, data.cid); data.tags = await Topics.filterTags(data.tags, data.cid);
Topics.checkContent(data.content); if (!data.fromQueue) {
Topics.checkContent(data.content);
}
const [categoryExists, canCreate, canTag] = await Promise.all([ const [categoryExists, canCreate, canTag] = await Promise.all([
categories.exists(data.cid), categories.exists(data.cid),
@@ -165,13 +167,13 @@ module.exports = function (Topics) {
data.cid = topicData.cid; data.cid = topicData.cid;
await guestHandleValid(data); await guestHandleValid(data);
if (!data.fromQueue) {
await user.isReadyToPost(uid, data.cid);
}
if (data.content) { if (data.content) {
data.content = utils.rtrim(data.content); data.content = utils.rtrim(data.content);
} }
Topics.checkContent(data.content); if (!data.fromQueue) {
await user.isReadyToPost(uid, data.cid);
Topics.checkContent(data.content);
}
// For replies to scheduled topics, don't have a timestamp older than topic's itself // For replies to scheduled topics, don't have a timestamp older than topic's itself
if (topicData.scheduled) { if (topicData.scheduled) {

View File

@@ -2,6 +2,7 @@
'use strict'; 'use strict';
const nconf = require('nconf'); const nconf = require('nconf');
const winston = require('winston');
const user = require('./index'); const user = require('./index');
const utils = require('../utils'); const utils = require('../utils');
@@ -69,6 +70,11 @@ UserEmail.sendValidationEmail = async function (uid, options) {
* - force, sends email even if it is too soon to send another * - force, sends email even if it is too soon to send another
*/ */
if (meta.config.sendValidationEmail !== 1) {
winston.verbose(`[user/email] Validation email for uid ${uid} not sent due to config settings`);
return;
}
options = options || {}; options = options || {};
// Fallback behaviour (email passed in as second argument) // Fallback behaviour (email passed in as second argument)
@@ -110,6 +116,7 @@ UserEmail.sendValidationEmail = async function (uid, options) {
await db.expireAt(`confirm:${confirm_code}`, Math.floor((Date.now() / 1000) + (60 * 60 * 24))); await db.expireAt(`confirm:${confirm_code}`, Math.floor((Date.now() / 1000) + (60 * 60 * 24)));
const username = await user.getUserField(uid, 'username'); const username = await user.getUserField(uid, 'username');
winston.verbose(`[user/email] Validation email for uid ${uid} sent to ${options.email}`);
events.log({ events.log({
type: 'email-confirmation-sent', type: 'email-confirmation-sent',
uid, uid,

View File

@@ -20,13 +20,6 @@
<input type="text" class="form-control input-lg" id="email:from_name" data-field="email:from_name" placeholder="NodeBB" /><br /> <input type="text" class="form-control input-lg" id="email:from_name" data-field="email:from_name" placeholder="NodeBB" /><br />
</div> </div>
<div class="checkbox">
<label for="removeEmailNotificationImages" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" id="removeEmailNotificationImages" data-field="removeEmailNotificationImages" name="removeEmailNotificationImages" />
<span class="mdl-switch__label">[[admin/settings/email:notifications.remove-images]]</span>
</label>
</div>
<div class="checkbox"> <div class="checkbox">
<label for="requireEmailAddress" class="mdl-switch mdl-js-switch mdl-js-ripple-effect"> <label for="requireEmailAddress" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" id="requireEmailAddress" data-field="requireEmailAddress" name="requireEmailAddress" /> <input class="mdl-switch__input" type="checkbox" id="requireEmailAddress" data-field="requireEmailAddress" name="requireEmailAddress" />
@@ -35,6 +28,13 @@
</div> </div>
<p class="help-block">[[admin/settings/email:require-email-address-warning]]</p> <p class="help-block">[[admin/settings/email:require-email-address-warning]]</p>
<div class="checkbox">
<label for="sendValidationEmail" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" id="sendValidationEmail" data-field="sendValidationEmail" name="sendValidationEmail" />
<span class="mdl-switch__label">[[admin/settings/email:send-validation-email]]</span>
</label>
</div>
<div class="checkbox"> <div class="checkbox">
<label for="includeUnverifiedEmails" class="mdl-switch mdl-js-switch mdl-js-ripple-effect"> <label for="includeUnverifiedEmails" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" id="includeUnverifiedEmails" data-field="includeUnverifiedEmails" name="includeUnverifiedEmails" /> <input class="mdl-switch__input" type="checkbox" id="includeUnverifiedEmails" data-field="includeUnverifiedEmails" name="includeUnverifiedEmails" />
@@ -50,6 +50,13 @@
</label> </label>
</div> </div>
<p class="help-block">[[admin/settings/email:prompt-help]]</p> <p class="help-block">[[admin/settings/email:prompt-help]]</p>
<div class="checkbox">
<label for="removeEmailNotificationImages" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" id="removeEmailNotificationImages" data-field="removeEmailNotificationImages" name="removeEmailNotificationImages" />
<span class="mdl-switch__label">[[admin/settings/email:notifications.remove-images]]</span>
</label>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -38,6 +38,7 @@ nconf.defaults({
const urlObject = url.parse(nconf.get('url')); const urlObject = url.parse(nconf.get('url'));
const relativePath = urlObject.pathname !== '/' ? urlObject.pathname : ''; const relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
nconf.set('relative_path', relativePath); nconf.set('relative_path', relativePath);
nconf.set('asset_base_url', `${relativePath}/assets`);
nconf.set('upload_path', path.join(nconf.get('base_dir'), nconf.get('upload_path'))); nconf.set('upload_path', path.join(nconf.get('base_dir'), nconf.get('upload_path')));
nconf.set('upload_url', '/assets/uploads'); nconf.set('upload_url', '/assets/uploads');
nconf.set('url_parsed', urlObject); nconf.set('url_parsed', urlObject);