ref #426 ip "cloaking"

still todo migration
"ips" will make more sense for staff now
qrange/hrange no longer need to be stored
bypass still work like before. will have .BP suffix, normal ips are .IP
filtering and stuff still works
bans page will now show .*'s in the cloaked view for range bans
in future version, this allows (even for those who cant see raw ips):
  - modlog, bans, post hisory filters including per-range
  - directly input ips/range cloak to ban, without selecting a post
  - upgrading existing bans from single to ranges
merge-requests/341/head
Thomas Lynch 2 years ago
parent 0b01673e06
commit 159e67b430
  1. 8
      db/bans.js
  2. 64
      db/posts.js
  3. 5
      helpers/decodequeryip.js
  4. 19
      helpers/processip.js
  5. 14
      models/forms/banposter.js
  6. 8
      models/forms/makepost.js
  7. 11
      models/pages/globalmanage/logs.js
  8. 2
      models/pages/manage/recent.js
  9. 2
      schedules/tasks/ips.js
  10. 7
      views/mixins/ban.pug
  11. 4
      views/mixins/post.pug
  12. 2
      views/mixins/report.pug
  13. 2
      views/pages/globalmanagelogs.pug
  14. 2
      views/pages/managelogs.pug
  15. 2
      views/pages/managerecent.pug

@ -10,9 +10,13 @@ module.exports = {
find: (ip, board) => {
let ipQuery;
if (typeof ip === 'object') { //object with hash and ranges in bancheck
if (typeof ip === 'object') {
ipQuery = {
'$in': [ip.single, ip.qrange, ip.hrange] //gets single and range ban in 1 query
'$in': [
ip.single, //full ip
ip.single.split('.').slice(0,2).join('.'), //qrange
ip.single.split('.').slice(0,1).join('.'), //hrange
],
}
} else {
ipQuery = ip;

@ -1,6 +1,7 @@
'use strict';
const Mongo = require(__dirname+'/db.js')
, { isIP } = require('net')
, Boards = require(__dirname+'/boards.js')
, Stats = require(__dirname+'/stats.js')
, db = Mongo.db.collection('posts')
@ -35,10 +36,12 @@ module.exports = {
} else {
projection['globalreports'] = 0;
}
if (ip instanceof RegExp) {
query['ip.single'] = ip;
} else if (typeof ip === 'string') {
query['ip.raw'] = ip;
if (ip != null) {
if (isIP(ip)) {
query['ip.raw'] = ip;
} else {
query['ip.single'] = ip;
}
}
if (permLevel > config.get.ipHashPermLevel) {
projection['ip.raw'] = 0;
@ -54,21 +57,6 @@ module.exports = {
}).sort({
'_id': -1
}).skip(offset).limit(limit).toArray();
posts.forEach(p => {
//kill me
p.ip.single = p.ip.single.slice(-10);
p.ip.qrange = p.ip.qrange.slice(-10);
p.ip.hrange = p.ip.hrange.slice(-10);
if (board) {
p.reports.forEach(r => {
r.ip.single = r.ip.single.slice(-10);
});
} else {
p.globalreports.forEach(r => {
r.ip.single = r.ip.single.slice(-10);
});
}
});
return posts;
},
@ -545,14 +533,6 @@ module.exports = {
},
'board': board
}, { projection }).toArray();
posts.forEach(p => {
p.ip.single = p.ip.single.slice(-10);
p.ip.qrange = p.ip.qrange.slice(-10);
p.ip.hrange = p.ip.hrange.slice(-10);
p.reports.forEach(r => {
r.ip.single = r.ip.single.slice(-10);
});
});
return posts;
},
@ -571,26 +551,20 @@ module.exports = {
'$exists': true
}
}
if (ip instanceof RegExp) {
query['$or'] = [
{ 'ip.single': ip },
{ 'globalreports.ip.single': ip }
];
} else if (typeof ip === 'string') {
query['$or'] = [
{ 'ip.raw': ip },
{ 'globalreports.ip.raw': ip }
];
if (ip != null) {
if (isIP(ip)) {
query['$or'] = [
{ 'ip.single': ip },
{ 'globalreports.ip.single': ip }
];
} else {
query['$or'] = [
{ 'ip.raw': ip },
{ 'globalreports.ip.raw': ip }
];
}
}
const posts = await db.find(query, { projection }).skip(offset).limit(limit).toArray();
posts.forEach(p => {
p.ip.single = p.ip.single.slice(-10);
p.ip.qrange = p.ip.qrange.slice(-10);
p.ip.hrange = p.ip.hrange.slice(-10);
p.globalreports.forEach(r => {
r.ip.single = r.ip.single.slice(-10);
});
});
return posts;
},

@ -8,10 +8,9 @@ module.exports = (query, permLevel) => {
const { ipHashPermLevel } = config.get;
if (query.ip && typeof query.ip === 'string') {
const decoded = decodeURIComponent(query.ip);
if (permLevel <= ipHashPermLevel && (isIP(decoded) || decoded.match(/[a-z0-9]{24}/i))) { //if perms to view raw ip or bypass, allow querying
if (permLevel <= ipHashPermLevel || !isIP(decoded)) {
//if they have perm to view raw IP, or its NOT a raw ip, return
return decoded;
} else if (decoded.length === 10) { //otherwise, only allow last 10 char substring
return new RegExp(`${escapeRegExp(decoded)}$`);
}
}
return null; //else, no ip filter

@ -12,10 +12,8 @@ module.exports = (req, res, next) => {
if (res.locals.anonymizer) {
const pseudoIp = res.locals.preFetchedBypassId || req.signedCookies.bypassid;
res.locals.ip = {
raw: pseudoIp,
single: pseudoIp,
qrange: pseudoIp,
hrange: pseudoIp,
raw: `${pseudoIp}.BP`,
single: `${pseudoIp}.BP`,
};
return next();
}
@ -31,20 +29,21 @@ module.exports = (req, res, next) => {
zeroElide: false,
zeroPad: false,
});
let qrange = ''
, hrange = '';
let qrange
, hrange
, single;
if (ipKind === 'ipv4') {
qrange = createCIDR(ipStr, 24).toString();
hrange = createCIDR(ipStr, 16).toString();
single = `${hashIp(hrange).substring(0,6)}.${hashIp(qrange).substring(0,3)}.${hashIp(ipStr).substring(0,3)}.IP`;
} else {
qrange = createCIDR(ipStr, 64).toString();
hrange = createCIDR(ipStr, 48).toString();
single = `${hashIp(hrange).substring(0,8)}.${hashIp(qrange).substring(0,7)}.${hashIp(ipStr).substring(0,7)}.IP`;
}
res.locals.ip = {
raw: ipHashPermLevel === -1 ? hashIp(ipStr) : ipStr,
single: hashIp(ipStr),
qrange: hashIp(qrange),
hrange: hashIp(hrange),
raw: ipHashPermLevel === -1 ? single : ipStr,
single,
}
next();
} catch(e) {

@ -26,15 +26,21 @@ module.exports = async (req, res, next) => {
const thisIpPosts = ipPosts[ip];
let type = 'single';
let banIp = {
single: ip,
raw: thisIpPosts[0].ip.raw
single: thisIpPosts[0].ip.single,
raw: thisIpPosts[0].ip.raw,
};
if (req.body.ban_h) {
type = 'half';
banIp.single = thisIpPosts[0].ip.hrange;
banIp.single = thisIpPosts[0].ip.single
.split('.')
.slice(0,1)
.join('.');
} else if (req.body.ban_q) {
type = 'quarter';
banIp.single = thisIpPosts[0].ip.qrange;
banIp.single = thisIpPosts[0].ip.single
.split('.')
.slice(0,2)
.join('.');
}
bans.push({
type,

@ -614,13 +614,13 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
}
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: single.slice(-10), raw: null } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-hashed`, 'newPost', { ...projectedPost, ip: { single: single.slice(-10), raw: null } });
Socketio.emitRoom('globalmanage-recent-hashed', 'newPost', { ...projectedPost, ip: { single, raw: null } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-hashed`, 'newPost', { ...projectedPost, ip: { single, raw: null } });
if (ipHashPermLevel > -1) {
//small optimisation for boards where this is manually set to -1 for privacy, no need to emit to rooms that cant be accessed
//even if they are empty it will create extra communication noise in redis, socket adapter, etc.
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { single: single.slice(-10), raw } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-raw`, 'newPost', { ...projectedPost, ip: { single: single.slice(-10), raw } });
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { single, raw } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-raw`, 'newPost', { ...projectedPost, ip: { single, raw } });
}
//now add other pages to be built in background

@ -3,6 +3,7 @@
const { Modlogs } = require(__dirname+'/../../../db/')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, { isIP } = require('net')
, limit = 50;
module.exports = async (req, res, next) => {
@ -19,10 +20,12 @@ module.exports = async (req, res, next) => {
filter.board = uri;
}
const ipMatch = decodeQueryIP(req.query, res.locals.permLevel);
if (ipMatch instanceof RegExp) {
filter['ip.single'] = ipMatch;
} else if (typeof ipMatch === 'string') {
filter['ip.raw'] = ipMatch;
if (ipMatch != null) {
if (isIP(ipMatch)) {
filter['ip.raw'] = ipMatch;
} else {
filter['ip.single'] = ipMatch;
}
}
let logs, maxPage;

@ -13,7 +13,7 @@ module.exports = async (req, res, next) => {
if (postId && +postId === parseInt(postId) && Number.isSafeInteger(+postId)) {
const fetchedPost = await Posts.getPost(req.params.board, +postId, true);
if (fetchedPost) {
ip = decodeQueryIP({ ip: fetchedPost.ip.single.slice(-10) }, res.locals.permlevel);
ip = decodeQueryIP({ ip: fetchedPost.ip.single }, res.locals.permlevel);
}
}

@ -33,8 +33,6 @@ module.exports = {
'ip.pruned': true,
'ip.raw': randomIP,
'ip.single': randomIP,
'ip.qrange': randomIP,
'ip.hrange': randomIP,
}
}
}

@ -10,8 +10,11 @@ mixin ban(ban, banpage)
else
| Global
td= ban.reason
- const ip = permLevel > ipHashPermLevel ? ban.ip.single.slice(-10) : ban.ip.raw;
td #{ip}
- const ip = permLevel > ipHashPermLevel ? ban.ip.single : ban.ip.raw;
if permLevel > ipHashPermLevel
td #{ip}#{ban.type === 'half' ? '.*.*' : (ban.type === 'quarter' ? '.*' : '')}
else
td #{ip}
td #{ban.type}
td #{(!banpage || ban.showUser === true) ? ban.issuer : 'Hidden User'}
- const banDate = new Date(ban.date);

@ -20,12 +20,12 @@ mixin post(post, truncate, manage=false, globalmanage=false, ban=false, overboar
input.post-check(type='checkbox', name='checkedposts' value=post.postId)
|
if manage
- const ip = permLevel > ipHashPermLevel ? post.ip.single.slice(-10) : post.ip.raw;
- const ip = permLevel > ipHashPermLevel ? post.ip.single : post.ip.raw;
a.bold(href=`${upLevel ? '../' : ''}recent.html?ip=${encodeURIComponent(ip)}`) [#{ip}]
else if modview
a.bold(href=`${upLevel ? '../' : ''}recent.html?postid=${post.postId}`) [+]
else if globalmanage
- const ip = permLevel > ipHashPermLevel ? post.ip.single.slice(-10) : post.ip.raw;
- const ip = permLevel > ipHashPermLevel ? post.ip.single : post.ip.raw;
a.bold(href=`?ip=${encodeURIComponent(ip)}`) [#{ip}]
|
if !post.thread

@ -2,7 +2,7 @@ mixin report(r, manage=false)
.reports.post-container
input.post-check(type='checkbox', name='checkedreports' value=r.id)
|
- const ip = permLevel > ipHashPermLevel ? r.ip.single.slice(-10) : r.ip.raw;
- const ip = permLevel > ipHashPermLevel ? r.ip.single : r.ip.raw;
a.bold(href=`${manage ? 'recent.html' : ''}?ip=${encodeURIComponent(ip)}`) [#{ip}]
|
- const reportDate = new Date(r.date);

@ -52,7 +52,7 @@ block content
|
a(href=`?username=${log.user}`) [+]
td
- const logIp = permLevel > ipHashPermLevel ? log.ip.single.slice(-10) : log.ip.raw;
- const logIp = permLevel > ipHashPermLevel ? log.ip.single : log.ip.raw;
a(href=`recent.html?ip=${encodeURIComponent(logIp)}`) #{logIp}
|
a(href=`?ip=${encodeURIComponent(logIp)}`) [+]

@ -40,7 +40,7 @@ block content
|
a(href=`?username=${log.user}`) [+]
td
- const logIp = permLevel > ipHashPermLevel ? log.ip.single.slice(-10) : log.ip.raw;
- const logIp = permLevel > ipHashPermLevel ? log.ip.single : log.ip.raw;
| #{logIp}
td #{log.actions}
td

@ -23,7 +23,7 @@ block content
if posts.length === 0
p No posts.
else
- const ip = permLevel > ipHashPermLevel ? posts[0].ip.single.slice(-10) : posts[0].ip.raw;
- const ip = permLevel > ipHashPermLevel ? posts[0].ip.single : posts[0].ip.raw;
if postId || (queryIp && queryIp === ip)
h4.no-m-p Post history for #{ip}
|

Loading…
Cancel
Save