diff --git a/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json b/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json index e978f28..263dfda 100644 --- a/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +++ b/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json @@ -1 +1 @@ -{"version":"4.0.16","results":[[":src/controllers/subscriptions.test.ts",{"duration":8.344391000000002,"failed":false}]]} \ No newline at end of file +{"version":"4.0.16","results":[[":src/controllers/subscriptions.test.ts",{"duration":7.715925999999996,"failed":false}]]} \ No newline at end of file diff --git a/src/controllers/orders.webhook.ts b/src/controllers/orders.webhook.ts index d15db28..8b51f57 100644 --- a/src/controllers/orders.webhook.ts +++ b/src/controllers/orders.webhook.ts @@ -1,12 +1,19 @@ import { Request, Response } from 'express'; +import { SubscriptionManager } from './subscriptions'; -export const handleOrdersWebhook = async (req: Request, res: Response) => { - try { - const event = req.body; - console.log('Received webhook event:', JSON.stringify(event, null, 2)); - res.status(200).json({ received: true }); - } catch (error) { - console.error('Error processing webhook:', error); - res.status(500).json({ error: 'Internal Server Error' }); - } -}; + +export function ordersHandlerBuilder(subscriptionManager: SubscriptionManager) { + const subsManager = subscriptionManager + return async function (req: Request, res: Response) { + try { + const event = req.body; + console.log('Received webhook event:', JSON.stringify(event, null, 2)); + subsManager.poll(event) + res.status(200).json({ received: true }); + } catch (error) { + console.error('Error processing webhook:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } + }; + +} diff --git a/src/controllers/subscriptions.ts b/src/controllers/subscriptions.ts index 98c4223..f58f99f 100644 --- a/src/controllers/subscriptions.ts +++ b/src/controllers/subscriptions.ts @@ -2,19 +2,24 @@ * Simulacion de que clientes estan subscritos a que eventos * */ -import { error } from "node:console" import { Shopify } from "../data/webhooks/order" import http from "node:http" +import { Request, Response } from 'express'; +import { generateHMACSignature } from "#middleware/hmac.js" +import { config, webhooksConfig } from "#config/index.js"; export type SubscriptionData = { topic: string, - subscriptors: { - host: string, - port: string, - endpoint: string, - method: "POST" | "GET" | "PUT" | "DELETE" - open: Date, - }[] + subscriptors: SubscriptorData[] +} + +export type SubscriptorData = { + host: string, + port: string, + endpoint: string, + secretkey: string, + method: "POST" | "GET" | "PUT" | "DELETE" + open: Date, } export type ShopifyEvent = { @@ -27,6 +32,38 @@ type Topic = string /** Mapa topic -> subscriber */ export const subscribers = new Map() +export function subscriptonHandlerBuilder(subscriptionManager: SubscriptionManager) { + const subsManager = subscriptionManager + return function (req: Request, res: Response) { + try { + const event = req.body; + const secretkey = req.body.secretkey || webhooksConfig.apiSecret || 1234 + + const subscriptorData: SubscriptorData = { + host: event.host, + endpoint: event.endpoint, + method: event.method, + open: new Date(), + port: event.port, + secretkey: secretkey // Es solo para mock no usar en prod + } + const topic = event.topic + + subsManager.addSubscriber({ + topic: topic, + subscriber: subscriptorData + }) + + console.log('Received webhook subscriber:', + JSON.stringify(event, null, 2)); + res.status(200).json({ received: true, key: secretkey }); + } catch (error) { + console.error('Error processing webhook:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } + } +} + export class SubscriptionManager { private subscriptions: Map = new Map() @@ -36,6 +73,18 @@ export class SubscriptionManager { } } + public addSubscriber(args: { topic: string, subscriber: SubscriptorData }) { + const topic = args.topic + if (topic == undefined) throw new Error("Topic vacio") + const topicSubscribers = this.subscriptions.get(topic) + if (topicSubscribers == undefined) { + this.subscriptions.set(topic, { + topic, + subscriptors: [args.subscriber] + }) + } + } + public poll(event: ShopifyEvent) { const topic = event.topic @@ -47,25 +96,33 @@ export class SubscriptionManager { const subscriptors = this.subscriptions.get(topic) for (const sub of subscriptors!.subscriptors) { + const body = { + id: 1234 + } + const parsedBody = JSON.stringify(body) + const signature = generateHMACSignature(parsedBody, sub.secretkey) const request = requestBuilder({ method: sub.method, headers: shopifyHeaderBuilder({ topic: topic, - signature: "1234" + signature: signature }), host: sub.host, port: sub.port, endpoint: sub.endpoint }) - + request.write(parsedBody) // Data puede venir en chunks! request.on("data", () => console.log) request.on("end", () => console.log) request.on("error", () => console.error) + + request.end() } } } +// TODO: Crear una fucnion especifica para los request con HMAC function requestBuilder(args: { method: string, headers: Object, @@ -82,7 +139,6 @@ function requestBuilder(args: { Object.entries(args.headers).forEach(([name, value]) => { request.setHeader(name, String(value)) }) - return request } @@ -93,6 +149,9 @@ function headerBuilder(args: {}) { } } +/** +* TODO: Estoy confiando que la firma esté ok +*/ function shopifyHeaderBuilder(args: { topic: string, signature: string diff --git a/src/middleware/hmac.ts b/src/middleware/hmac.ts new file mode 100644 index 0000000..833e690 --- /dev/null +++ b/src/middleware/hmac.ts @@ -0,0 +1,11 @@ +import crypto from "crypto" + +//HMAC ES SIMETRICO +//El cliente y el server acuerdan la clave + +export function generateHMACSignature(msg: string, key: string) { + return crypto.createHmac("sha256", key) + .update(msg) + .digest("base64") +} + diff --git a/src/routes/webhook.ts b/src/routes/webhook.ts index 6e72fd6..6b26e9b 100644 --- a/src/routes/webhook.ts +++ b/src/routes/webhook.ts @@ -1,12 +1,16 @@ import { Router } from 'express'; -import { handleWebhook } from '../controllers/webhook'; -import { verifyWebhookSignature } from '../middleware/verifySig'; +import { ordersHandlerBuilder } from '#controllers/orders.webhook.js'; +import { SubscriptionManager, subscriptonHandlerBuilder } from '#controllers/subscriptions.js'; const webhookRouter = Router(); +const subscriptions = new SubscriptionManager() + // subto -webhookRouter.post('/subto', verifyWebhookSignature, handleWebhook); +webhookRouter.post('/subto', subscriptonHandlerBuilder(subscriptions)); + // Simulacion de los webhook de shopify -webhookRouter.post('/shopify/orders', verifyWebhookSignature, handleWebhook); +// Al llamar se supone que se genera el evento +webhookRouter.post('/shopify/orders', ordersHandlerBuilder(subscriptions)); export default webhookRouter;