import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; import { env } from './env.js'; import { redis } from './redis.js'; import { deleteCaptchaChallenge, getCaptchaChallenge, hashCaptchaAnswer } from './modules/verification/service.js'; import { automodQueue, backupQueue, birthdayQueue, feedsQueue, presenceQueue, retentionQueue, ticketQueue, verificationQueue } from './queues.js'; import { collectMetricsSnapshot, formatPrometheus } from './metrics.js'; function readBody(req: IncomingMessage): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); req.on('error', reject); }); } function parseFormBody(body: string): Record { return Object.fromEntries(new URLSearchParams(body)); } function renderCaptchaPage(token: string, question: string, error?: string): string { const errorBlock = error ? `

${error}

` : ''; return ` Nexumi Verification

Nexumi Verification

Solve: ${question} = ?

${errorBlock}
`; } async function handleCaptchaGet(url: URL, res: ServerResponse): Promise { const token = url.searchParams.get('token'); if (!token) { res.statusCode = 400; res.end('Missing token'); return; } const challenge = await getCaptchaChallenge(redis, token); if (!challenge) { res.statusCode = 404; res.end('Captcha expired'); return; } // External providers (reCAPTCHA / hCaptcha / Turnstile) are served by the WebUI. if (challenge.provider !== 'MATH' && env.WEBUI_URL) { const target = `${env.WEBUI_URL.replace(/\/$/, '')}/verify/captcha?token=${encodeURIComponent(token)}`; res.statusCode = 302; res.setHeader('Location', target); res.end(); return; } res.statusCode = 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(renderCaptchaPage(token, challenge.question ?? '?')); } async function handleCaptchaPost(req: IncomingMessage, res: ServerResponse): Promise { const body = await readBody(req); const form = parseFormBody(body); const token = form.token; const answer = form.answer; if (!token || !answer) { res.statusCode = 400; res.end('Missing fields'); return; } const challenge = await getCaptchaChallenge(redis, token); if (!challenge) { res.statusCode = 404; res.end('Captcha expired'); return; } if (challenge.provider !== 'MATH') { if (env.WEBUI_URL) { const target = `${env.WEBUI_URL.replace(/\/$/, '')}/verify/captcha?token=${encodeURIComponent(token)}`; res.statusCode = 302; res.setHeader('Location', target); res.end(); return; } res.statusCode = 400; res.end('Use the WebUI captcha URL for this provider'); return; } if (!challenge.answerHash || hashCaptchaAnswer(answer) !== challenge.answerHash) { res.statusCode = 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(renderCaptchaPage(token, challenge.question ?? '?', 'Wrong answer. Try again.')); return; } await deleteCaptchaChallenge(redis, token); await verificationQueue.add( 'verificationComplete', { guildId: challenge.guildId, userId: challenge.userId, captchaProvider: challenge.provider }, { removeOnComplete: 1000, removeOnFail: 500 } ); res.statusCode = 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end('

Verified

You can return to Discord.

'); } async function approximateQueueWaiting(): Promise { const counts = await Promise.all([ automodQueue.getWaitingCount(), backupQueue.getWaitingCount(), birthdayQueue.getWaitingCount(), feedsQueue.getWaitingCount(), presenceQueue.getWaitingCount(), retentionQueue.getWaitingCount(), ticketQueue.getWaitingCount() ]); return counts.reduce((sum, n) => sum + n, 0); } export function startHealthServer(): void { const server = createServer(async (req, res) => { if (!req.url) { res.statusCode = 400; res.end('Bad Request'); return; } const url = new URL(req.url, `http://127.0.0.1:${env.HEALTH_PORT}`); if (url.pathname === '/health') { res.statusCode = 200; res.end('ok'); return; } if (url.pathname === '/metrics') { const auth = req.headers.authorization; if (!env.METRICS_TOKEN || auth !== `Bearer ${env.METRICS_TOKEN}`) { res.statusCode = 401; res.end('Unauthorized'); return; } try { const snapshot = await collectMetricsSnapshot(redis); snapshot.queueWaiting = await approximateQueueWaiting(); res.statusCode = 200; res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8'); res.end(formatPrometheus(snapshot)); } catch { res.statusCode = 500; res.end('metrics error'); } return; } if (url.pathname === '/verify/captcha' && req.method === 'GET') { await handleCaptchaGet(url, res); return; } if (url.pathname === '/verify/captcha' && req.method === 'POST') { await handleCaptchaPost(req, res); return; } res.statusCode = 404; res.end('Not Found'); }); server.listen(env.HEALTH_PORT); }