mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 11:59: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 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 = () =>
|
const context = () =>
|
||||||
vm.createContext(
|
vm.createContext(
|
||||||
{
|
{
|
||||||
|
@ -30,7 +48,7 @@ const context = () =>
|
||||||
listeners.push(fn);
|
listeners.push(fn);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Response: fetch.Response,
|
Response: SafeResponse,
|
||||||
URL: URL,
|
URL: URL,
|
||||||
crypto: crypto,
|
crypto: crypto,
|
||||||
setTimeout: setTimeout,
|
setTimeout: setTimeout,
|
||||||
|
@ -75,6 +93,7 @@ const server = http.createServer((req, res) => {
|
||||||
console.log(
|
console.log(
|
||||||
`${loggedStatus} [${timeEnd - timeStart}ms] - ${req.method} ${req.url}`
|
`${loggedStatus} [${timeEnd - timeStart}ms] - ${req.method} ${req.url}`
|
||||||
);
|
);
|
||||||
|
isResponseConstructorAllowed = false;
|
||||||
},
|
},
|
||||||
request: new fetch.Request(
|
request: new fetch.Request(
|
||||||
new URL(`http://${req.headers.host || 'localhost'}${req.url}`),
|
new URL(`http://${req.headers.host || 'localhost'}${req.url}`),
|
||||||
|
@ -95,6 +114,7 @@ const server = http.createServer((req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isResponseConstructorAllowed = true;
|
||||||
for (let listener of listeners) {
|
for (let listener of listeners) {
|
||||||
try {
|
try {
|
||||||
listener(event);
|
listener(event);
|
||||||
|
|
|
@ -7,7 +7,8 @@ import {
|
||||||
import { respond, withSession } from '../utils/api-tools';
|
import { respond, withSession } from '../utils/api-tools';
|
||||||
import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild';
|
import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild';
|
||||||
|
|
||||||
const fail = respond(
|
const fail = () =>
|
||||||
|
respond(
|
||||||
{
|
{
|
||||||
error: 'guild not found',
|
error: 'guild not found',
|
||||||
},
|
},
|
||||||
|
@ -38,14 +39,14 @@ export const GetPickerData = withSession(
|
||||||
// Save a Discord API request by checking if this user is a member by session first
|
// Save a Discord API request by checking if this user is a member by session first
|
||||||
const checkGuild = guilds.find((guild) => guild.id === guildID);
|
const checkGuild = guilds.find((guild) => guild.id === guildID);
|
||||||
if (!checkGuild) {
|
if (!checkGuild) {
|
||||||
return fail;
|
return fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = await getGuild(guildID, {
|
const guild = await getGuild(guildID, {
|
||||||
skipCachePull: url.searchParams.has('__no_cache'),
|
skipCachePull: url.searchParams.has('__no_cache'),
|
||||||
});
|
});
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
return fail;
|
return fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberRolesP = getGuildMemberRoles({
|
const memberRolesP = getGuildMemberRoles({
|
||||||
|
@ -57,7 +58,7 @@ export const GetPickerData = withSession(
|
||||||
|
|
||||||
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
|
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
|
||||||
if (!memberRoles) {
|
if (!memberRoles) {
|
||||||
return fail;
|
return fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
const presentableGuild: PresentableGuild = {
|
const presentableGuild: PresentableGuild = {
|
||||||
|
|
|
@ -22,7 +22,7 @@ const AuthErrorResponse = (extra?: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LoginCallback = resolveFailures(
|
export const LoginCallback = resolveFailures(
|
||||||
AuthErrorResponse(),
|
AuthErrorResponse,
|
||||||
async (request: Request): Promise<Response> => {
|
async (request: Request): Promise<Response> => {
|
||||||
const query = new URL(request.url).searchParams;
|
const query = new URL(request.url).searchParams;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export class Router {
|
||||||
this.routingTree[lowerMethod][rootPath] = handler;
|
this.routingTree[lowerMethod][rootPath] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle(request: Request): Promise<Response> | Response {
|
async handle(request: Request): Promise<Response> {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
if (url.pathname === '/' || url.pathname === '') {
|
if (url.pathname === '/' || url.pathname === '') {
|
||||||
|
|
|
@ -16,14 +16,16 @@ export const respond = (obj: Record<string, any>, init?: ResponseInit) =>
|
||||||
new Response(JSON.stringify(obj), init);
|
new Response(JSON.stringify(obj), init);
|
||||||
|
|
||||||
export const resolveFailures = (
|
export const resolveFailures = (
|
||||||
handleWith: Response,
|
handleWith: () => Response,
|
||||||
handler: (request: Request) => Promise<Response> | Response
|
handler: (request: Request) => Promise<Response> | Response
|
||||||
) => async (request: Request): Promise<Response> => {
|
) => async (request: Request): Promise<Response> => {
|
||||||
try {
|
try {
|
||||||
return handler(request);
|
return handler(request);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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) =>
|
const NotAuthenticated = (extra?: string) =>
|
||||||
respond(
|
respond(
|
||||||
{
|
{
|
||||||
err: extra || 'not authenticated',
|
error: extra || 'not authenticated',
|
||||||
},
|
},
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue