diff --git a/.env.example b/.env.example index 201f450..6a738fd 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,9 @@ # CHECK_KEYS=true # QUOTA_DISPLAY_MODE=full # QUEUE_MODE=fair +# BLOCKED_ORIGINS=reddit.com,9gag.com +# BLOCK_MESSAGE="You must be over the age of majority in your country to use this service." +# BLOCK_REDIRECT="https://roblox.com/" # Note: CHECK_KEYS is disabled by default in local development mode, but enabled # by default in production mode. diff --git a/src/config.ts b/src/config.ts index 4934925..939e6c5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -95,6 +95,22 @@ type Config = { * `none`: Requests are not queued and users have to retry manually */ queueMode: DequeueMode; + /** + * Comma-separated list of origins to block. Requests matching any of these + * origins or referers will be rejected. + * Partial matches are allowed, so `reddit` will match `www.reddit.com`. + * Include only the hostname, not the protocol or path, e.g: + * `reddit.com,9gag.com,gaiaonline.com` + */ + blockedOrigins?: string; + /** + * Message to return when rejecting requests from blocked origins. + */ + blockMessage?: string; + /** + * Desination URL to redirect blocked requests to, for non-JSON requests. + */ + blockRedirect?: string; }; // To change configs, create a file called .env in the root directory. @@ -127,6 +143,12 @@ export const config: Config = { undefined ), queueMode: getEnvWithDefault("QUEUE_MODE", "fair"), + blockedOrigins: getEnvWithDefault("BLOCKED_ORIGINS", undefined), + blockMessage: getEnvWithDefault( + "BLOCK_MESSAGE", + "You must be over the age of majority in your country to use this service." + ), + blockRedirect: getEnvWithDefault("BLOCK_REDIRECT", "https://www.9gag.com"), } as const; /** Prevents the server from starting if config state is invalid. */ @@ -208,6 +230,9 @@ export const OMITTED_KEYS: (keyof Config)[] = [ "firebaseRtdbUrl", "gatekeeperStore", "maxIpsPerUser", + "blockedOrigins", + "blockMessage", + "blockRedirect", ]; const getKeys = Object.keys as (obj: T) => Array; diff --git a/src/proxy/check-origin.ts b/src/proxy/check-origin.ts new file mode 100644 index 0000000..edd1ae6 --- /dev/null +++ b/src/proxy/check-origin.ts @@ -0,0 +1,40 @@ +import { config } from "../config"; +import { RequestHandler } from "express"; + +const BLOCKED_REFERERS = config.blockedOrigins?.split(",") || []; + +/** Disallow requests from blocked origins and referers. */ +export const checkOrigin: RequestHandler = (req, res, next) => { + const blocks = BLOCKED_REFERERS || []; + for (const block of blocks) { + if ( + req.headers.origin?.includes(block) || + req.headers.referer?.includes(block) + ) { + req.log.warn( + { origin: req.headers.origin, referer: req.headers.referer }, + "Blocked request from origin or referer" + ); + if (!req.accepts("html")) { + return res.status(403).json({ + error: { type: "blocked_origin", message: config.blockMessage }, + }); + } else { + const destination = config.blockRedirect || "https://openai.com"; + return res.status(403).send( + ` + + Redirecting + + + +

${config.blockMessage}

+

Please hold while you are redirected to a more suitable service.

+ +` + ); + } + } + } + next(); +}; diff --git a/src/server.ts b/src/server.ts index 7e55669..90f99ef 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,6 +12,7 @@ import { handleInfoPage } from "./info-page"; import { logQueue } from "./prompt-logging"; import { start as startRequestQueue } from "./proxy/queue"; import { init as initUserStore } from "./proxy/auth/user-store"; +import { checkOrigin } from "./proxy/check-origin"; const PORT = config.port; @@ -61,6 +62,7 @@ app.use( app.set("trust proxy", true); // routes +app.use(checkOrigin); app.get("/", handleInfoPage); app.use("/admin", adminRouter); app.use("/proxy", proxyRouter);