diff --git a/apps/server/src/faucet/FaucetController.ts b/apps/server/src/faucet/FaucetController.ts index 06abb230..5d18d82a 100644 --- a/apps/server/src/faucet/FaucetController.ts +++ b/apps/server/src/faucet/FaucetController.ts @@ -1,9 +1,10 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { Controller, Get, HttpException, Inject, Param, Query, UseInterceptors } from '@nestjs/common'; +import { Controller, HttpException, Inject, Param, Post, Query, UseGuards, UseInterceptors} from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { EnvironmentNetwork } from '@waveshq/walletkit-core'; import { TransactionResponse } from 'ethers'; +import {RecaptchaGuard} from "../recaptcha/RecaptchaGuard"; import { AddressValidationInterceptor } from './AddressValidationInterceptor'; import { DefaultNetworkInterceptor } from './DefaultNetworkInterceptor'; import { FaucetService } from './FaucetService'; @@ -16,7 +17,8 @@ export class FaucetController { private configService: ConfigService, ) {} - @Get(':address') + @Post(':address') + @UseGuards(RecaptchaGuard) @UseInterceptors(AddressValidationInterceptor, DefaultNetworkInterceptor) async sendFunds( @Param('address') address: string, diff --git a/apps/server/src/faucet/FaucetModule.ts b/apps/server/src/faucet/FaucetModule.ts index e0bf3686..f38ab27b 100644 --- a/apps/server/src/faucet/FaucetModule.ts +++ b/apps/server/src/faucet/FaucetModule.ts @@ -1,12 +1,14 @@ +import {HttpModule} from "@nestjs/axios"; import { CacheModule } from '@nestjs/cache-manager'; import { Module } from '@nestjs/common'; +import {RecaptchaGuard} from "../recaptcha/RecaptchaGuard"; import { FaucetController } from './FaucetController'; import { FaucetService } from './FaucetService'; @Module({ - imports: [CacheModule.register()], + imports: [CacheModule.register(), HttpModule], controllers: [FaucetController], - providers: [FaucetService], + providers: [FaucetService, RecaptchaGuard], }) export class FaucetModule {} diff --git a/apps/server/src/faucet/FaucetService.ts b/apps/server/src/faucet/FaucetService.ts index b5c19076..32b6331a 100644 --- a/apps/server/src/faucet/FaucetService.ts +++ b/apps/server/src/faucet/FaucetService.ts @@ -12,12 +12,20 @@ export class FaucetService { private readonly privateKey: string; - constructor(private configService: ConfigService) { + constructor( + private configService: ConfigService, + ) { this.logger = new Logger(FaucetService.name); this.privateKey = this.configService.getOrThrow('privateKey'); } - async sendFundsToUser(address: string, amount: string, network: EnvironmentNetwork): Promise { + async sendFundsToUser( + address: string, + amount: string, + network: EnvironmentNetwork, + ): Promise { + + // Send funds to user if recaptcha validation is successful const evmProviderService = new EVMProviderService(network); const wallet = new ethers.Wallet(this.privateKey, evmProviderService.provider); const nonce = await evmProviderService.provider.getTransactionCount(wallet.address); diff --git a/apps/server/src/recaptcha/RecaptchaGuard.ts b/apps/server/src/recaptcha/RecaptchaGuard.ts new file mode 100644 index 00000000..07414b7d --- /dev/null +++ b/apps/server/src/recaptcha/RecaptchaGuard.ts @@ -0,0 +1,40 @@ +import { HttpService } from '@nestjs/axios'; +import { ExecutionContext, Injectable, Logger } from '@nestjs/common'; +import { Request } from 'express'; + +@Injectable() +export class RecaptchaGuard { + private readonly logger: Logger; + + constructor(private readonly httpService: HttpService) { + this.logger = new Logger(RecaptchaGuard.name); + } + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + return this.validateRecaptcha(request); + } + + async validateRecaptcha(request: Request): Promise { + const response = request.body.recaptchaValue; + + if (!response) { + this.logger.log('Invalid body in recaptcha request'); + return false; + } + + const { data } = await this.httpService + .post( + `https://www.google.com/recaptcha/api/siteverify`, + null, // Since we're sending data in the body, set it to null + { + params: { + secret: process.env.SECRET_KEY, + response, + }, + }, + ) + .toPromise(); + return data.success; + } +}