From 2afb86f97351826f3d2ce0c843a1a1b6c4cf90bc Mon Sep 17 00:00:00 2001 From: EUForest Date: Mon, 9 Mar 2026 22:54:47 +0800 Subject: [PATCH] feat(auth): add user captcha generation endpoint - Add handler for /v1/auth/captcha/generate endpoint - Implement captcha generation logic based on configuration - Support local image captcha generation with Redis storage - Return Turnstile site key for Turnstile mode - Check EnableUserLoginCaptcha configuration --- .../handler/auth/generateCaptchaHandler.go | 18 +++++ internal/logic/auth/generateCaptchaLogic.go | 70 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 internal/handler/auth/generateCaptchaHandler.go create mode 100644 internal/logic/auth/generateCaptchaLogic.go diff --git a/internal/handler/auth/generateCaptchaHandler.go b/internal/handler/auth/generateCaptchaHandler.go new file mode 100644 index 0000000..d263da7 --- /dev/null +++ b/internal/handler/auth/generateCaptchaHandler.go @@ -0,0 +1,18 @@ +package auth + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/auth" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Generate captcha +func GenerateCaptchaHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := auth.NewGenerateCaptchaLogic(c.Request.Context(), svcCtx) + resp, err := l.GenerateCaptcha() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/logic/auth/generateCaptchaLogic.go b/internal/logic/auth/generateCaptchaLogic.go new file mode 100644 index 0000000..068eb2b --- /dev/null +++ b/internal/logic/auth/generateCaptchaLogic.go @@ -0,0 +1,70 @@ +package auth + +import ( + "context" + + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/captcha" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" +) + +type GenerateCaptchaLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Generate captcha +func NewGenerateCaptchaLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateCaptchaLogic { + return &GenerateCaptchaLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GenerateCaptchaLogic) GenerateCaptcha() (resp *types.GenerateCaptchaResponse, err error) { + resp = &types.GenerateCaptchaResponse{} + + // Get verify config from database + verifyCfg, err := l.svcCtx.SystemModel.GetVerifyConfig(l.ctx) + if err != nil { + l.Logger.Error("[GenerateCaptchaLogic] GetVerifyConfig error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetVerifyConfig error: %v", err.Error()) + } + + var config struct { + CaptchaType string `json:"captcha_type"` + TurnstileSiteKey string `json:"turnstile_site_key"` + TurnstileSecret string `json:"turnstile_secret"` + } + tool.SystemConfigSliceReflectToStruct(verifyCfg, &config) + + resp.Type = config.CaptchaType + + // If captcha type is local, generate captcha image + if config.CaptchaType == "local" { + captchaService := captcha.NewService(captcha.Config{ + Type: captcha.CaptchaTypeLocal, + RedisClient: l.svcCtx.Redis, + }) + + id, image, err := captchaService.Generate(l.ctx) + if err != nil { + l.Logger.Error("[GenerateCaptchaLogic] Generate captcha error: ", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Generate captcha error: %v", err.Error()) + } + + resp.Id = id + resp.Image = image + } else if config.CaptchaType == "turnstile" { + // For Turnstile, just return the site key + resp.Id = config.TurnstileSiteKey + } + + return resp, nil +}