UNPKG

node-recaptcha-v3

Version:

A Node.js library for verifying Google reCAPTCHA v3 tokens in your applications.

100 lines (99 loc) 4.54 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const constants_1 = require("./constants"); const axios_1 = __importDefault(require("axios")); const exceptions_1 = require("./exceptions"); class ReCaptchaV3 { constructor(config) { const { secretKey, threshold = constants_1.THRESHOLD, statusCode = constants_1.FORBIDDEN, message = 'reCAPTCHA verification failed', apiEndPoint = constants_1.reCAPTCHA_API } = config; this.secretKey = this.validateNonEmptyString(secretKey, 'Secret key cannot be empty'); this.scoreThreshold = this.validateScoreRange(threshold); this.statusCode = statusCode; this.errorMessage = message; this.apiEndPoint = apiEndPoint; } /** * Validate that a string is non-empty. */ validateNonEmptyString(value, errorMessage) { if (!value || typeof value !== 'string' || value.trim() === '') { throw new Error(errorMessage); } return value.trim(); } /** * Ensure the score is within 0 and 1. */ validateScoreRange(score) { if (score < 0 || score > 1) { throw new Error('Score must be between 0 and 1.'); } return score; } /** * Send a validation request to Google's reCAPTCHA API. */ validateReCaptchaToken(token) { return __awaiter(this, void 0, void 0, function* () { try { const response = yield axios_1.default.post(this.apiEndPoint, null, { params: { secret: this.secretKey, response: token } }); const { success, score } = response.data; return { success, score }; } catch (error) { throw new exceptions_1.ReCaptchaV3Exception('Failed to communicate with reCAPTCHA API', this.statusCode); } }); } /** * Helper to handle error responses. */ respondWithError(res, message, statusCode) { res.status(statusCode).json({ error: message }); } /** * Middleware to validate reCAPTCHA tokens. */ verify(customThreshold, customStatusCode, customMessage) { const threshold = this.validateScoreRange(customThreshold !== null && customThreshold !== void 0 ? customThreshold : this.scoreThreshold); const statusCode = customStatusCode !== null && customStatusCode !== void 0 ? customStatusCode : this.statusCode; const errorMessage = customMessage !== null && customMessage !== void 0 ? customMessage : this.errorMessage; return (req, res, next) => __awaiter(this, void 0, void 0, function* () { var _a, _b; const token = ((_a = req.body) === null || _a === void 0 ? void 0 : _a.reCaptchaV3Token) || ((_b = req.headers) === null || _b === void 0 ? void 0 : _b['re-captcha-v3-token']); if (!token) { return this.respondWithError(res, errorMessage, statusCode); } try { const { success, score } = yield this.validateReCaptchaToken(token); if (!success || score < threshold) { return this.respondWithError(res, errorMessage, statusCode); } req.reCaptchaV3Score = score; next(); } catch (error) { const errorResponseMessage = error instanceof exceptions_1.ReCaptchaV3Exception ? error.message : 'Unexpected error occurred'; this.respondWithError(res, errorResponseMessage, statusCode); } }); } } exports.default = ReCaptchaV3;