captcha: don't do the encryption thing, just use a ttl cache

This commit is contained in:
Alex Gleason 2024-10-04 12:07:12 -05:00
parent 03c9340eb2
commit e57dd8911c
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
4 changed files with 23 additions and 26 deletions

View file

@ -39,11 +39,6 @@ export class DittoWallet {
return this.deriveKey(Conf.wallet.dbKeyPath); 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. */ /** VAPID secret key, used for web push notifications. ES256. */
static get vapidKey(): Uint8Array { static get vapidKey(): Uint8Array {
return this.deriveKey(Conf.wallet.vapidKeyPath); return this.deriveKey(Conf.wallet.vapidKeyPath);

View file

@ -279,7 +279,7 @@ app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysContro
app.post('/api/v1/ditto/names', requireSigner, nameRequestController); app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController); 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/zap_splits', getZapSplitsController);
app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController); app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController);

View file

@ -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. * BIP-32 derivation paths for different crypto use-cases.
* The `DITTO_NSEC` is used as the seed. * The `DITTO_NSEC` is used as the seed.
@ -109,10 +113,6 @@ class Conf {
get dbKeyPath(): string { get dbKeyPath(): string {
return Deno.env.get('WALLET_DB_KEY_PATH') || "m/0'/1'"; 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. */ /** VAPID private key path. */
get vapidKeyPath(): string { get vapidKeyPath(): string {
return Deno.env.get('WALLET_VAPID_KEY_PATH') || "m/0'/3'"; return Deno.env.get('WALLET_VAPID_KEY_PATH') || "m/0'/3'";

View file

@ -1,10 +1,17 @@
import { createCanvas, loadImage } from '@gfx/canvas-wasm'; import { createCanvas, loadImage } from '@gfx/canvas-wasm';
import { encodeBase64 } from '@std/encoding/base64'; import TTLCache from '@isaacs/ttlcache';
import { AppController } from '@/app.ts'; import { AppController } from '@/app.ts';
import { DittoWallet } from '@/DittoWallet.ts'; import { Conf } from '@/config.ts';
import { aesEncrypt } from '@/utils/aes.ts';
interface Point {
x: number;
y: number;
}
const captchas = new TTLCache<string, Point>();
/** Puzzle captcha controller. */
export const captchaController: AppController = async (c) => { export const captchaController: AppController = async (c) => {
const { puzzle, piece, solution } = await generateCaptcha( const { puzzle, piece, solution } = await generateCaptcha(
await Deno.readFile(new URL('../../../captcha/tj-holowaychuk.jpg', import.meta.url)), await Deno.readFile(new URL('../../../captcha/tj-holowaychuk.jpg', import.meta.url)),
@ -18,28 +25,23 @@ export const captchaController: AppController = async (c) => {
}, },
); );
const answerData = { const id = crypto.randomUUID();
solution, const now = new Date();
created_at: new Date().toISOString(), const ttl = Conf.captchaTTL;
};
const encoded = new TextEncoder().encode(JSON.stringify(answerData)); captchas.set(id, solution, { ttl });
const encrypted = await aesEncrypt(DittoWallet.captchaKey, encoded);
return c.json({ return c.json({
type: 'puzzle', type: 'puzzle',
token: crypto.randomUUID(), // Useless, but Pleroma does it. id,
puzzle: puzzle.toDataURL(), puzzle: puzzle.toDataURL(),
piece: piece.toDataURL(), piece: piece.toDataURL(),
answer_data: encodeBase64(encrypted), created_at: now.toISOString(),
expires_at: new Date(now.getTime() + ttl).toISOString(),
}); });
}; };
interface Point { /** Generate a puzzle captcha, returning canvases for the board and piece. */
x: number;
y: number;
}
async function generateCaptcha( async function generateCaptcha(
from: Uint8Array, from: Uint8Array,
mask: Uint8Array, mask: Uint8Array,