From f53ea06c231557b66c4809181c71d4d3d01c4820 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Thu, 6 Oct 2022 13:35:38 -0700 Subject: [PATCH] Revert "reject" This reverts commit 5fd3ba38099212fa87ebadbb6cb8dc3871b362c5. --- lib/session.js | 74 ++++++++++------------------------ pages/api/collect.js | 94 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/lib/session.js b/lib/session.js index d635f75a..dd196aaa 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,14 +1,12 @@ -import { parseToken } from 'next-basics'; -import { validate } from 'uuid'; import { uuid } from 'lib/crypto'; import redis, { DELETED } from 'lib/redis'; import { getClientInfo, getJsonBody } from 'lib/request'; -import { createSession, getSessionByUuid, getWebsiteByUuid } from 'queries'; +import { parseToken } from 'next-basics'; +import { validate } from 'uuid'; export async function getSession(req) { const { payload } = getJsonBody(req); const hasRedis = process.env.REDIS_URL; - const hasClickhouse = process.env.CLICKHOUSE_URL; if (!payload) { throw new Error('Invalid request'); @@ -37,14 +35,16 @@ export async function getSession(req) { websiteId = Number(await redis.client.get(`website:${website_uuid}`)); } - // Check database if does not exists in Redis - if (!websiteId) { - const website = await getWebsiteByUuid(website_uuid); - websiteId = website ? website.website_id : null; - } + // // Check database if does not exists in Redis + // if (!websiteId) { + // const website = await getWebsiteByUuid(website_uuid); + // websiteId = website ? website.website_id : null; + // } if (!websiteId || websiteId === DELETED) { - throw new Error(`Website not found: ${website_uuid}`); + throw new Error( + `Website not found: ${website_uuid} : ${process.env.REDIS_URL} : ${process.env.CLICKHOUSE_URL}`, + ); } const { userAgent, browser, os, ip, country, device } = await getClientInfo(req, payload); @@ -53,49 +53,17 @@ export async function getSession(req) { let sessionId = null; let session = null; - if (!hasClickhouse) { - // Check if session exists - if (hasRedis) { - sessionId = Number(await redis.client.get(`session:${session_uuid}`)); - } - - // Check database if does not exists in Redis - if (!sessionId) { - session = await getSessionByUuid(session_uuid); - sessionId = session ? session.session_id : null; - } - - if (!sessionId) { - try { - session = await createSession(websiteId, { - session_uuid, - hostname, - browser, - os, - screen, - language, - country, - device, - }); - } catch (e) { - if (!e.message.toLowerCase().includes('unique constraint')) { - throw e; - } - } - } - } else { - session = { - session_id: sessionId, - session_uuid, - hostname, - browser, - os, - screen, - language, - country, - device, - }; - } + session = { + session_id: sessionId, + session_uuid, + hostname, + browser, + os, + screen, + language, + country, + device, + }; return { website_id: websiteId, diff --git a/pages/api/collect.js b/pages/api/collect.js index 66e9a8a4..f253d09e 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -1,5 +1,95 @@ -import { forbidden } from 'next-basics'; +const { Resolver } = require('dns').promises; +import isbot from 'isbot'; +import ipaddr from 'ipaddr.js'; +import { createToken, unauthorized, send, badRequest, forbidden } from 'next-basics'; +import { savePageView, saveEvent } from 'queries'; +import { useCors, useSession } from 'lib/middleware'; +import { getJsonBody, getIpAddress } from 'lib/request'; +import { secret, uuid } from 'lib/crypto'; export default async (req, res) => { - return forbidden(res); + await useCors(req, res); + + if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) { + return unauthorized(res); + } + + const ignoreIps = process.env.IGNORE_IP; + const ignoreHostnames = process.env.IGNORE_HOSTNAME; + + if (ignoreIps || ignoreHostnames) { + const ips = []; + + if (ignoreIps) { + ips.push(...ignoreIps.split(',').map(n => n.trim())); + } + + if (ignoreHostnames) { + const resolver = new Resolver(); + const promises = ignoreHostnames + .split(',') + .map(n => resolver.resolve4(n.trim()).catch(() => {})); + + await Promise.all(promises).then(resolvedIps => { + ips.push(...resolvedIps.filter(n => n).flatMap(n => n)); + }); + } + + const clientIp = getIpAddress(req); + + const blocked = ips.find(ip => { + if (ip === clientIp) return true; + + // CIDR notation + if (ip.indexOf('/') > 0) { + const addr = ipaddr.parse(clientIp); + const range = ipaddr.parseCIDR(ip); + + if (addr.kind() === range[0].kind() && addr.match(range)) return true; + } + + return false; + }); + + if (blocked) { + return forbidden(res); + } + } + + await useSession(req, res); + + const { + session: { website_id, session }, + } = req; + + const { type, payload } = getJsonBody(req); + + let { url, referrer, event_name, event_data } = payload; + + if (process.env.REMOVE_TRAILING_SLASH) { + url = url.replace(/\/$/, ''); + } + + const event_uuid = uuid(); + + if (type === 'pageview') { + await savePageView(website_id, { session, url, referrer }); + } else if (type === 'event') { + await saveEvent(website_id, { + session, + event_uuid, + url, + event_name, + event_data, + }); + } else { + return badRequest(res); + } + + const token = createToken( + { website_id, session_id: session.session_id, session_uuid: session.session_uuid }, + secret(), + ); + + return send(res, token); };