Enhance Twilio Call Provider and Nginx Configuration

- Updated TwilioCallProvider to support X-Forwarded-Proto and X-Forwarded-Host headers for improved webhook signature validation when behind proxies.
- Enabled trust proxy in StartServer to ensure correct interpretation of forwarded headers.
- Removed outdated incoming call policy documentation.
- Added Nginx configuration to handle X-Forwarded-Proto and X-Forwarded-Host headers, ensuring proper proxy behavior and preventing crashes when services are unavailable.
This commit is contained in:
Nawaz Dhandala
2026-01-17 21:18:27 +00:00
parent ea47d2bd2c
commit e0a9ab8cfb
6 changed files with 30 additions and 1330 deletions

View File

@@ -1,5 +1,4 @@
import CallProviderFactory from "../Providers/CallProviderFactory";
import { NotificationWebhookHost } from "../Config";
import {
DialStatusData,
ICallProvider,
@@ -33,6 +32,7 @@ import IncomingCallLogItem from "Common/Models/DatabaseModels/IncomingCallLogIte
import ProjectCallSMSConfig from "Common/Models/DatabaseModels/ProjectCallSMSConfig";
import User from "Common/Models/DatabaseModels/User";
import Phone from "Common/Types/Phone";
import { Host, HttpProtocol } from "Common/Server/EnvironmentConfig";
const router: ExpressRouter = Express.getRouter();
@@ -289,7 +289,7 @@ router.post(
"Please wait while we connect you to the on-call engineer.";
// Construct status callback URL
const statusCallbackUrl: string = `${NotificationWebhookHost}/notification/incoming-call/dial-status/${createdCallLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
const statusCallbackUrl: string = `${HttpProtocol}${Host}/notification/incoming-call/dial-status/${createdCallLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
// Generate greeting + dial TwiML
const twiml: string = generateGreetingAndDialTwiml(
@@ -737,7 +737,7 @@ async function dialNextUser(
});
// Construct status callback URL
const statusCallbackUrl: string = `${NotificationWebhookHost}/notification/incoming-call/dial-status/${callLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
const statusCallbackUrl: string = `${HttpProtocol}${Host}/notification/incoming-call/dial-status/${callLog.id?.toString()}/${createdCallLogItem.id?.toString()}`;
// Generate dial TwiML with escalation message
const escalationMessage: string = `Connecting you to the next available engineer.`;

View File

@@ -3,7 +3,9 @@ import Email from "Common/Types/Email";
import EmailServer from "Common/Types/Email/EmailServer";
import BadDataException from "Common/Types/Exception/BadDataException";
import ObjectID from "Common/Types/ObjectID";
import { AdminDashboardClientURL } from "Common/Server/EnvironmentConfig";
import {
AdminDashboardClientURL,
} from "Common/Server/EnvironmentConfig";
import GlobalConfigService from "Common/Server/Services/GlobalConfigService";
import GlobalConfig, {
EmailServerType,

View File

@@ -255,8 +255,18 @@ export default class TwilioCallProvider implements ICallProvider {
const authToken: string = this.config.authToken;
// Build the full URL that Twilio used to generate the signature
const protocol: string = request.protocol || "https";
const host: string = request.get("host") || "";
// When behind a proxy, use X-Forwarded-Proto and X-Forwarded-Host headers
// These headers are set by reverse proxies (nginx, load balancers, etc.)
const forwardedProto: string | undefined = request.get(
"x-forwarded-proto",
) as string | undefined;
const forwardedHost: string | undefined = request.get(
"x-forwarded-host",
) as string | undefined;
// Use forwarded headers if available, otherwise fall back to request properties
const protocol: string = forwardedProto || request.protocol || "https";
const host: string = forwardedHost || request.get("host") || "";
const url: string = `${protocol}://${host}${request.originalUrl}`;
const params: Record<string, string> = {};

View File

@@ -45,6 +45,9 @@ const app: ExpressApplication = Express.getExpressApp();
app.disable("x-powered-by");
app.set("port", process.env["PORT"]);
app.set("view engine", "ejs");
// Enable trust proxy to correctly interpret X-Forwarded-* headers from reverse proxies
// This is needed for req.protocol, req.ip to be correct when behind nginx/load balancers
app.set("trust proxy", true);
app.use(CookieParser());
const jsonBodyParserMiddleware: RequestHandler = ExpressJson({

File diff suppressed because it is too large Load Diff

View File

@@ -545,12 +545,19 @@ ${PROVISION_SSL_CERTIFICATE_KEY_DIRECTIVE}
}
location /notification {
# This is for nginx not to crash when service is not available.
# This is for nginx not to crash when service is not available.
resolver 127.0.0.1 valid=30s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Use upstream X-Forwarded-Proto if available (for webhook signature validation behind proxies like ngrok)
# Falls back to $scheme if not set
set $forwarded_proto $http_x_forwarded_proto;
if ($forwarded_proto = '') {
set $forwarded_proto $scheme;
}
proxy_set_header X-Forwarded-Proto $forwarded_proto;
proxy_set_header X-Forwarded-Host $host;
# enable WebSockets (for ws://sockjs not connected error in the accounts source: https://stackoverflow.com/questions/41381444/websocket-connection-failed-error-during-websocket-handshake-unexpected-respon)
proxy_http_version 1.1;