Uploaded file gets corrupted when using GRPC custom middleware in tyk-gateway 2.7.0

const {resolve} = require('path');
const grpc = require('grpc');
const introspectInternal = require('./introspect-internal');

const port = process.env.PORT || 5566;
const listenAddress = `0.0.0.0:${port}`;

const tyk = grpc.load({
  file: 'coprocess_object.proto',
  root: resolve(__dirname, 'tyk-protobuf/proto')
}).coprocess;

// Keep lowercase headers
// getHeaders :: Object -> Object
const getHeaders = request => Object.entries(request.headers)
  .reduce((acc, [header, value]) => {
    acc[header.toLowerCase()] = value;

    return acc;
  }, {});

// Handler
// handleError :: (Error, Object) -> undefined
const handleError = (err, req) => {
  console.error('[GRPC]', err.toString(), JSON.stringify(err.stack || ''), '- headers:', JSON.stringify(req.request.headers));

  const response = JSON.parse(JSON.stringify(req));
  
  response.request.return_overrides.response_code = 401;
  response.request.return_overrides.headers = {
    'Content-Type': 'application/json'
  };
  response.request.return_overrides.response_error = err.message;

  return response;
};

// Middleware
// authMiddleware :: (Object, Function) -> undefined
const authMiddleware = async (obj, callback) => {
  // Deep clone
  // const response = JSON.parse(JSON.stringify(obj)); // result in key not authorised
  const response = obj;

  // Keep lowercase headers
  const headers = getHeaders(response.request);

  // We take the value from the "Authorization" header:
  const authorizationToken = (headers.authorization || ''))
    .substring('Bearer '.length);
  if (authorizationToken === '') {
    throw new Error('"authorization" header is missing or empty');
  }

  const req = {
    headers,
    body: {
      token: authorizationToken
    },
    params: {},
    url: response.request.url,
    tokenResponse: {}
  };

  console.log('[GRPC] req:', JSON.stringify(req));

  const token = await introspectInternal.introspect(req);
  req.tokenResponse.Authorization = token.authorization;
  req.tokenResponse['X-Forwarded-Authorization'] = req.token;

  // The token should be attached to the object metadata, this is used internally for key management:
  response.metadata = Object.assign(
    {token: req.tokenResponse['X-Forwarded-Authorization']},
    req.tokenResponse
  );

  // If the Authrorization and X-Forwarded-Authorization are empty in tokenResponse, we return the call:
  if (!req.tokenResponse.Authorization && !req.tokenResponse['X-Forwarded-Authorization']) {
    callback(null, obj);

    return;
  }

  if (req.tokenResponse['X-Forwarded-Authorization']) {
    response.request.set_headers['X-Forwarded-Authorization'] = 'Bearer ' + req.tokenResponse['X-Forwarded-Authorization'];
  }
  response.request.set_headers.Authorization = 'Bearer ' + req.tokenResponse.Authorization;

  // At this point the token is valid and a session state object is initialized and attached to the coprocess.Object:
  const session = new tyk.SessionState();
  session.alias = req.tokenResponse['X-Forwarded-Authorization'];
  response.session = session;

  callback(null, response);
};

// The dispatch function is called for every hook:
const dispatch = async (call, callback) => {
  try {
    const obj = call.request;
    // We dispatch the request based on the hook name, we pass obj.request which is the coprocess.Object:
    switch (obj.hook_name) {
      case 'MyAuthMiddleware':
        await authMiddleware(obj, callback);
        break;
      default:
        callback(null, obj);
        break;
    }
  } catch (err) {
    const response = handleError(err, call.request);
    callback(null, response);
  }
};
const dispatchEvent = () => {};
const reload = () => {};

const main = () => {
  const server = new grpc.Server();
  server.addService(tyk.Dispatcher.service, {
    dispatch,
    dispatchEvent,
    reload
  });
  server.bind(listenAddress, grpc.ServerCredentials.createInsecure());
  console.log('[GRPC]', `GRPC server running on ${listenAddress}`);
  server.start();
};

module.exports = main;