[CHORE] Move captcha library

- This is a fork of https://github.com/dchest/captcha, as
https://gitea.com/go-chi/captcha is a fork of
github.com/go-macaron/captcha which is a fork (although not properly
credited) of a older version of https://github.com/dchest/captcha. Hence
why I've just forked the original.
- The fork includes some QoL improvements (uses standard library for
determistic RNG instead of rolling your own crypto), and removal of
audio support (500KiB unused data that bloated the binary otherwise).
Flips the image over the x-asis.
47270f2b55..main
- This move is needed for the next commit, because
gitea.com/go-chi/captcha included the gitea.com/go-chi/cache dependency.
This commit is contained in:
Gusted 2024-08-27 01:47:10 +02:00
parent 190b5a3859
commit 0404662e99
No known key found for this signature in database
GPG key ID: FD821B732837125F
6 changed files with 83 additions and 17 deletions

2
go.mod
View file

@ -5,6 +5,7 @@ go 1.23.0
require (
code.forgejo.org/f3/gof3/v3 v3.7.0
code.forgejo.org/forgejo/reply v1.0.2
code.forgejo.org/go-chi/captcha v0.0.0-20240827192619-ac88f17cdd8e
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf
code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/gitea-vet v0.2.3
@ -13,7 +14,6 @@ require (
connectrpc.com/connect v1.16.2
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358

4
go.sum
View file

@ -8,6 +8,8 @@ code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEj
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
code.forgejo.org/go-chi/captcha v0.0.0-20240827192619-ac88f17cdd8e h1:8hqxBSf1M5JavIhz/Rgx3BP8kkwtCe2SP6AVTE6jjm8=
code.forgejo.org/go-chi/captcha v0.0.0-20240827192619-ac88f17cdd8e/go.mod h1:yxZHJ6up9d/mQUu8NHHoCtJj5VcyB+5ArkUY45Hp3hE=
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf h1:gJRuqEPd3/U0/1YM+uSgbC/fpR8qrcMdvT6E7eSetyM=
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf/go.mod h1:PcnIg89MAhO1yExkw1QXXNDiPssVdCsMmwUo67g7GD4=
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
@ -30,8 +32,6 @@ gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHc
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=

View file

@ -51,7 +51,7 @@ import (
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
"gitea.com/go-chi/captcha"
"code.forgejo.org/go-chi/captcha"
chi_middleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/klauspost/compress/gzhttp"
@ -254,7 +254,7 @@ func Routes() *web.Route {
if setting.Service.EnableCaptcha {
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
routes.Methods("GET,HEAD", "/captcha/*", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...)
routes.Methods("GET,HEAD", "/captcha/*", append(mid, captcha.Server(captcha.StdWidth, captcha.StdHeight).ServeHTTP)...)
}
if setting.Metrics.Enabled {

View file

@ -15,24 +15,47 @@ import (
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/turnstile"
mc "gitea.com/go-chi/cache"
"gitea.com/go-chi/captcha"
"code.forgejo.org/go-chi/captcha"
)
var (
imageCaptchaOnce sync.Once
cpt *captcha.Captcha
imageCachePrefix = "captcha:"
)
// GetImageCaptcha returns global image captcha
func GetImageCaptcha() *captcha.Captcha {
type imageCaptchaStore struct {
c mc.Cache
}
func (c *imageCaptchaStore) Set(id string, digits []byte) {
if err := c.c.Put(imageCachePrefix+id, string(digits), int64(captcha.Expiration.Seconds())); err != nil {
log.Error("Couldn't store captcha cache for %q: %v", id, err)
}
}
func (c *imageCaptchaStore) Get(id string, clear bool) (digits []byte) {
val, ok := c.c.Get(imageCachePrefix + id).(string)
if !ok {
return digits
}
if clear {
if err := c.c.Delete(imageCachePrefix + id); err != nil {
log.Error("Couldn't delete captcha cache for %q: %v", id, err)
}
}
return []byte(val)
}
// GetImageCaptcha returns image captcha ID.
func GetImageCaptcha() string {
imageCaptchaOnce.Do(func() {
cpt = captcha.NewCaptcha(captcha.Options{
SubURL: setting.AppSubURL,
captcha.SetCustomStore(&imageCaptchaStore{c: cache.GetCache()})
})
cpt.Store = cache.GetCache()
})
return cpt
return captcha.New()
}
// SetCaptchaData sets common captcha data
@ -52,6 +75,8 @@ func SetCaptchaData(ctx *Context) {
}
const (
imgCaptchaIDField = "img-captcha-id"
imgCaptchaResponseField = "img-captcha-response"
gRecaptchaResponseField = "g-recaptcha-response"
hCaptchaResponseField = "h-captcha-response"
mCaptchaResponseField = "m-captcha-response"
@ -69,7 +94,7 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = GetImageCaptcha().VerifyReq(ctx.Req)
valid = captcha.VerifyString(ctx.Req.Form.Get(imgCaptchaIDField), ctx.Req.Form.Get(imgCaptchaResponseField))
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, ctx.Req.Form.Get(gRecaptchaResponseField))
case setting.HCaptcha:

View file

@ -1,10 +1,11 @@
{{if .EnableCaptcha}}{{if eq .CaptchaType "image"}}
<div class="inline field tw-text-center">
{{.Captcha.CreateHTML}}
<input type="hidden" name="img-captcha-id" value="{{.Captcha}}">
<img style="transform: scaleX(-1)" onclick="this.src=`{{AppSubUrl}}/captcha/{{.Captcha}}.png?reload=${Date.now()}`" class="captcha-img" src="{{AppSubUrl}}/captcha/{{.Captcha}}.png">
</div>
<div class="required field {{if .Err_Captcha}}error{{end}}">
<label for="captcha">{{ctx.Locale.Tr "captcha"}}</label>
<input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off">
<input id="captcha" name="img-captcha-response" autocomplete="off">
</div>
{{else if eq .CaptchaType "recaptcha"}}
<div class="inline field tw-text-center required">

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
@ -167,3 +168,42 @@ func TestSignupEmailChangeForActiveUser(t *testing.T) {
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "exampleUserY"})
assert.Equal(t, "wrong-email-2@example.com", user.Email)
}
func TestSignupImageCaptcha(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer test.MockVariableValue(&setting.Service.RegisterEmailConfirm, false)()
defer test.MockVariableValue(&setting.Service.EnableCaptcha, true)()
defer test.MockVariableValue(&setting.Service.CaptchaType, "image")()
c := cache.GetCache()
req := NewRequest(t, "GET", "/user/sign_up")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
idCaptcha, ok := htmlDoc.Find("input[name='img-captcha-id']").Attr("value")
assert.True(t, ok)
digits, ok := c.Get("captcha:" + idCaptcha).(string)
assert.True(t, ok)
assert.Len(t, digits, 6)
digitStr := ""
// Convert digits to ASCII digits.
for _, digit := range digits {
digitStr += string(digit + '0')
}
req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
"user_name": "captcha-test",
"email": "captcha-test@example.com",
"password": "examplePassword!1",
"retype": "examplePassword!1",
"img-captcha-id": idCaptcha,
"img-captcha-response": digitStr,
})
MakeRequest(t, req, http.StatusSeeOther)
loginUserWithPassword(t, "captcha-test", "examplePassword!1")
unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "captcha-test", IsActive: true})
}