fix(api): sending duplicated responses for bot events (improved code as well)

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-11-30 21:40:25 +01:00
parent d80754a6fd
commit afff099c5b
6 changed files with 101 additions and 144 deletions

View File

@@ -0,0 +1,26 @@
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
import type { TrackHandlerPayload } from '@openpanel/sdk';
import type {
FastifyReply,
FastifyRequest,
HookHandlerDoneFunction,
} from 'fastify';
export async function clientHook(
req: FastifyRequest<{
Body: TrackHandlerPayload;
}>,
reply: FastifyReply,
) {
try {
const client = await validateSdkRequest(req.headers);
req.projectId = client.projectId;
req.client = client;
} catch (error) {
if (error instanceof SdkAuthError) {
return reply.status(401).send(error.message);
}
return reply.status(500).send('Internal server error');
}
}

View File

@@ -0,0 +1,51 @@
import { isBot } from '@/bots';
import { createBotEvent } from '@openpanel/db';
import type { TrackHandlerPayload } from '@openpanel/sdk';
import type { FastifyReply, FastifyRequest } from 'fastify';
type DeprecatedEventPayload = {
name: string;
properties: Record<string, unknown>;
timestamp: string;
};
export async function isBotHook(
req: FastifyRequest<{
Body: TrackHandlerPayload | DeprecatedEventPayload;
}>,
reply: FastifyReply,
) {
const bot = req.headers['user-agent']
? isBot(req.headers['user-agent'])
: null;
if (bot && req.client?.projectId) {
if ('type' in req.body && req.body.type === 'track') {
const path = (req.body.payload.properties?.__path ||
req.body.payload.properties?.path) as string | undefined;
if (path) {
await createBotEvent({
...bot,
projectId: req.client.projectId,
path: path ?? '',
createdAt: new Date(),
});
}
// Handle deprecated events (v1)
} else if ('name' in req.body && 'properties' in req.body) {
const path = (req.body.properties?.__path || req.body.properties?.path) as
| string
| undefined;
if (path) {
await createBotEvent({
...bot,
projectId: req.client.projectId,
path: path ?? '',
createdAt: new Date(),
});
}
}
return reply.status(202).send('OK');
}
}

View File

@@ -1,58 +1,12 @@
import { isBot } from '@/bots';
import * as controller from '@/controllers/event.controller';
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
import { logger } from '@/utils/logger';
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
import type { FastifyPluginCallback } from 'fastify';
import { createBotEvent } from '@openpanel/db';
import type { PostEventPayload } from '@openpanel/sdk';
import { clientHook } from '@/hooks/client.hook';
import { isBotHook } from '@/hooks/is-bot.hook';
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
fastify.addHook(
'preHandler',
async (
req: FastifyRequest<{
Body: PostEventPayload;
}>,
reply,
) => {
try {
const client = await validateSdkRequest(req.headers).catch((error) => {
if (error instanceof SdkAuthError) {
return reply.status(401).send(error.message);
}
logger.error('Failed to validate sdk request', { error });
return reply.status(401).send('Unknown validation error');
});
if (!client?.projectId) {
return reply.status(401).send('No project found for this client');
}
req.projectId = client.projectId;
req.client = client;
const bot = req.headers['user-agent']
? isBot(req.headers['user-agent'])
: null;
if (bot) {
const path = (req.body?.properties?.__path ||
req.body?.properties?.path) as string | undefined;
await createBotEvent({
...bot,
projectId: client.projectId,
path: path ?? '',
createdAt: new Date(req.body?.timestamp),
});
reply.status(202).send('OK');
}
} catch (error) {
logger.error('Failed to create bot event', { error });
reply.status(401).send();
return;
}
},
);
fastify.addHook('preHandler', clientHook);
fastify.addHook('preHandler', isBotHook);
fastify.route({
method: 'POST',

View File

@@ -1,39 +1,11 @@
import { isBot } from '@/bots';
import * as controller from '@/controllers/profile.controller';
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
import { logger } from '@/utils/logger';
import { clientHook } from '@/hooks/client.hook';
import { isBotHook } from '@/hooks/is-bot.hook';
import type { FastifyPluginCallback } from 'fastify';
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
fastify.addHook('preHandler', async (req, reply) => {
try {
const client = await validateSdkRequest(req.headers).catch((error) => {
if (error instanceof SdkAuthError) {
return reply.status(401).send(error.message);
}
logger.error('Failed to validate sdk request', { error });
return reply.status(401).send('Unknown validation error');
});
if (!client?.projectId) {
return reply.status(401).send('No project found for this client');
}
req.projectId = client.projectId;
req.client = client;
const bot = req.headers['user-agent']
? isBot(req.headers['user-agent'])
: null;
if (bot) {
return reply.status(202).send('OK');
}
} catch (error) {
logger.error('Failed to create bot event', { error });
reply.status(401).send();
return;
}
});
fastify.addHook('preHandler', clientHook);
fastify.addHook('preHandler', isBotHook);
fastify.route({
method: 'POST',

View File

@@ -1,62 +1,16 @@
import { isBot } from '@/bots';
import { handler } from '@/controllers/track.controller';
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
import { logger } from '@/utils/logger';
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
import { clientHook } from '@/hooks/client.hook';
import { isBotHook } from '@/hooks/is-bot.hook';
import { createBotEvent } from '@openpanel/db';
import type { TrackHandlerPayload } from '@openpanel/sdk';
const trackRouter: FastifyPluginCallback = (fastify, opts, done) => {
fastify.addHook(
'preHandler',
async (
req: FastifyRequest<{
Body: TrackHandlerPayload;
}>,
reply,
) => {
try {
const client = await validateSdkRequest(req.headers).catch((error) => {
if (error instanceof SdkAuthError) {
return reply.status(401).send(error.message);
}
logger.error('Failed to validate sdk request', { error });
return reply.status(401).send('Unknown validation error');
});
if (!client?.projectId) {
return reply.status(401).send('No project found for this client');
}
req.projectId = client.projectId;
req.client = client;
const bot = req.headers['user-agent']
? isBot(req.headers['user-agent'])
: null;
if (bot) {
if (req.body.type === 'track') {
const path = (req.body.payload.properties?.__path ||
req.body.payload.properties?.path) as string | undefined;
await createBotEvent({
...bot,
projectId: client.projectId,
path: path ?? '',
createdAt: new Date(),
});
}
reply.status(202).send('OK');
}
} catch (error) {
logger.error('Failed to create bot event', { error });
reply.status(401).send();
return;
}
},
);
fastify.addHook('preHandler', clientHook);
fastify.addHook('preHandler', isBotHook);
fastify.route({
method: 'POST',

View File

@@ -32,9 +32,11 @@ export class SdkAuthError extends Error {
}
}
type ClientWithProjectId = Client & { projectId: string };
export async function validateSdkRequest(
headers: RawRequestDefaultExpression['headers'],
): Promise<Client> {
): Promise<ClientWithProjectId> {
const clientIdNew = headers['openpanel-client-id'] as string;
const clientIdOld = headers['mixan-client-id'] as string;
const clientSecretNew = headers['openpanel-client-secret'] as string;
@@ -57,13 +59,11 @@ export async function validateSdkRequest(
throw createError('Ingestion: Missing client id');
}
const client = await db.client
.findUnique({
where: {
id: clientId,
},
})
.catch(() => null);
const client = await db.client.findUnique({
where: {
id: clientId,
},
});
if (!client) {
throw createError('Ingestion: Invalid client id');
@@ -91,17 +91,17 @@ export async function validateSdkRequest(
});
if (domainAllowed) {
return client;
return client as ClientWithProjectId;
}
if (client.cors === '*' && origin) {
return client;
return client as ClientWithProjectId;
}
}
if (client.secret && clientSecret) {
if (await verifyPassword(clientSecret, client.secret)) {
return client;
return client as ClientWithProjectId;
}
}