Add captcha verify controller

This commit is contained in:
Alex Gleason 2024-10-04 12:43:29 -05:00
parent e57dd8911c
commit 467a49bd40
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
2 changed files with 57 additions and 1 deletions

View file

@ -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);

View file

@ -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<string, Point>();
/** 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;
}