aws4-axios icon indicating copy to clipboard operation
aws4-axios copied to clipboard

not compatible with axios 1.x

Open zoli-kasa opened this issue 3 years ago • 2 comments

using the example

const axios = require("axios");
const aws4Interceptor = require("aws4-axios").aws4Interceptor;

const interceptor = aws4Interceptor({
  region: "eu-west-2",
  service: "execute-api",
});

axios.interceptors.request.use(interceptor);

axios.get("https://example.com/foo").then((res) => {
  console.log(res);
});

with latest axios (1.1.2) i get the following error

node:internal/modules/cjs/loader:488
      throw e;
      ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/helpers/buildURL' is not defined by "exports" in /HIDDEN/iamtest/node_modules/axios/package.json
    at new NodeError (node:internal/errors:372:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:472:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:753:3)
    at resolveExports (node:internal/modules/cjs/loader:482:36)
    at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/HIDDEN/iamtest/node_modules/aws4-axios/dist/interceptor.js:55:34) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

with axios 0.27, it works fine.

zoli-kasa avatar Oct 11 '22 07:10 zoli-kasa

We have noticed the same exact thing, working for axios version 0.27.2 but broken for version 1.1.2

jeremy-brooks avatar Oct 13 '22 07:10 jeremy-brooks

Looking at the aws4-axios package.json, I see that this package is specifying "axios": ">=0.25.0" as a peer dependency.

What if, for now this was changed to "axios": ">=0.25.0 < 1"?

Then when axios fixes it's issue, this can be updated to >=1.0.0 or similar?

jeremy-brooks avatar Oct 14 '22 07:10 jeremy-brooks

Same here. 👍

nguyentoanit avatar Nov 02 '22 01:11 nguyentoanit

Still broken with axios 1.2.1.

npm is also partly to blame here as the resolutions in package.json should allow 0.2x to be installed but attempting to override it leads to invalid: "0.27.2" from node_modules ....

In addition to specifying "axios": ">=0.25.0 <1:" in the overrides field in package.json, deleting node_modules and package-lock.json is required as per https://github.com/npm/cli/issues/4232

ivantm avatar Dec 15 '22 06:12 ivantm

The axios team stated in multiple issues that they will not export helper functions in the foreseeable future. In my opinion, a path forward would be to replicate the used lib utility functions in the aws-axios codebase, as they are not overly complicated / difficult to maintain. @jamesmbourne What do you think? Would you be open to a pr?

florianbepunkt avatar Dec 15 '22 12:12 florianbepunkt

We are facing the same issue too, works with 0.27.2

kesavab avatar Jan 05 '23 18:01 kesavab

@jamesmbourne Could you have a look at my proposal https://github.com/jamesmbourne/aws4-axios/issues/701#issuecomment-1353024764 ? Would you be open to a PR?

florianbepunkt avatar Jan 06 '23 08:01 florianbepunkt

@jamesmbourne Could you have a look at my proposal https://github.com/jamesmbourne/aws4-axios/issues/701#issuecomment-1353024764 ? Would you be open to a PR?

I'm tempted to fork this just to be able to address this issue.

jasonwadsworth avatar Jan 30 '23 18:01 jasonwadsworth

@jamesmbourne Could you have a look at my proposal #701 (comment) ? Would you be open to a PR?

I'm tempted to fork this just to be able to address this issue.

that's great, go for it if you have time! the package looks kind of abandoned :(

zoli-kasa avatar Jan 31 '23 10:01 zoli-kasa

Hi all, sorry I haven't had the time to put any effort into maintain this package over the past year.

I'm not currently using this package for any of my projects (I've switched to https://github.com/sindresorhus/got for most things).

That said, it's clear that this package is still depended upon by a lot of projects, so I'll try and at least get it to a state where it's compatible with axios>=1.0.0.

If anyone has forked the project to add support for this, I'd be very welcoming of PRs and will endeavour to get those merged and released ASAP.

jamesmbourne avatar Feb 17 '23 22:02 jamesmbourne

I've taken a look at this and currently I think we would need to include half of Axios's helpers dir in this repo. The most complex function used is https://github.com/axios/axios/blob/v1.x/lib/helpers/buildURL.js due to a number of other dependencies.

If I went ahead and copied these implementations into this repo, there is a high likelihood of something breaking due to subtle differences in how the "copy" of Axios in this repo behaves compared with the real version.

Please could you give a 👍 on this issue: https://github.com/axios/axios/issues/4793

Having the final request URL available in interceptors seems like the only sensible way to go about achieving compatibility with Axios 1.x to me.

jamesmbourne avatar Feb 18 '23 00:02 jamesmbourne

@jamesmbourne Is it only about the final url? If so, this should work:

const client = axios.create({
  baseURL: "https://some-domain.com/api/",
  params: { foo: "bar" },
});

client.interceptors.request.use(async (config) => {
  console.log("config", config);

  const uri = client.getUri(config);
  console.log("final uri", uri); // final uri https://some-domain.com/api/foooooo?foo=bar
  return config;
});

try {
  const a = await client.get("/foooooo");
} catch (error) {
  console.log(error);
}

Just a quick demo:

import { fromIni } from "@aws-sdk/credential-providers";
import aws4 from "aws4";
import axios from "axios"; // current axios version
import type { AwsCredentialIdentity, Provider } from "@aws-sdk/types";
import type {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosRequestHeaders,
  InternalAxiosRequestConfig,
  Method,
} from "axios";

const getTransformer = (config: AxiosRequestConfig) => {
  const { transformRequest } = config;

  if (transformRequest) {
    if (typeof transformRequest === "function") {
      return transformRequest;
    } else if (transformRequest.length) {
      return transformRequest[0];
    }
  }

  throw new Error("Could not get default transformRequest function from Axios defaults");
};

const client = axios.create({
  baseURL: "https://some-url.com",
});

export const aws4Interceptor =
  ({
    instance = axios,
    credentials,
    options,
  }: {
    instance: AxiosInstance;
    options?: InterceptorOptions;
    credentials: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
  }): ((config: InternalAxiosRequestConfig) => Promise<InternalAxiosRequestConfig>) =>
  async (config): Promise<InternalAxiosRequestConfig> => {
    const url = instance.getUri(config);
    console.log("final url", url);

    const { host, pathname, search } = new URL(url);
    const { data, headers, method } = config;
    const transformRequest = getTransformer(config);
    // @ts-ignore
    const transformedData = transformRequest(data, headers);

    // Remove all the default Axios headers
    const {
      common,
      delete: _delete, // 'delete' is a reserved word
      get,
      head,
      post,
      put,
      patch,
      ...headersToSign
    } = headers as any as InternalAxiosHeaders;
    // Axios type definitions do not match the real shape of this object

    const signingOptions: SigningOptions = {
      method: method && method.toUpperCase(),
      host,
      path: pathname + search,
      region: options?.region,
      service: options?.service,
      signQuery: options?.signQuery,
      body: transformedData,
      headers: headersToSign as any,
    };

    const resolvedCredentials =
      typeof credentials === "function"
        ? await credentials()
        : credentials ?? {
            accessKeyId: "",
            secretAccessKey: "",
          };

    // using aws4
    aws4.sign(signingOptions as any, resolvedCredentials); // TODO typing
    config.headers = signingOptions.headers as any; // TODO typing

    if (signingOptions.signQuery) {
      const originalUrl = new URL(url);
      const signedUrl = new URL(originalUrl.origin + signingOptions.path);
      config.url = signedUrl.toString();
    }

    return config;
  };

const interceptor = aws4Interceptor({
  instance: client,
  credentials: fromIni({ profile: "some-profile" }),
  options: {
    region: "eu-central-1",
    service: "execute-api",
  },
});
client.interceptors.request.use(interceptor);

try {
  const a = await client.get("/games", { params: { foo: "bar" } });
  console.log("request succeed");
} catch (error) {
  console.log("req failed", error);
}

export type InternalAxiosHeaders = Record<Method | "common", Record<string, string>>;

export interface SigningOptions {
  host?: string;
  headers?: AxiosRequestHeaders;
  path?: string;
  body?: unknown;
  region?: string;
  service?: string;
  signQuery?: boolean;
  method?: string;
}

export interface Credentials {
  accessKeyId: string;
  secretAccessKey: string;
  sessionToken?: string;
}

export type InterceptorOptions = {
  /**
   * Target service. Will use default aws4 behavior if not given.
   */
  service?: string;
  /**
   * AWS region name. Will use default aws4 behavior if not given.
   */
  region?: string;
  /**
   * Whether to sign query instead of adding Authorization header. Default to false.
   */
  signQuery?: boolean;
  /**
   * ARN of the IAM Role to be assumed to get the credentials from.
   * The credentials will be cached and automatically refreshed as needed.
   * Will not be used if credentials are provided.
   */
  assumeRoleArn?: string;
  /**
   * Number of seconds before the assumed Role expiration
   * to invalidate the cache.
   * Used only if assumeRoleArn is provided.
   */
  assumedRoleExpirationMarginSec?: number;
};

florianbepunkt avatar Mar 26 '23 15:03 florianbepunkt

@jamesmbourne the axios internals are now exposed again: https://github.com/axios/axios/pull/5677, released as of 1.4.0.

tusbar avatar May 01 '23 09:05 tusbar

Great news, thanks for sharing @tusbar! I’ll take a look at getting a PR ready using the newly exposed helpers.

jamesmbourne avatar May 02 '23 22:05 jamesmbourne

@jamesmbourne As shown above, it might not be necessary to rely on the internal helpers. Axios maintainers marked them as unsafe and announced that they plan to refactor them. I am using the PoC above https://github.com/jamesmbourne/aws4-axios/issues/701#issuecomment-1484136280 for signing requests to an apigw and it works fine.

florianbepunkt avatar May 03 '23 06:05 florianbepunkt

@jamesmbourne As shown above, it might not be necessary to rely on the internal helpers. Axios maintainers marked them as unsafe and announced that they plan to refactor them. I am using the PoC above #701 (comment) for signing requests to an apigw and it works fine.

@florianbepunkt aren't you planning to release that as a standalone NPM package?

zoli-kasa avatar May 03 '23 07:05 zoli-kasa

@zoli-kasa Haven't thought about it. I prefer contributing to existing solutions. Publishing another package would add to (unnecessarily) fragmenting the solution space. I'm not against using the internal helpers and I have used this library extensively in the past. I just wondered whether the task at hand can be accomplished with the public API, which might make maintenance of this lib easier in the future.

florianbepunkt avatar May 03 '23 07:05 florianbepunkt

Thanks for the suggestions all, especially @florianbepunkt!

client.getUri works nicely. I've pushed to https://github.com/jamesmbourne/aws4-axios/pull/835 and all the tests were passing.

I just need to update the docs and then we should be able to get a new v3 release out.

jamesmbourne avatar May 04 '23 20:05 jamesmbourne

:tada: This issue has been resolved in version 3.0.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar May 04 '23 21:05 github-actions[bot]

We just upgraded to Axios 1.6.0 and found that aws4-axios is not compatible. Could you please look into it? Thanks!

Bingjiling avatar Nov 21 '23 15:11 Bingjiling