Files
documenso/packages/lib/server-only/webhooks/zapier/unsubscribe.ts
2026-06-29 13:15:13 +10:00

59 lines
1.8 KiB
TypeScript

import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
import { prisma } from '@documenso/prisma';
import { validateApiToken } from './validateApiToken';
export const unsubscribeHandler = async (req: Request) => {
try {
const authorization = req.headers.get('authorization');
if (!authorization) {
throw new AppError(AppErrorCode.UNAUTHORIZED, { message: 'Unauthorized' });
}
const { webhookId } = await req.json();
const result = await validateApiToken({ authorization }).catch(() => {
throw new AppError(AppErrorCode.UNAUTHORIZED, { message: 'Unauthorized' });
});
const userId = result.userId ?? result.user.id;
const teamId = result.teamId ?? undefined;
// Re-verify the token holder still has MANAGE_TEAM on the team, mirroring the
// tRPC delete-webhook-by-id mutation. Guards against stale-privilege use of a
// token minted while the holder was privileged.
const deletedWebhook = await prisma.webhook.delete({
where: {
id: webhookId,
team: buildTeamWhereQuery({
teamId,
userId,
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
}),
},
});
return Response.json(deletedWebhook);
} catch (err) {
if (err instanceof AppError) {
// Map authorization failures to 401, keep other AppErrors as 400 to
// preserve the existing Zapier contract.
const status = err.code === AppErrorCode.UNAUTHORIZED ? 401 : 400;
return Response.json({ message: err.message }, { status });
}
console.error(err);
return Response.json(
{
message: 'Internal Server Error',
},
{ status: 500 },
);
}
};