Google recaptcha module
The NestJS module to protect your endpoints via google recaptcha.
Usage example here
Installation
$ npm i @nestlab/google-recaptcha
For using application type GraphQL ApplicationType.GraphQL
you need to install @nestjs/graphql
.
$ npm i @nestjs/graphql
Configuration
Configuration for REST application
@Module({
imports: [
GoogleRecaptchaModule.forRoot({
secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
response: req => req.headers.recaptcha,
skipIf: process.env.NODE_ENV !== 'production',
network: GoogleRecaptchaNetwork.Recaptcha,
agent: null
})
],
})
export class AppModule {
}
Configuration for 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',
agent: null,
actions: ['SignUp', 'SignIn'],
score: 0.8,
})
],
})
export class AppModule {
}
Configuration for GraphQL application
@Module({
imports: [
GoogleRecaptchaModule.forRoot({
secretKey: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
response: (req: IncomingMessage) => (req.headers.recaptcha || '').toString(),
skipIf: process.env.NODE_ENV !== 'production',
network: GoogleRecaptchaNetwork.Recaptcha,
applicationType: ApplicationType.GraphQL,
agent: null
})
],
})
export class AppModule {
}
Tip: header names transforming to lower case.
For example: If you send 'Recaptcha' header then use (req) => req.headers.recaptcha
Configuration options
Property | Description |
---|
secretKey | Required. Type: string Google recaptcha secret key |
response | Required. Type: (request) => string Function that returns response (recaptcha token) by request |
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 |
network | Optional. Type: GoogleRecaptchaNetwork | boolean 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 |
applicationType | Optional. Type: ApplicationType Default: ApplicationType.Rest Application type affect on type of request argument on response provider function Request types: ApplicationType.Rest - (req: express.Request | fastify.Request) => string | Promise<string> ApplicationType.GraphQL - (req: http.IncommingMessage) => string | Promise<string> |
agent | Optional. Type: https.Agent If you need to use an agent |
score | Optional. Type: number | (score: number) => boolean Score validator for reCAPTCHA v3. number - minimum available score. (score: number) => boolean - function with custom validation rules. |
actions | Optional. Type: string[] Available action list for reCAPTCHA v3. You can make this check stricter by passing the action property parameter to @Recaptcha(...) decorator. |
If you want import configs from your ConfigService via custom getter function that will return GoogleRecaptchaModuleOptions
object.
@Module({
imports: [
GoogleRecaptchaModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => configService.googleRecaptchaOptions,
inject: [ConfigService],
})
],
})
export class AppModule {
}
Usage
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);
}
}
}
Guard
Use @Recaptcha
decorator to protect your endpoints.
@Controller('feedback')
export class FeedbackController {
@Recaptcha()
@Post('send')
async send(): Promise<any> {
}
}
You can override default property that contain recaptcha for specific endpoint.
@Controller('feedback')
export class FeedbackController {
@Recaptcha({response: req => req.body.recaptha})
@Post('send')
async send(): Promise<any> {
}
}
Also 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> {
}
}
If you want use google recaptcha guard in combination with another guards then you can use @UseGuards
decorator.
@Controller('feedback')
export class FeedbackController {
@SetRecaptchaOptions({action: 'Send', score: 0.8})
@UseGuards(Guard1, GoogleRecaptchaGuard, Guard2)
@Post('send')
async send(): Promise<any> {
}
}
GraphQL guard
Use @Recaptcha
decorator to protect your resolver.
@Recaptcha()
@Resolver(of => Recipe)
export class RecipesResolver {
@Query(returns => Recipe)
async recipe(@Args('id') id: string): Promise<Recipe> {
}
}
You can override default property that contain recaptcha for specific query, mutation or subscription.
@Recaptcha()
@Resolver(of => Recipe)
export class RecipesResolver {
@Query(returns => Recipe)
async recipe(@Args('id') id: string): Promise<Recipe> {
}
@Recaptcha({response: (req: IncomingMessage) => (req.headers['x-recaptcha'] || '').toString()})
@Query(returns => [Recipe])
recipes(@Args() recipesArgs: RecipesArgs): Promise<Recipe[]> {
}
}
Error handling
Google recaptcha guard will throw GoogleRecaptchaException on error.
GoogleRecaptchaException
has data with google recaptcha error codes.
GoogleRecaptchaException
← HttpException
← Error
.
You can handle it via ExceptionFilter.
Example exception filter implementation.
@Catch(GoogleRecaptchaException)
export class GoogleRecaptchaFilter implements ExceptionFilter {
catch(exception: GoogleRecaptchaException, host: ArgumentsHost): any {
}
}
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();
Enjoy!