mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-01 09:51:39 -05:00
[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:
parent
190b5a3859
commit
0404662e99
6 changed files with 83 additions and 17 deletions
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ go 1.23.0
|
||||||
require (
|
require (
|
||||||
code.forgejo.org/f3/gof3/v3 v3.7.0
|
code.forgejo.org/f3/gof3/v3 v3.7.0
|
||||||
code.forgejo.org/forgejo/reply v1.0.2
|
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.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf
|
||||||
code.gitea.io/actions-proto-go v0.4.0
|
code.gitea.io/actions-proto-go v0.4.0
|
||||||
code.gitea.io/gitea-vet v0.2.3
|
code.gitea.io/gitea-vet v0.2.3
|
||||||
|
@ -13,7 +14,6 @@ require (
|
||||||
connectrpc.com/connect v1.16.2
|
connectrpc.com/connect v1.16.2
|
||||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||||
gitea.com/go-chi/cache v0.2.0
|
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
|
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -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/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 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
|
||||||
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
|
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 h1:gJRuqEPd3/U0/1YM+uSgbC/fpR8qrcMdvT6E7eSetyM=
|
||||||
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf/go.mod h1:PcnIg89MAhO1yExkw1QXXNDiPssVdCsMmwUo67g7GD4=
|
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=
|
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/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 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
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 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
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=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
|
|
|
@ -51,7 +51,7 @@ import (
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
|
_ "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"
|
chi_middleware "github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
|
@ -254,7 +254,7 @@ func Routes() *web.Route {
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha {
|
if setting.Service.EnableCaptcha {
|
||||||
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
|
// 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 {
|
if setting.Metrics.Enabled {
|
||||||
|
|
|
@ -15,24 +15,47 @@ import (
|
||||||
"code.gitea.io/gitea/modules/recaptcha"
|
"code.gitea.io/gitea/modules/recaptcha"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/turnstile"
|
"code.gitea.io/gitea/modules/turnstile"
|
||||||
|
mc "gitea.com/go-chi/cache"
|
||||||
|
|
||||||
"gitea.com/go-chi/captcha"
|
"code.forgejo.org/go-chi/captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
imageCaptchaOnce sync.Once
|
imageCaptchaOnce sync.Once
|
||||||
cpt *captcha.Captcha
|
imageCachePrefix = "captcha:"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetImageCaptcha returns global image captcha
|
type imageCaptchaStore struct {
|
||||||
func GetImageCaptcha() *captcha.Captcha {
|
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() {
|
imageCaptchaOnce.Do(func() {
|
||||||
cpt = captcha.NewCaptcha(captcha.Options{
|
captcha.SetCustomStore(&imageCaptchaStore{c: cache.GetCache()})
|
||||||
SubURL: setting.AppSubURL,
|
|
||||||
})
|
})
|
||||||
cpt.Store = cache.GetCache()
|
return captcha.New()
|
||||||
})
|
|
||||||
return cpt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCaptchaData sets common captcha data
|
// SetCaptchaData sets common captcha data
|
||||||
|
@ -52,6 +75,8 @@ func SetCaptchaData(ctx *Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
imgCaptchaIDField = "img-captcha-id"
|
||||||
|
imgCaptchaResponseField = "img-captcha-response"
|
||||||
gRecaptchaResponseField = "g-recaptcha-response"
|
gRecaptchaResponseField = "g-recaptcha-response"
|
||||||
hCaptchaResponseField = "h-captcha-response"
|
hCaptchaResponseField = "h-captcha-response"
|
||||||
mCaptchaResponseField = "m-captcha-response"
|
mCaptchaResponseField = "m-captcha-response"
|
||||||
|
@ -69,7 +94,7 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
|
||||||
var err error
|
var err error
|
||||||
switch setting.Service.CaptchaType {
|
switch setting.Service.CaptchaType {
|
||||||
case setting.ImageCaptcha:
|
case setting.ImageCaptcha:
|
||||||
valid = GetImageCaptcha().VerifyReq(ctx.Req)
|
valid = captcha.VerifyString(ctx.Req.Form.Get(imgCaptchaIDField), ctx.Req.Form.Get(imgCaptchaResponseField))
|
||||||
case setting.ReCaptcha:
|
case setting.ReCaptcha:
|
||||||
valid, err = recaptcha.Verify(ctx, ctx.Req.Form.Get(gRecaptchaResponseField))
|
valid, err = recaptcha.Verify(ctx, ctx.Req.Form.Get(gRecaptchaResponseField))
|
||||||
case setting.HCaptcha:
|
case setting.HCaptcha:
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
{{if .EnableCaptcha}}{{if eq .CaptchaType "image"}}
|
{{if .EnableCaptcha}}{{if eq .CaptchaType "image"}}
|
||||||
<div class="inline field tw-text-center">
|
<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>
|
||||||
<div class="required field {{if .Err_Captcha}}error{{end}}">
|
<div class="required field {{if .Err_Captcha}}error{{end}}">
|
||||||
<label for="captcha">{{ctx.Locale.Tr "captcha"}}</label>
|
<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>
|
</div>
|
||||||
{{else if eq .CaptchaType "recaptcha"}}
|
{{else if eq .CaptchaType "recaptcha"}}
|
||||||
<div class="inline field tw-text-center required">
|
<div class="inline field tw-text-center required">
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
@ -167,3 +168,42 @@ func TestSignupEmailChangeForActiveUser(t *testing.T) {
|
||||||
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "exampleUserY"})
|
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "exampleUserY"})
|
||||||
assert.Equal(t, "wrong-email-2@example.com", user.Email)
|
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})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue