add iid action to force captcha

add parsing of pixelLog by iid without canvas and coords
This commit is contained in:
HF 2022-08-04 01:30:30 +02:00
parent fb79e521fc
commit f90c542046
7 changed files with 292 additions and 86 deletions

View File

@ -22,9 +22,9 @@ async function submitIIDAction(
}
function ModIIDtools() {
const [iIDAction, selectIIDAction] = useState('ban');
const [iIDAction, selectIIDAction] = useState('givecaptcha');
const [iid, selectIid] = useState('');
const [resp, setResp] = useState(null);
const [resp, setResp] = useState('');
const [submitting, setSubmitting] = useState(false);
return (
@ -62,29 +62,31 @@ function ModIIDtools() {
selectIid(newIid);
}}
/>
<button
type="button"
onClick={() => {
if (submitting) {
return;
}
setSubmitting(true);
submitIIDAction(
iIDAction,
iid,
(ret) => {
setSubmitting(false);
setResp(ret);
},
);
}}
>
{(submitting) ? '...' : t`Submit`}
</button>
</p>
<button
type="button"
onClick={() => {
if (submitting) {
return;
}
setSubmitting(true);
submitIIDAction(
iIDAction,
iid,
(ret) => {
setSubmitting(false);
setResp(ret);
},
);
}}
>
{(submitting) ? '...' : t`Submit`}
</button>
<textarea
rows="10"
cols="20"
style={{
width: '100%',
}}
rows={(resp) ? resp.split('\n').length : 10}
id="iparea"
value={resp}
readOnly

View File

@ -88,12 +88,11 @@ async function submitWatchAction(
function ModWatchtools() {
const [selectedCanvas, selectCanvas] = useState(0);
const [colors, setColors] = useState([]);
const [tlcoords, selectTLCoords] = useState(keepState.tlcoords);
const [brcoords, selectBRCoords] = useState(keepState.brcoords);
const [interval, selectInterval] = useState(keepState.interval);
const [sortBy, setSortBy] = useState(0);
const [table, setTable] = useState(null);
const [table, setTable] = useState({});
const [iid, selectIid] = useState(keepState.iid);
const [resp, setResp] = useState(null);
const [submitting, setSubmitting] = useState(false);
@ -110,24 +109,8 @@ function ModWatchtools() {
selectCanvas(canvasId);
}, [canvasId]);
useEffect(() => {
const colorsRGB = canvases[selectedCanvas].colors;
const newColors = [];
for (let i = 0; i < colorsRGB.length; i += 1) {
const [r, g, b] = colorsRGB[i];
newColors.push(`rgb(${r},${g},${b})`);
}
setColors(newColors);
}, [selectedCanvas]);
let columns;
let types;
let rows;
if (table) {
columns = table.columns;
types = table.types;
rows = table.rows;
}
const { columns, types, rows } = table;
const cidColumn = (types) ? (types.indexOf('cid')) : -1;
return (
<div style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
@ -310,7 +293,7 @@ function ModWatchtools() {
{(submitting) ? '...' : t`Get Users`}
</button>
<br />
{(table) && (
{(rows && columns && types) && (
<React.Fragment key="pxltable">
<div className="modaldivider" />
<table
@ -350,14 +333,23 @@ function ModWatchtools() {
);
}
case 'clr': {
const color = colors[val];
const style = (color)
? { backgroundColor: color }
: undefined;
return (<td style={style}>{val}</td>);
const cid = (cidColumn > 0)
? row[cidColumn] : selectedCanvas;
const rgb = canvases[cid]
&& canvases[cid].colors
&& canvases[cid].colors[val];
if (!rgb) {
return (<td>{val}</td>);
}
const color = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
return (
<td style={{ backgroundColor: color }}>{val}</td>
);
}
case 'coord': {
const { ident } = canvases[selectedCanvas];
const cid = (cidColumn > 0)
? row[cidColumn] : selectedCanvas;
const ident = canvases[cid] && canvases[cid].ident;
const coords = `./#${ident},${val},47`;
return (
<td>
@ -378,6 +370,12 @@ function ModWatchtools() {
/></td>
);
}
case 'cid': {
const cid = (cidColumn > 0)
? row[cidColumn] : selectedCanvas;
const ident = canvases[cid] && canvases[cid].ident;
return (<td>{ident}</td>);
}
case 'user': {
const seperator = val.lastIndexOf(',');
if (seperator === -1) {
@ -405,6 +403,6 @@ function ModWatchtools() {
}
// possible types:
// 'coord', 'clr', 'ts', 'user', 'uuid', 'string', 'number', 'flag'
// 'coord', 'clr', 'ts', 'user', 'uuid', 'string', 'number', 'flag', 'cid'
export default React.memo(ModWatchtools);

View File

@ -14,6 +14,7 @@ import { validateCoorRange } from '../utils/validation';
import CanvasCleaner from './CanvasCleaner';
import { Blacklist, Whitelist, RegUser } from '../data/sql';
import { getIPofIID } from '../data/sql/IPInfo';
import { forceCaptcha } from '../data/redis/captcha';
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json';
import {
@ -21,6 +22,8 @@ import {
protectCanvasArea,
} from './Image';
import {
getIIDSummary,
getIIDPixels,
getSummaryFromArea,
getPixelsFromArea,
} from './parsePixelLog';
@ -30,7 +33,7 @@ import rollbackCanvasArea from './rollback';
* Execute IP based actions (banning, whitelist, etc.)
* @param action what to do with the ip
* @param ip already sanizized ip
* @return true if successful
* @return text of success
*/
export async function executeIPAction(action, ips, logger = null) {
const ipArray = ips.split('\n');
@ -40,11 +43,11 @@ export async function executeIPAction(action, ips, logger = null) {
if (action === 'iidtoip') {
const resIp = await getIPofIID(ip);
const idPart = ip.slice(0, ip.indexOf('-'));
const iidPart = ip.slice(0, ip.indexOf('-'));
if (resIp) {
out += `${idPart}: ${resIp}\n`;
out += `${iidPart}: ${resIp}\n`;
} else {
out += `${idPart}: N/A\n`;
out += `${iidPart}: N/A\n`;
}
continue;
}
@ -94,6 +97,36 @@ export async function executeIPAction(action, ips, logger = null) {
return out;
}
/*
* Execute IID based actions
* @param action what to do with the iid
* @param iid already sanizized iid
* @return text of success
*/
export async function executeIIDAction(action, iid, logger = null) {
const ip = await getIPofIID(iid);
if (!ip) {
return `Could not resolve ${iid}`;
}
const iidPart = iid.slice(0, iid.indexOf('-'));
switch (action) {
case 'givecaptcha': {
const succ = await forceCaptcha(ip);
if (succ === null) {
return 'Captchas are deactivated on this server.';
}
if (succ) {
return `Forced captcha on ${iidPart}`;
}
return `${iidPart} would have gotten captcha anyway`;
}
default:
return `Failed to ${action} ${iid}`;
}
}
/*
* Execute Image based actions (upload, protect, etc.)
* @param action what to do with the image
@ -197,9 +230,7 @@ export async function executeWatchAction(
const ts = parseInt(time, 10);
const canvas = canvases[canvasid];
let error = null;
if (!ulcoor || !brcoor) {
error = 'Not all coordinates defined';
} else if (!canvas) {
if (!canvas) {
error = 'Invalid canvas selected';
} else if (!action) {
error = 'No cleanaction given';
@ -210,6 +241,28 @@ export async function executeWatchAction(
return { info: error };
}
let ret;
if (!ulcoor && !brcoor && iid) {
if (action === 'summary') {
ret = await getIIDSummary(
iid,
time,
);
}
if (action === 'all') {
ret = await getIIDPixels(
iid,
time,
);
}
if (typeof ret === 'string') {
return { info: ret };
}
if (typeof ret !== 'undefined') {
return ret;
}
}
const parseCoords = validateCoorRange(ulcoor, brcoor, canvas.size);
if (typeof parseCoords === 'string') {
return { info: parseCoords };
@ -224,31 +277,27 @@ export async function executeWatchAction(
}
if (action === 'summary') {
const ret = await getSummaryFromArea(
ret = await getSummaryFromArea(
canvasid,
x, y, u, v,
time,
iid,
);
if (typeof ret === 'string') {
return { info: ret };
}
return ret;
}
if (action === 'all') {
const ret = await getPixelsFromArea(
ret = await getPixelsFromArea(
canvasid,
x, y, u, v,
time,
iid,
);
if (typeof ret === 'string') {
return { info: ret };
}
}
if (typeof ret === 'string') {
return { info: ret };
}
if (typeof ret !== 'undefined') {
return ret;
}
return { info: 'Invalid action given' };
}
@ -272,9 +321,7 @@ export async function executeCleanerAction(
}
const canvas = canvases[canvasid];
let error = null;
if (!ulcoor || !brcoor) {
error = 'Not all coordinates defined';
} else if (!canvas) {
if (!canvas) {
error = 'Invalid canvas selected';
} else if (!action) {
error = 'No cleanaction given';
@ -319,9 +366,7 @@ export async function executeProtAction(
}
const canvas = canvases[canvasid];
let error = null;
if (!ulcoor || !brcoor) {
error = 'Not all coordinates defined';
} else if (!canvas) {
if (!canvas) {
error = 'Invalid canvas selected';
} else if (!action) {
error = 'No imageaction given';
@ -392,9 +437,7 @@ export async function executeRollback(
}
const canvas = canvases[canvasid];
let error = null;
if (!ulcoor || !brcoor) {
error = 'Not all coordinates defined';
} else if (!canvas) {
if (!canvas) {
error = 'Invalid canvas selected';
} else if (!date) {
error = 'No date given';

View File

@ -39,6 +39,137 @@ function parseFile(cb) {
});
}
/*
* Get summary of pixels per canvas placed by iid
* @param iid Limit on one user (optional)
* @param time timestamp of when to start
* @return array of parsed pixel log lines
* string if error
*/
export async function getIIDSummary(
iid,
time,
) {
const filterIP = await getIPofIID(iid);
if (!filterIP) {
return 'Could not resolve IID to IP';
}
const cids = {};
try {
await parseFile((parts) => {
const [tsStr, ipFull,, cid, x, y,, clrStr] = parts;
const ts = parseInt(tsStr, 10);
if (ts >= time) {
const ip = getIPv6Subnet(ipFull);
if (ip === filterIP) {
const clr = parseInt(clrStr, 10);
let curVals = cids[cid];
if (!curVals) {
curVals = [0, 0, 0, 0, 0];
cids[cid] = curVals;
}
curVals[0] += 1;
curVals[1] = x;
curVals[2] = y;
curVals[3] = clr;
curVals[4] = ts;
}
}
});
} catch (err) {
return `Could not parse logfile: ${err.message}`;
}
const columns = ['rid', '#', 'canvas', 'last', 'clr', 'time'];
const types = ['number', 'number', 'cid', 'coord', 'clr', 'ts'];
const rows = [];
const cidKeys = Object.keys(cids);
for (let i = 0; i < cidKeys.length; i += 1) {
const cid = cidKeys[i];
const [pxls, x, y, clr, ts] = cids[cid];
rows.push([
i,
pxls,
cid,
`${x},${y}`,
clr,
ts,
]);
}
return {
columns,
types,
rows,
};
}
/*
* Get pixels by iid
* @param iid Limit on one user (optional)
* @param time timestamp of when to start
* @return array of parsed pixel log lines
* string if error
*/
export async function getIIDPixels(
iid,
time,
maxRows = 300,
) {
const filterIP = await getIPofIID(iid);
if (!filterIP) {
return 'Could not resolve IID to IP';
}
const pixels = [];
try {
await parseFile((parts) => {
const [tsStr, ipFull,, cid, x, y,, clrStr] = parts;
const ts = parseInt(tsStr, 10);
if (ts >= time) {
const ip = getIPv6Subnet(ipFull);
if (ip === filterIP) {
const clr = parseInt(clrStr, 10);
pixels.push([
cid,
x,
y,
clr,
ts,
]);
}
}
});
} catch (err) {
return `Could not parse logfile: ${err.message}`;
}
const pixelF = (pixels.length > maxRows)
? pixels.slice(maxRows * -1)
: pixels;
const columns = ['rid', 'canvas', 'coord', 'clr', 'time'];
const types = ['number', 'cid', 'coord', 'clr', 'ts'];
const rows = [];
for (let i = 0; i < pixelF.length; i += 1) {
const [cid, x, y, clr, ts] = pixelF[i];
rows.push([
i,
cid,
`${x},${y}`,
clr,
ts,
]);
}
return {
columns,
types,
rows,
};
}
/*
* Get summary of users placing in area of current day
* @param canvasId id of canvas
@ -86,15 +217,15 @@ export async function getSummaryFromArea(
const uid = parseInt(uidStr, 10);
let curVals = ips[ip];
if (!curVals) {
curVals = [0, ip, uid, 0, 0, 0, 0];
curVals = [0, uid, 0, 0, 0, 0];
ips[ip] = curVals;
uids.push(uid);
}
curVals[0] += 1;
curVals[3] = x;
curVals[4] = y;
curVals[5] = clr;
curVals[6] = ts;
curVals[2] = x;
curVals[3] = y;
curVals[4] = clr;
curVals[5] = ts;
}
});
} catch (err) {
@ -125,7 +256,8 @@ export async function getSummaryFromArea(
const rows = [];
for (let i = 0; i < ipKeys.length; i += 1) {
const [pxls, ip, uid, x, y, clr, ts] = ips[ipKeys[i]];
const ip = ipKeys[i];
const [pxls, uid, x, y, clr, ts] = ips[ip];
const row = [i, pxls];
if (printIIDs) {
const ipInfo = ip2Info.get(ip);
@ -217,7 +349,9 @@ export async function getPixelsFromArea(
const uid2Name = await getNamesToIds(uids);
const ip2Id = await getIdsToIps(ips);
const pixelF = (pixels.length > 300) ? pixels.slice(maxRows * -1) : pixels;
const pixelF = (pixels.length > maxRows)
? pixels.slice(maxRows * -1)
: pixels;
let printIIDs = false;
let printUsers = false;

View File

@ -141,7 +141,6 @@ export async function checkCaptchaSolution(
/*
* check if captcha is needed
*
* @param ip
* @return boolean true if needed
*/
@ -157,3 +156,18 @@ export async function needCaptcha(ip) {
logger.info(`CAPTCHA ${ip} got captcha`);
return true;
}
/*
* force ip to get captcha
* @param ip
* @return true if we triggered captcha
* false if user would have gotton one anyway
*/
export async function forceCaptcha(ip) {
if (CAPTCHA_TIME < 0) {
return null;
}
const key = `human:${getIPv6Subnet(ip)}`;
const ret = await redis.del(key);
return (ret > 0);
}

View File

@ -88,6 +88,9 @@ const IPInfo = sequelize.define('IPInfo', {
});
export async function getIPofIID(uuid) {
if (!uuid) {
return null;
}
let result = null;
try {
result = await IPInfo.findOne({

View File

@ -14,6 +14,7 @@ import { escapeMd } from '../../core/utils';
import logger, { modtoolsLogger } from '../../core/logger';
import {
executeIPAction,
executeIIDAction,
executeImageAction,
executeProtAction,
executeRollback,
@ -115,6 +116,17 @@ router.post('/', upload.single('image'), async (req, res, next) => {
res.status(200).json(ret);
return;
}
if (req.body.iidaction) {
const {
iidaction, iid,
} = req.body;
const ret = await executeIIDAction(
iidaction,
iid,
);
res.status(200).send(ret);
return;
}
if (req.body.cleaneraction) {
const {
cleaneraction, ulcoor, brcoor, canvasid,