パッケージの詳細

@nestlab/google-recaptcha

chvarkov65.2kMIT3.10.0

Google recaptcha module for NestJS.

nestjs, recaptcha, google recaptcha, nestjs recaptcha

readme

Google recaptcha module

This package provides protection for endpoints using reCAPTCHA for NestJS REST and GraphQL applications. By integrating with reCAPTCHA, this package helps to prevent automated abuse such as spam and bots, improving the security and reliability of your application.

NPM Version Licence NPM Downloads Build status Coverage Status

Table of Contents

Usage example here

Installation

$ npm i @nestlab/google-recaptcha

Changes

The list of changes made in the project can be found in the CHANGELOG.md file.

Configuration

Options

GoogleRecaptchaModuleOptions

Property Description
response Required.
Type: (request) => string
Function that returns response (recaptcha token) by request
secretKey Optional.
Type: string
Google recaptcha secret key. Must be set if you don't use reCAPTCHA Enterprise
debug Optional.
Type: boolean
Default: false
Enables logging requests, responses, errors and transformed results
logger Optional.
Type: Logger
Default: new Logger()
Instance of custom logger that extended from Logger (@nestjs/common)
skipIf Optional.
Type: boolean \
`(request) => boolean \ Promise<boolean>`
Function that returns true if you allow the request to skip the recaptcha verification. Useful for involing other check methods (e.g. custom privileged API key) or for development or testing
enterprise Optional.
Type: GoogleRecaptchaEnterpriseOptions
Options for using reCAPTCHA Enterprise API. Cannot be used with secretKey option.
network Optional.
Type: GoogleRecaptchaNetwork \
string
Default: GoogleRecaptchaNetwork.Google
If your server has trouble connecting to https://google.com then you can set networks:
GoogleRecaptchaNetwork.Google = 'https://www.google.com/recaptcha/api/siteverify'
GoogleRecaptchaNetwork.Recaptcha = 'https://recaptcha.net/recaptcha/api/siteverify'
or set any api url
score Optional.
Type: number \
(score: number) => boolean
Score validator for reCAPTCHA v3 or enterprise.
number - minimum available score.
(score: number) => boolean - function with custom validation rules.
actions Optional.
Type: string[]
Available action list for reCAPTCHA v3 or enterprise.
You can make this check stricter by passing the action property parameter to @Recaptcha(...) decorator.
remoteIp Optional.
Type: (request) => string
A function that returns a remote IP address from the request
axiosConfig Optional.
Type: AxiosRequestConfig
Allows to setup proxy, response timeout, https agent etc...
global Optional.
Type: boolean
Default: false Defines a module in the global scope.

GoogleRecaptchaEnterpriseOptions

Property Description
projectId Required.
Type: string
Google Cloud project ID
siteKey Required.
Type: string
reCAPTCHA key associated with the site/app.
apiKey Required.
Type: string
API key associated with the current project.
Must have permission reCAPTCHA Enterprise API.
You can manage credentials here.

The module provides two static methods for configuration: forRoot and forRootAsync.

forRoot

forRoot(options: GoogleRecaptchaModuleOptions): DynamicModule

The forRoot method accepts a GoogleRecaptchaModuleOptions object that configures the module. This method should be used in the root AppModule.
Example usage:

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
            response: req => req.headers.recaptcha,
        })
    ],
})
export class AppModule {
}

forRootAsync

forRootAsync(options: ModuleAsyncOptions): DynamicModule

The forRootAsync method is similar to forRoot, but allows for asynchronous configuration.
It accepts a GoogleRecaptchaModuleAsyncOptions object that returns a configuration object or a Promise that resolves to a configuration object.
Read more about ConfigService and custom getter function.

Example usage:

@Module({
    imports: [
        GoogleRecaptchaModule.forRootAsync({
            imports: [ConfigModule],
            useFactory: (configService: ConfigService) => configService.googleRecaptchaOptions,
            inject: [ConfigService],
        })
    ],
})
export class AppModule {
}

REST application

REST reCAPTCHA V2

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
            response: req => req.headers.recaptcha,
            skipIf: process.env.NODE_ENV !== 'production',
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req) => req.headers.recaptcha


REST reCAPTCHA V3

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
            response: req => req.headers.recaptcha,
            skipIf: process.env.NODE_ENV !== 'production',
            actions: ['SignUp', 'SignIn'],
            score: 0.8,
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req) => req.headers.recaptcha


REST reCAPTCHA Enterprise

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            response: (req) => req.headers.recaptcha,
            skipIf: process.env.NODE_ENV !== 'production',
            actions: ['SignUp', 'SignIn'],
            score: 0.8,
            enterprise: {
                projectId: process.env.RECAPTCHA_ENTERPRISE_PROJECT_ID,
                siteKey: process.env.RECAPTCHA_ENTERPRISE_SITE_KEY,
                apiKey: process.env.RECAPTCHA_ENTERPRISE_API_KEY,
            },
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req) => req.headers.recaptcha

Graphql application

Graphql reCAPTCHA V2

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
            response: (req: IncomingMessage) => (req.headers.recaptcha || '').toString(),
            skipIf: process.env.NODE_ENV !== 'production',
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req: IncomingMessage) => (req.headers.recaptcha || '').toString()


Graphql reCAPTCHA V3

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
            response: (req: IncomingMessage) => (req.headers.recaptcha || '').toString(),
            skipIf: process.env.NODE_ENV !== 'production',
            actions: ['SignUp', 'SignIn'],
            score: 0.8,
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req: IncomingMessage) => (req.headers.recaptcha || '').toString()


Graphql reCAPTCHA Enterprise

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            response: (req: IncomingMessage) => (req.headers.recaptcha || '').toString(),
            skipIf: process.env.NODE_ENV !== 'production',
            actions: ['SignUp', 'SignIn'],
            score: 0.8,
            enterprise: {
                projectId: process.env.RECAPTCHA_ENTERPRISE_PROJECT_ID,
                siteKey: process.env.RECAPTCHA_ENTERPRISE_SITE_KEY,
                apiKey: process.env.RECAPTCHA_ENTERPRISE_API_KEY,
            },
        }),
    ],
})
export class AppModule {
}

Tip: header names transforming to lower case.

For example: If you send 'Recaptcha' header then use (req) => req.headers.recaptcha

Configuration for reCAPTCHA Enterprise

@Module({
    imports: [
        GoogleRecaptchaModule.forRoot({
            response: (req) => req.headers.recaptcha,
            skipIf: process.env.NODE_ENV !== 'production',
            actions: ['SignUp', 'SignIn'],
            score: 0.8,
            enterprise: { 
                projectId: process.env.RECAPTCHA_ENTERPRISE_PROJECT_ID, 
                siteKey: process.env.RECAPTCHA_ENTERPRISE_SITE_KEY, 
                apiKey: process.env.RECAPTCHA_ENTERPRISE_API_KEY, 
            },
        }),
    ],
})
export class AppModule {
}

Usage

Usage in REST application

To protect your REST endpoints, you can use the @Recaptcha decorator.
Example:


@Controller('feedback')
export class FeedbackController {
    @Recaptcha()
    @Post('send')
    async send(): Promise<any> {
        // TODO: Your implementation.
    }
}

You can also override the default property that contains reCAPTCHA for a specific endpoint.


@Controller('feedback')
export class FeedbackController {
    @Recaptcha({response: req => req.body.recaptha})
    @Post('send')
    async send(): Promise<any> {
        // TODO: Your implementation.
    }
}

Additionally, you can override reCAPTCHA v3 options.


@Controller('feedback')
export class FeedbackController {
    @Recaptcha({response: req => req.body.recaptha, action: 'Send', score: 0.8})
    @Post('send')
    async send(): Promise<any> {
        // TODO: Your implementation.
    }
}

To get the verification result, you can use the @RecaptchaResult() decorator.


@Controller('feedback')
export class FeedbackController {
    @Recaptcha()
    @Post('send')
    async send(@RecaptchaResult() recaptchaResult: RecaptchaVerificationResult): Promise<any> {
        console.log(`Action: ${recaptchaResult.action} Score: ${recaptchaResult.score}`);
        // TODO: Your implementation.
    }
}

If you want to use the Google reCAPTCHA guard in combination with other guards, you can use the @UseGuards decorator.


@Controller('feedback')
export class FeedbackController {
    @SetRecaptchaOptions({action: 'Send', score: 0.8})
    @UseGuards(Guard1, GoogleRecaptchaGuard, Guard2)
    @Post('send')
    async send(): Promise<any> {
        // TODO: Your implementation.
    }
}

You can find a usage example in the following link.

Usage in Graphql application

To protect your resolver, use the @Recaptcha decorator.

@Recaptcha()
@Resolver(of => Recipe)
export class RecipesResolver {
    @Query(returns => Recipe)
    async recipe(@Args('id') id: string): Promise<Recipe> {
        // TODO: Your implementation.
    }
}

Obtain verification result:

@Recaptcha()
@Resolver(of => Recipe)
export class RecipesResolver {
    @Query(returns => Recipe)
    async recipe(@Args('id') id: string,
                 @RecaptchaResult() recaptchaResult: RecaptchaVerificationResult): Promise<Recipe> {
        console.log(`Action: ${recaptchaResult.action} Score: ${recaptchaResult.score}`);
        // TODO: Your implementation.
    }
}

You can override the default recaptcha property for a specific endpoint.

@Recaptcha()
@Resolver(of => Recipe)
export class RecipesResolver {
    @Query(returns => Recipe)
    async recipe(@Args('id') id: string): Promise<Recipe> {
        // TODO: Your implementation.
    }

    // Overridden default header. This query using X-Recaptcha header 
    @Recaptcha({response: (req: IncomingMessage) => (req.headers['x-recaptcha'] || '').toString()})
    @Query(returns => [Recipe])
    recipes(@Args() recipesArgs: RecipesArgs): Promise<Recipe[]> {
        // TODO: Your implementation.
    }
}

Validate in service

@Injectable()
export class SomeService {
    constructor(private readonly recaptchaValidator: GoogleRecaptchaValidator) {
    }

    async someAction(recaptchaToken: string): Promise<void> {
        const result = await this.recaptchaValidator.validate({
            response: recaptchaToken,
            score: 0.8,
            action: 'SomeAction',
        });

        if (!result.success) {
            throw new GoogleRecaptchaException(result.errors);
        }
        // TODO: Your implemetation
    }
}

Validate in service (Enterprise)

@Injectable()
export class SomeService {
    constructor(private readonly recaptchaEnterpriseValidator: GoogleRecaptchaEnterpriseValidator) {
    }

    async someAction(recaptchaToken: string): Promise<void> {
        const result = await this.recaptchaEnterpriseValidator.validate({
            response: recaptchaToken,
            score: 0.8,
            action: 'SomeAction',
        });

        if (!result.success) {
            throw new GoogleRecaptchaException(result.errors);
        }

        const riskAnalytics = result.getEnterpriseRiskAnalytics();

        // TODO: Your implemetation
    }
}

Dynamic Recaptcha configuration

The RecaptchaConfigRef class provides a convenient way to modify Recaptcha validation parameters within your application. This can be particularly useful in scenarios where the administration of Recaptcha is managed dynamically, such as by an administrator. The class exposes methods that allow the customization of various Recaptcha options.

RecaptchaConfigRef API:

@Injectable()
class RecaptchaConfigRef {
  // Sets the secret key for Recaptcha validation.
  setSecretKey(secretKey: string): this;

  // Sets enterprise-specific options for Recaptcha validation
  setEnterpriseOptions(options: GoogleRecaptchaEnterpriseOptions): this;

  // Sets the score threshold for Recaptcha validation.
  setScore(score: ScoreValidator): this;

  // Sets conditions under which Recaptcha validation should be skipped.
  setSkipIf(skipIf: SkipIfValue): this;
}

Usage example:

@Injectable()
export class RecaptchaAdminService implements OnApplicationBootstrap {
    constructor(private readonly recaptchaConfigRef: RecaptchaConfigRef) {
    }

    async onApplicationBootstrap(): Promise<void> {
        // TODO: Pull recaptcha configs from your database 

        this.recaptchaConfigRef
            .setSecretKey('SECRET_KEY_VALUE')
            .setScore(0.3);
    }

    async updateSecretKey(secretKey: string): Promise<void> {
        // TODO: Save new secret key to your database

        this.recaptchaConfigRef.setSecretKey(secretKey);
    }
}

After call this.recaptchaConfigRef.setSecretKey(...) - @Recaptcha guard and GoogleRecaptchaValidator will use new secret key.

Error handling

GoogleRecaptchaException

GoogleRecaptchaException extends HttpException extends Error.

The GoogleRecaptchaException is an exception that can be thrown by the GoogleRecaptchaGuard when an error occurs. It extends the HttpException class provided by NestJS, which means that it can be caught by an ExceptionFilter in the same way as any other HTTP exception.

One important feature of the GoogleRecaptchaException is that it contains an array of Error Code values in the errorCodes property. These values can be used to diagnose and handle the error.

Error code Description Status code
ErrorCode.MissingInputSecret The secret parameter is missing. (Throws from reCAPTCHA api). 500
ErrorCode.InvalidInputSecret The secret parameter is invalid or malformed. (Throws from reCAPTCHA api). 500
ErrorCode.MissingInputResponse The response parameter is missing. (Throws from reCAPTCHA api). 400
ErrorCode.InvalidInputResponse The response parameter is invalid or malformed. (Throws from reCAPTCHA api). 400
ErrorCode.BadRequest The request is invalid or malformed. (Throws from reCAPTCHA api). 500
ErrorCode.TimeoutOrDuplicate The response is no longer valid: either is too old or has been used previously. (Throws from reCAPTCHA api). 400
ErrorCode.UnknownError Unknown error. (Throws from reCAPTCHA api). 500
ErrorCode.ForbiddenAction Forbidden action. (Throws from guard when expected action not equals to received). 400
ErrorCode.LowScore Low score (Throws from guard when expected score less than received). 400
ErrorCode.InvalidKeys keys were copied incorrectly, the wrong keys were used for the environment (e.g. development vs production), or if the keys were revoked or deleted from the Google reCAPTCHA admin console.. (Throws from reCAPTCHA api). 400
ErrorCode.NetworkError Network error (like ECONNRESET, ECONNREFUSED...). 500
ErrorCode.SiteMismatch Site mismatch (Throws from reCAPTCHA Enterprise api only). 400
ErrorCode.BrowserError Browser error (Throws from reCAPTCHA Enterprise api only). 400

GoogleRecaptchaNetworkException

The GoogleRecaptchaNetworkException is an exception that extends the GoogleRecaptchaException class and is thrown in the case of a network error.
It contains a networkErrorCode property, which contains the error code of the network error, retrieved from the code property of the AxiosError object.

You can handle it via ExceptionFilter.

Example exception filter implementation.


@Catch(GoogleRecaptchaException)
export class GoogleRecaptchaFilter implements ExceptionFilter {
    catch(exception: GoogleRecaptchaException, host: ArgumentsHost): any {
        // TODO: Your exception filter implementation
    }
}

And add your filter to application


async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.useGlobalFilters(new ErrorFilter(), new GoogleRecaptchaFilter());
    await app.listen(3000);
}
bootstrap();

Contribution

We welcome any contributions to improve our package! If you find a bug, have a feature request, or want to suggest an improvement, feel free to submit an issue on our GitHub repository.

If you want to contribute to the codebase directly, please follow our contributing guidelines outlined in the CONTRIBUTING.md file in the repository.

We value the contributions of our community and appreciate all efforts to make this package better for everyone. Thank you for your support!

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

更新履歴

Changelog

v3.10.0

  • Upgraded axios 1.7.7 => 1.8.4

v3.9.0

  • Support NestJS 11. Upgraded peer dependencies versions:

    • @nestjs/common: >=8.0.0 <12.0.0
    • @nestjs/core: >=8.0.0 <12.0.0
  • Fixed getting networkErrorCode from AxiosError

  • Upgraded axios 1.7.4 => 1.7.7

v3.8.0

  • Updated GoogleRecaptchaModuleAsyncOptions interface

v3.7.0

  • Added RecaptchaConfigRef for dynamic configuration

v3.6.0

  • Added remoteIp?: (req) => string option into:
    • Module options
    • Decorator options to override default
    • Validator options

v3.5.0

  • Added global?: boolean module option

v3.4.1

  • Fixed import LiteralObject for NestJS 10

v3.4.0

  • Upgraded peer dependencies versions:
    • @nestjs/common: >=8.0.0 <11.0.0
    • @nestjs/core: >=8.0.0 <11.0.0

v3.3.1

  • Reworked readme docs.
  • Added changelog file.

v3.3.0

  • Reworked to use axios instead of @nestjs/axios.
  • Removed peer dependencies:
    • @nestjs/axios
    • rxjs

v3.2.0

  • Upgraded peer dependencies versions:
    • @nestjs/axios: >=1.0.0 <2.0.0
    • axios: >=1.0.0 <2.0.0

v3.1.9

  • Declared used axios package as peerDependency.

v3.1.8

  • Fixed async module options type in ts strict mode.
  • Declared used rxjs package as peerDependency.

v3.1.7

  • Smallfix with logging recaptcha results.
  • Fixed resolving error codes for enterprise validator.

v3.1.6

  • Fixed handling enterprise response without token properties info.

v3.1.5

  • Fixed recaptcha enterprise error handling.

v3.1.4

  • Fixed instance of response for recaptcha v2.
  • Fixed error handling for recaptcha enterprise.
  • Internal fixes.
  • Test coverage.

v3.1.3

  • Fixed response type for RecaptchaVerificationResult.getEnterpriseRiskAnalytics().

v3.1.2

  • Fixed http exception statuses for error codes: site-mismatch, browser-error (HTTP status - 400).
  • Added error code: incorrect-captcha-sol.

v3.1.1

  • Minor type fixes by eslint rules.
  • Fixes in: README.md, package.json.

v3.1.0

  • Added support reCAPTCHA Enterprise API.
  • Updated module options:
    • Updated secretKey as optional (shouldn't use for enterprise configuration).
    • Added enterprise option
Property Description
enterprise.projectId Required.
Type: string
Google Cloud project ID
enterprise.siteKey Required.
Type: string
reCAPTCHA key associated with the site/app.
enterprise.apiKey Required.
Type: string
API key associated with the current project.
Must have permission reCAPTCHA Enterprise API.
You can manage credentials here.

Updated GoogleRecaptchaValidator interface

class GoogleRecaptchaValidator {
    validate(options: VerifyResponseOptions): Promise<RecaptchaVerificationResult<VerifyResponseV3>>;
}

Addded recaptcha validator for enterprise

@Injectable()
export class SomeService {
    constructor(private readonly recaptchaEnterpriseValidator: GoogleRecaptchaEnterpriseValidator) {
    }

    async someAction(recaptchaToken: string): Promise<void> {
        const result = await this.recaptchaEnterpriseValidator.validate({
            response: recaptchaToken,
            score: 0.8,
            action: 'SomeAction',
        });

        if (!result.success) {
            throw new GoogleRecaptchaException(result.errors);
        }

        const riskAnalytics = result.getEnterpriseRiskAnalytics();

        console.log('score', riskAnalysis.score);
        console.log('score', riskAnalysis.reasons);

        // TODO: Your implemetation
    }
}

v3.0.3

  • Updated README.md.

v3.0.2

  • Added debug mode and logging
  • Added module options
    • debug?: boolean enables debug mode
    • logger?: Logger instance of Logger from @nestjs/common or extended from this

v3.0.1

  • Fixed published root dir

v3.0.0

  • Compat with NestJS 9
  • Removed deprecated options:
    • applicationType?: ApplicationType (now detect it automatically)
    • agent?: https.Agent (use option axiosConfig.httpsAgent)

v2.1.2

  • Fixed decorators reexports

v2.1.1

  • Removed source maps. Little fixes in readme file.

v2.1.0

  • Added request type auto detection from execution contextapplicationType configuration option marked as deprecated. Will removed in next major release.

v2.0.8

  • Fixed README.md.

2.0.7

  • Added axiosConfig: AxiosRequestConfig option.
  • Option agent?: https.Agent marked as deprecated.
  • Added GoogleRecaptchaNetworkException.

v2.0.6

  • Added support NestJS 8.
  • Dynamic loading HttpModule from @nestjs/axios or @nestjs/common.

v2.0.5

  • Fixed dynamic module loading.

v2.0.4

  • Added RecaptchaResult decorator.

v2.0.3

  • Added SetRecaptchaOptions decorator.

v2.0.2

  • Added error handling for invalid-keys.

v2.0.1

  • Removed console.log

v2.0.0

  • Added validation by action and score for reCAPTCHA v3.
  • Updated external interfaces. Affected places:
    • service GoogleRecaptchaValidator
    • decorator Recaptcha
    • module options (added optional default parameters)

v1.2.4

  • Fixed readme.

v.1.2.3

  • Updated readme. Added example to use validation in service.

v1.2.2

  • Added support GraphQL.

v1.2.1

  • Added LICENSE, CONTRIBUTING.md to build. Fixed readme.

v1.2.0

v1.1.11

Removed unused dev dependencies. Updated readme.

v1.1.10

  • Extended peer dependencies versions:
    • @nestjs/core: >=6.0.0 <8.0.0
    • @nestjs/common: >=6.0.0 <8.0.0

v1.1.9

  • Fixed global option for forRootAsync method.

v1.1.8

  • Module declared as global.

v1.1.7

  • Fixed readme.md file.

v1.1.6

  • Updated skipIf option to boolean | ((request: any) => boolean | Promise<boolean>)

v1.1.5

  • Updated skipIf argument from () => boolean to (request) => boolean | Promise<boolean>.

v1.1.4

  • Added option to use recaptcha.net and agent support.

v1.1.3

  • Async module initialization.

v1.1.2

  • Added override ability default recaptcha property.

v1.1.1

  • Updated GoogleRecaptchaException.

v1.1.0

  • Added GoogleRecaptchaException. Error handling via exception filter.

v1.0.13

  • Reexported types