mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
captcha: don't do the encryption thing, just use a ttl cache
This commit is contained in:
parent
03c9340eb2
commit
e57dd8911c
4 changed files with 23 additions and 26 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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'";
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue