Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to enable CAPTCHA validation for login #21638

Merged
merged 28 commits into from Nov 22, 2022
Merged
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ad92a8e
Allow to enable CAPTCHA validation for login
Oct 31, 2022
f3cef9c
fix some stupid things
Oct 31, 2022
11dc7ef
as per KN4CK3R
zeripath Oct 31, 2022
c6da162
Merge remote-tracking branch 'origin/main' into REQUIRE_CAPTCHA_FOR_L…
zeripath Oct 31, 2022
3d70bc9
Consolidate CAPTCHA set-up and verification code
zeripath Oct 31, 2022
6ad5f14
fix type
Nov 1, 2022
9c2351f
Merge pull request #1 from zeripath/patch-21638-captcha-for-login
Nov 1, 2022
d02b04f
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
Nov 8, 2022
b839e86
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 10, 2022
86a2bd3
CAPTCHA response field should be private variables
Nov 10, 2022
674847d
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
Nov 11, 2022
12f07ec
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 11, 2022
66c458f
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 11, 2022
7569531
Update modules/context/captcha.go
Nov 12, 2022
34125ae
Update modules/context/captcha.go
Nov 12, 2022
1774038
Add comment to CAPTCHA filed <label></label>
Nov 12, 2022
7c676d0
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
Nov 12, 2022
cc7d1c4
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 15, 2022
b7700ba
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 15, 2022
ced03dd
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 19, 2022
ebe6456
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 19, 2022
7e19272
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 19, 2022
9a09578
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 21, 2022
9d93336
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 21, 2022
c0b1133
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 21, 2022
7033873
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 22, 2022
c9d235d
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 22, 2022
1eb6e2e
Merge branch 'main' into REQUIRE_CAPTCHA_FOR_LOGIN
lunny Nov 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions modules/context/captcha.go
Original file line number Diff line number Diff line change
@@ -5,9 +5,15 @@
package context

import (
"fmt"
"sync"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"

"gitea.com/go-chi/captcha"
@@ -28,3 +34,55 @@ func GetImageCaptcha() *captcha.Captcha {
})
return cpt
}

// SetCaptchaData sets common captcha data
func SetCaptchaData(ctx *Context) {
if !setting.Service.EnableCaptcha {
return
}
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
}

const (
GRecaptchaResponseField = "g-recaptcha-response"
HcaptchaResponseField = "h-captcha-response"
McaptchaResponseField = "m-captcha-response"
)

// VerifyCaptcha verifies Captcha data
func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) {
if !setting.Service.EnableCaptcha {
return
}

var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, ctx.Req.Form.Get(GRecaptchaResponseField))
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, ctx.Req.Form.Get(HcaptchaResponseField))
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(McaptchaResponseField))
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tpl, form)
}
}
91 changes: 9 additions & 82 deletions routers/web/auth/auth.go
Original file line number Diff line number Diff line change
@@ -17,11 +17,8 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -171,14 +168,7 @@ func SignIn(ctx *context.Context) {
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled()

if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
}

ctx.HTML(http.StatusOK, tplSignIn)
@@ -209,37 +199,10 @@ func SignInPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.SignInForm)

if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL

var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}
context.SetCaptchaData(ctx)

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignIn, &form)
context.VerifyCaptcha(ctx, tplSignIn, form)
if ctx.Written() {
return
}
}
@@ -459,14 +422,7 @@ func SignUp(ctx *context.Context) {

ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"

ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["PageIsSignUp"] = true

// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -482,14 +438,7 @@ func SignUpPost(ctx *context.Context) {

ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"

ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["PageIsSignUp"] = true

// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -503,31 +452,9 @@ func SignUpPost(ctx *context.Context) {
return
}

if setting.Service.EnableCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
return
}
context.VerifyCaptcha(ctx, tplSignUp, form)
if ctx.Written() {
return
}

if !form.IsEmailDomainAllowed() {
27 changes: 2 additions & 25 deletions routers/web/auth/linkaccount.go
Original file line number Diff line number Diff line change
@@ -14,10 +14,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
@@ -231,28 +228,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
}

if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
context.VerifyCaptcha(ctx, tplLinkAccount, form)
if ctx.Written() {
return
}
}
49 changes: 4 additions & 45 deletions routers/web/auth/openid.go
Original file line number Diff line number Diff line change
@@ -13,10 +13,7 @@ import (
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -369,14 +366,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
ctx.Data["PageIsSignIn"] = true
ctx.Data["PageIsOpenIDRegister"] = true
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["OpenID"] = oid

if setting.Service.AllowOnlyInternalRegistration {
@@ -385,42 +375,11 @@ func RegisterOpenIDPost(ctx *context.Context) {
}

if setting.Service.EnableCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
context.VerifyCaptcha(ctx, tplSignUpOID, form)
}

length := setting.MinPasswordLength
15 changes: 4 additions & 11 deletions services/forms/user_form.go
Original file line number Diff line number Diff line change
@@ -90,13 +90,10 @@ func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.E

// RegisterForm form for registering
type RegisterForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
Email string `binding:"Required;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Retype string
GRecaptchaResponse string `form:"g-recaptcha-response"`
HcaptchaResponse string `form:"h-captcha-response"`
McaptchaResponse string `form:"m-captcha-response"`
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
Email string `binding:"Required;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Retype string
}

// Validate validates the fields
@@ -160,10 +157,6 @@ type SignInForm struct {
// TODO remove required from password for SecondFactorAuthentication
Password string `binding:"Required;MaxSize(255)"`
Remember bool
// Captcha
GRecaptchaResponse string `form:"g-recaptcha-response"`
HcaptchaResponse string `form:"h-captcha-response"`
McaptchaResponse string `form:"m-captcha-response"`
}

// Validate validates the fields
7 changes: 2 additions & 5 deletions services/forms/user_form_auth_openid.go
Original file line number Diff line number Diff line change
@@ -27,11 +27,8 @@ func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) bind

// SignUpOpenIDForm form for signin up with OpenID
type SignUpOpenIDForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
GRecaptchaResponse string `form:"g-recaptcha-response"`
HcaptchaResponse string `form:"h-captcha-response"`
McaptchaResponse string `form:"m-captcha-response"`
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
}

// Validate validates the fields