Skip to content

Commit 7cd575b

Browse files
authored
fix: Enable trustProxy for cloud-hosted Engines to correct IP allowlist (#699)
* fix: Enable trustProxy for cloud-hosted Engines to correct IP allowlist * log unauthorized IP
1 parent a5b31d4 commit 7cd575b

File tree

4 files changed

+41
-32
lines changed

4 files changed

+41
-32
lines changed

src/server/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ export const initServer = async () => {
5555
connectionTimeout: SERVER_CONNECTION_TIMEOUT,
5656
disableRequestLogging: true,
5757
// env.TRUST_PROXY is used to determine if the X-Forwarded-For header should be trusted.
58+
// This option is force enabled for cloud-hosted Engines.
5859
// See: https://fastify.dev/docs/latest/Reference/Server/#trustproxy
59-
trustProxy: env.TRUST_PROXY,
60+
trustProxy: env.TRUST_PROXY || !!env.ENGINE_TIER,
6061
...(env.ENABLE_HTTPS ? httpsObject : {}),
6162
}).withTypeProvider<TypeBoxTypeProvider>();
6263

src/server/middleware/auth.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { parseJWT } from "@thirdweb-dev/auth";
22
import {
33
ThirdwebAuth,
4-
ThirdwebAuthUser,
54
getToken as getJWT,
5+
type ThirdwebAuthUser,
66
} from "@thirdweb-dev/auth/fastify";
77
import { AsyncWallet } from "@thirdweb-dev/wallets/evm/wallets/async";
88
import { createHash } from "crypto";
9-
import { FastifyInstance } from "fastify";
10-
import { FastifyRequest } from "fastify/types/request";
11-
import jsonwebtoken, { JwtPayload } from "jsonwebtoken";
9+
import type { FastifyInstance } from "fastify";
10+
import type { FastifyRequest } from "fastify/types/request";
11+
import jsonwebtoken, { type JwtPayload } from "jsonwebtoken";
1212
import { validate as uuidValidate } from "uuid";
1313
import { getPermissions } from "../../db/permissions/getPermissions";
1414
import { createToken } from "../../db/tokens/createToken";
@@ -87,7 +87,7 @@ export const withAuth = async (server: FastifyInstance) => {
8787
await revokeToken({ id: payload.jti });
8888
} catch {
8989
logger({
90-
service: "worker",
90+
service: "server",
9191
level: "error",
9292
message: `[Auth] Failed to revoke token ${payload.jti}`,
9393
});
@@ -267,10 +267,15 @@ const handleWebsocketAuth = async (
267267

268268
const isIpInAllowlist = await checkIpInAllowlist(req);
269269
if (!isIpInAllowlist) {
270+
logger({
271+
service: "server",
272+
level: "error",
273+
message: `Unauthorized IP address: ${req.ip}`,
274+
});
270275
return {
271276
isAuthed: false,
272277
error:
273-
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security",
278+
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
274279
};
275280
}
276281

@@ -336,9 +341,14 @@ const handleKeypairAuth = async (args: {
336341

337342
const isIpInAllowlist = await checkIpInAllowlist(req);
338343
if (!isIpInAllowlist) {
339-
error =
340-
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security";
341-
throw error;
344+
logger({
345+
service: "server",
346+
level: "error",
347+
message: `Unauthorized IP address: ${req.ip}`,
348+
});
349+
throw new Error(
350+
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
351+
);
342352
}
343353
return { isAuthed: true };
344354
} catch (e) {
@@ -391,12 +401,16 @@ const handleAccessToken = async (
391401
}
392402

393403
const isIpInAllowlist = await checkIpInAllowlist(req);
394-
395404
if (!isIpInAllowlist) {
405+
logger({
406+
service: "server",
407+
level: "error",
408+
message: `Unauthorized IP address: ${req.ip}`,
409+
});
396410
return {
397411
isAuthed: false,
398412
error:
399-
"Unauthorized IP Address. See: https://portal.thirdweb.com/engine/features/security",
413+
"Unauthorized IP address. See: https://portal.thirdweb.com/engine/features/security",
400414
};
401415
}
402416

@@ -505,7 +519,6 @@ const hashRequestBody = (req: FastifyRequest): string => {
505519
/**
506520
* Check if the request IP is in the allowlist.
507521
* Fetches cached config if available.
508-
* env.TRUST_PROXY is used to determine if the X-Forwarded-For header should be trusted.
509522
* @param req FastifyRequest
510523
* @returns boolean
511524
* @async

src/server/routes/system/health.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Static, Type } from "@sinclair/typebox";
2-
import { FastifyInstance } from "fastify";
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { isDatabaseReachable } from "../../../db/client";
55
import { env } from "../../../utils/env";
6-
import { isRedisReachable, redis } from "../../../utils/redis/redis";
6+
import { isRedisReachable } from "../../../utils/redis/redis";
77
import { createCustomError } from "../../middleware/error";
88

99
type EngineFeature = "KEYPAIR_AUTH" | "CONTRACT_SUBSCRIPTIONS" | "IP_ALLOWLIST";
@@ -63,8 +63,8 @@ export async function healthCheck(fastify: FastifyInstance) {
6363

6464
res.status(StatusCodes.OK).send({
6565
status: "OK",
66-
engineVersion: process.env.ENGINE_VERSION,
67-
engineTier: process.env.ENGINE_TIER ?? "SELF_HOSTED",
66+
engineVersion: env.ENGINE_VERSION,
67+
engineTier: env.ENGINE_TIER ?? "SELF_HOSTED",
6868
features: getFeatures(),
6969
});
7070
},
@@ -73,11 +73,9 @@ export async function healthCheck(fastify: FastifyInstance) {
7373

7474
const getFeatures = (): EngineFeature[] => {
7575
// IP Allowlist is always available as a feature, but added as a feature for backwards compatibility.
76-
const features: EngineFeature[] = ["IP_ALLOWLIST"];
76+
const features: EngineFeature[] = ["CONTRACT_SUBSCRIPTIONS", "IP_ALLOWLIST"];
7777

7878
if (env.ENABLE_KEYPAIR_AUTH) features.push("KEYPAIR_AUTH");
79-
// Contract Subscriptions requires Redis.
80-
if (redis) features.push("CONTRACT_SUBSCRIPTIONS");
8179

8280
return features;
8381
};

src/utils/env.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ export const UrlSchema = z
2525
{ message: "Invalid URL" },
2626
);
2727

28-
const boolSchema = (defaultBool: "true" | "false") =>
29-
z
30-
.string()
31-
.default(defaultBool)
32-
.refine((s) => s === "true" || s === "false", "must be 'true' or 'false'")
33-
.transform((s) => s === "true");
34-
3528
export const env = createEnv({
3629
server: {
3730
NODE_ENV: z
@@ -48,6 +41,8 @@ export const env = createEnv({
4841
.array(z.enum(["server", "worker", "cache", "websocket"]))
4942
.parse(s.split(",")),
5043
),
44+
ENGINE_VERSION: z.string().optional(),
45+
ENGINE_TIER: z.string().optional(),
5146
THIRDWEB_API_SECRET_KEY: z.string().min(1),
5247
ADMIN_WALLET_ADDRESS: z.string().min(1),
5348
ENCRYPTION_PASSWORD: z.string().min(1),
@@ -58,15 +53,15 @@ export const env = createEnv({
5853
),
5954
PORT: z.coerce.number().default(3005),
6055
HOST: z.string().default("0.0.0.0"),
61-
ENABLE_HTTPS: boolSchema("false"),
56+
ENABLE_HTTPS: z.coerce.boolean().default(false),
6257
HTTPS_PASSPHRASE: z.string().default("thirdweb-engine"),
63-
TRUST_PROXY: z.boolean().default(false),
58+
TRUST_PROXY: z.coerce.boolean().default(false),
6459
CLIENT_ANALYTICS_URL: z
6560
.union([UrlSchema, z.literal("")])
6661
.default("https://c.thirdweb.com/event"),
6762
SDK_BATCH_TIME_LIMIT: z.coerce.number().default(0),
6863
SDK_BATCH_SIZE_LIMIT: z.coerce.number().default(100),
69-
ENABLE_KEYPAIR_AUTH: boolSchema("false"),
64+
ENABLE_KEYPAIR_AUTH: z.coerce.boolean().default(false),
7065
CONTRACT_SUBSCRIPTIONS_DELAY_SECONDS: z.coerce
7166
.number()
7267
.nonnegative()
@@ -82,7 +77,7 @@ export const env = createEnv({
8277

8378
// Prometheus
8479
METRICS_PORT: z.coerce.number().default(4001),
85-
METRICS_ENABLED: boolSchema("true"),
80+
METRICS_ENABLED: z.coerce.boolean().default(true),
8681

8782
/**
8883
* Limits
@@ -108,6 +103,8 @@ export const env = createEnv({
108103
NODE_ENV: process.env.NODE_ENV,
109104
LOG_LEVEL: process.env.LOG_LEVEL,
110105
LOG_SERVICES: process.env.LOG_SERVICES,
106+
ENGINE_VERSION: process.env.ENGINE_VERSION,
107+
ENGINE_TIER: process.env.ENGINE_TIER,
111108
THIRDWEB_API_SECRET_KEY: process.env.THIRDWEB_API_SECRET_KEY,
112109
ADMIN_WALLET_ADDRESS: process.env.ADMIN_WALLET_ADDRESS,
113110
ENCRYPTION_PASSWORD: process.env.ENCRYPTION_PASSWORD,

0 commit comments

Comments
 (0)