From 467a49bd409cdb68ff17e4113de73b5776615a9e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 4 Oct 2024 12:43:29 -0500 Subject: [PATCH] Add captcha verify controller --- src/app.ts | 3 +- src/controllers/api/captcha.ts | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 42c5b8dd..935156c1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -38,7 +38,7 @@ import { import { appCredentialsController, createAppController } from '@/controllers/api/apps.ts'; import { blocksController } from '@/controllers/api/blocks.ts'; import { bookmarksController } from '@/controllers/api/bookmarks.ts'; -import { captchaController } from '@/controllers/api/captcha.ts'; +import { captchaController, captchaVerifyController } from '@/controllers/api/captcha.ts'; import { adminRelaysController, adminSetRelaysController, @@ -280,6 +280,7 @@ app.post('/api/v1/ditto/names', requireSigner, nameRequestController); app.get('/api/v1/ditto/names', requireSigner, nameRequestsController); app.get('/api/v1/ditto/captcha', captchaController); +app.post('/api/v1/ditto/captcha/:id/verify', requireSigner, captchaVerifyController); app.get('/api/v1/ditto/zap_splits', getZapSplitsController); app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController); diff --git a/src/controllers/api/captcha.ts b/src/controllers/api/captcha.ts index b0e01dfd..65ec4f2f 100644 --- a/src/controllers/api/captcha.ts +++ b/src/controllers/api/captcha.ts @@ -1,5 +1,6 @@ import { createCanvas, loadImage } from '@gfx/canvas-wasm'; import TTLCache from '@isaacs/ttlcache'; +import { z } from 'zod'; import { AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; @@ -9,6 +10,11 @@ interface Point { y: number; } +interface Dimensions { + w: number; + h: number; +} + const captchas = new TTLCache(); /** Puzzle captcha controller. */ @@ -103,3 +109,52 @@ function getPieceCoords(cw: number, ch: number, pw: number, ph: number): Point { return { x, y }; } + +const pointSchema = z.object({ + x: z.number(), + y: z.number(), +}); + +/** Verify the captcha solution and sign an event in the database. */ +export const captchaVerifyController: AppController = async (c) => { + const id = c.req.param('id'); + const result = pointSchema.safeParse(await c.req.json()); + + if (!result.success) { + return c.json({ error: 'Invalid input' }, { status: 422 }); + } + + const solution = captchas.get(id); + + if (!solution) { + return c.json({ error: 'Captcha expired' }, { status: 410 }); + } + + const dim = { w: 50, h: 50 }; + const point = result.data; + + const success = areIntersecting( + { ...point, ...dim }, + { ...solution, ...dim }, + ); + + if (success) { + captchas.delete(id); + return new Response(null, { status: 204 }); + } + + return c.json({ error: 'Incorrect solution' }, { status: 400 }); +}; + +type Rectangle = Point & Dimensions; + +function areIntersecting(rect1: Rectangle, rect2: Rectangle, threshold = 0.5) { + const r1cx = rect1.x + rect1.w / 2; + const r2cx = rect2.x + rect2.w / 2; + const r1cy = rect1.y + rect1.h / 2; + const r2cy = rect2.y + rect2.h / 2; + const dist = Math.sqrt((r2cx - r1cx) ** 2 + (r2cy - r1cy) ** 2); + const e1 = Math.sqrt(rect1.h ** 2 + rect1.w ** 2) / 2; + const e2 = Math.sqrt(rect2.h ** 2 + rect2.w ** 2) / 2; + return dist < (e1 + e2) * threshold; +}