gfsd
This commit is contained in:
@@ -323,6 +323,196 @@ export default (router, tpl) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.get(/^\/admin\/bans(\/)?$/, lib.modAuth, async (req, res) => {
|
||||
const bannedFingerprints = await db`
|
||||
select bf.*,
|
||||
u.login as anon_login, u.user as anon_user,
|
||||
admin.user as banned_by_user
|
||||
from banned_fingerprints bf
|
||||
left join anon_identities ai on ai.fingerprint = bf.fingerprint
|
||||
left join "user" u on u.id = ai.user_id
|
||||
left join "user" admin on admin.id = bf.banned_by
|
||||
order by bf.created_at desc
|
||||
`;
|
||||
|
||||
const bannedHardware = await db`
|
||||
select bh.*,
|
||||
admin.user as banned_by_user
|
||||
from banned_hardware_fingerprints bh
|
||||
left join "user" admin on admin.id = bh.banned_by
|
||||
order by bh.created_at desc
|
||||
`;
|
||||
|
||||
const bannedIps = await db`
|
||||
select bi.*,
|
||||
admin.user as banned_by_user
|
||||
from banned_ips bi
|
||||
left join "user" admin on admin.id = bi.banned_by
|
||||
order by bi.created_at desc
|
||||
`;
|
||||
|
||||
const anonIdentities = await db`
|
||||
select ai.*,
|
||||
u.id as user_id, u.login, u.user, u.banned as is_banned, u.ban_reason,
|
||||
bf.id as is_fp_banned,
|
||||
bh.id as is_hw_banned
|
||||
from anon_identities ai
|
||||
left join "user" u on u.id = ai.user_id
|
||||
left join banned_fingerprints bf on bf.fingerprint = ai.fingerprint
|
||||
left join banned_hardware_fingerprints bh on bh.hw_fingerprint = ai.hw_fingerprint
|
||||
order by ai.last_seen desc
|
||||
limit 100
|
||||
`;
|
||||
|
||||
const recentActivity = await db`
|
||||
select al.*,
|
||||
u.login, u.user
|
||||
from anon_activity_log al
|
||||
left join "user" u on u.id = al.user_id
|
||||
order by al.created_at desc
|
||||
limit 100
|
||||
`;
|
||||
|
||||
res.reply({
|
||||
body: tpl.render("admin/bans", {
|
||||
session: req.session,
|
||||
csrf_token: req.session ? req.session.csrf_token : '',
|
||||
bannedFingerprints,
|
||||
bannedHardware,
|
||||
bannedIps,
|
||||
anonIdentities,
|
||||
recentActivity,
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/fingerprint\/ban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { fingerprint, reason, duration, ban_ips, user_id } = req.post || {};
|
||||
if (!fingerprint) throw new Error('Missing fingerprint');
|
||||
|
||||
const expires = duration === 'permanent' || !duration ? null : new Date(Date.now() + parseInt(duration, 10) * 3600000);
|
||||
|
||||
const result = await security.banAnonymousUser({
|
||||
userId: user_id ? +user_id : null,
|
||||
fingerprint,
|
||||
bannedBy: req.session.id,
|
||||
reason: reason || 'Banned by moderator',
|
||||
expires,
|
||||
banIps: ban_ips !== false
|
||||
});
|
||||
|
||||
await audit.log(req.session.id, 'ban_fingerprint', 'fingerprint', null, { fingerprint, reason, duration });
|
||||
|
||||
return res.json({ success: true, result });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/fingerprint\/unban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { fingerprint } = req.post || {};
|
||||
if (!fingerprint) throw new Error('Missing fingerprint');
|
||||
|
||||
await db`DELETE FROM banned_fingerprints WHERE fingerprint = ${fingerprint}`;
|
||||
|
||||
// Also unban any shadow user associated with this fingerprint
|
||||
const ident = await db`SELECT user_id FROM anon_identities WHERE fingerprint = ${fingerprint}`;
|
||||
if (ident.length > 0) {
|
||||
await db`UPDATE "user" SET banned = false, ban_reason = null, ban_expires = null WHERE id = ${ident[0].user_id}`;
|
||||
}
|
||||
|
||||
await audit.log(req.session.id, 'unban_fingerprint', 'fingerprint', null, { fingerprint });
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/ip\/ban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { ip, reason, duration } = req.post || {};
|
||||
if (!ip) throw new Error('Missing IP address');
|
||||
|
||||
const expires = duration === 'permanent' || !duration ? null : new Date(Date.now() + parseInt(duration, 10) * 3600000);
|
||||
const ipHash = security.hashIP(ip);
|
||||
|
||||
await db`
|
||||
INSERT INTO banned_ips (ip, ip_hash, banned_by, reason, expires_at)
|
||||
VALUES (${ip}, ${ipHash}, ${req.session.id}, ${reason || 'Banned by moderator'}, ${expires})
|
||||
ON CONFLICT (ip) DO UPDATE
|
||||
SET reason = EXCLUDED.reason,
|
||||
expires_at = EXCLUDED.expires_at,
|
||||
banned_by = EXCLUDED.banned_by,
|
||||
ip_hash = EXCLUDED.ip_hash
|
||||
`;
|
||||
|
||||
await audit.log(req.session.id, 'ban_ip', 'ip', null, { ip, reason, duration });
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/ip\/unban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { ip } = req.post || {};
|
||||
if (!ip) throw new Error('Missing IP');
|
||||
|
||||
await db`DELETE FROM banned_ips WHERE ip = ${ip} OR ip_hash = ${ip}`;
|
||||
|
||||
await audit.log(req.session.id, 'unban_ip', 'ip', null, { ip });
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/hardware\/ban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { hw_fingerprint, reason, duration, ban_ips, user_id } = req.post || {};
|
||||
if (!hw_fingerprint) throw new Error('Missing hardware fingerprint');
|
||||
|
||||
const expires = duration === 'permanent' || !duration ? null : new Date(Date.now() + parseInt(duration, 10) * 3600000);
|
||||
|
||||
const result = await security.banAnonymousUser({
|
||||
userId: user_id ? +user_id : null,
|
||||
hwFingerprint: hw_fingerprint,
|
||||
bannedBy: req.session.id,
|
||||
reason: reason || 'Banned by moderator',
|
||||
expires,
|
||||
banIps: ban_ips !== false,
|
||||
banHardware: true
|
||||
});
|
||||
|
||||
await audit.log(req.session.id, 'ban_hardware', 'hardware', null, { hw_fingerprint, reason, duration });
|
||||
|
||||
return res.json({ success: true, result });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(/^\/api\/v2\/admin\/bans\/hardware\/unban\/?$/, lib.modAuth, async (req, res) => {
|
||||
try {
|
||||
const { hw_fingerprint } = req.post || {};
|
||||
if (!hw_fingerprint) throw new Error('Missing hardware fingerprint');
|
||||
|
||||
await db`DELETE FROM banned_hardware_fingerprints WHERE hw_fingerprint = ${hw_fingerprint}`;
|
||||
|
||||
await audit.log(req.session.id, 'unban_hardware', 'hardware', null, { hw_fingerprint });
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get(/^\/admin\/user\/(?<userId>\d+)\/ips(\/)?$/, lib.auth, async (req, res) => {
|
||||
const userId = +req.params.userId;
|
||||
const user = await db`select "user", login from "user" where id = ${userId} limit 1`;
|
||||
@@ -481,6 +671,19 @@ export default (router, tpl) => {
|
||||
where id = ${+user_id}
|
||||
`;
|
||||
|
||||
// If this is an anonymous identity, cascade the ban to fingerprint and IPs
|
||||
const anonIdent = await db`SELECT fingerprint FROM anon_identities WHERE user_id = ${+user_id} LIMIT 1`;
|
||||
if (anonIdent.length > 0 || (targetUser[0].login && targetUser[0].login.startsWith('anon_'))) {
|
||||
await security.banAnonymousUser({
|
||||
userId: +user_id,
|
||||
fingerprint: anonIdent[0]?.fingerprint || null,
|
||||
bannedBy: req.session.id,
|
||||
reason,
|
||||
expires,
|
||||
banIps: true
|
||||
});
|
||||
}
|
||||
|
||||
// Log it in audit
|
||||
await audit.log(req.session.id, 'ban_user', 'user', +user_id, { reason, duration, target_user: targetUser[0].user });
|
||||
|
||||
@@ -516,6 +719,12 @@ export default (router, tpl) => {
|
||||
where id = ${+user_id}
|
||||
`;
|
||||
|
||||
// Clean up any banned fingerprint for this identity
|
||||
const anonIdent = await db`SELECT fingerprint FROM anon_identities WHERE user_id = ${+user_id} LIMIT 1`;
|
||||
if (anonIdent.length > 0) {
|
||||
await db`DELETE FROM banned_fingerprints WHERE fingerprint = ${anonIdent[0].fingerprint}`;
|
||||
}
|
||||
|
||||
// Log it in audit
|
||||
await audit.log(req.session.id, 'unban_user', 'user', +user_id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user