diff --git a/src/apis/form.ts b/src/apis/form.ts index a07b490..a0a5be4 100644 --- a/src/apis/form.ts +++ b/src/apis/form.ts @@ -56,7 +56,7 @@ interface BodyType { commentType: string; message: string; "h-captcha-response": string; - "g-recaptcha-response": string; + "g-recaptcha-response"?: string; } const BodyTypeSchema = Joi.object({ @@ -64,7 +64,7 @@ const BodyTypeSchema = Joi.object({ commentType: Joi.string().required(), message: Joi.string().required(), "h-captcha-response": Joi.string().required(), - "g-recaptcha-response": Joi.string().required() + "g-recaptcha-response": Joi.string().optional().allow("") }); const handleForm = ( @@ -72,7 +72,6 @@ const handleForm = ( reply: FastifyReply ) => { if (BodyTypeSchema.validate(request.body).error) { - console.log(request.body) reply.badRequest( `${BodyTypeSchema.validate(request.body).error}` ); @@ -82,7 +81,7 @@ const handleForm = ( verify(config.app.hcaptcha.secret, request.body["h-captcha-response"]) .then((data) => { const hook = new Webhook(config.app.webhook); - if (data.success === true) { + if (data.success) { const embed = new MessageBuilder() .setAuthor( `${ip}, ${request.body.email}, https://abuseipdb.com/check/${ip}` @@ -92,13 +91,15 @@ const handleForm = ( .setTimestamp(); reply.send( - "Thanks for your message, and thanks for doing the captcha!\nPlease ignore how different this page looks to the page you were on earlier. I'll figure it out eventually!" + "Thanks for your message, we will get back to you as soon as possible." ); hook.send(embed); } else { - reply.send( - "Seems like captcha failed, you didn't complete the captcha or you are a bot. Please try again.\nPlease note that your IP has been logged in our systems for manual review to check if you're an abusive user. If you're seen as abusive, you will be blacklisted.\nYour message has not been sent." + (data); + reply.unauthorized( + "Captcha failed or expired, please try again. If this keeps happening, assume the captcha is broken and contact us on Matrix." + + " Error: " + data["error-codes"] ); hook.send( diff --git a/src/apis/user.ts b/src/apis/user.ts index b8bd820..c568d17 100644 --- a/src/apis/user.ts +++ b/src/apis/user.ts @@ -60,6 +60,18 @@ const userApi = (fastify: FastifyInstance) => { changeUsername(request, reply); } ); + fastify.post( + "/api/v1/user/banip", + (request: FastifyRequest<{ Body: BanIpType }>, reply: FastifyReply) => { + banIp(request, reply); + } + ); + fastify.post( + "/api/v1/user/unbanip", + (request: FastifyRequest<{ Body: UnbanIpType }>, reply: FastifyReply) => { + unbanIp(request, reply); + } + ); }; interface BodyType { @@ -326,4 +338,82 @@ const changeUsername = async ( } }; +interface BanIpType { + ip: string; + reason: string; + bannedBy: string; +} + +const BanIpTypeSchema = Joi.object({ + ip: Joi.string().ip().required(), + reason: Joi.string().required(), + bannedBy: Joi.string().required() +}); + +const banIp = async (request: FastifyRequest<{ Body: BanIpType }>, reply: FastifyReply) => { + if (userMap.get("isAdmin")) { + if (BanIpTypeSchema.validate(request.body).error) { + reply.badRequest(`${BanIpTypeSchema.validate(request.body).error}`); + } else { + const collection = db.collection("banned"); + + if (await collection.findOne({ ip: request.body.ip })) { + reply.conflict("IP already banned."); + } else { + await collection.insertOne({ ...request.body, time: Math.floor(Date.now() / 1000) }).then((result) => { + if (result.insertedId !== undefined) { + reply.send("IP banned."); + } else { + reply.internalServerError("An error occurred while writing to MongoDB."); + } + }); + } + } + } else { + reply.unauthorized("You need to be an admin to ban IPs."); + } +} + +interface UnbanIpType { + ip: string; + reason: string; + unbannedBy: string; +} + +const UnbanIpTypeSchema = Joi.object({ + ip: Joi.string().ip().required(), + reason: Joi.string().required(), + unbannedBy: Joi.string().required() +}); + +const unbanIp = async (request: FastifyRequest<{ Body: UnbanIpType }>, reply: FastifyReply) => { + if (userMap.get("isAdmin")) { + if (UnbanIpTypeSchema.validate(request.body).error) { + reply.badRequest(`${UnbanIpTypeSchema.validate(request.body).error}`); + } else { + const collection = db.collection("blocklist"); + + const unbannedCollection = db.collection("unbanned"); + + await collection.deleteOne({ ip: request.body.ip }).then((result) => { + if (result.deletedCount === 1) { + reply.send("IP unbanned."); + } else { + reply.notFound("IP is not banned."); + } + }); + + await unbannedCollection.insertOne({ ...request.body, time: Math.floor(Date.now() / 1000) }).then((result) => { + if (result.insertedId !== undefined) { + reply.send("Wrote unban to unbanned database."); + } else { + reply.internalServerError("An error occurred while writing to MongoDB."); + } + }); + } + } else { + reply.unauthorized("You need to be an admin to ban IPs."); + } +} + export default userApi; diff --git a/src/index.ts b/src/index.ts index 48825f6..b9172dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,8 @@ import log from "./utils/logUtil"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; import config from "./utils/config"; -import { initializeDb, dbCleanUp } from "./utils/db"; +import { initializeDb, dbCleanUp, db } from "./utils/db"; +import getIp from "./utils/getIp"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -40,6 +41,14 @@ fastify.register(pointOfView, { viewExt: "hbs" }); +fastify.addHook("preHandler", async (request: FastifyRequest, reply: FastifyReply) => { + const collection = db.collection("banned"); + + if (getIp(request) === await collection.findOne({ ip: getIp(request) }).then((doc) => doc?.["ip"])) { + reply.unauthorized("You are banned."); + } +}); + fastify.get("/", (request: FastifyRequest, reply: FastifyReply) => { reply.view("index", { port: config.app.port, diff --git a/src/templates/user.hbs b/src/templates/user.hbs index 7f5d294..e9c4ea2 100644 --- a/src/templates/user.hbs +++ b/src/templates/user.hbs @@ -77,4 +77,30 @@ + +