mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 03:49:11 +00:00
fix(api): prevent creation of Response objects outside of request time
This commit is contained in:
parent
9c935f2847
commit
823a99b4eb
5 changed files with 40 additions and 17 deletions
|
@ -21,6 +21,24 @@ const workerShims = {
|
|||
|
||||
let listeners = [];
|
||||
|
||||
let isResponseConstructorAllowed = false;
|
||||
|
||||
/**
|
||||
* SafeResponse wraps a fetch Response to yell loudly if constructed at an unsafe time.
|
||||
* Cloudflare will reject all Response objects that aren't created during a request, so no pre-generation is allowed.
|
||||
*/
|
||||
class SafeResponse extends fetch.Response {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
if (!isResponseConstructorAllowed) {
|
||||
throw new Error(
|
||||
'Response object created outside of request context. This will be rejected by Cloudflare.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const context = () =>
|
||||
vm.createContext(
|
||||
{
|
||||
|
@ -30,7 +48,7 @@ const context = () =>
|
|||
listeners.push(fn);
|
||||
}
|
||||
},
|
||||
Response: fetch.Response,
|
||||
Response: SafeResponse,
|
||||
URL: URL,
|
||||
crypto: crypto,
|
||||
setTimeout: setTimeout,
|
||||
|
@ -75,6 +93,7 @@ const server = http.createServer((req, res) => {
|
|||
console.log(
|
||||
`${loggedStatus} [${timeEnd - timeStart}ms] - ${req.method} ${req.url}`
|
||||
);
|
||||
isResponseConstructorAllowed = false;
|
||||
},
|
||||
request: new fetch.Request(
|
||||
new URL(`http://${req.headers.host || 'localhost'}${req.url}`),
|
||||
|
@ -95,6 +114,7 @@ const server = http.createServer((req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
isResponseConstructorAllowed = true;
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
listener(event);
|
||||
|
|
|
@ -7,14 +7,15 @@ import {
|
|||
import { respond, withSession } from '../utils/api-tools';
|
||||
import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild';
|
||||
|
||||
const fail = respond(
|
||||
{
|
||||
error: 'guild not found',
|
||||
},
|
||||
{
|
||||
status: 404,
|
||||
}
|
||||
);
|
||||
const fail = () =>
|
||||
respond(
|
||||
{
|
||||
error: 'guild not found',
|
||||
},
|
||||
{
|
||||
status: 404,
|
||||
}
|
||||
);
|
||||
|
||||
export const GetPickerData = withSession(
|
||||
(session?: SessionData) => async (request: Request): Promise<Response> => {
|
||||
|
@ -38,14 +39,14 @@ export const GetPickerData = withSession(
|
|||
// Save a Discord API request by checking if this user is a member by session first
|
||||
const checkGuild = guilds.find((guild) => guild.id === guildID);
|
||||
if (!checkGuild) {
|
||||
return fail;
|
||||
return fail();
|
||||
}
|
||||
|
||||
const guild = await getGuild(guildID, {
|
||||
skipCachePull: url.searchParams.has('__no_cache'),
|
||||
});
|
||||
if (!guild) {
|
||||
return fail;
|
||||
return fail();
|
||||
}
|
||||
|
||||
const memberRolesP = getGuildMemberRoles({
|
||||
|
@ -57,7 +58,7 @@ export const GetPickerData = withSession(
|
|||
|
||||
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
|
||||
if (!memberRoles) {
|
||||
return fail;
|
||||
return fail();
|
||||
}
|
||||
|
||||
const presentableGuild: PresentableGuild = {
|
||||
|
|
|
@ -22,7 +22,7 @@ const AuthErrorResponse = (extra?: string) =>
|
|||
);
|
||||
|
||||
export const LoginCallback = resolveFailures(
|
||||
AuthErrorResponse(),
|
||||
AuthErrorResponse,
|
||||
async (request: Request): Promise<Response> => {
|
||||
const query = new URL(request.url).searchParams;
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export class Router {
|
|||
this.routingTree[lowerMethod][rootPath] = handler;
|
||||
}
|
||||
|
||||
async handle(request: Request): Promise<Response> | Response {
|
||||
async handle(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/' || url.pathname === '') {
|
||||
|
|
|
@ -16,14 +16,16 @@ export const respond = (obj: Record<string, any>, init?: ResponseInit) =>
|
|||
new Response(JSON.stringify(obj), init);
|
||||
|
||||
export const resolveFailures = (
|
||||
handleWith: Response,
|
||||
handleWith: () => Response,
|
||||
handler: (request: Request) => Promise<Response> | Response
|
||||
) => async (request: Request): Promise<Response> => {
|
||||
try {
|
||||
return handler(request);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return handleWith || respond({ error: 'internal server error' }, { status: 500 });
|
||||
return (
|
||||
handleWith() || respond({ error: 'internal server error' }, { status: 500 })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -109,7 +111,7 @@ export const cacheLayer = <Identity, Data>(
|
|||
const NotAuthenticated = (extra?: string) =>
|
||||
respond(
|
||||
{
|
||||
err: extra || 'not authenticated',
|
||||
error: extra || 'not authenticated',
|
||||
},
|
||||
{ status: 403 }
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue