| @@ -177,7 +177,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. | |||
| - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] | |||
| - `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`) | |||
| @@ -72,10 +72,11 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin | |||
| // RegisterForm form for registering | |||
| type RegisterForm struct { | |||
| UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` | |||
| Email string `binding:"Required;Email;MaxSize(254)"` | |||
| Password string `binding:"Required;MaxSize(255)"` | |||
| Retype string | |||
| UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` | |||
| Email string `binding:"Required;Email;MaxSize(254)"` | |||
| Password string `binding:"Required;MaxSize(255)"` | |||
| Retype string | |||
| GRecaptchaResponse string `form:"g-recaptcha-response"` | |||
| } | |||
| // Validate valideates the fields | |||
| @@ -22,8 +22,9 @@ func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) b | |||
| // SignUpOpenIDForm form for signin up with OpenID | |||
| type SignUpOpenIDForm struct { | |||
| UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` | |||
| Email string `binding:"Required;Email;MaxSize(254)"` | |||
| UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` | |||
| Email string `binding:"Required;Email;MaxSize(254)"` | |||
| GRecaptchaResponse string `form:"g-recaptcha-response"` | |||
| } | |||
| // Validate valideates the fields | |||
| @@ -0,0 +1,47 @@ | |||
| // 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" | |||
| ) | |||
| // Response is the structure of JSON returned from API | |||
| 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" | |||
| // Verify calls Google Recaptcha API to verify token | |||
| 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) | |||
| } | |||
| var jsonResponse Response | |||
| err = json.Unmarshal(body, &jsonResponse) | |||
| if err != nil { | |||
| return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err) | |||
| } | |||
| return jsonResponse.Success, nil | |||
| } | |||
| @@ -75,6 +75,12 @@ const ( | |||
| RepoCreatingPublic = "public" | |||
| ) | |||
| // enumerates all the types of captchas | |||
| const ( | |||
| ImageCaptcha = "image" | |||
| ReCaptcha = "recaptcha" | |||
| ) | |||
| // settings | |||
| var ( | |||
| // AppVer settings | |||
| @@ -1165,6 +1171,9 @@ var Service struct { | |||
| EnableReverseProxyAuth bool | |||
| EnableReverseProxyAutoRegister bool | |||
| EnableCaptcha bool | |||
| CaptchaType string | |||
| RecaptchaSecret string | |||
| RecaptchaSitekey string | |||
| DefaultKeepEmailPrivate bool | |||
| DefaultAllowCreateOrganization bool | |||
| EnableTimetracking bool | |||
| @@ -1189,7 +1198,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.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha) | |||
| 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) | |||
| @@ -80,6 +80,23 @@ | |||
| } | |||
| } | |||
| } | |||
| @media only screen and (min-width: 768px) { | |||
| .g-recaptcha { | |||
| margin: 0 auto !important; | |||
| width: 304px; | |||
| padding-left: 30px; | |||
| } | |||
| } | |||
| @media screen and (max-height: 575px){ | |||
| #rc-imageselect, .g-recaptcha { | |||
| transform:scale(0.77); | |||
| -webkit-transform:scale(0.77); | |||
| transform-origin:0 0; | |||
| -webkit-transform-origin:0 0; | |||
| } | |||
| } | |||
| .user.activate, | |||
| .user.forgot.password, | |||
| .user.reset.password, | |||
| @@ -17,6 +17,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/recaptcha" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/modules/util" | |||
| @@ -641,6 +642,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["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | |||
| ctx.Data["ShowRegistrationButton"] = false | |||
| @@ -666,6 +669,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["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | |||
| ctx.Data["ShowRegistrationButton"] = false | |||
| @@ -732,6 +737,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["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | |||
| ctx.Data["ShowRegistrationButton"] = false | |||
| @@ -755,12 +762,21 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au | |||
| return | |||
| } | |||
| if setting.Service.EnableCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| ctx.Data["Err_Captcha"] = true | |||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form) | |||
| return | |||
| } | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha { | |||
| valid, _ := recaptcha.Verify(form.GRecaptchaResponse) | |||
| 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) | |||
| @@ -858,6 +874,9 @@ func SignUp(ctx *context.Context) { | |||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | |||
| ctx.Data["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | |||
| ctx.HTML(200, tplSignUp) | |||
| @@ -871,6 +890,9 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo | |||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | |||
| ctx.Data["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| //Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true | |||
| if !setting.Service.ShowRegistrationButton { | |||
| ctx.Error(403) | |||
| @@ -882,12 +904,21 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo | |||
| return | |||
| } | |||
| if setting.Service.EnableCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| ctx.Data["Err_Captcha"] = true | |||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form) | |||
| return | |||
| } | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha { | |||
| valid, _ := recaptcha.Verify(form.GRecaptchaResponse) | |||
| 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) | |||
| @@ -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["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["OpenID"] = oid | |||
| userName, _ := ctx.Session.Get("openid_determined_username").(string) | |||
| if userName != "" { | |||
| @@ -333,14 +336,26 @@ 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["CaptchaType"] = setting.Service.CaptchaType | |||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||
| ctx.Data["OpenID"] = oid | |||
| if setting.Service.EnableCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) { | |||
| ctx.Data["Err_Captcha"] = true | |||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form) | |||
| return | |||
| } | |||
| if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha { | |||
| ctx.Req.ParseForm() | |||
| valid, _ := recaptcha.Verify(form.GRecaptchaResponse) | |||
| 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 | |||
| @@ -67,6 +67,11 @@ | |||
| {{if .RequireU2F}} | |||
| <script src="{{AppSubUrl}}/vendor/plugins/u2f/index.js"></script> | |||
| {{end}} | |||
| {{if .EnableCaptcha}} | |||
| {{if eq .CaptchaType "recaptcha"}} | |||
| <script src="https://www.google.com/recaptcha/api.js" async></script> | |||
| {{end}} | |||
| {{end}} | |||
| {{if .RequireTribute}} | |||
| <script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script> | |||
| @@ -29,7 +29,7 @@ | |||
| <label for="retype">{{.i18n.Tr "re_type"}}</label> | |||
| <input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required> | |||
| </div> | |||
| {{if .EnableCaptcha}} | |||
| {{if and .EnableCaptcha (eq .CaptchaType "image")}} | |||
| <div class="inline field"> | |||
| <label></label> | |||
| {{.Captcha.CreateHtml}} | |||
| @@ -39,6 +39,11 @@ | |||
| <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | |||
| </div> | |||
| {{end}} | |||
| {{if and .EnableCaptcha (eq .CaptchaType "recaptcha")}} | |||
| <div class="inline field required"> | |||
| <div class="g-recaptcha" data-sitekey="{{ .RecaptchaSitekey }}"></div> | |||
| </div> | |||
| {{end}} | |||
| <div class="inline field"> | |||
| <label></label> | |||
| @@ -20,7 +20,7 @@ | |||
| <label for="email">{{.i18n.Tr "email"}}</label> | |||
| <input id="email" name="email" type="email" value="{{.email}}" required> | |||
| </div> | |||
| {{if .EnableCaptcha}} | |||
| {{if and .EnableCaptcha (eq .CaptchaType "image")}} | |||
| <div class="inline field"> | |||
| <label></label> | |||
| {{.Captcha.CreateHtml}} | |||
| @@ -30,6 +30,11 @@ | |||
| <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | |||
| </div> | |||
| {{end}} | |||
| {{if and .EnableCaptcha (eq .CaptchaType "recaptcha")}} | |||
| <div class="inline field required"> | |||
| <div class="g-recaptcha" data-sitekey="{{ .RecaptchaSitekey }}"></div> | |||
| </div> | |||
| {{end}} | |||
| <div class="inline field"> | |||
| <label for="openid">OpenID URI</label> | |||
| <input id="openid" value="{{ .OpenID }}" readonly> | |||