UNPKG

@graphql-mesh/hmac-upstream-signature

Version:
111 lines (110 loc) 5.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultParamsSerializer = exports.defaultExecutionRequestSerializer = void 0; exports.useHmacUpstreamSignature = useHmacUpstreamSignature; exports.useHmacSignatureValidation = useHmacSignatureValidation; const tslib_1 = require("tslib"); const json_stable_stringify_1 = tslib_1.__importDefault(require("json-stable-stringify")); const transport_common_1 = require("@graphql-mesh/transport-common"); const utils_1 = require("@graphql-mesh/utils"); const DEFAULT_EXTENSION_NAME = 'hmac-signature'; const DEFAULT_SHOULD_SIGN_FN = () => true; const defaultExecutionRequestSerializer = (executionRequest) => (0, json_stable_stringify_1.default)({ query: (0, transport_common_1.defaultPrintFn)(executionRequest.document), variables: executionRequest.variables, }); exports.defaultExecutionRequestSerializer = defaultExecutionRequestSerializer; const defaultParamsSerializer = (params) => (0, json_stable_stringify_1.default)({ query: params.query, variables: params.variables, }); exports.defaultParamsSerializer = defaultParamsSerializer; function createCryptoKey({ textEncoder, crypto, secret, usages, }) { return crypto.subtle.importKey('raw', textEncoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, usages); } function useHmacUpstreamSignature(options) { if (!options.secret) { throw new Error('Property "secret" is required for useHmacUpstreamSignature plugin'); } const shouldSign = options.shouldSign || DEFAULT_SHOULD_SIGN_FN; const extensionName = options.extensionName || DEFAULT_EXTENSION_NAME; const serializeExecutionRequest = options.serializeExecutionRequest || exports.defaultExecutionRequestSerializer; let key$; let fetchAPI; let textEncoder; return { onYogaInit({ yoga }) { fetchAPI = yoga.fetchAPI; }, onSubgraphExecute({ subgraphName, subgraph, executionRequest, setExecutionRequest, logger }) { logger.debug(`running shouldSign for subgraph ${subgraphName}`); if (shouldSign({ subgraphName, subgraph, executionRequest })) { logger.debug(`shouldSign is true for subgraph ${subgraphName}, signing request`); textEncoder ||= new fetchAPI.TextEncoder(); key$ ||= createCryptoKey({ textEncoder, crypto: fetchAPI.crypto, secret: options.secret, usages: ['sign'], }); return (0, utils_1.mapMaybePromise)(key$, async (key) => { key$ = key; const serializedExecutionRequest = serializeExecutionRequest(executionRequest); const encodedContent = textEncoder.encode(serializedExecutionRequest); const signature = await fetchAPI.crypto.subtle.sign('HMAC', key, encodedContent); const extensionValue = btoa(String.fromCharCode(...new Uint8Array(signature))); logger.debug(`produced hmac signature for subgraph ${subgraphName}, signature: ${signature}, signed payload: ${serializedExecutionRequest}`); setExecutionRequest({ ...executionRequest, extensions: { ...executionRequest.extensions, [extensionName]: extensionValue, }, }); }); } else { logger.debug(`shouldSign is false for subgraph ${subgraphName}, skipping hmac signature`); } }, }; } function useHmacSignatureValidation(options) { if (!options.secret) { throw new Error('Property "secret" is required for useHmacSignatureValidation plugin'); } const extensionName = options.extensionName || DEFAULT_EXTENSION_NAME; let key$; let textEncoder; let logger; const paramsSerializer = options.serializeParams || exports.defaultParamsSerializer; return { onYogaInit({ yoga }) { logger = yoga.logger; }, onParams({ params, fetchAPI }) { textEncoder ||= new fetchAPI.TextEncoder(); const extension = params.extensions?.[extensionName]; if (!extension) { logger.warn(`Missing HMAC signature: extension ${extensionName} not found in request.`); throw new Error(`Missing HMAC signature: extension ${extensionName} not found in request.`); } key$ ||= createCryptoKey({ textEncoder, crypto: fetchAPI.crypto, secret: options.secret, usages: ['verify'], }); return key$.then(async (key) => { const sigBuf = Uint8Array.from(atob(extension), c => c.charCodeAt(0)); const serializedParams = paramsSerializer(params); logger.debug(`HMAC signature will be calculate based on serialized params: ${serializedParams}`); const result = await fetchAPI.crypto.subtle.verify('HMAC', key, sigBuf, textEncoder.encode(serializedParams)); if (!result) { logger.error(`HMAC signature does not match the body content. short circuit request.`); throw new Error(`Invalid HMAC signature: extension ${extensionName} does not match the body content.`); } }); }, }; }