Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

not compatible with axios 1.x #701

Closed
zoli-kasa opened this issue Oct 11, 2022 · 20 comments · Fixed by #835
Closed

not compatible with axios 1.x #701

zoli-kasa opened this issue Oct 11, 2022 · 20 comments · Fixed by #835
Labels

Comments

@zoli-kasa
Copy link

zoli-kasa commented Oct 11, 2022

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.

@jeremy-brooks
Copy link

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

@jeremy-brooks
Copy link

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?

@nguyentoanit
Copy link
Contributor

Same here. 👍

@ivantm
Copy link

ivantm commented Dec 15, 2022

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 npm/cli#4232

@florianbepunkt
Copy link
Contributor

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?

@kesavab
Copy link

kesavab commented Jan 5, 2023

We are facing the same issue too, works with 0.27.2

@florianbepunkt
Copy link
Contributor

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

@jasonwadsworth
Copy link

@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.

@zoli-kasa
Copy link
Author

@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 :(

@jamesmbourne
Copy link
Owner

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
Copy link
Owner

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: axios/axios#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.

@florianbepunkt
Copy link
Contributor

florianbepunkt commented Mar 26, 2023

@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;
};

@tusbar
Copy link

tusbar commented May 1, 2023

@jamesmbourne the axios internals are now exposed again: axios/axios#5677, released as of 1.4.0.

@jamesmbourne
Copy link
Owner

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

@florianbepunkt
Copy link
Contributor

@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.

@zoli-kasa
Copy link
Author

zoli-kasa commented May 3, 2023

@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?

@florianbepunkt
Copy link
Contributor

@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.

@jamesmbourne
Copy link
Owner

Thanks for the suggestions all, especially @florianbepunkt!

client.getUri works nicely. I've pushed to #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.

@github-actions
Copy link

github-actions bot commented May 4, 2023

🎉 This issue has been resolved in version 3.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@Bingjiling
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants