@graphql-mesh/hmac-upstream-signature
Version:
111 lines (110 loc) • 5.56 kB
JavaScript
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.`);
}
});
},
};
}
;