diff --git a/src/DittoWallet.ts b/src/DittoWallet.ts index 25412f62..95616325 100644 --- a/src/DittoWallet.ts +++ b/src/DittoWallet.ts @@ -39,11 +39,6 @@ export class DittoWallet { return this.deriveKey(Conf.wallet.dbKeyPath); } - /** Captcha encryption key for encrypting answer data in AES-GCM. */ - static get captchaKey(): Uint8Array { - return this.deriveKey(Conf.wallet.captchaKeyPath); - } - /** VAPID secret key, used for web push notifications. ES256. */ static get vapidKey(): Uint8Array { return this.deriveKey(Conf.wallet.vapidKeyPath); diff --git a/src/app.ts b/src/app.ts index b8469e11..42c5b8dd 100644 --- a/src/app.ts +++ b/src/app.ts @@ -279,7 +279,7 @@ 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/pleroma/captcha', captchaController); +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); diff --git a/src/config.ts b/src/config.ts index d471076d..c7f5e7cb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -99,6 +99,10 @@ class Conf { }, }, }; + /** Time-to-live for captchas in milliseconds. */ + static get captchaTTL(): number { + return Number(Deno.env.get('CAPTCHA_TTL') || 5 * 60 * 1000); + } /** * BIP-32 derivation paths for different crypto use-cases. * The `DITTO_NSEC` is used as the seed. @@ -109,10 +113,6 @@ class Conf { get dbKeyPath(): string { return Deno.env.get('WALLET_DB_KEY_PATH') || "m/0'/1'"; }, - /** Private key for AES-GCM encryption of captcha answer data. */ - get captchaKeyPath(): string { - return Deno.env.get('WALLET_CAPTCHA_KEY_PATH') || "m/0'/2'"; - }, /** VAPID private key path. */ get vapidKeyPath(): string { return Deno.env.get('WALLET_VAPID_KEY_PATH') || "m/0'/3'"; diff --git a/src/controllers/api/captcha.ts b/src/controllers/api/captcha.ts index 9599c641..b0e01dfd 100644 --- a/src/controllers/api/captcha.ts +++ b/src/controllers/api/captcha.ts @@ -1,10 +1,17 @@ import { createCanvas, loadImage } from '@gfx/canvas-wasm'; -import { encodeBase64 } from '@std/encoding/base64'; +import TTLCache from '@isaacs/ttlcache'; import { AppController } from '@/app.ts'; -import { DittoWallet } from '@/DittoWallet.ts'; -import { aesEncrypt } from '@/utils/aes.ts'; +import { Conf } from '@/config.ts'; +interface Point { + x: number; + y: number; +} + +const captchas = new TTLCache(); + +/** Puzzle captcha controller. */ export const captchaController: AppController = async (c) => { const { puzzle, piece, solution } = await generateCaptcha( await Deno.readFile(new URL('../../../captcha/tj-holowaychuk.jpg', import.meta.url)), @@ -18,28 +25,23 @@ export const captchaController: AppController = async (c) => { }, ); - const answerData = { - solution, - created_at: new Date().toISOString(), - }; + const id = crypto.randomUUID(); + const now = new Date(); + const ttl = Conf.captchaTTL; - const encoded = new TextEncoder().encode(JSON.stringify(answerData)); - const encrypted = await aesEncrypt(DittoWallet.captchaKey, encoded); + captchas.set(id, solution, { ttl }); return c.json({ type: 'puzzle', - token: crypto.randomUUID(), // Useless, but Pleroma does it. + id, puzzle: puzzle.toDataURL(), piece: piece.toDataURL(), - answer_data: encodeBase64(encrypted), + created_at: now.toISOString(), + expires_at: new Date(now.getTime() + ttl).toISOString(), }); }; -interface Point { - x: number; - y: number; -} - +/** Generate a puzzle captcha, returning canvases for the board and piece. */ async function generateCaptcha( from: Uint8Array, mask: Uint8Array,