Add preliminary captcha controller

This commit is contained in:
Alex Gleason
2024-10-03 19:23:22 -05:00
parent 205b9a77fe
commit f83ad0dbce
5 changed files with 83 additions and 1 deletions

View File

@@ -5,6 +5,8 @@ import { logger } from '@hono/hono/logger';
import { NostrEvent, NostrSigner, NStore, NUploader } from '@nostrify/nostrify';
import Debug from '@soapbox/stickynotes/debug';
import '@/startup.ts';
import { Time } from '@/utils/time.ts';
import {
@@ -36,6 +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 {
adminRelaysController,
adminSetRelaysController,
@@ -113,7 +116,6 @@ import { errorHandler } from '@/controllers/error.ts';
import { frontendController } from '@/controllers/frontend.ts';
import { metricsController } from '@/controllers/metrics.ts';
import { indexController } from '@/controllers/site.ts';
import '@/startup.ts';
import { manifestController } from '@/controllers/manifest.ts';
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
import { nostrController } from '@/controllers/well-known/nostr.ts';
@@ -277,6 +279,8 @@ app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysContro
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
app.get('/api/v1/ditto/captcha', captchaController);
app.get('/api/v1/ditto/zap_splits', getZapSplitsController);
app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController);

View File

@@ -0,0 +1,65 @@
import { createCanvas, loadImage } from '@gfx/canvas-wasm';
import { AppController } from '@/app.ts';
export const captchaController: AppController = async (c) => {
const { puzzle, piece, solution } = await generateCaptcha(
await Deno.readFile(new URL('../../../captcha/tj-holowaychuk.jpg', import.meta.url)),
{
cw: 300,
ch: 300,
pw: 50,
ph: 50,
alpha: 0.6,
},
);
return c.json({
puzzle: puzzle.toDataURL(),
piece: piece.toDataURL(),
});
};
interface Point {
x: number;
y: number;
}
async function generateCaptcha(
from: Uint8Array,
opts: {
pw: number;
ph: number;
cw: number;
ch: number;
alpha: number;
},
) {
const { pw, ph, cw, ch, alpha } = opts;
const puzzle = createCanvas(cw, ch);
const ctx = puzzle.getContext('2d');
const image = await loadImage(from);
ctx.drawImage(image, 0, 0, image.width(), image.height(), 0, 0, cw, ch);
const piece = createCanvas(pw, ph);
const pctx = piece.getContext('2d');
const solution = getPieceCoords(puzzle.width, puzzle.height, pw, ph);
pctx.drawImage(puzzle, solution.x, solution.y, pw, ph, 0, 0, pw, ph);
ctx.fillStyle = `rgba(0, 0, 0, ${alpha})`;
ctx.fillRect(solution.x, solution.y, pw, ph);
return {
puzzle,
piece,
solution,
};
}
function getPieceCoords(cw: number, ch: number, pw: number, ph: number): Point {
// Random x coordinate such that the piece fits within the canvas horizontally
const x = Math.floor(Math.random() * (cw - pw));
// Random y coordinate such that the piece fits within the canvas vertically
const y = Math.floor(Math.random() * (ch - ph));
return { x, y };
}