node-recaptcha-v3
Version:
A Node.js library for verifying Google reCAPTCHA v3 tokens in your applications.
100 lines (99 loc) • 4.54 kB
JavaScript
;
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;