works for board recent and global recent, fixed lastPostId issue and IP display issue

just neds a second pair of eyes to check its not leaking IPs anywhere
merge-requests/208/head
Thomas Lynch 4 years ago
parent e063f20507
commit 6f64448b33
  1. 5
      controllers/pages.js
  2. 32
      db/posts.js
  3. 9
      gulp/res/css/style.css
  4. 39
      gulp/res/js/live.js
  5. 1
      gulp/res/js/localstorage.js
  6. 1
      gulpfile.js
  7. 1
      helpers/sessionrefresh.js
  8. 67
      models/forms/makepost.js
  9. 23
      models/pages/globalmanage/recent.js
  10. 25
      models/pages/manage/recent.js
  11. 48
      socketio.js
  12. 9
      views/pages/globalmanagerecent.pug
  13. 8
      views/pages/managerecent.pug

@ -49,7 +49,7 @@ router.get('/randombanner', randombanner); //random banner
//board manage pages
router.get('/:board/manage/reports.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageReports);
router.get('/:board/manage/recent.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageRecent);
router.get('/:board/manage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageRecent);
router.get('/:board/manage/bans.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageBans);
router.get('/:board/manage/logs.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageLogs);
router.get('/:board/manage/settings.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageSettings);
@ -62,8 +62,7 @@ router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, session
//global manage pages
router.get('/globalmanage/reports.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageReports);
router.get('/globalmanage/bans.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageBans);
router.get('/globalmanage/recent.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageRecent);
router.get('/globalmanage/recent.json', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, (req, res, next) => { res.json({}) })
router.get('/globalmanage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageRecent);
router.get('/globalmanage/boards.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), globalManageBoards);
router.get('/globalmanage/globallogs.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageLogs);
router.get('/globalmanage/news.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageNews);

@ -4,8 +4,8 @@ const Mongo = require(__dirname+'/db.js')
, Boards = require(__dirname+'/boards.js')
, Stats = require(__dirname+'/stats.js')
, db = Mongo.db.collection('posts')
, { quoteLimit, previewReplies, stickyPreviewReplies, statsCountOnionUsers
, early404Replies, early404Fraction } = require(__dirname+'/../configs/main.js');
, { quoteLimit, previewReplies, stickyPreviewReplies, statsCountOnionUsers,
ipHashPermLevel, early404Replies, early404Fraction } = require(__dirname+'/../configs/main.js');
module.exports = {
@ -22,29 +22,49 @@ module.exports = {
return Math.ceil(threadsBefore/10) || 1; //1 because 0 threads before is page 1
},
getGlobalRecent: (offset=0, limit=20, ip) => {
getGlobalRecent: (offset=0, limit=20, ip, permLevel) => {
//global recent posts for recent section of global manage page
const query = {};
const projection = {
'salt': 0,
'password': 0,
'reports': 0,
};
if (ip instanceof RegExp) {
query['ip.single'] = ip;
} else if (typeof ip === 'string') {
query['ip.raw'] = ip;
}
return db.find(query).sort({
if (permLevel > ipHashPermLevel) {
projection['ip.raw'] = 0;
}
return db.find(query, {
projection
}).sort({
'_id': -1
}).skip(offset).limit(limit).toArray();
},
getBoardRecent: (offset=0, limit=20, ip, board) => {
getBoardRecent: (offset=0, limit=20, ip, board, permLevel) => {
const query = {
board
};
const projection = {
'salt': 0,
'password': 0,
'globalreports': 0,
};
if (ip instanceof RegExp) {
query['ip.single'] = ip;
} else if (typeof ip === 'string') {
query['ip.raw'] = ip;
}
return db.find(query).sort({
if (permLevel > ipHashPermLevel) {
projection['ip.raw'] = 0;
}
return db.find(query, {
projection
}).sort({
'_id': -1
}).skip(offset).limit(limit).toArray();
},

@ -213,6 +213,10 @@ pre {
flex-flow: row wrap;
}
.wrapbar + form hr:first-of-type {
margin-top: 1px;
}
.pages, #livetext, #threadstats {
box-sizing: border-box;
padding: 10px;
@ -222,6 +226,10 @@ pre {
margin-bottom: 5px;
}
.pages.jsonly {
padding: 8px;
}
a, a:visited, a.post-name {
text-decoration: underline;
color: var(--link-color);
@ -350,6 +358,7 @@ p {
background: black;
color: black;
cursor: none;
padding: 0 1px;
}
.spoiler:hover, .spoiler:hover a {

@ -8,29 +8,41 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
let supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window;
const livecolor = document.getElementById('livecolor');
const liveElem = document.getElementById('livetext');
const livetext = (isThread || isGlobalRecent) && liveElem ? liveElem.childNodes[1] : null;
const livetext = (isThread || isRecent) && liveElem ? liveElem.childNodes[1] : null;
let room = liveElem && liveElem.dataset.room;
const permLevel = liveElem ? liveElem.dataset.permLevel : 4;
const updateButton = document.getElementById('updatepostsbutton');
const updateLive = (message, color, showRelativeTime) => {
livecolor.style.backgroundColor = color;
livetext.nodeValue = `${message}`;
}
let lastPostId;
let lastPostIds = {};
let liveTimeout;
const anchors = document.getElementsByClassName('anchor');
if (anchors.length > 0) {
lastPostId = anchors[anchors.length - 1].id;
const postContainers = document.getElementsByClassName('post-container');
for (let i = 0; i < postContainers.length; i++) {
const postContainer = postContainers[i];
const { board, postId } = postContainer.dataset;
lastPostIds[board] = Math.max((lastPostIds[board] || 0), postId);
}
console.log(lastPostIds)
const newPost = (data) => {
//insert at end of thread, but insert at top for globalmanage
console.log('got new post', data);
lastPostId = data.postId;
const postData = data;
lastPostIds[postData.board] = postData.postId;
//create a new post
const postHtml = post({ post: postData, modview:isModView, globalmanage:isGlobalRecent, upLevel:isThread });
const postHtml = post({
ipHashPermLevel,
permLevel,
post: postData,
modview: isModView,
manage: (isRecent && !isGlobalRecent),
globalmanage: isGlobalRecent,
upLevel: isThread
});
let insertPoint;
if (isGlobalRecent) {
if (isRecent) {
const firstHr = document.querySelector('hr');
const newHr = document.createElement('hr');
const threadWrapper = document.createElement('div');
@ -61,7 +73,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
}
const newReply = document.createElement('a');
const space = document.createTextNode(' ');
newReply.href = `/${postData.board}/${(isModView || isGlobalRecent) ? 'manage/' : ''}thread/${postData.thread || postData.postId}.html#${postData.postId}`;
newReply.href = `/${postData.board}/${(isModView || isRecent) ? 'manage/' : ''}thread/${postData.thread || postData.postId}.html#${postData.postId}`;
newReply.textContent = `>>${postData.postId}`;
newReply.classList.add('quote');
replies.appendChild(newReply);
@ -105,8 +117,11 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
} catch (e) {
console.error(e);
}
if (json && json.replies && json.replies.length > 0) {
newPosts = json.replies.filter(r => r.postId > lastPostId); //filter to only newer posts
const postsList = (json && json.replies) ? json.replies : json;
if (postsList && Array.isArray(postsList) && postsList.length > 0) {
newPosts = postsList.filter(r => {
return r.postId > (lastPostIds[r.board] || 0);
}); //filter to only newer posts
if (newPosts.length > 0) {
for (let i = 0; i < newPosts.length; i++) {
newPost(newPosts[i]);
@ -240,7 +255,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
scrollSetting.checked = scrollEnabled;
scrollSetting.addEventListener('change', toggleScroll, false);
if (isThread || isGlobalRecent) {
if (isThread || isRecent) {
updateButton.addEventListener('click', forceUpdate);
liveEnabled ? enableLive() : disableLive();
}

@ -3,6 +3,7 @@ const isThread = /\/\w+\/thread\/\d+.html/.test(window.location.pathname);
const isModView = /\/\w+\/manage\/(thread\/)?(index|\d+).html/.test(window.location.pathname);
const isManage = /\/(\w+\/manage|globalmanage)\/(recent|reports|bans|boards|logs|settings|banners|accounts|news).html/.test(window.location.pathname);
const isGlobalRecent = window.location.pathname === '/globalmanage/recent.html';
const isRecent = isGlobalRecent || window.location.pathname.endsWith('/manage/recent.html');
function setLocalStorage(key, value) {
try {

@ -280,6 +280,7 @@ const codeThemes = ['${codeThemes.join("', '")}'];
const captchaType = '${configs.captchaOptions.type}';
const captchaGridSize = ${configs.captchaOptions.grid.size};
const SERVER_TIMEZONE = '${Intl.DateTimeFormat().resolvedOptions().timeZone}';
const ipHashPermLevel = ${configs.ipHashPermLevel};
const settings = ${JSON.stringify(configs.frontendScriptDefault)};
`;
fs.writeFileSync('gulp/res/js/locals.js', locals);

@ -9,7 +9,6 @@ module.exports = async (req, res, next) => {
res.locals = {};
}
res.locals.user = await cache.get(`users:${req.session.user}`);
if (!res.locals.user) {
const account = await Accounts.findOne(req.session.user);
if (!account) {

@ -557,41 +557,44 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
}
}
const projectedPost = {
'date': data.date,
'name': data.name,
'country': data.country,
'board': req.params.board,
'tripcode': data.tripcode,
'capcode': data.capcode,
'subject': data.subject,
'message': data.message,
'nomarkup': data.nomarkup,
'thread': data.thread,
'postId': postId,
'email': data.email,
'spoiler': data.spoiler,
'banmessage': null,
'userId': data.userId,
'files': data.files,
'reports': [],
'globalreports': [],
'quotes': data.quotes,
'backlinks': [],
'replyposts': 0,
'replyfiles': 0,
'sticky': data.sticky,
'locked': data.locked,
'bumplocked': data.bumplocked,
'cyclic': data.cyclic,
}
if (data.thread) {
//only emit for replies and with some omissions
const projectedPost = {
'date': data.date,
'name': data.name,
'country': data.country,
'board': req.params.board,
'tripcode': data.tripcode,
'capcode': data.capcode,
'subject': data.subject,
'message': data.message,
'nomarkup': data.nomarkup,
'thread': data.thread,
'postId': postId,
'email': data.email,
'spoiler': data.spoiler,
'banmessage': null,
'userId': data.userId,
'files': data.files,
'reports': [],
'globalreports': [],
'quotes': data.quotes,
'backlinks': [],
'replyposts': 0,
'replyfiles': 0,
'sticky': data.sticky,
'locked': data.locked,
'bumplocked': data.bumplocked,
'cyclic': data.cyclic,
}
//dont emit thread to this socket, because the room onyl exists when the thread is open
Socketio.emitRoom(`${res.locals.board._id}-${data.thread}`, 'newPost', projectedPost);
const { raw, single } = data.ip;
Socketio.emitRoom('globalmanage-recent-hashed', 'newPost', { ...projectedPost, ip: { single, raw: null } });
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { single, raw } });
}
const { raw, single } = data.ip;
//but emit it to manage pages because they need to get all posts through socket including thread
Socketio.emitRoom('globalmanage-recent-hashed', 'newPost', { ...projectedPost, ip: { single, raw: null } });
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { single, raw } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-hashed`, 'newPost', { ...projectedPost, ip: { single, raw: null } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-raw`, 'newPost', { ...projectedPost, ip: { single, raw } });
//now add other pages to be built in background
if (enableCaptcha) {

@ -12,19 +12,22 @@ module.exports = async (req, res, next) => {
let posts;
try {
posts = await Posts.getGlobalRecent(offset, limit, ipMatch);
posts = await Posts.getGlobalRecent(offset, limit, ipMatch, res.locals.permLevel);
} catch (err) {
return next(err)
}
res
.set('Cache-Control', 'private, max-age=5')
.render('globalmanagerecent', {
csrf: req.csrfToken(),
posts,
page,
ip: ipMatch ? req.query.ip : null,
queryString,
});
res.set('Cache-Control', 'private, max-age=5');
if (req.path.endsWith('.json')) {
res.json(posts.reverse());
} else {
res.render('globalmanagerecent', {
csrf: req.csrfToken(),
posts,
page,
ip: ipMatch ? req.query.ip : null,
queryString,
});
}
}

@ -19,20 +19,23 @@ module.exports = async (req, res, next) => {
let posts;
try {
posts = await Posts.getBoardRecent(offset, limit, ip, req.params.board);
posts = await Posts.getBoardRecent(offset, limit, ip, req.params.board, res.locals.permLevel);
} catch (err) {
return next(err)
}
res
.set('Cache-Control', 'private, max-age=5')
.render('managerecent', {
csrf: req.csrfToken(),
posts,
page,
postId,
queryIp: ip ? req.query.ip : null,
queryString,
});
res.set('Cache-Control', 'private, max-age=5');
if (req.path.endsWith('.json')) {
res.json(posts.reverse());
} else {
res.render('managerecent', {
csrf: req.csrfToken(),
posts,
page,
postId,
queryIp: ip ? req.query.ip : null,
queryString,
});
}
}

@ -1,12 +1,8 @@
'use strict';
const { redis: redisConfig, ipHashPermLevel } = require(__dirname+'/configs/main.js')
, roomRegex = /[a-z0-9]+-\d+/i
, roomPermsMap = {
'globalmanage-recent-hashed': 1,
'globalmanage-recent-raw': ipHashPermLevel,
}
, authedRooms = new Set(Object.keys(roomPermsMap));
, hasPerms = require(__dirname+'/helpers/checks/hasperms.js')
, roomRegex = /^(?<roomBoard>[a-z0-9]+)-(?<roomName>[a-z0-9-]+)$/i;
module.exports = {
@ -30,13 +26,41 @@ module.exports = {
startRooms: () => {
module.exports.io.on('connection', socket => {
socket.on('room', room => {
if ((!roomRegex.test(room) && !authedRooms.has(room)) //if not a valid room name
|| (authedRooms.has(room) && (!socket.request.locals.user //or the room requires auth and no session
|| socket.request.locals.user.authLevel > roomPermsMap[room]))) { //or not enough perms for that room
return socket.disconnect(true); //disconnect them
const roomMatch = room.match(roomRegex);
if (roomMatch && roomMatch.groups) {
const { roomBoard, roomName } = roomMatch.groups;
const { user } = socket.request.locals;
let requiredAuth = 4; //default level is 4, anyone cos of public rooms
let authLevel = user ? user.authLevel : 4;
/* todo: maybe this could be a bit more flexible to support
other *-hashed/raw pages, but its good for now */
if (room === 'globalmanage-recent-raw'
|| room === 'globalmanage-recent-hashed') {
//if its globalmanage, level 1 required
requiredAuth = 1;
} else if (roomName === 'manage-recent-hashed'
|| roomName === 'manage-recent-raw') {
requiredAuth = 3; //board mod minimum
if (user && authLevel === 4) {
if (user.ownedBoards.includes(board)) {
authLevel = 2; //user is BO
} else if (user.modBoards.includes(board)) {
authLevel = 3; //user is mod
}
}
}
if (room.endsWith('-raw')) {
//if its a -raw room, prioritise ipHashPermLevel
requiredAuth = Math.min(requiredAuth, ipHashPermLevel);
}
if (authLevel <= requiredAuth) {
//user has perms to join
socket.join(room);
return socket.send('joined');
}
}
socket.join(room); //otherwise join the room
socket.send('joined'); //send joined so frontend knows to show "connected"
//otherwise just disconnect them
socket.disconnect(true);
});
});
},

@ -10,10 +10,11 @@ block content
br
.wrapbar
+globalmanagenav('recent')
.jsonly#livetext(data-room=`globalmanage-recent-${permLevel > ipHashPermLevel ? 'hashed' : 'raw'}`)
.dot#livecolor
| Connecting...
input.postform-style.ml-5.di#updatepostsbutton(type='button' value='Update')
if page === 1
.jsonly#livetext(data-perm-level=permLevel data-room=`globalmanage-recent-${permLevel > ipHashPermLevel ? 'hashed' : 'raw'}`)
.dot#livecolor
| Connecting...
input.postform-style.ml-5.di#updatepostsbutton(type='button' value='Update')
form(action=`/forms/global/actions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
if posts.length === 0

@ -10,7 +10,13 @@ block head
block content
+boardheader('Recent Posts')
br
+managenav('recent')
.wrapbar
+managenav('recent')
if page === 1
.jsonly#livetext(data-perm-level=permLevel data-room=`${board._id}-manage-recent-${permLevel > ipHashPermLevel ? 'hashed' : 'raw'}`)
.dot#livecolor
| Connecting...
input.postform-style.ml-5.di#updatepostsbutton(type='button' value='Update')
hr(size=1)
form(action=`/forms/board/${board._id}/modactions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)

Loading…
Cancel
Save