diff --git a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts index ae7829c..57f74de 100644 --- a/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts +++ b/mvp/Whatsapp-bot/src/webhook/webhook-listener.ts @@ -1,5 +1,6 @@ import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import * as http from 'http'; +import * as crypto from 'crypto'; import { ApiClient } from '../api/api-client.service'; const QRImage = require('qrcode'); @@ -21,7 +22,7 @@ export class WebhookListener implements OnApplicationBootstrap { this.logger.log(`Webhook listener en puerto ${port}`); this.logger.log(`WHATSAPP_START → POST /whatsapp-start`); this.logger.log(`WHATSAPP_PDF → POST /whatsapp-pdf`); - this.logger.log(`QR vinculación → GET /qr?key=FUNNEL_API_KEY`); + this.logger.log(`QR vinculación → GET /qr (HTTP Basic, contraseña = QR_TOKEN)`); }); } @@ -75,19 +76,30 @@ export class WebhookListener implements OnApplicationBootstrap { }); } - // Página de vinculación: muestra el QR de Baileys como imagen escaneable. Protegida por ?key=. + // Comparación en tiempo constante (sobre hashes, sin filtrar longitud). + private tokenValido(a: string, b: string): boolean { + if (!a || !b) return false; + const ha = crypto.createHash('sha256').update(a).digest(); + const hb = crypto.createHash('sha256').update(b).digest(); + return crypto.timingSafeEqual(ha, hb); + } + + // Página de vinculación: muestra el QR de Baileys como imagen escaneable. + // Auth por cabecera (HTTP Basic, contraseña = QR_TOKEN), nunca por query string. private async handleQrPage(req: http.IncomingMessage, res: http.ServerResponse) { - const expected = process.env.FUNNEL_API_KEY || ''; - let provided = ''; - try { - provided = new URL(req.url || '', 'http://localhost').searchParams.get('key') || ''; - } catch { - /* url malformada */ - } - if (!expected || provided !== expected) { + const expected = process.env.QR_TOKEN || ''; + const auth = (req.headers['authorization'] as string) || ''; + const pass = auth.startsWith('Basic ') + ? Buffer.from(auth.slice(6), 'base64').toString('utf8').split(':').slice(1).join(':') + : ''; + if (!this.tokenValido(pass, expected)) { res - .writeHead(401, { 'Content-Type': 'text/html; charset=utf-8' }) - .end('
Añade ?key=<FUNNEL_API_KEY> a la URL.
'); + .writeHead(401, { + 'WWW-Authenticate': 'Basic realm="Reformix QR", charset="UTF-8"', + 'Content-Type': 'text/html; charset=utf-8', + 'Referrer-Policy': 'no-referrer', + }) + .end('Usuario: cualquiera · Contraseña: el QR_TOKEN.
'); return; } @@ -119,7 +131,13 @@ export class WebhookListener implements OnApplicationBootstrap { '