mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 00:58:36 +00:00
refactor request handler to not depend upon cloudflare globals (#616)
This commit is contained in:
parent
d1ae8d02cf
commit
9ede925416
6 changed files with 190 additions and 172 deletions
|
|
@ -10,16 +10,20 @@ export function byteStringToUint8Array(byteString: string) {
|
|||
return ui;
|
||||
}
|
||||
|
||||
export async function isKeyValid(targetId: string, headerKey: string): Promise<boolean> {
|
||||
const headerData = byteStringToUint8Array(atob(headerKey));
|
||||
const secretKeyData = encoder.encode(KEY_DATA);
|
||||
const secretKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
secretKeyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['verify'],
|
||||
);
|
||||
export type KeyValidator = (targetId: string, headerKey: string) => Promise<boolean>;
|
||||
|
||||
return await crypto.subtle.verify('HMAC', secretKey, headerData, encoder.encode(targetId));
|
||||
}
|
||||
export const createIsKeyValid =
|
||||
(keyData: string): KeyValidator =>
|
||||
async (targetId: string, headerKey: string): Promise<boolean> => {
|
||||
const headerData = byteStringToUint8Array(atob(headerKey));
|
||||
const secretKeyData = encoder.encode(keyData);
|
||||
const secretKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
secretKeyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['verify'],
|
||||
);
|
||||
|
||||
return await crypto.subtle.verify('HMAC', secretKey, headerData, encoder.encode(targetId));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
import './dev-polyfill';
|
||||
import { createServer } from 'http';
|
||||
import { handleRequest } from './handler';
|
||||
import { createRequestHandler } from './handler';
|
||||
import { devStorage } from './dev-polyfill';
|
||||
import { isKeyValid } from './auth';
|
||||
import { createServerAdapter } from '@whatwg-node/server';
|
||||
import { Router } from 'itty-router';
|
||||
import { withParams, json } from 'itty-router-extras';
|
||||
import { createIsKeyValid } from './auth';
|
||||
|
||||
// eslint-disable-next-line no-process-env
|
||||
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 4010;
|
||||
|
||||
/**
|
||||
* KV Storage for the CDN
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
declare let HIVE_DATA: KVNamespace;
|
||||
|
||||
/**
|
||||
* Secret used to sign the CDN keys
|
||||
*/
|
||||
declare let KEY_DATA: string;
|
||||
|
||||
const handleRequest = createRequestHandler({
|
||||
getRawStoreValue: value => HIVE_DATA.get(value),
|
||||
isKeyValid: createIsKeyValid(KEY_DATA),
|
||||
});
|
||||
|
||||
function main() {
|
||||
const app = createServerAdapter(Router());
|
||||
|
||||
|
|
@ -55,7 +72,7 @@ function main() {
|
|||
}),
|
||||
);
|
||||
|
||||
app.get('*', (request: Request) => handleRequest(request, isKeyValid));
|
||||
app.get('*', (request: Request) => handleRequest(request));
|
||||
|
||||
const server = createServer(app);
|
||||
|
||||
|
|
|
|||
21
packages/services/cdn-worker/src/global.d.ts
vendored
21
packages/services/cdn-worker/src/global.d.ts
vendored
|
|
@ -1,21 +0,0 @@
|
|||
export {};
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* KV Storage for the CDN
|
||||
*/
|
||||
let HIVE_DATA: KVNamespace;
|
||||
/**
|
||||
* Secret used to sign the CDN keys
|
||||
*/
|
||||
let KEY_DATA: string;
|
||||
let SENTRY_DSN: string;
|
||||
/**
|
||||
* Name of the environment, e.g. staging, production
|
||||
*/
|
||||
let SENTRY_ENVIRONMENT: string;
|
||||
/**
|
||||
* Id of the release
|
||||
*/
|
||||
let SENTRY_RELEASE: string;
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import {
|
|||
MissingAuthKey,
|
||||
MissingTargetIDErrorResponse,
|
||||
} from './errors';
|
||||
import { isKeyValid } from './auth';
|
||||
import { buildSchema, introspectionFromSchema } from 'graphql';
|
||||
import type { KeyValidator } from './auth';
|
||||
|
||||
async function createETag(value: string) {
|
||||
const myText = new TextEncoder().encode(value);
|
||||
|
|
@ -96,7 +96,7 @@ const AUTH_HEADER_NAME = 'x-hive-cdn-key';
|
|||
|
||||
async function parseIncomingRequest(
|
||||
request: Request,
|
||||
keyValidator: typeof isKeyValid,
|
||||
keyValidator: KeyValidator,
|
||||
): Promise<
|
||||
| { error: Response }
|
||||
| {
|
||||
|
|
@ -151,46 +151,63 @@ async function parseIncomingRequest(
|
|||
}
|
||||
}
|
||||
|
||||
export async function handleRequest(request: Request, keyValidator: typeof isKeyValid) {
|
||||
const parsedRequest = await parseIncomingRequest(request, keyValidator);
|
||||
/**
|
||||
* Handler for verifying whether an access key is valid.
|
||||
*/
|
||||
type IsKeyValid = (targetId: string, headerKey: string) => Promise<boolean>;
|
||||
|
||||
if ('error' in parsedRequest) {
|
||||
return parsedRequest.error;
|
||||
}
|
||||
/**
|
||||
* Read a raw value from the store.
|
||||
*/
|
||||
type GetRawStoreValue = (targetId: string) => Promise<string | null>;
|
||||
|
||||
const { targetId, artifactType, storageKeyType } = parsedRequest;
|
||||
|
||||
const kvStorageKey = `target:${targetId}:${storageKeyType}`;
|
||||
const rawValue = await HIVE_DATA.get(kvStorageKey);
|
||||
|
||||
if (rawValue) {
|
||||
const etag = await createETag(`${kvStorageKey}|${rawValue}`);
|
||||
const ifNoneMatch = request.headers.get('if-none-match');
|
||||
|
||||
if (ifNoneMatch && ifNoneMatch === etag) {
|
||||
return new Response(null, { status: 304 });
|
||||
}
|
||||
|
||||
switch (artifactType) {
|
||||
case 'schema':
|
||||
return artifactTypesHandlers.schema(targetId, artifactType, rawValue, etag);
|
||||
case 'supergraph':
|
||||
return artifactTypesHandlers.supergraph(targetId, artifactType, rawValue, etag);
|
||||
case 'sdl':
|
||||
return artifactTypesHandlers.sdl(targetId, artifactType, rawValue, etag);
|
||||
case 'introspection':
|
||||
return artifactTypesHandlers.introspection(targetId, artifactType, rawValue, etag);
|
||||
case 'metadata':
|
||||
return artifactTypesHandlers.metadata(targetId, artifactType, rawValue, etag);
|
||||
default:
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`CDN Artifact not found for targetId=${targetId}, artifactType=${artifactType}, storageKeyType=${storageKeyType}`,
|
||||
);
|
||||
return new CDNArtifactNotFound(artifactType, targetId);
|
||||
}
|
||||
interface RequestHandlerDependencies {
|
||||
isKeyValid: IsKeyValid;
|
||||
getRawStoreValue: GetRawStoreValue;
|
||||
}
|
||||
|
||||
export const createRequestHandler =
|
||||
(deps: RequestHandlerDependencies) =>
|
||||
async (request: Request): Promise<Response> => {
|
||||
const parsedRequest = await parseIncomingRequest(request, deps.isKeyValid);
|
||||
|
||||
if ('error' in parsedRequest) {
|
||||
return parsedRequest.error;
|
||||
}
|
||||
|
||||
const { targetId, artifactType, storageKeyType } = parsedRequest;
|
||||
|
||||
const kvStorageKey = `target:${targetId}:${storageKeyType}`;
|
||||
const rawValue = await deps.getRawStoreValue(kvStorageKey);
|
||||
|
||||
if (rawValue) {
|
||||
const etag = await createETag(`${kvStorageKey}|${rawValue}`);
|
||||
const ifNoneMatch = request.headers.get('if-none-match');
|
||||
|
||||
if (ifNoneMatch && ifNoneMatch === etag) {
|
||||
return new Response(null, { status: 304 });
|
||||
}
|
||||
|
||||
switch (artifactType) {
|
||||
case 'schema':
|
||||
return artifactTypesHandlers.schema(targetId, artifactType, rawValue, etag);
|
||||
case 'supergraph':
|
||||
return artifactTypesHandlers.supergraph(targetId, artifactType, rawValue, etag);
|
||||
case 'sdl':
|
||||
return artifactTypesHandlers.sdl(targetId, artifactType, rawValue, etag);
|
||||
case 'introspection':
|
||||
return artifactTypesHandlers.introspection(targetId, artifactType, rawValue, etag);
|
||||
case 'metadata':
|
||||
return artifactTypesHandlers.metadata(targetId, artifactType, rawValue, etag);
|
||||
default:
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`CDN Artifact not found for targetId=${targetId}, artifactType=${artifactType}, storageKeyType=${storageKeyType}`,
|
||||
);
|
||||
return new CDNArtifactNotFound(artifactType, targetId);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,36 @@
|
|||
import Toucan from 'toucan-js';
|
||||
import { isKeyValid } from './auth';
|
||||
import { createIsKeyValid } from './auth';
|
||||
import { UnexpectedError } from './errors';
|
||||
import { handleRequest } from './handler';
|
||||
import { createRequestHandler } from './handler';
|
||||
|
||||
/**
|
||||
* KV Storage for the CDN
|
||||
*/
|
||||
declare let HIVE_DATA: KVNamespace;
|
||||
|
||||
/**
|
||||
* Secret used to sign the CDN keys
|
||||
*/
|
||||
declare let KEY_DATA: string;
|
||||
|
||||
declare let SENTRY_DSN: string;
|
||||
/**
|
||||
* Name of the environment, e.g. staging, production
|
||||
*/
|
||||
declare let SENTRY_ENVIRONMENT: string;
|
||||
/**
|
||||
* Id of the release
|
||||
*/
|
||||
declare let SENTRY_RELEASE: string;
|
||||
|
||||
const handleRequest = createRequestHandler({
|
||||
getRawStoreValue: value => HIVE_DATA.get(value),
|
||||
isKeyValid: createIsKeyValid(KEY_DATA),
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
try {
|
||||
event.respondWith(handleRequest(event.request, isKeyValid));
|
||||
event.respondWith(handleRequest(event.request));
|
||||
} catch (error) {
|
||||
const sentry = new Toucan({
|
||||
dsn: SENTRY_DSN,
|
||||
|
|
|
|||
|
|
@ -1,43 +1,20 @@
|
|||
import '../src/dev-polyfill';
|
||||
import { handleRequest } from '../src/handler';
|
||||
import { createRequestHandler } from '../src/handler';
|
||||
import {
|
||||
InvalidArtifactTypeResponse,
|
||||
InvalidAuthKey,
|
||||
MissingAuthKey,
|
||||
MissingTargetIDErrorResponse,
|
||||
} from '../src/errors';
|
||||
import { isKeyValid } from '../src/auth';
|
||||
import { createIsKeyValid, KeyValidator } from '../src/auth';
|
||||
import { createHmac } from 'crypto';
|
||||
|
||||
describe('CDN Worker', () => {
|
||||
const KeyValidators: Record<string, typeof isKeyValid> = {
|
||||
const KeyValidators: Record<string, KeyValidator> = {
|
||||
AlwaysTrue: () => Promise.resolve(true),
|
||||
AlwaysFalse: () => Promise.resolve(false),
|
||||
Bcrypt: isKeyValid,
|
||||
};
|
||||
|
||||
function mockWorkerEnv(input: { HIVE_DATA: Map<string, string>; KEY_DATA: string }) {
|
||||
Object.defineProperties(globalThis, {
|
||||
HIVE_DATA: {
|
||||
value: input.HIVE_DATA,
|
||||
},
|
||||
KEY_DATA: {
|
||||
value: input.KEY_DATA,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function clearWorkerEnv() {
|
||||
Object.defineProperties(globalThis, {
|
||||
HIVE_DATA: {
|
||||
value: undefined,
|
||||
},
|
||||
KEY_DATA: {
|
||||
value: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createToken(secret: string, targetId: string): string {
|
||||
const encoder = new TextEncoder();
|
||||
const secretKeyData = encoder.encode(secret);
|
||||
|
|
@ -45,17 +22,15 @@ describe('CDN Worker', () => {
|
|||
return createHmac('sha256', secretKeyData).update(encoder.encode(targetId)).digest('base64');
|
||||
}
|
||||
|
||||
afterEach(clearWorkerEnv);
|
||||
|
||||
test('in /schema and /metadata the response should contain content-type: application/json header', async () => {
|
||||
const SECRET = '123456';
|
||||
const targetId = 'fake-target-id';
|
||||
const map = new Map();
|
||||
map.set(`target:${targetId}:schema`, JSON.stringify({ sdl: `type Query { dummy: String }` }));
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -66,7 +41,7 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const schemaResponse = await handleRequest(schemaRequest, KeyValidators.Bcrypt);
|
||||
const schemaResponse = await handleRequest(schemaRequest);
|
||||
expect(schemaResponse.status).toBe(200);
|
||||
expect(schemaResponse.headers.get('content-type')).toBe('application/json');
|
||||
|
||||
|
|
@ -76,7 +51,7 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const metadataResponse = await handleRequest(metadataRequest, KeyValidators.Bcrypt);
|
||||
const metadataResponse = await handleRequest(metadataRequest);
|
||||
expect(metadataResponse.status).toBe(200);
|
||||
expect(metadataResponse.headers.get('content-type')).toBe('application/json');
|
||||
});
|
||||
|
|
@ -87,9 +62,9 @@ describe('CDN Worker', () => {
|
|||
const map = new Map();
|
||||
map.set(`target:${targetId}:schema`, JSON.stringify({ sdl: `type Query { dummy: String }` }));
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -99,7 +74,7 @@ describe('CDN Worker', () => {
|
|||
'x-hive-cdn-key': token,
|
||||
},
|
||||
});
|
||||
const firstResponse = await handleRequest(firstRequest, KeyValidators.Bcrypt);
|
||||
const firstResponse = await handleRequest(firstRequest);
|
||||
const etag = firstResponse.headers.get('etag');
|
||||
|
||||
expect(firstResponse.status).toBe(200);
|
||||
|
|
@ -114,7 +89,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': etag!,
|
||||
},
|
||||
});
|
||||
const secondResponse = await handleRequest(secondRequest, KeyValidators.Bcrypt);
|
||||
const secondResponse = await handleRequest(secondRequest);
|
||||
expect(secondResponse.status).toBe(304);
|
||||
expect(secondResponse.body).toBeNull();
|
||||
|
||||
|
|
@ -125,7 +100,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': '"non-existing-etag"',
|
||||
},
|
||||
});
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest, KeyValidators.Bcrypt);
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest);
|
||||
expect(wrongEtagResponse.status).toBe(200);
|
||||
expect(wrongEtagResponse.body).toBeDefined();
|
||||
});
|
||||
|
|
@ -139,9 +114,9 @@ describe('CDN Worker', () => {
|
|||
JSON.stringify({ sdl: `type Query { dummy: String }` }),
|
||||
);
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -151,7 +126,7 @@ describe('CDN Worker', () => {
|
|||
'x-hive-cdn-key': token,
|
||||
},
|
||||
});
|
||||
const firstResponse = await handleRequest(firstRequest, KeyValidators.Bcrypt);
|
||||
const firstResponse = await handleRequest(firstRequest);
|
||||
const etag = firstResponse.headers.get('etag');
|
||||
|
||||
expect(firstResponse.status).toBe(200);
|
||||
|
|
@ -166,7 +141,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': etag!,
|
||||
},
|
||||
});
|
||||
const secondResponse = await handleRequest(secondRequest, KeyValidators.Bcrypt);
|
||||
const secondResponse = await handleRequest(secondRequest);
|
||||
expect(secondResponse.status).toBe(304);
|
||||
expect(secondResponse.body).toBeNull();
|
||||
|
||||
|
|
@ -177,7 +152,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': '"non-existing-etag"',
|
||||
},
|
||||
});
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest, KeyValidators.Bcrypt);
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest);
|
||||
expect(wrongEtagResponse.status).toBe(200);
|
||||
expect(wrongEtagResponse.body).toBeDefined();
|
||||
});
|
||||
|
|
@ -188,9 +163,9 @@ describe('CDN Worker', () => {
|
|||
const map = new Map();
|
||||
map.set(`target:${targetId}:metadata`, JSON.stringify({ sdl: `type Query { dummy: String }` }));
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -200,7 +175,7 @@ describe('CDN Worker', () => {
|
|||
'x-hive-cdn-key': token,
|
||||
},
|
||||
});
|
||||
const firstResponse = await handleRequest(firstRequest, KeyValidators.Bcrypt);
|
||||
const firstResponse = await handleRequest(firstRequest);
|
||||
const etag = firstResponse.headers.get('etag');
|
||||
|
||||
expect(firstResponse.status).toBe(200);
|
||||
|
|
@ -215,7 +190,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': etag!,
|
||||
},
|
||||
});
|
||||
const secondResponse = await handleRequest(secondRequest, KeyValidators.Bcrypt);
|
||||
const secondResponse = await handleRequest(secondRequest);
|
||||
expect(secondResponse.status).toBe(304);
|
||||
expect(secondResponse.body).toBeNull();
|
||||
|
||||
|
|
@ -226,7 +201,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': '"non-existing-etag"',
|
||||
},
|
||||
});
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest, KeyValidators.Bcrypt);
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest);
|
||||
expect(wrongEtagResponse.status).toBe(200);
|
||||
expect(wrongEtagResponse.body).toBeDefined();
|
||||
});
|
||||
|
|
@ -237,9 +212,9 @@ describe('CDN Worker', () => {
|
|||
const map = new Map();
|
||||
map.set(`target:${targetId}:schema`, JSON.stringify({ sdl: `type Query { dummy: String }` }));
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -249,7 +224,7 @@ describe('CDN Worker', () => {
|
|||
'x-hive-cdn-key': token,
|
||||
},
|
||||
});
|
||||
const firstResponse = await handleRequest(firstRequest, KeyValidators.Bcrypt);
|
||||
const firstResponse = await handleRequest(firstRequest);
|
||||
const etag = firstResponse.headers.get('etag');
|
||||
|
||||
expect(firstResponse.status).toBe(200);
|
||||
|
|
@ -264,7 +239,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': etag!,
|
||||
},
|
||||
});
|
||||
const secondResponse = await handleRequest(secondRequest, KeyValidators.Bcrypt);
|
||||
const secondResponse = await handleRequest(secondRequest);
|
||||
expect(secondResponse.status).toBe(304);
|
||||
expect(secondResponse.body).toBeNull();
|
||||
|
||||
|
|
@ -275,7 +250,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': '"non-existing-etag"',
|
||||
},
|
||||
});
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest, KeyValidators.Bcrypt);
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest);
|
||||
expect(wrongEtagResponse.status).toBe(200);
|
||||
expect(wrongEtagResponse.body).toBeDefined();
|
||||
});
|
||||
|
|
@ -291,9 +266,9 @@ describe('CDN Worker', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -303,7 +278,7 @@ describe('CDN Worker', () => {
|
|||
'x-hive-cdn-key': token,
|
||||
},
|
||||
});
|
||||
const firstResponse = await handleRequest(firstRequest, KeyValidators.Bcrypt);
|
||||
const firstResponse = await handleRequest(firstRequest);
|
||||
const etag = firstResponse.headers.get('etag');
|
||||
|
||||
expect(firstResponse.status).toBe(200);
|
||||
|
|
@ -318,7 +293,7 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': etag!,
|
||||
},
|
||||
});
|
||||
const secondResponse = await handleRequest(secondRequest, KeyValidators.Bcrypt);
|
||||
const secondResponse = await handleRequest(secondRequest);
|
||||
expect(secondResponse.status).toBe(304);
|
||||
expect(secondResponse.body).toBeNull();
|
||||
|
||||
|
|
@ -329,55 +304,55 @@ describe('CDN Worker', () => {
|
|||
'if-none-match': '"non-existing-etag"',
|
||||
},
|
||||
});
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest, KeyValidators.Bcrypt);
|
||||
const wrongEtagResponse = await handleRequest(wrongEtagRequest);
|
||||
expect(wrongEtagResponse.status).toBe(200);
|
||||
expect(wrongEtagResponse.body).toBeDefined();
|
||||
});
|
||||
|
||||
describe('Basic parsing errors', () => {
|
||||
it('Should throw when target id is missing', async () => {
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: '',
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: KeyValidators.AlwaysTrue,
|
||||
getRawStoreValue: (_key: string) => Promise.resolve(null),
|
||||
});
|
||||
|
||||
const request = new Request('https://fake-worker.com/', {});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.AlwaysTrue);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof MissingTargetIDErrorResponse).toBeTruthy();
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
it('Should throw when requested resource is not valid', async () => {
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: '',
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: KeyValidators.AlwaysTrue,
|
||||
getRawStoreValue: (_key: string) => Promise.resolve(null),
|
||||
});
|
||||
|
||||
const request = new Request('https://fake-worker.com/fake-target-id/error', {});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.AlwaysTrue);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof InvalidArtifactTypeResponse).toBeTruthy();
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
it('Should throw when auth key is missing', async () => {
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: '',
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: KeyValidators.AlwaysTrue,
|
||||
getRawStoreValue: (_key: string) => Promise.resolve(null),
|
||||
});
|
||||
|
||||
const request = new Request('https://fake-worker.com/fake-target-id/sdl', {});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.AlwaysTrue);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof MissingAuthKey).toBeTruthy();
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
it('Should throw when key validation function fails', async () => {
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: '',
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: KeyValidators.AlwaysFalse,
|
||||
getRawStoreValue: (_key: string) => Promise.resolve(null),
|
||||
});
|
||||
|
||||
const request = new Request('https://fake-worker.com/fake-target-id/sdl', {
|
||||
|
|
@ -386,7 +361,7 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.AlwaysFalse);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof InvalidAuthKey).toBeTruthy();
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
|
@ -399,9 +374,9 @@ describe('CDN Worker', () => {
|
|||
const map = new Map();
|
||||
map.set(`target:${targetId}:schema`, JSON.stringify({ sdl: `type Query { dummy: String }` }));
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: map,
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, targetId);
|
||||
|
|
@ -412,16 +387,17 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.Bcrypt);
|
||||
const response = await handleRequest(request);
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it('Should throw on mismatch of token target and actual target', async () => {
|
||||
const SECRET = '123456';
|
||||
const map = new Map();
|
||||
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: SECRET,
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid(SECRET),
|
||||
getRawStoreValue: (key: string) => map.get(key),
|
||||
});
|
||||
|
||||
const token = createToken(SECRET, 'fake-target-id');
|
||||
|
|
@ -432,15 +408,15 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.Bcrypt);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof InvalidAuthKey).toBeTruthy();
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
||||
it('Should throw on invalid token hash', async () => {
|
||||
mockWorkerEnv({
|
||||
HIVE_DATA: new Map(),
|
||||
KEY_DATA: '123456',
|
||||
const handleRequest = createRequestHandler({
|
||||
isKeyValid: createIsKeyValid('123456'),
|
||||
getRawStoreValue: (key: string) => new Map().get(key),
|
||||
});
|
||||
|
||||
const request = new Request(`https://fake-worker.com/some-target/sdl`, {
|
||||
|
|
@ -449,7 +425,7 @@ describe('CDN Worker', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const response = await handleRequest(request, KeyValidators.Bcrypt);
|
||||
const response = await handleRequest(request);
|
||||
expect(response instanceof InvalidAuthKey).toBeTruthy();
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue