@nestlab/google-recaptcha
Advanced tools
Comparing version 1.2.4 to 2.0.0
@@ -1,2 +0,2 @@ | ||
import { RecaptchaResponseProvider } from '../types'; | ||
export declare function Recaptcha(response?: RecaptchaResponseProvider): MethodDecorator & ClassDecorator; | ||
import { VerifyResponseDecoratorOptions } from '../interfaces/verify-response-decorator-options'; | ||
export declare function Recaptcha(options?: VerifyResponseDecoratorOptions): MethodDecorator & ClassDecorator; |
@@ -7,6 +7,6 @@ "use strict"; | ||
const provider_declarations_1 = require("../provider.declarations"); | ||
function Recaptcha(response) { | ||
return common_1.applyDecorators(common_1.SetMetadata(provider_declarations_1.RECAPTCHA_RESPONSE_PROVIDER, response), common_1.UseGuards(google_recaptcha_guard_1.GoogleRecaptchaGuard)); | ||
function Recaptcha(options) { | ||
return common_1.applyDecorators(common_1.SetMetadata(provider_declarations_1.RECAPTCHA_VALIDATION_OPTIONS, options), common_1.UseGuards(google_recaptcha_guard_1.GoogleRecaptchaGuard)); | ||
} | ||
exports.Recaptcha = Recaptcha; | ||
//# sourceMappingURL=recaptcha.js.map |
@@ -8,3 +8,5 @@ export declare enum ErrorCode { | ||
TimeoutOrDuplicate = "timeout-or-duplicate", | ||
UnknownError = "unknown-error" | ||
UnknownError = "unknown-error", | ||
ForbiddenAction = "forbidden-action", | ||
LowScore = "low-score" | ||
} |
@@ -13,3 +13,5 @@ "use strict"; | ||
ErrorCode["UnknownError"] = "unknown-error"; | ||
ErrorCode["ForbiddenAction"] = "forbidden-action"; | ||
ErrorCode["LowScore"] = "low-score"; | ||
})(ErrorCode = exports.ErrorCode || (exports.ErrorCode = {})); | ||
//# sourceMappingURL=error-code.js.map |
@@ -22,2 +22,6 @@ "use strict"; | ||
return 'Invalid module configuration. Please check public-secret keys.'; | ||
case error_code_1.ErrorCode.LowScore: | ||
return 'Low recaptcha score.'; | ||
case error_code_1.ErrorCode.ForbiddenAction: | ||
return 'Forbidden recaptcha action.'; | ||
case error_code_1.ErrorCode.UnknownError: | ||
@@ -32,3 +36,5 @@ case error_code_1.ErrorCode.BadRequest: | ||
errorCode === error_code_1.ErrorCode.MissingInputResponse || | ||
errorCode === error_code_1.ErrorCode.TimeoutOrDuplicate | ||
errorCode === error_code_1.ErrorCode.TimeoutOrDuplicate || | ||
errorCode === error_code_1.ErrorCode.ForbiddenAction || | ||
errorCode === error_code_1.ErrorCode.LowScore | ||
? common_1.HttpStatus.BAD_REQUEST | ||
@@ -35,0 +41,0 @@ : common_1.HttpStatus.INTERNAL_SERVER_ERROR; |
@@ -38,7 +38,8 @@ "use strict"; | ||
} | ||
const provider = this.reflector.get(provider_declarations_1.RECAPTCHA_RESPONSE_PROVIDER, context.getHandler()); | ||
const response = provider | ||
? await provider(request) | ||
const options = this.reflector.get(provider_declarations_1.RECAPTCHA_VALIDATION_OPTIONS, context.getHandler()); | ||
const response = (options === null || options === void 0 ? void 0 : options.response) ? await (options === null || options === void 0 ? void 0 : options.response(request)) | ||
: await this.options.response(request); | ||
const result = await this.validator.validate(response); | ||
const score = (options === null || options === void 0 ? void 0 : options.score) || this.options.score; | ||
const action = options === null || options === void 0 ? void 0 : options.action; | ||
const result = await this.validator.validate({ response, score, action }); | ||
if (result.success) { | ||
@@ -45,0 +46,0 @@ return true; |
@@ -1,2 +0,2 @@ | ||
import { RecaptchaResponseProvider } from '../types'; | ||
import { RecaptchaResponseProvider, ScoreValidator } from '../types'; | ||
import { ApplicationType } from '../enums/application-type'; | ||
@@ -7,2 +7,3 @@ export interface GoogleRecaptchaGuardOptions { | ||
skipIf?: boolean | ((request: any) => boolean | Promise<boolean>); | ||
score?: ScoreValidator; | ||
} |
import { ErrorCode } from '../enums/error-code'; | ||
export interface GoogleRecaptchaValidationResult { | ||
success: boolean; | ||
score?: number; | ||
errors: ErrorCode[]; | ||
} |
/// <reference types="node" /> | ||
import * as https from 'https'; | ||
import { GoogleRecaptchaNetwork } from '../enums/google-recaptcha-network'; | ||
import { ScoreValidator } from '../types'; | ||
export interface GoogleRecaptchaValidatorOptions { | ||
secretKey: string; | ||
actions?: string[]; | ||
score?: ScoreValidator; | ||
network?: GoogleRecaptchaNetwork | string; | ||
agent?: https.Agent; | ||
} |
{ | ||
"name": "@nestlab/google-recaptcha", | ||
"version": "1.2.4", | ||
"version": "2.0.0", | ||
"description": "Google recaptcha module for NestJS.", | ||
@@ -22,3 +22,3 @@ "keywords": [ | ||
"build": "rimraf dist && tsc && cp package.json dist && cp README.md dist && cp LICENSE dist && cp CONTRIBUTING.md dist", | ||
"test": "jest", | ||
"test": "jest --silent=false", | ||
"test:cov": "jest --coverage" | ||
@@ -25,0 +25,0 @@ }, |
export declare const RECAPTCHA_OPTIONS: unique symbol; | ||
export declare const RECAPTCHA_RESPONSE_PROVIDER: unique symbol; | ||
export declare const RECAPTCHA_VALIDATION_OPTIONS: unique symbol; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RECAPTCHA_RESPONSE_PROVIDER = exports.RECAPTCHA_OPTIONS = void 0; | ||
exports.RECAPTCHA_VALIDATION_OPTIONS = exports.RECAPTCHA_OPTIONS = void 0; | ||
exports.RECAPTCHA_OPTIONS = Symbol('RECAPTCHA_OPTIONS'); | ||
exports.RECAPTCHA_RESPONSE_PROVIDER = Symbol('RECAPTCHA_RESPONSE_PROVIDER'); | ||
exports.RECAPTCHA_VALIDATION_OPTIONS = Symbol('RECAPTCHA_VALIDATION_OPTIONS'); | ||
//# sourceMappingURL=provider.declarations.js.map |
@@ -47,2 +47,21 @@ # Google recaptcha module | ||
**Configuration for reCAPTCHA V3** | ||
```typescript | ||
@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** | ||
@@ -82,2 +101,4 @@ | ||
| `agent` | Optional.<br> Type: `https.Agent`<br> If you need to use an agent | | ||
| `score` | Optional.<br> Type: `number` \| `(score: number) => boolean`<br> Score validator for reCAPTCHA v3. <br> `number` - minimum available score. <br> `(score: number) => boolean` - function with custom validation rules. | | ||
| `actions` | Optional.<br> Type: `string[]`<br> Available action list for reCAPTCHA v3. <br> You can make this check stricter by passing the action property parameter to `@Recaptcha(...)` decorator. | | ||
@@ -111,3 +132,7 @@ If you want import configs from your [ConfigService](https://docs.nestjs.com/techniques/configuration#getting-started) via [custom getter function](https://docs.nestjs.com/techniques/configuration#custom-getter-functions) that will return `GoogleRecaptchaModuleOptions` object. | ||
async someAction(recaptchaToken: string): Promise<void> { | ||
const result = await this.recaptchaValidator.validate(recaptchaToken); | ||
const result = await this.recaptchaValidator.validate({ | ||
response: recaptchaToken, | ||
score: 0.8, | ||
action: 'SomeAction', | ||
}); | ||
@@ -145,3 +170,3 @@ if (!result.success) { | ||
export class FeedbackController { | ||
@Recaptcha(req => req.body.recaptha) | ||
@Recaptcha({response: req => req.body.recaptha}) | ||
@Post('send') | ||
@@ -155,2 +180,17 @@ async send(): Promise<any> { | ||
Also you can override recaptcha v3 options. | ||
```typescript | ||
@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. | ||
} | ||
} | ||
``` | ||
If you want use google recaptcha guard in combination with another guards then you can use `@UseGuards` decorator. | ||
@@ -198,3 +238,3 @@ | ||
// Overridden default header. This query using X-Recaptcha header | ||
@Recaptcha((req: IncomingMessage) => (req.headers['x-recaptcha'] || '').toString()) | ||
@Recaptcha({response: (req: IncomingMessage) => (req.headers['x-recaptcha'] || '').toString()}) | ||
@Query(returns => [Recipe]) | ||
@@ -201,0 +241,0 @@ recipes(@Args() recipesArgs: RecipesArgs): Promise<Recipe[]> { |
import { HttpService } from '@nestjs/common'; | ||
import { GoogleRecaptchaValidatorOptions } from '../interfaces/google-recaptcha-validator-options'; | ||
import { GoogleRecaptchaValidationResult } from '../interfaces/google-recaptcha-validation-result'; | ||
import { VerifyResponseOptions } from '../interfaces/verify-response-decorator-options'; | ||
export declare class GoogleRecaptchaValidator { | ||
@@ -10,3 +11,7 @@ private readonly http; | ||
constructor(http: HttpService, options: GoogleRecaptchaValidatorOptions); | ||
validate(response: string): Promise<GoogleRecaptchaValidationResult>; | ||
validate(options: VerifyResponseOptions): Promise<GoogleRecaptchaValidationResult>; | ||
private verifyResponse; | ||
private isValidAction; | ||
private isValidScore; | ||
private isUseV3; | ||
} |
@@ -19,4 +19,4 @@ "use strict"; | ||
const qs = require("querystring"); | ||
const google_recaptcha_network_1 = require("../enums/google-recaptcha-network"); | ||
const error_code_1 = require("../enums/error-code"); | ||
const google_recaptcha_network_1 = require("../enums/google-recaptcha-network"); | ||
let GoogleRecaptchaValidator = class GoogleRecaptchaValidator { | ||
@@ -29,3 +29,19 @@ constructor(http, options) { | ||
} | ||
validate(response) { | ||
async validate(options) { | ||
const result = await this.verifyResponse(options.response); | ||
if (!this.isUseV3(result)) { | ||
return result; | ||
} | ||
console.log(result); | ||
if (!this.isValidAction(result.action, options)) { | ||
result.success = false; | ||
result.errors.push(error_code_1.ErrorCode.ForbiddenAction); | ||
} | ||
if (!this.isValidScore(result.score, options.score)) { | ||
result.success = false; | ||
result.errors.push(error_code_1.ErrorCode.LowScore); | ||
} | ||
return result; | ||
} | ||
verifyResponse(response) { | ||
const data = qs.stringify({ secret: this.options.secretKey, response }); | ||
@@ -39,6 +55,7 @@ const url = this.options.network || this.defaultNetwork; | ||
.then(res => res.data) | ||
.then(result => ({ | ||
success: result.success, | ||
errors: result['error-codes'] || [], | ||
})) | ||
.then(result => (Object.assign(Object.assign({}, result), { errors: result['error-codes'] || [] }))) | ||
.then(result => { | ||
delete result['error-codes']; | ||
return result; | ||
}) | ||
.catch(() => ({ | ||
@@ -49,2 +66,24 @@ success: false, | ||
} | ||
isValidAction(action, options) { | ||
if (options.action) { | ||
return options.action === action; | ||
} | ||
return this.options.actions | ||
? this.options.actions.includes(action) | ||
: true; | ||
} | ||
isValidScore(score, validator) { | ||
const finalValidator = validator || this.options.score; | ||
if (finalValidator) { | ||
if (typeof finalValidator === 'function') { | ||
return finalValidator(score); | ||
} | ||
return score >= finalValidator; | ||
} | ||
return true; | ||
} | ||
isUseV3(v) { | ||
return ('score' in v && typeof v['score'] === 'number') && | ||
('action' in v && typeof v['action'] === 'string'); | ||
} | ||
}; | ||
@@ -51,0 +90,0 @@ GoogleRecaptchaValidator = __decorate([ |
export declare type RecaptchaResponseProvider = (req: any) => string | Promise<string>; | ||
export declare type ScoreValidator = number | ((score: number) => boolean); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51389
58
541
279