2020-11-29 13:22:14 +00:00
/ *
* functions for admintools
*
* /
/* eslint-disable no-await-in-loop */
import sharp from 'sharp' ;
import Sequelize from 'sequelize' ;
2022-08-06 03:55:27 +00:00
import isIPAllowed from './isAllowed' ;
2022-03-31 13:10:50 +00:00
import { validateCoorRange } from '../utils/validation' ;
import CanvasCleaner from './CanvasCleaner' ;
2022-08-06 03:55:27 +00:00
import { RegUser } from '../data/sql' ;
import {
cleanCacheForIP ,
} from '../data/redis/isAllowedCache' ;
2022-08-03 23:30:30 +00:00
import { forceCaptcha } from '../data/redis/captcha' ;
2022-08-06 03:55:27 +00:00
import {
isWhitelisted ,
whitelistIP ,
unwhitelistIP ,
} from '../data/sql/Whitelist' ;
import {
getBanInfo ,
banIP ,
unbanIP ,
} from '../data/sql/Ban' ;
2022-08-06 03:59:33 +00:00
import {
getInfoToIp ,
getIPofIID ,
getIIDofIP ,
} from '../data/sql/IPInfo' ;
2020-11-29 13:22:14 +00:00
// eslint-disable-next-line import/no-unresolved
import canvases from './canvases.json' ;
import {
imageABGR2Canvas ,
protectCanvasArea ,
} from './Image' ;
2022-08-02 15:14:37 +00:00
import {
2022-08-03 23:30:30 +00:00
getIIDSummary ,
getIIDPixels ,
2022-08-02 15:14:37 +00:00
getSummaryFromArea ,
getPixelsFromArea ,
} from './parsePixelLog' ;
2020-11-29 13:22:14 +00:00
import rollbackCanvasArea from './rollback' ;
/ *
* Execute IP based actions ( banning , whitelist , etc . )
* @ param action what to do with the ip
* @ param ip already sanizized ip
2022-08-03 23:30:30 +00:00
* @ return text of success
2020-11-29 13:22:14 +00:00
* /
2022-03-31 13:10:50 +00:00
export async function executeIPAction ( action , ips , logger = null ) {
2022-08-06 03:59:33 +00:00
const valueArray = ips . split ( '\n' ) ;
2020-11-29 13:22:14 +00:00
let out = '' ;
2022-08-06 03:59:33 +00:00
for ( let i = 0 ; i < valueArray . length ; i += 1 ) {
const value = valueArray [ i ] . trim ( ) ;
if ( ! value ) {
2022-08-02 17:58:23 +00:00
continue ;
2020-11-29 13:22:14 +00:00
}
2022-08-02 17:58:23 +00:00
2022-08-06 03:59:33 +00:00
if ( logger ) logger ( ` ${ action } ${ value } ` ) ;
if ( action === 'iidtoip' ) {
const ip = await getIPofIID ( value ) ;
out += ( ip ) ? ` ${ ip } \n ` : ` ${ value } \n ` ;
2020-11-29 13:22:14 +00:00
continue ;
}
2022-08-06 03:59:33 +00:00
if ( action === 'iptoiid' ) {
const iid = await getIIDofIP ( value ) ;
out += ( iid ) ? ` ${ iid } \n ` : ` ${ value } \n ` ;
2020-11-29 13:22:14 +00:00
}
}
return out ;
}
2022-08-03 23:30:30 +00:00
/ *
* Execute IID based actions
* @ param action what to do with the iid
* @ param iid already sanizized iid
* @ return text of success
* /
2022-08-06 03:55:27 +00:00
export async function executeIIDAction (
action ,
iid ,
reason ,
expire ,
muid ,
logger = null ,
) {
2022-08-03 23:30:30 +00:00
const ip = await getIPofIID ( iid ) ;
if ( ! ip ) {
return ` Could not resolve ${ iid } ` ;
}
const iidPart = iid . slice ( 0 , iid . indexOf ( '-' ) ) ;
2022-08-06 03:59:33 +00:00
if ( logger ) logger ( ` ${ action } ${ iid } ${ ip } ` ) ;
2022-08-03 23:30:30 +00:00
switch ( action ) {
2022-08-06 03:55:27 +00:00
case 'status' : {
const allowed = await isIPAllowed ( ip , true ) ;
2022-08-06 03:59:33 +00:00
let out = ` Allowed to place: ${ allowed . allowed } \n ` ;
2022-08-06 03:55:27 +00:00
const info = await getInfoToIp ( ip ) ;
out += ` Country: ${ info . country } \n `
+ ` CIDR: ${ info . cidr } \n `
+ ` org: ${ info . org || 'N/A' } \n `
+ ` desc: ${ info . descr || 'N/A' } \n `
+ ` asn: ${ info . asn } \n `
+ ` proxy: ${ info . isProxy } \n ` ;
if ( info . pcheck ) {
const { pcheck } = info ;
out += ` pc: ${ pcheck . slice ( 0 , pcheck . indexOf ( ',' ) ) } \n ` ;
}
const whitelisted = await isWhitelisted ( ip ) ;
out += ` whitelisted: ${ whitelisted } \n ` ;
const ban = await getBanInfo ( ip ) ;
if ( ! ban ) {
out += 'banned: false\n' ;
} else {
2022-08-06 03:59:33 +00:00
out += 'banned: true\n'
+ ` reason: ${ ban . reason } \n ` ;
2022-08-06 03:55:27 +00:00
if ( ban . expires ) {
out += ` expires: ${ ban . expires . toLocaleString ( ) } \n ` ;
}
if ( ban . mod ) {
out += ` by: @[ ${ ban . mod . name } ]( ${ ban . mod . id } ) \n ` ;
}
}
return out ;
}
2022-08-03 23:30:30 +00:00
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 ` ;
}
2022-08-06 03:55:27 +00:00
case 'ban' : {
2022-08-06 03:59:33 +00:00
const expireTs = parseInt ( expire , 10 ) ;
if ( Number . isNaN ( expireTs ) || ( expireTs && expireTs < Date . now ( ) ) ) {
2022-08-06 03:55:27 +00:00
return 'No valid expiration time' ;
}
2022-08-06 03:59:33 +00:00
if ( ! reason || ! reason . trim ( ) ) {
2022-08-06 03:55:27 +00:00
return 'No reason specified' ;
}
2022-08-06 03:59:33 +00:00
const ret = await banIP ( ip , reason , expireTs || null , muid ) ;
2022-08-06 03:55:27 +00:00
if ( ret ) {
return 'Successfully banned user' ;
}
2022-08-06 03:59:33 +00:00
return 'Updated existing ban of user' ;
2022-08-06 03:55:27 +00:00
}
case 'unban' : {
const ret = await unbanIP ( ip ) ;
if ( ret ) {
return 'Successfully unbanned user' ;
}
return 'User is not banned' ;
}
2022-08-06 03:59:33 +00:00
case 'whitelist' : {
2022-08-06 03:55:27 +00:00
const ret = await whitelistIP ( ip ) ;
if ( ret ) {
await cleanCacheForIP ( ip ) ;
return 'Successfully whitelisted user' ;
}
return 'User is already whitelisted' ;
}
case 'unwhitelist' : {
const ret = await unwhitelistIP ( ip ) ;
if ( ret ) {
await cleanCacheForIP ( ip ) ;
return 'Successfully removed user from whitelist' ;
}
return 'User is not on whitelist' ;
}
2022-08-03 23:30:30 +00:00
default :
return ` Failed to ${ action } ${ iid } ` ;
}
}
2020-11-29 13:22:14 +00:00
/ *
* Execute Image based actions ( upload , protect , etc . )
* @ param action what to do with the image
* @ param file imagefile
* @ param coords coord sin X _Y format
* @ param canvasid numerical canvas id as string
* @ return [ ret , msg ] http status code and message
* /
export async function executeImageAction (
2022-03-31 13:10:50 +00:00
action ,
file ,
coords ,
canvasid ,
logger = null ,
2020-11-29 13:22:14 +00:00
) {
if ( ! coords ) {
return [ 403 , 'Coordinates not defined' ] ;
}
if ( ! canvasid ) {
return [ 403 , 'canvasid not defined' ] ;
}
const splitCoords = coords . trim ( ) . split ( '_' ) ;
if ( splitCoords . length !== 2 ) {
return [ 403 , 'Invalid Coordinate Format' ] ;
}
const [ x , y ] = splitCoords . map ( ( z ) => Math . floor ( Number ( z ) ) ) ;
const canvas = canvases [ canvasid ] ;
let error = null ;
if ( Number . isNaN ( x ) ) {
error = 'x is not a valid number' ;
} else if ( Number . isNaN ( y ) ) {
error = 'y is not a valid number' ;
} else if ( ! action ) {
error = 'No imageaction given' ;
} else if ( ! canvas ) {
error = 'Invalid canvas selected' ;
} else if ( canvas . v ) {
error = 'Can not upload Image to 3D canvas' ;
}
if ( error !== null ) {
return [ 403 , error ] ;
}
const canvasMaxXY = canvas . size / 2 ;
const canvasMinXY = - canvasMaxXY ;
if ( x < canvasMinXY || y < canvasMinXY
|| x >= canvasMaxXY || y >= canvasMaxXY ) {
return [ 403 , 'Coordinates are outside of canvas' ] ;
}
const protect = ( action === 'protect' ) ;
const wipe = ( action === 'wipe' ) ;
try {
const { data , info } = await sharp ( file . buffer )
. ensureAlpha ( )
. raw ( )
. toBuffer ( { resolveWithObject : true } ) ;
const pxlCount = await imageABGR2Canvas (
canvasid ,
x , y ,
data ,
info . width , info . height ,
wipe , protect ,
) ;
// eslint-disable-next-line max-len
2022-06-25 14:11:54 +00:00
if ( logger ) logger ( ` loaded image wth * ${ pxlCount } *pxls to # ${ canvas . ident } , ${ x } , ${ y } (+* ${ x } *+ \\ _+* ${ y } *+ - +* ${ x + info . width - 1 } *+ \\ _+* ${ y + info . height - 1 } *+) ` ) ;
2020-11-29 13:22:14 +00:00
return [
200 ,
2022-06-25 14:11:54 +00:00
` Successfully loaded image wth ${ pxlCount } pxls to ${ x } / ${ y } ` ,
2020-11-29 13:22:14 +00:00
] ;
} catch {
return [ 400 , 'Can not read image file' ] ;
}
}
2022-08-02 15:14:37 +00:00
/ *
* Check who placed on a canvas area
* @ param action if every pixel or summary should be returned
* @ param ulcoor coords of upper - left corner in X _Y format
* @ param brcoor coords of bottom - right corner in X _Y format
* @ param canvasid numerical canvas id as string
* @ return Object with { info , cols , rows }
* /
export async function executeWatchAction (
action ,
ulcoor ,
brcoor ,
time ,
iid ,
canvasid ,
) {
if ( ! canvasid ) {
return { info : 'canvasid not defined' } ;
}
const ts = parseInt ( time , 10 ) ;
const canvas = canvases [ canvasid ] ;
let error = null ;
2022-08-03 23:30:30 +00:00
if ( ! canvas ) {
2022-08-02 15:14:37 +00:00
error = 'Invalid canvas selected' ;
} else if ( ! action ) {
error = 'No cleanaction given' ;
} else if ( Number . isNaN ( ts ) ) {
error = 'Invalid time given' ;
}
if ( error ) {
return { info : error } ;
}
2022-08-03 23:30:30 +00:00
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 ;
}
}
2022-08-02 15:14:37 +00:00
const parseCoords = validateCoorRange ( ulcoor , brcoor , canvas . size ) ;
if ( typeof parseCoords === 'string' ) {
return { info : parseCoords } ;
}
const [ x , y , u , v ] = parseCoords ;
2022-08-02 17:58:23 +00:00
if ( ( u - x > 1000 || v - y > 1000 )
&& Date . now ( ) - ts > 5 * 60 * 1000
&& ! iid
) {
return { info : 'Cann not watch so many pixels' } ;
2022-08-02 15:14:37 +00:00
}
if ( action === 'summary' ) {
2022-08-03 23:30:30 +00:00
ret = await getSummaryFromArea (
2022-08-02 15:14:37 +00:00
canvasid ,
x , y , u , v ,
time ,
iid ,
) ;
}
if ( action === 'all' ) {
2022-08-03 23:30:30 +00:00
ret = await getPixelsFromArea (
2022-08-02 15:14:37 +00:00
canvasid ,
x , y , u , v ,
time ,
iid ,
) ;
2022-08-03 23:30:30 +00:00
}
if ( typeof ret === 'string' ) {
return { info : ret } ;
}
if ( typeof ret !== 'undefined' ) {
2022-08-02 17:58:23 +00:00
return ret ;
2022-08-02 15:14:37 +00:00
}
return { info : 'Invalid action given' } ;
}
2020-11-29 13:22:14 +00:00
/ *
2022-03-31 13:10:50 +00:00
* Execute actions for cleaning / filtering canvas
2020-11-29 13:22:14 +00:00
* @ param action what to do
* @ param ulcoor coords of upper - left corner in X _Y format
* @ param brcoor coords of bottom - right corner in X _Y format
* @ param canvasid numerical canvas id as string
* @ return [ ret , msg ] http status code and message
* /
2022-03-31 13:10:50 +00:00
export async function executeCleanerAction (
action ,
ulcoor ,
brcoor ,
canvasid ,
logger = null ,
2020-11-29 13:22:14 +00:00
) {
if ( ! canvasid ) {
return [ 403 , 'canvasid not defined' ] ;
}
2022-03-31 13:10:50 +00:00
const canvas = canvases [ canvasid ] ;
let error = null ;
2022-08-03 23:30:30 +00:00
if ( ! canvas ) {
2022-03-31 13:10:50 +00:00
error = 'Invalid canvas selected' ;
} else if ( ! action ) {
error = 'No cleanaction given' ;
}
if ( error ) {
return [ 403 , error ] ;
}
2020-11-29 13:22:14 +00:00
2022-03-31 13:10:50 +00:00
const parseCoords = validateCoorRange ( ulcoor , brcoor , canvas . size ) ;
if ( typeof parseCoords === 'string' ) {
return [ 403 , parseCoords ] ;
2020-11-29 13:22:14 +00:00
}
2022-03-31 13:10:50 +00:00
const [ x , y , u , v ] = parseCoords ;
error = CanvasCleaner . set ( canvasid , x , y , u , v , action ) ;
if ( error ) {
return [ 403 , error ] ;
2020-11-29 13:22:14 +00:00
}
2022-03-31 13:10:50 +00:00
// eslint-disable-next-line max-len
2022-06-25 14:11:54 +00:00
const report = ` set Canvas Cleaner to *" ${ action } "* from # ${ canvas . ident } , ${ x } , ${ y } to # ${ canvas . ident } , ${ u } , ${ v } ` ;
2022-03-31 13:10:50 +00:00
if ( logger ) logger ( report ) ;
return [ 200 , report ] ;
}
2020-11-29 13:22:14 +00:00
2022-03-31 13:10:50 +00:00
/ *
* Execute actions for protecting areas
* @ param action what to do
* @ param ulcoor coords of upper - left corner in X _Y format
* @ param brcoor coords of bottom - right corner in X _Y format
* @ param canvasid numerical canvas id as string
* @ return [ ret , msg ] http status code and message
* /
export async function executeProtAction (
action ,
ulcoor ,
brcoor ,
canvasid ,
logger = null ,
) {
if ( ! canvasid ) {
return [ 403 , 'canvasid not defined' ] ;
}
2020-11-29 13:22:14 +00:00
const canvas = canvases [ canvasid ] ;
let error = null ;
2022-08-03 23:30:30 +00:00
if ( ! canvas ) {
2020-11-29 13:22:14 +00:00
error = 'Invalid canvas selected' ;
2022-03-31 13:10:50 +00:00
} else if ( ! action ) {
error = 'No imageaction given' ;
2020-11-29 13:22:14 +00:00
} else if ( action !== 'protect' && action !== 'unprotect' ) {
error = 'Invalid action (must be protect or unprotect)' ;
}
if ( error !== null ) {
return [ 403 , error ] ;
}
2022-03-31 13:10:50 +00:00
const parseCoords = validateCoorRange ( ulcoor , brcoor , canvas . size ) ;
if ( typeof parseCoords === 'string' ) {
return [ 403 , parseCoords ] ;
2020-11-29 13:22:14 +00:00
}
2022-03-31 13:10:50 +00:00
const [ x , y , u , v ] = parseCoords ;
2020-11-29 13:22:14 +00:00
const width = u - x + 1 ;
const height = v - y + 1 ;
2022-03-31 13:10:50 +00:00
if ( width * height > 10000000 ) {
return [ 403 , 'Can not set protection to more than 10m pixels at onec' ] ;
}
2020-11-29 13:22:14 +00:00
const protect = action === 'protect' ;
const pxlCount = await protectCanvasArea (
canvasid ,
x ,
y ,
width ,
height ,
protect ,
) ;
2022-03-31 13:10:50 +00:00
if ( logger ) {
logger (
( protect )
2022-04-01 15:59:46 +00:00
// eslint-disable-next-line max-len
2022-06-25 14:11:54 +00:00
? ` protected * ${ width } *x* ${ height } * area at # ${ canvas . ident } , ${ x } , ${ y } with * ${ pxlCount } *pxls (+* ${ x } *+ \\ _+* ${ y } *+ - +* ${ u } *+ \\ _+* ${ v } *+) `
2022-04-01 15:59:46 +00:00
// eslint-disable-next-line max-len
2022-06-25 14:11:54 +00:00
: ` unprotect * ${ width } *x* ${ height } * area at # ${ canvas . ident } , ${ x } , ${ y } with * ${ pxlCount } *pxls (+* ${ x } *+ \\ _+* ${ y } *+ - +* ${ u } *+ \\ _+* ${ v } *+) ` ,
2022-03-31 13:10:50 +00:00
) ;
}
2020-11-29 13:22:14 +00:00
return [
200 ,
( protect )
// eslint-disable-next-line max-len
2022-06-25 13:40:22 +00:00
? ` Successfully protected ${ width } x ${ height } area at # ${ canvas . ident } , ${ x } , ${ y } with ${ pxlCount } pxls ( ${ ulcoor } - ${ brcoor } ) `
2020-11-29 13:22:14 +00:00
// eslint-disable-next-line max-len
2022-06-25 13:40:22 +00:00
: ` Successfully unprotected ${ width } x ${ height } area at # ${ canvas . ident } , ${ x } , ${ y } with ${ pxlCount } pxls ( ${ ulcoor } - ${ brcoor } ) ` ,
2020-11-29 13:22:14 +00:00
] ;
}
/ *
* Execute rollback
* @ param date in format YYYYMMdd
* @ param ulcoor coords of upper - left corner in X _Y format
* @ param brcoor coords of bottom - right corner in X _Y format
* @ param canvasid numerical canvas id as string
* @ return [ ret , msg ] http status code and message
* /
export async function executeRollback (
2022-03-31 13:10:50 +00:00
date ,
ulcoor ,
brcoor ,
canvasid ,
logger = null ,
2022-04-01 12:40:08 +00:00
isAdmin = false ,
2020-11-29 13:22:14 +00:00
) {
if ( ! canvasid ) {
return [ 403 , 'canvasid not defined' ] ;
}
const canvas = canvases [ canvasid ] ;
let error = null ;
2022-08-03 23:30:30 +00:00
if ( ! canvas ) {
2022-03-31 13:10:50 +00:00
error = 'Invalid canvas selected' ;
2020-11-29 13:22:14 +00:00
} else if ( ! date ) {
error = 'No date given' ;
2021-06-03 14:16:12 +00:00
} else if ( Number . isNaN ( Number ( date ) ) || date . length !== 8 ) {
2020-11-29 13:22:14 +00:00
error = 'Invalid date' ;
}
if ( error !== null ) {
return [ 403 , error ] ;
}
2022-03-31 13:10:50 +00:00
const parseCoords = validateCoorRange ( ulcoor , brcoor , canvas . size ) ;
if ( typeof parseCoords === 'string' ) {
return [ 403 , parseCoords ] ;
2020-11-29 13:22:14 +00:00
}
2022-03-31 13:10:50 +00:00
const [ x , y , u , v ] = parseCoords ;
2020-11-29 13:22:14 +00:00
const width = u - x + 1 ;
const height = v - y + 1 ;
2022-04-01 12:40:08 +00:00
if ( ! isAdmin && width * height > 1000000 ) {
return [ 403 , 'Can not rollback more than 1m pixels at once' ] ;
2020-11-29 13:22:14 +00:00
}
const pxlCount = await rollbackCanvasArea (
canvasid ,
x ,
y ,
width ,
height ,
date ,
) ;
2022-03-31 13:10:50 +00:00
if ( logger ) {
logger (
2020-11-29 13:22:14 +00:00
// eslint-disable-next-line max-len
2022-06-25 14:11:54 +00:00
` rolled back to * ${ date } * for * ${ width } *x* ${ height } * area at # ${ canvas . ident } , ${ x } , ${ y } with * ${ pxlCount } *pxls (+* ${ x } *+ \\ _+* ${ y } *+ - +* ${ u } *+ \\ _+* ${ v } *+) ` ,
2022-03-31 13:10:50 +00:00
) ;
}
2020-11-29 13:22:14 +00:00
return [
200 ,
// eslint-disable-next-line max-len
2022-04-01 15:59:46 +00:00
` Successfully rolled back to ${ date } for ${ width } x ${ height } area at # ${ canvas . ident } , ${ x } , ${ y } with ${ pxlCount } pxls ( ${ ulcoor } - ${ brcoor } ) ` ,
2020-11-29 13:22:14 +00:00
] ;
}
/ *
* Get list of mods
* @ return [ [ id1 , name2 ] , [ id2 , name2 ] , ... ] list
* /
export async function getModList ( ) {
const mods = await RegUser . findAll ( {
where : Sequelize . where ( Sequelize . literal ( 'roles & 1' ) , '!=' , 0 ) ,
attributes : [ 'id' , 'name' ] ,
raw : true ,
} ) ;
return mods . map ( ( mod ) => [ mod . id , mod . name ] ) ;
}
export async function removeMod ( userId ) {
if ( Number . isNaN ( userId ) ) {
throw new Error ( 'Invalid userId' ) ;
}
let user = null ;
try {
user = await RegUser . findByPk ( userId ) ;
} catch {
throw new Error ( 'Database error on remove mod' ) ;
}
if ( ! user ) {
throw new Error ( 'User not found' ) ;
}
try {
await user . update ( {
isMod : false ,
} ) ;
return ` Moderation rights removed from user ${ userId } ` ;
} catch {
throw new Error ( 'Couldn\'t remove Mod from user' ) ;
}
}
export async function makeMod ( name ) {
2022-03-31 13:10:50 +00:00
if ( ! name ) {
throw new Error ( 'No username given' ) ;
}
2020-11-29 13:22:14 +00:00
let user = null ;
try {
user = await RegUser . findOne ( {
where : {
name ,
} ,
} ) ;
} catch {
throw new Error ( ` Invalid user ${ name } ` ) ;
}
if ( ! user ) {
throw new Error ( ` User ${ name } not found ` ) ;
}
try {
await user . update ( {
isMod : true ,
} ) ;
return [ user . id , user . name ] ;
} catch {
throw new Error ( 'Couldn\'t remove Mod from user' ) ;
}
}