lib/common/captcha/graphic-captcha.service.ts
The GraphicCaptchaService class extends the CaptchaService class with a specialization for graphical captchas. It manages the generation and validation of graphic captchas.
Methods |
|
constructor(logger: Logger, cacheService: CacheService)
|
||||||||||||
Parameters :
|
Async generateCaptcha |
generateCaptcha()
|
Inherited from
CaptchaService
|
Defined in
CaptchaService:55
|
Generates a new graphical captcha and stores it in the cache.
Returns :
Promise<CaptchaResponse>
|
Private generateColor |
generateColor()
|
Generates a random RGB color.
Returns :
string
|
Private Async getCaptchaExp |
getCaptchaExp()
|
Retrieves the captcha expiration time defined in configuration.
Returns :
unknown
|
Private Async getCaptchaFontFamily |
getCaptchaFontFamily()
|
Retrieves the captcha font family defined in configuration.
Returns :
unknown
|
Private Async makeImageFromText | ||||||||
makeImageFromText(text: string)
|
||||||||
Generates an image from the provided text.
Parameters :
Returns :
unknown
|
Async validateCaptcha | ||||||||
validateCaptcha(request: CaptchaRequest)
|
||||||||
Inherited from
CaptchaService
|
||||||||
Defined in
CaptchaService:76
|
||||||||
Validates the provided captcha request against the cached value.
Parameters :
Returns :
Promise<boolean>
|
import {
CaptchaRequest,
CaptchaResponse,
CaptchaService,
} from "./captcha.types";
import { v4 as uuidv4 } from "uuid";
import { Inject, Logger } from "@nestjs/common";
import { createCanvas, registerFont } from "canvas";
import { CAPTCHA_CACHE_PREFIX } from "./captcha.constants";
import * as path from "path";
import { StringUtils } from "../../shared/utils/string.utils";
import { NumberUtils } from "../../shared/utils/number.utils";
import { LOGGER } from "../../shared/modules/log/log.constants";
import { CacheService } from "../../shared/modules/cache/cache.types";
import { CaptchaConfig } from "../../../gen-src/captcha.config";
import generateRandomString = StringUtils.generateRandomString;
import generateRandomInt = NumberUtils.generateRandomInt;
/**
* The GraphicCaptchaService class extends the CaptchaService class with a specialization for graphical captchas.
* It manages the generation and validation of graphic captchas.
*/
export class GraphicCaptchaService extends CaptchaService<CaptchaResponse> {
/**
* @param {Logger} logger - An instance of Logger.
* @param {CacheService} cacheService - An instance of CacheService.
*/
constructor(
@Inject(LOGGER) private readonly logger: Logger,
private readonly cacheService: CacheService,
) {
super();
}
/**
* Generates a new graphical captcha and stores it in the cache.
* @returns {Promise<CaptchaResponse>} - A promise resolving to a GraphicCaptchaResponse object containing the captcha id and image.
*/
async generateCaptcha(): Promise<CaptchaResponse> {
const captchaEnabled = await this.cacheService.getBoolean(
CaptchaConfig.ENABLED,
);
if (!captchaEnabled) {
return undefined;
}
const id = uuidv4();
const val = generateRandomString(5, 7);
const image = await this.makeImageFromText(val);
const capEx = await this.getCaptchaExp();
await this.cacheService.set(`${CAPTCHA_CACHE_PREFIX}:${id}`, val, capEx);
this.logger.debug(`Generated captcha with id: ${id} and value: ${val}`);
return { id, image: `data:image/png;base64,${image}`, type: "default" };
}
/**
* Validates the provided captcha request against the cached value.
* @param {CaptchaRequest} request - The captcha request to be validated.
* @returns {Promise<boolean>} - A promise resolving to a boolean indicating whether the captcha is valid or not.
*/
async validateCaptcha(request: CaptchaRequest): Promise<boolean> {
const key = `${CAPTCHA_CACHE_PREFIX}:${request.id}`;
const val = await this.cacheService.get(key);
this.cacheService.del(key);
if (!val) {
this.logger.warn(`Invalid captcha id: ${request.id}`);
return false;
}
if (val !== request.data) {
this.logger.warn(`Incorrect captcha value for id: ${request.id}`);
return false;
}
return true;
}
/**
* Generates an image from the provided text.
* @param {string} text - The text to be drawn on the image.
* @returns {Promise<string>} - A promise resolving to a base64 encoded image.
*/
private async makeImageFromText(text: string) {
const canvas = createCanvas(200, 50);
const ctx = canvas.getContext("2d");
const capFontFamily = await this.getCaptchaFontFamily();
const capFontPath = await this.getCaptchaFontPath();
registerFont(capFontPath, { family: capFontFamily });
ctx.fillStyle = this.generateColor();
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = `30px ${capFontFamily}`;
ctx.textBaseline = "middle";
for (let i = 0; i < text.length; i++) {
const char = text[i];
ctx.fillStyle = this.generateColor();
ctx.fillText(
char,
(i * canvas.width) / text.length + Math.random() * 10 - 5,
canvas.height / 2 + Math.random() * 10 - 5,
);
}
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = this.generateColor();
ctx.beginPath();
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);
ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);
ctx.stroke();
}
return canvas.toBuffer().toString("base64");
}
/**
* Generates a random RGB color.
* @returns {string} - An RGB color string.
*/
private generateColor() {
return `rgb(${generateRandomInt(255)},${generateRandomInt(255)},${generateRandomInt(255)})`;
}
/**
* Retrieves the captcha expiration time defined in configuration.
* @returns {Promise<number>} - A promise resolving to the captcha expiration time.
*/
private async getCaptchaExp() {
return await this.cacheService.getNumber(CaptchaConfig.EXPIRATION);
}
/**
* Retrieves the captcha font family defined in configuration.
* @returns {Promise<string>} - A promise resolving to the captcha font family.
*/
private async getCaptchaFontFamily() {
return await this.cacheService.get(CaptchaConfig.FONT_FAMILY);
}
/**
* Retrieves the captcha font path defined in configuration and normalizes it.
* @returns {Promise<string>} - A promise resolving to the normalized captcha font path.
*/
private async getCaptchaFontPath() {
const dir =
process.cwd() + (await this.cacheService.get(CaptchaConfig.FONT_PATH));
return path.normalize(dir);
}
}