Skip to content

Commit 0e7840b

Browse files
fix(http-outgoingmessage-headers): crash
cause: .slip on undefined dued to catching imported type
1 parent 3af7c48 commit 0e7840b

12 files changed

Lines changed: 215 additions & 10 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// took form https://github.com/calcom/cal.diy/blob/e3eaa69339c549365f7749634e5d27f3aef1462f/packages/features/bot-detection/BotDetectionService.ts
2+
import { checkBotId } from "botid/server";
3+
import type { IncomingHttpHeaders } from "node:http";
4+
5+
import type { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
6+
import type { FeaturesRepository } from "@calcom/features/flags/features.repository";
7+
import { ErrorCode } from "@calcom/lib/errorCodes";
8+
import { ErrorWithCode } from "@calcom/lib/errors";
9+
import { HttpError } from "@calcom/lib/http-error";
10+
import logger from "@calcom/lib/logger";
11+
12+
interface BotDetectionConfig {
13+
eventTypeId?: number;
14+
headers: IncomingHttpHeaders;
15+
}
16+
17+
const log = logger.getSubLogger({ prefix: ["[BotDetectionService]"] });
18+
19+
export class BotDetectionService {
20+
constructor(
21+
private featuresRepository: FeaturesRepository,
22+
private eventTypeRepository: EventTypeRepository
23+
) {}
24+
25+
private instanceHasBotIdEnabled() {
26+
return process.env.NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER === "1";
27+
}
28+
29+
async checkBotDetection(config: BotDetectionConfig): Promise<void> {
30+
if (!this.instanceHasBotIdEnabled()) return;
31+
32+
const { eventTypeId, headers } = config;
33+
34+
// If no eventTypeId provided, skip bot detection
35+
if (!eventTypeId) {
36+
return;
37+
}
38+
39+
if (!Number.isInteger(eventTypeId) || eventTypeId <= 0) {
40+
throw new ErrorWithCode(
41+
ErrorCode.BadRequest,
42+
`Invalid eventTypeId: ${eventTypeId}. Must be a positive integer.`
43+
);
44+
}
45+
46+
// Fetch only the teamId from the event type
47+
const eventType = await this.eventTypeRepository.getTeamIdByEventTypeId({
48+
id: eventTypeId,
49+
});
50+
51+
// Only check for team events
52+
if (!eventType?.teamId) {
53+
return;
54+
}
55+
56+
// Check if BotID feature is enabled for this team (also checks global scope - enabling on all teams)
57+
const isBotIDEnabled = await this.featuresRepository.checkIfTeamHasFeature(
58+
eventType.teamId,
59+
"booker-botid"
60+
);
61+
62+
if (!isBotIDEnabled) {
63+
return;
64+
}
65+
66+
// Perform bot detection
67+
const verification = await checkBotId({
68+
advancedOptions: {
69+
headers,
70+
},
71+
});
72+
73+
// Log verification results with detailed information
74+
const verificationDetails = {
75+
isBot: verification.isBot,
76+
isHuman: verification.isHuman,
77+
isVerifiedBot: verification.isVerifiedBot,
78+
verifiedBotName: verification.verifiedBotName,
79+
verifiedBotCategory: verification.verifiedBotCategory,
80+
bypassed: verification.bypassed,
81+
classificationReason: verification.classificationReason,
82+
teamId: eventType.teamId,
83+
eventTypeId,
84+
};
85+
86+
if (verification.isBot) {
87+
log.warn("Bot detected - blocking request", verificationDetails);
88+
throw new HttpError({ statusCode: 403, message: "Access denied" });
89+
}
90+
}
91+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// took form https://github.com/calcom/cal.diy/blob/e3eaa69339c549365f7749634e5d27f3aef1462f/packages/features/bot-detection/BotDetectionService.ts
2+
import { checkBotId } from "botid/server";
3+
import type { IncomingHttpHeaders } from "node:http";
4+
5+
import type { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
6+
import type { FeaturesRepository } from "@calcom/features/flags/features.repository";
7+
import { ErrorCode } from "@calcom/lib/errorCodes";
8+
import { ErrorWithCode } from "@calcom/lib/errors";
9+
import { HttpError } from "@calcom/lib/http-error";
10+
import logger from "@calcom/lib/logger";
11+
12+
interface BotDetectionConfig {
13+
eventTypeId?: number;
14+
headers: IncomingHttpHeaders;
15+
}
16+
17+
const log = logger.getSubLogger({ prefix: ["[BotDetectionService]"] });
18+
19+
export class BotDetectionService {
20+
constructor(
21+
private featuresRepository: FeaturesRepository,
22+
private eventTypeRepository: EventTypeRepository
23+
) {}
24+
25+
private instanceHasBotIdEnabled() {
26+
return process.env.NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER === "1";
27+
}
28+
29+
async checkBotDetection(config: BotDetectionConfig): Promise<void> {
30+
if (!this.instanceHasBotIdEnabled()) return;
31+
32+
const { eventTypeId, headers } = config;
33+
34+
// If no eventTypeId provided, skip bot detection
35+
if (!eventTypeId) {
36+
return;
37+
}
38+
39+
if (!Number.isInteger(eventTypeId) || eventTypeId <= 0) {
40+
throw new ErrorWithCode(
41+
ErrorCode.BadRequest,
42+
`Invalid eventTypeId: ${eventTypeId}. Must be a positive integer.`
43+
);
44+
}
45+
46+
// Fetch only the teamId from the event type
47+
const eventType = await this.eventTypeRepository.getTeamIdByEventTypeId({
48+
id: eventTypeId,
49+
});
50+
51+
// Only check for team events
52+
if (!eventType?.teamId) {
53+
return;
54+
}
55+
56+
// Check if BotID feature is enabled for this team (also checks global scope - enabling on all teams)
57+
const isBotIDEnabled = await this.featuresRepository.checkIfTeamHasFeature(
58+
eventType.teamId,
59+
"booker-botid"
60+
);
61+
62+
if (!isBotIDEnabled) {
63+
return;
64+
}
65+
66+
// Perform bot detection
67+
const verification = await checkBotId({
68+
advancedOptions: {
69+
headers,
70+
},
71+
});
72+
73+
// Log verification results with detailed information
74+
const verificationDetails = {
75+
isBot: verification.isBot,
76+
isHuman: verification.isHuman,
77+
isVerifiedBot: verification.isVerifiedBot,
78+
verifiedBotName: verification.verifiedBotName,
79+
verifiedBotCategory: verification.verifiedBotCategory,
80+
bypassed: verification.bypassed,
81+
classificationReason: verification.classificationReason,
82+
teamId: eventType.teamId,
83+
eventTypeId,
84+
};
85+
86+
if (verification.isBot) {
87+
log.warn("Bot detected - blocking request", verificationDetails);
88+
throw new HttpError({ statusCode: 403, message: "Access denied" });
89+
}
90+
}
91+
}

recipes/http-outgoingmessage-headers/tests/check-if-exists/expected.cjs renamed to recipes/http-outgoingmessage-headers/tests/check-if-exists/expected.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/check-if-exists/input.cjs renamed to recipes/http-outgoingmessage-headers/tests/check-if-exists/input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/iterating-over-headers/expected.cjs renamed to recipes/http-outgoingmessage-headers/tests/iterating-over-headers/expected.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/iterating-over-headers/input.cjs renamed to recipes/http-outgoingmessage-headers/tests/iterating-over-headers/input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/reading-headers/expected.cjs renamed to recipes/http-outgoingmessage-headers/tests/reading-headers/expected.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
function handler(req, res) {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/reading-headers/input.cjs renamed to recipes/http-outgoingmessage-headers/tests/reading-headers/input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
function handler(req, res) {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/supported-methods/expected.cjs renamed to recipes/http-outgoingmessage-headers/tests/supported-methods/expected.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

recipes/http-outgoingmessage-headers/tests/supported-methods/input.cjs renamed to recipes/http-outgoingmessage-headers/tests/supported-methods/input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const http = require('http');
1+
const http = require('node:http');
22

33
const server = http.createServer((req, res) => {
44
res.setHeader('content-type', 'application/json');

0 commit comments

Comments
 (0)