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
Show file tree
Hide file tree
Changes from 23 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
3 changes: 3 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,9 @@ ROUTER = console
;; Enable captcha validation for registration
;ENABLE_CAPTCHA = false
;;
;; Enable this to require captcha validation for login
;REQUIRE_CAPTCHA_FOR_LOGIN = false
;;
;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha.
;CAPTCHA_TYPE = image
;;
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
- `ENABLE_REVERSE_PROXY_FULL_NAME`: **false**: Enable this to allow to auto-registration with a
provided full name for the user.
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
- `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: Enable this to require captcha validation for login. You also must enable `ENABLE_CAPTCHA`.
- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation
even for External Accounts (i.e. GitHub, OpenID Connect, etc). You also must enable `ENABLE_CAPTCHA`.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha\]
Expand Down
3 changes: 2 additions & 1 deletion docs/content/doc/advanced/config-cheat-sheet.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ menu:
- `ENABLE_NOTIFY_MAIL`: 是否发送工单创建等提醒邮件,需要 `Mailer` 被激活。
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: 允许反向代理认证,更多细节见:https://github.com/gogits/gogs/issues/165
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: 允许通过反向认证做自动注册。
- `ENABLE_CAPTCHA`: 注册时使用图片验证码。
- `ENABLE_CAPTCHA`: **false**: 注册时使用图片验证码。
- `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: 登录时需要图片验证码。需要同时开启 `ENABLE_CAPTCHA`。

### Service - Expore (`service.explore`)

Expand Down
59 changes: 59 additions & 0 deletions modules/context/captcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -28,3 +34,56 @@ 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
This conversation was marked as resolved.
Show resolved Hide resolved
// No-op if captchas are not enabled
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("%v", err)
}

if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tpl, form)
}
}
2 changes: 2 additions & 0 deletions modules/setting/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var Service = struct {
EnableReverseProxyEmail bool
EnableReverseProxyFullName bool
EnableCaptcha bool
RequireCaptchaForLogin bool
RequireExternalRegistrationCaptcha bool
RequireExternalRegistrationPassword bool
CaptchaType string
Expand Down Expand Up @@ -130,6 +131,7 @@ func newService() {
Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
Service.EnableReverseProxyFullName = sec.Key("ENABLE_REVERSE_PROXY_FULL_NAME").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
Service.RequireCaptchaForLogin = sec.Key("REQUIRE_CAPTCHA_FOR_LOGIN").MustBool(false)
Service.RequireExternalRegistrationCaptcha = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA").MustBool(Service.EnableCaptcha)
Service.RequireExternalRegistrationPassword = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_PASSWORD").MustBool()
Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
Expand Down
63 changes: 19 additions & 44 deletions routers/web/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -163,6 +160,10 @@ func SignIn(ctx *context.Context) {
ctx.Data["PageIsLogin"] = true
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled()

if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.SetCaptchaData(ctx)
}

ctx.HTML(http.StatusOK, tplSignIn)
}

Expand All @@ -189,6 +190,16 @@ func SignInPost(ctx *context.Context) {
}

form := web.GetForm(ctx).(*forms.SignInForm)

if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.SetCaptchaData(ctx)

context.VerifyCaptcha(ctx, tplSignIn, form)
if ctx.Written() {
return
}
}

u, source, err := auth_service.UserSignIn(form.UserName, form.Password)
if err != nil {
if user_model.IsErrUserNotExist(err) || user_model.IsErrEmailAddressNotExist(err) {
Expand Down Expand Up @@ -383,14 +394,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
Expand All @@ -406,14 +410,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
Expand All @@ -427,31 +424,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() {
Expand Down
28 changes: 2 additions & 26 deletions routers/web/auth/linkaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ 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/setting"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
Expand Down Expand Up @@ -221,28 +217,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
}
}
Expand Down
49 changes: 4 additions & 45 deletions routers/web/auth/openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
Expand Down Expand Up @@ -357,14 +354,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 {
Expand All @@ -373,42 +363,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
Expand Down
11 changes: 4 additions & 7 deletions services/forms/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,10 @@ func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.E

// RegisterForm form for registering
type RegisterForm struct {
UserName string `binding:"Required;Username;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;Username;MaxSize(40)"`
Email string `binding:"Required;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Retype string
}

// Validate validates the fields
Expand Down
7 changes: 2 additions & 5 deletions services/forms/user_form_auth_openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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;Username;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;Username;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
}

// Validate validates the fields
Expand Down
24 changes: 24 additions & 0 deletions templates/user/auth/captcha.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{if .EnableCaptcha}}{{if eq .CaptchaType "image"}}
<div class="inline field">
<label>{{/* This is CAPTCHA field */}}</label>
{{.Captcha.CreateHTML}}
</div>
<div class="required inline field {{if .Err_Captcha}}error{{end}}">
<label for="captcha">{{.locale.Tr "captcha"}}</label>
<input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off">
</div>
{{else if eq .CaptchaType "recaptcha"}}
<div class="inline field required">
<div class="g-recaptcha" data-sitekey="{{.RecaptchaSitekey}}"></div>
</div>
{{else if eq .CaptchaType "hcaptcha"}}
<div class="inline field required">
<div class="h-captcha" data-sitekey="{{.HcaptchaSitekey}}"></div>
</div>
{{else if eq .CaptchaType "mcaptcha"}}
<div class="inline field df ac db-small captcha-field">
<span>{{.locale.Tr "captcha"}}</span>
<div class="border-secondary w-100-small" id="mcaptcha__widget-container" style="width: 50%; height: 5em"></div>
<div class="m-captcha" data-sitekey="{{.McaptchaSitekey}}" data-instance-url="{{.McaptchaURL}}"></div>
</div>
{{end}}{{end}}
Loading