@@ -301,7 +301,13 @@ 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 | |||
; Type of captcha you want to use. Options: image, recaptcha | |||
CAPTCHA_TYPE = image | |||
; Enable recaptcha to use Google's recaptcha service | |||
; Go to https://www.google.com/recaptcha/admin to sign up for a key | |||
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 |
@@ -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> |