refactor: move go-chi/captcha into Gitea (#39529)

The `gitea.com/go-chi/captcha` package only exists for Gitea,
so it moves into `modules/imagecaptcha`.

- Reloading an expired challenge shows a new image instead of a broken one
- Every answer is consumed on its first check
- OpenID registration stops after a failed captcha

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwindandwxiaoguang authored and GitHub committed 2026-10-02 22:21:41 +08:00
1 parent e6ffbea888
commit 873efed5a6
17 files changed
+656 -59

No files matched your search

+4 -5
View File
@@ -19,6 +19,7 @@ import (
user_model "gitea.dev/models/user"
"gitea.dev/modules/auth/password"
"gitea.dev/modules/httplib"
"gitea.dev/modules/imagecaptcha"
"gitea.dev/modules/log"
"gitea.dev/modules/optional"
"gitea.dev/modules/session"
@@ -70,7 +71,7 @@ func prepareCommonAuthPageData(ctx *context.Context, opt CommonAuthOptions) {
ctx.Data["McaptchaURL"] = strings.TrimSuffix(setting.Service.McaptchaURL, "/")
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
if setting.Service.CaptchaType == setting.ImageCaptcha {
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CreateImageCaptcha"] = imagecaptcha.CreateNew
}
}
}
@@ -298,8 +299,7 @@ func SignInPost(ctx *context.Context) {
form := web.GetForm[*forms.SignInForm](ctx)
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.VerifyCaptcha(ctx, tplSignIn, form)
if ctx.Written() {
if !context.VerifyCaptcha(ctx, tplSignIn, form) {
return
}
}
@@ -553,8 +553,7 @@ func SignUpPost(ctx *context.Context) {
return
}
context.VerifyCaptcha(ctx, tplSignUp, form)
if ctx.Written() {
if !context.VerifyCaptcha(ctx, tplSignUp, form) {
return
}
+17
View File
@@ -17,8 +17,10 @@ import (
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"gitea.dev/modules/util"
"gitea.dev/modules/web"
"gitea.dev/services/auth/source/oauth2"
"gitea.dev/services/contexttest"
"gitea.dev/services/forms"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
@@ -199,3 +201,18 @@ func TestOpenIDRequireTwoFactor(t *testing.T) {
openIDRequireTwoFactor(ctx, user2, false, "https://example.com/id")
assert.False(t, ctx.Written())
}
func TestRegisterOpenIDPostRejectsWrongCaptcha(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&setting.Service.EnableCaptcha, true)()
defer test.MockVariableValue(&setting.Service.CaptchaType, setting.ImageCaptcha)()
sess := session.NewMockMemStore("dummy-sid-openid-register")
require.NoError(t, sess.Set("openid_verified_uri", "https://example.com/openid"))
ctx, _ := contexttest.MockContext(t, "POST /user/openid/register", contexttest.MockContextOption{SessionStore: sess})
contexttest.MockRequestPostForm(ctx.Req, url.Values{"captcha_id": {"unknown"}, "captcha": {"000000"}})
web.SetForm(ctx, &forms.SignUpOpenIDForm{UserName: "openid-captcha-user", Email: "openid-captcha-user@example.com"})
RegisterOpenIDPost(ctx)
assert.Equal(t, true, ctx.Data["Err_Captcha"])
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "openid-captcha-user"})
}
+2 -2
View File
@@ -38,6 +38,7 @@ func prepareLinkAccountPageData(ctx *context.Context) {
ctx.Data["ShowRegistrationButton"] = false
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
// FIXME: this logic is not right: captcha is enabled, but LinkAccountPostSignIn never checks for captcha
prepareCommonAuthPageData(ctx, CommonAuthOptions{
EnableCaptcha: setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha,
})
@@ -198,8 +199,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
}
if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
context.VerifyCaptcha(ctx, tplLinkAccount, form)
if ctx.Written() {
if !context.VerifyCaptcha(ctx, tplLinkAccount, form) {
return
}
}
+3 -1
View File
@@ -378,7 +378,9 @@ func RegisterOpenIDPost(ctx *context.Context) {
ctx.ServerError("", err)
return
}
context.VerifyCaptcha(ctx, tplSignUpOID, form)
if !context.VerifyCaptcha(ctx, tplSignUpOID, form) {
return
}
}
length := max(setting.MinPasswordLength, 256)