From d898d997d72db0faaef11797e9c584c61038e278 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sun, 19 Apr 2026 00:46:42 +0800 Subject: [PATCH] feat(auth): svg captcha challenge factory Made-with: Cursor --- server/service/captcha/challenge.test.ts | 13 +++++++++++++ server/service/captcha/challenge.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 server/service/captcha/challenge.test.ts create mode 100644 server/service/captcha/challenge.ts diff --git a/server/service/captcha/challenge.test.ts b/server/service/captcha/challenge.test.ts new file mode 100644 index 0000000..cd8f9dc --- /dev/null +++ b/server/service/captcha/challenge.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "bun:test"; +import { createCaptchaChallenge } from "./challenge"; + +describe("createCaptchaChallenge", () => { + test("returns captchaId and non-empty svg", () => { + const { captchaId, imageSvg } = createCaptchaChallenge(); + expect(captchaId).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, + ); + expect(imageSvg.length).toBeGreaterThan(50); + expect(imageSvg.includes("svg")).toBe(true); + }); +}); diff --git a/server/service/captcha/challenge.ts b/server/service/captcha/challenge.ts new file mode 100644 index 0000000..54e22f7 --- /dev/null +++ b/server/service/captcha/challenge.ts @@ -0,0 +1,18 @@ +import svgCaptcha from "svg-captcha"; +import { captchaCreate } from "./store"; + +/** 易混淆字符已剔除,长度 5 */ +const CHAR_PRESET = "abcdefghjkmnpqrstuvwxyz23456789"; + +export function createCaptchaChallenge(): { captchaId: string; imageSvg: string } { + const { data: imageSvg, text } = svgCaptcha.create({ + size: 5, + noise: 2, + color: true, + charPreset: CHAR_PRESET, + background: "#f4f4f5", + }); + const answerNormalized = text.toLowerCase(); + const { captchaId } = captchaCreate(answerNormalized); + return { captchaId, imageSvg }; +}