File

lib/common/captcha/graphic-captcha.service.ts

Description

The GraphicCaptchaService class extends the CaptchaService class with a specialization for graphical captchas. It manages the generation and validation of graphic captchas.

Extends

CaptchaService

Index

Methods

Constructor

constructor(logger: Logger, cacheService: CacheService)
Parameters :
Name Type Optional Description
logger Logger No
  • An instance of Logger.
cacheService CacheService No
  • An instance of CacheService.

Methods

Async generateCaptcha
generateCaptcha()
Inherited from CaptchaService
Defined in CaptchaService:55

Generates a new graphical captcha and stores it in the cache.

  • A promise resolving to a GraphicCaptchaResponse object containing the captcha id and image.
Private generateColor
generateColor()

Generates a random RGB color.

Returns : string
  • An RGB color string.
Private Async getCaptchaExp
getCaptchaExp()

Retrieves the captcha expiration time defined in configuration.

Returns : unknown
  • A promise resolving to the captcha expiration time.
Private Async getCaptchaFontFamily
getCaptchaFontFamily()

Retrieves the captcha font family defined in configuration.

Returns : unknown
  • A promise resolving to the captcha font family.
Private Async getCaptchaFontPath
getCaptchaFontPath()

Retrieves the captcha font path defined in configuration and normalizes it.

Returns : unknown
  • A promise resolving to the normalized captcha font path.
Private Async makeImageFromText
makeImageFromText(text: string)

Generates an image from the provided text.

Parameters :
Name Type Optional Description
text string No
  • The text to be drawn on the image.
Returns : unknown
  • A promise resolving to a base64 encoded image.
Async validateCaptcha
validateCaptcha(request: CaptchaRequest)
Inherited from CaptchaService
Defined in CaptchaService:76

Validates the provided captcha request against the cached value.

Parameters :
Name Type Optional Description
request CaptchaRequest No
  • The captcha request to be validated.
Returns : Promise<boolean>
  • A promise resolving to a boolean indicating whether the captcha is valid or not.
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);
  }
}

results matching ""

    No results matching ""