From a46718613b704a400670a7f6a8fff8e61243fe99 Mon Sep 17 00:00:00 2001 From: Fluf Monster Date: Fri, 25 May 2018 01:41:30 +0000 Subject: [PATCH] Add Recaptcha functionality to Gitea --- custom/conf/app.ini.sample | 7 ++- .../doc/advanced/config-cheat-sheet.en-us.md | 5 +- modules/recaptcha/recaptcha.go | 46 +++++++++++++++++++ modules/setting/setting.go | 8 +++- modules/templates/helper.go | 6 +++ routers/user/auth.go | 33 +++++++++++++ routers/user/auth_openid.go | 15 ++++++ templates/user/auth/signup_inner.tmpl | 7 +++ .../user/auth/signup_openid_register.tmpl | 6 +++ 9 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 modules/recaptcha/recaptcha.go diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 8392576da..a088ae1dc 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -299,7 +299,12 @@ ENABLE_NOTIFY_MAIL = false ENABLE_REVERSE_PROXY_AUTHENTICATION = false ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false ; Enable captcha validation for registration -ENABLE_CAPTCHA = true +ENABLE_CAPTCHA = false +; Enable recaptcha to use Google's recaptcha service +; Go to https://www.google.com/recaptcha/admin to sign up for a key +ENABLE_RECAPTCHA = false +RECAPTCHA_SECRET = +RECAPTCHA_SITEKEY = ; Default value for KeepEmailPrivate ; Each new user will get the value of this setting copied into their profile DEFAULT_KEEP_EMAIL_PRIVATE = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 69f588ebe..25dc7981a 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -176,7 +176,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication. - `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for reverse authentication. -- `ENABLE_CAPTCHA`: **true**: Enable this to use captcha validation for registration. +- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration. +- `ENABLE_RECAPTCHA`: **false**: Enable this to use Google's Recaptcha service for registration +- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha +- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha ## Webhook (`webhook`) diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go new file mode 100644 index 000000000..1ee93ce8a --- /dev/null +++ b/modules/recaptcha/recaptcha.go @@ -0,0 +1,46 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package recaptcha + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "code.gitea.io/gitea/modules/setting" +) + +type Response struct { + Success bool `json:"success"` + ChallengeTS time.Time `json:"challenge_ts"` + Hostname string `json:"hostname"` + ErrorCodes []string `json:"error-codes"` +} + +const apiURL = "https://www.google.com/recaptcha/api/siteverify" + +func Verify(response string) (bool, error) { + resp, err := http.PostForm(apiURL, + url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}}) + if err != nil { + return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err) + } + jsonResponse := Response{Success: false} // set a default of fail for CAPTCHA + err = json.Unmarshal(body, &jsonResponse) + if err != nil { + return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) + } + + return jsonResponse.Success, nil + +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index adf4bb74f..0b848de33 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1158,6 +1158,9 @@ var Service struct { EnableReverseProxyAuth bool EnableReverseProxyAutoRegister bool EnableCaptcha bool + EnableRecaptcha bool + RecaptchaSecret string + RecaptchaSitekey string DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool EnableTimetracking bool @@ -1182,7 +1185,10 @@ func newService() { Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() - Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool() + Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false) + Service.EnableRecaptcha = sec.Key("ENABLE_RECAPTCHA").MustBool(false) + Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("") + Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("") Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index bf5c0130b..1a954dae8 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -186,6 +186,12 @@ func NewFuncMap() []template.FuncMap { "ParseDeadline": func(deadline string) []string { return strings.Split(deadline, "|") }, + "EnableRecaptcha": func() bool { + return setting.Service.EnableRecaptcha + }, + "RecaptchaSitekey": func() string { + return setting.Service.RecaptchaSitekey + }, }} } diff --git a/routers/user/auth.go b/routers/user/auth.go index 9a59f52db..4691aa856 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/recaptcha" "github.com/go-macaron/captcha" "github.com/markbates/goth" @@ -640,6 +641,8 @@ func LinkAccount(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("link_account") ctx.Data["LinkAccountMode"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -665,6 +668,8 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeSignIn"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -731,6 +736,8 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au ctx.Data["LinkAccountMode"] = true ctx.Data["LinkAccountModeRegister"] = true ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -760,6 +767,16 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au return } + if setting.Service.EnableRecaptcha { + ctx.Req.ParseForm() + valid, _ := recaptcha.Verify(ctx.Req.Form.Get("g-recaptcha-response")) + if !valid { + ctx.Data["Err_Captcha"] = true + ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form) + return + } + } + if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype { ctx.Data["Err_Password"] = true ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form) @@ -857,6 +874,9 @@ func SignUp(ctx *context.Context) { ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.HTML(200, tplSignUp) @@ -870,6 +890,9 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey + //Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true if !setting.Service.ShowRegistrationButton { ctx.Error(403) @@ -887,6 +910,16 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo return } + if setting.Service.EnableRecaptcha { + ctx.Req.ParseForm() + valid, _ := recaptcha.Verify(ctx.Req.Form.Get("g-recaptcha-response")) + if !valid { + ctx.Data["Err_Captcha"] = true + ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form) + return + } + } + if form.Password != form.Retype { ctx.Data["Err_Password"] = true ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form) diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 9fe3424aa..a53be2d40 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/setting" "github.com/go-macaron/captcha" @@ -308,6 +309,8 @@ func RegisterOpenID(ctx *context.Context) { ctx.Data["PageIsOpenIDRegister"] = true ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["OpenID"] = oid userName, _ := ctx.Session.Get("openid_determined_username").(string) if userName != "" { @@ -333,6 +336,8 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si ctx.Data["PageIsOpenIDRegister"] = true ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha + ctx.Data["EnableRecaptcha"] = setting.Service.EnableRecaptcha + ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["OpenID"] = oid if setting.Service.EnableCaptcha && !cpt.VerifyReq(ctx.Req) { @@ -341,6 +346,16 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si return } + if setting.Service.EnableRecaptcha { + ctx.Req.ParseForm() + valid, _ := recaptcha.Verify(ctx.Req.Form.Get("g-recaptcha-response")) + if !valid { + ctx.Data["Err_Captcha"] = true + ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form) + return + } + } + len := setting.MinPasswordLength if len < 256 { len = 256 diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index 52386f6dc..58d47128f 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -40,6 +40,13 @@ {{end}} + {{if .EnableRecaptcha}} +
+
+ +
+ {{end}} +
diff --git a/templates/user/auth/signup_openid_register.tmpl b/templates/user/auth/signup_openid_register.tmpl index 741ffdf3b..1fa3cead7 100644 --- a/templates/user/auth/signup_openid_register.tmpl +++ b/templates/user/auth/signup_openid_register.tmpl @@ -30,6 +30,12 @@
{{end}} + {{if .EnableRecaptcha}} +
+
+ +
+ {{end}}