chvarkov / google-recaptcha

Google recaptcha module for NestJS❤
MIT License
82 stars 13 forks source link

agent configuration behind proxy results in "Unexpected error" #46

Closed jaunusa closed 3 years ago

jaunusa commented 3 years ago

Using @nestlab/google-recaptcha on localhost everything works like a charm. Moving on to a Test environment, which is behind a proxy with authentication, I always get an error Unexpected error. Node environment variables HTTP_PROXY and HTTPS_PROXY are set in the following format: http://<user>:<password>@<domain>:<port>

Expecting GoogleRecaptchaModule to grab the proxy setting automatically, the first thing I tried was leaving the config for agent empty. Then I tried setting the agent property like this:

import {HttpsProxyAgent} from "https-proxy-agent";
...
useFactory: async (configService: ConfigService) => ({
   ...
   agent: new HttpsProxyAgent('http://<user>:<password>@<domain>:<port>')
})

Resulting error with and without agent set:

Error: Unexpected error. Please submit issue to @nestlab/google-recaptcha.
    at GoogleRecaptchaGuard.canActivate (/usr/src/app/node_modules/@nestlab/google-recaptcha/guards/google-recaptcha.guard.js:47:15)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async GuardsConsumer.tryActivate (/usr/src/app/node_modules/@nestjs/core/guards/guards-consumer.js:16:17)
    at async canActivateFn (/usr/src/app/node_modules/@nestjs/core/router/router-execution-context.js:132:33)
    at async /usr/src/app/node_modules/@nestjs/core/router/router-execution-context.js:42:31
    at async /usr/src/app/node_modules/@nestjs/core/router/router-proxy.js:9:17

Error code: "unknown-error"

I don't know a lot about proxies with authentication and I have no idea what could cause the problem.

I tried to call the google recaptcha verify api by using axios with some strange request configuration. This works behind the proxy:

const config: AxiosRequestConfig = {};
config.proxy = false;
config.httpAgent = new HttpsProxyAgent('http://<user>:<password>@<domain>:<port>');
config.httpsAgent = new HttpsProxyAgent('http://<user>:<password>@<domain>:<port>');
axios.create(config).post('https://www.google.com/recaptcha/api/siteverify?secret=...&response=...')

It is important to set proxy for axios config to false, otherwise it fails.

Is this a special case for my proxy or are node request libraries expected to work like that? Any idea if you can fix this for the GoogleRecaptcha Module?

chvarkov commented 3 years ago

Hi @jaunusa Thanks for submit this issue. Yes it look like error on google recaptcha module side. I will fix it in couple days.

jaunusa commented 3 years ago

@chvarkov any idea how I could fix this problem, without having to implement the recaptcha functionality myself? I had a look at the nestjs HttpModule with the intention to be able to set some global axios configuration. But at the end I realized, that a global HttpModule config won't affect the @nestlab/google-recaptcha implementation. Or am I misunderstanding the pupose of useExisting?

chvarkov commented 3 years ago

@jaunusa Hi. As solution I can do next things:

  1. Add axiosConfig: AxiosRequestConfig property for module config, and apply your settings on each request.
  2. Mark agent: https.Agent as deprecated, (apply it for request it only when it defined).
  3. Add new error code or new exception type when network is not available (Internet is not available, wrong proxy, etc..). What do you think about it? Is it will solve your problem?
jaunusa commented 3 years ago

Your solution sounds reasonable. With an AxiosRequestConfig parameter it's then more coupled to axios, but under the hood there is nothing else then axios in nestjs as I can see. Alternatively there is maybe I way you could use a preconfigured instance of HttpModule, where the axios proxy is set. Can this be done with a second useExisting parameter for the google-recaptcha module?

chvarkov commented 3 years ago

@jaunusa I seems I found way for reuse your http settings.

WAY 1 If you use global shared module with http

  1. Config
@Injectable()
export class ConfigService {
  getRecaptchaOptions(): GoogleRecaptchaModuleOptions {
    return {
      secretKey: 'SomeKey',
      response: (req) => req.authorization.recaptcha,
    };
  }
}

@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}
  1. Shared module.
    @Global()
    @Module({
    imports: [
    HttpModule.register({
      // Some settings
      proxy: {
        port: 7777,
        host: 'SomeProxyHost',
      },
    }),
    ],
    exports: [
    HttpModule,
    ]
    })
    export class SharedModule {}
  2. Recaptcha module
@Module({
  imports: [
    SharedModule,
    GoogleRecaptchaModule.forRootAsync({
      imports: [
        ConfigModule,
      ],
      useFactory: (config, http) => ({
        ...config.getRecaptchaOptions(),
        axiosConfig: http.axiosRef.defaults,
      }),
      inject: [
        ConfigService,
        HttpService,
      ],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

WAY 2 Import HttpModule to RecaptchaModule

 @Module({
  imports: [
    GoogleRecaptchaModule.forRootAsync({
      imports: [
        HttpModule.register({
          // Some settings
          proxy: {
            port: 7777,
            host: 'SomeProxyHost',
          },
        }),
        ConfigModule,
      ],
      useFactory: (config, http) => ({
        ...config.getRecaptchaOptions(),
        axiosConfig: http.axiosRef.defaults,
      }),
      inject: [
        ConfigService,
        HttpService,
      ],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

It's not useExisting but you can set your default http configs. What do you think about it? Is this solution resolve your issue?

chvarkov commented 3 years ago

@jaunusa P.S. Not published yet. I testing..

jaunusa commented 3 years ago

Looks great, seems as the most flexible way by still relying on nestjs HttpModule configuration. If both ways really work, I could set the axios config in the HttpModule registration and it should fix the problem. What I tried was implementing WAY 1, but I didn't succeeded in exporting a configured HttpModule and reusing it in another module. I'm going to try WAY 1 again when you finished testing.

chvarkov commented 3 years ago

@jaunusa Added axiosConfig option. Added GoogleRecaptchaNetworkException that extends from GoogleRecaptchaException. You can check network error code in property networkErrorCode: string. The changes published and available since 2.0.7 version.

jaunusa commented 3 years ago

@chvarkov Tested with version 2.0.8, problem is fixed. Thank you very much for maintaining this module.