From a49ed0a2d5934ad7af2e9cfbf7c5ccf171599591 Mon Sep 17 00:00:00 2001 From: DD Date: Fri, 21 Apr 2023 23:36:15 +0300 Subject: [PATCH] refactor(proxy): rely on auth header instead (#9422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(proxy): rely on auth header instead * chore: typo * chore: language Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * chore: more language Co-authored-by: Aura Román * chore: more language nitpicks Co-authored-by: ckohen * fix: unnecessary async --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Aura Román Co-authored-by: ckohen --- packages/proxy-container/README.md | 5 ++- packages/proxy-container/src/index.ts | 6 +-- packages/proxy/src/handlers/proxyRequests.ts | 42 ++++++++------------ packages/proxy/src/util/responseHelpers.ts | 23 ++++++++++- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/packages/proxy-container/README.md b/packages/proxy-container/README.md index b499fb418..78b8c0239 100644 --- a/packages/proxy-container/README.md +++ b/packages/proxy-container/README.md @@ -23,7 +23,7 @@ Quickly spin up an instance: -`docker run -d --restart unless-stopped --name proxy -p 127.0.0.1:8080:8080 -e DISCORD_TOKEN=abc discordjs/proxy` +`docker run -d --restart unless-stopped --name proxy -p 127.0.0.1:8080:8080 discordjs/proxy` Use it: @@ -48,6 +48,9 @@ const rest = new REST({ }); ``` +**Do note that you should not use the same proxy with multiple bots. We cannot guarantee you won't hit rate limits. +Webhooks with tokens or other requests that don't include the Authorization header are okay, though!** + ## Links - [Website][website] ([source][website-source]) diff --git a/packages/proxy-container/src/index.ts b/packages/proxy-container/src/index.ts index dff6f964e..0af44f7f5 100644 --- a/packages/proxy-container/src/index.ts +++ b/packages/proxy-container/src/index.ts @@ -3,12 +3,8 @@ import process from 'node:process'; import { proxyRequests } from '@discordjs/proxy'; import { REST } from '@discordjs/rest'; -if (!process.env.DISCORD_TOKEN) { - throw new Error('A DISCORD_TOKEN env var is required'); -} - // We want to let upstream handle retrying -const api = new REST({ rejectOnRateLimit: () => true, retries: 0 }).setToken(process.env.DISCORD_TOKEN); +const api = new REST({ rejectOnRateLimit: () => true, retries: 0 }); const server = createServer(proxyRequests(api)); const port = Number.parseInt(process.env.PORT ?? '8080', 10); diff --git a/packages/proxy/src/handlers/proxyRequests.ts b/packages/proxy/src/handlers/proxyRequests.ts index 0c9fbbf84..599413e37 100644 --- a/packages/proxy/src/handlers/proxyRequests.ts +++ b/packages/proxy/src/handlers/proxyRequests.ts @@ -1,18 +1,6 @@ import { URL } from 'node:url'; -import { - DiscordAPIError, - HTTPError, - RateLimitError, - type RequestMethod, - type REST, - type RouteLike, -} from '@discordjs/rest'; -import { - populateAbortErrorResponse, - populateGeneralErrorResponse, - populateSuccessfulResponse, - populateRatelimitErrorResponse, -} from '../util/responseHelpers.js'; +import type { RequestMethod, REST, RouteLike } from '@discordjs/rest'; +import { populateSuccessfulResponse, populateErrorResponse } from '../util/responseHelpers.js'; import type { RequestHandler } from '../util/util'; /** @@ -36,29 +24,33 @@ export function proxyRequests(rest: REST): RequestHandler { // eslint-disable-next-line unicorn/no-unsafe-regex, prefer-named-capture-group const fullRoute = parsedUrl.pathname.replace(/^\/api(\/v\d+)?/, '') as RouteLike; + const headers: Record = { + 'Content-Type': req.headers['content-type']!, + }; + + if (req.headers.authorization) { + headers.authorization = req.headers.authorization; + } + try { const discordResponse = await rest.raw({ body: req, fullRoute, // This type cast is technically incorrect, but we want Discord to throw Method Not Allowed for us method: method as RequestMethod, + // We forward the auth header anyway + auth: false, passThroughBody: true, query: parsedUrl.searchParams, - headers: { - 'Content-Type': req.headers['content-type']!, - }, + headers, }); await populateSuccessfulResponse(res, discordResponse); } catch (error) { - if (error instanceof DiscordAPIError || error instanceof HTTPError) { - populateGeneralErrorResponse(res, error); - } else if (error instanceof RateLimitError) { - populateRatelimitErrorResponse(res, error); - } else if (error instanceof Error && error.name === 'AbortError') { - populateAbortErrorResponse(res); - } else { - // Unclear if there's better course of action here for unknown errors. Any web framework allows to pass in an error handler for something like this + const knownError = populateErrorResponse(res, error); + if (!knownError) { + // Unclear if there's better course of action here for unknown errors. + // Any web framework allows to pass in an error handler for something like this // at which point the user could dictate what to do with the error - otherwise we could just 500 throw error; } diff --git a/packages/proxy/src/util/responseHelpers.ts b/packages/proxy/src/util/responseHelpers.ts index 7127ab633..092b93c74 100644 --- a/packages/proxy/src/util/responseHelpers.ts +++ b/packages/proxy/src/util/responseHelpers.ts @@ -1,6 +1,6 @@ import type { ServerResponse } from 'node:http'; import { pipeline } from 'node:stream/promises'; -import type { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest'; +import { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest'; import type { Dispatcher } from 'undici'; /** @@ -59,3 +59,24 @@ export function populateAbortErrorResponse(res: ServerResponse): void { res.statusCode = 504; res.statusMessage = 'Upstream timed out'; } + +/** + * Tries to populate a server response from an error object + * + * @param res - The server response to populate + * @param error - The error to check and use + * @returns - True if the error is known and the response object was populated, otherwise false + */ +export function populateErrorResponse(res: ServerResponse, error: unknown): boolean { + if (error instanceof DiscordAPIError || error instanceof HTTPError) { + populateGeneralErrorResponse(res, error); + } else if (error instanceof RateLimitError) { + populateRatelimitErrorResponse(res, error); + } else if (error instanceof Error && error.name === 'AbortError') { + populateAbortErrorResponse(res); + } else { + return false; + } + + return true; +}