| @@ -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_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication. | ||||
| - `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration | - `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration | ||||
| for reverse authentication. | 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`) | ## Webhook (`webhook`) | ||||
| @@ -72,10 +72,11 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin | |||||
| // RegisterForm form for registering | // RegisterForm form for registering | ||||
| type RegisterForm struct { | 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 | // 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 | // SignUpOpenIDForm form for signin up with OpenID | ||||
| type SignUpOpenIDForm struct { | 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 | // 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" | RepoCreatingPublic = "public" | ||||
| ) | ) | ||||
| // enumerates all the types of captchas | |||||
| const ( | |||||
| ImageCaptcha = "image" | |||||
| ReCaptcha = "recaptcha" | |||||
| ) | |||||
| // settings | // settings | ||||
| var ( | var ( | ||||
| // AppVer settings | // AppVer settings | ||||
| @@ -1165,6 +1171,9 @@ var Service struct { | |||||
| EnableReverseProxyAuth bool | EnableReverseProxyAuth bool | ||||
| EnableReverseProxyAutoRegister bool | EnableReverseProxyAutoRegister bool | ||||
| EnableCaptcha bool | EnableCaptcha bool | ||||
| CaptchaType string | |||||
| RecaptchaSecret string | |||||
| RecaptchaSitekey string | |||||
| DefaultKeepEmailPrivate bool | DefaultKeepEmailPrivate bool | ||||
| DefaultAllowCreateOrganization bool | DefaultAllowCreateOrganization bool | ||||
| EnableTimetracking bool | EnableTimetracking bool | ||||
| @@ -1189,7 +1198,10 @@ func newService() { | |||||
| Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() | Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() | ||||
| Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() | Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() | ||||
| Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").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.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() | ||||
| Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) | Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) | ||||
| Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").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.activate, | ||||
| .user.forgot.password, | .user.forgot.password, | ||||
| .user.reset.password, | .user.reset.password, | ||||
| @@ -17,6 +17,7 @@ import ( | |||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/recaptcha" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
| @@ -641,6 +642,8 @@ func LinkAccount(ctx *context.Context) { | |||||
| ctx.Data["Title"] = ctx.Tr("link_account") | ctx.Data["Title"] = ctx.Tr("link_account") | ||||
| ctx.Data["LinkAccountMode"] = true | ctx.Data["LinkAccountMode"] = true | ||||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | 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["DisableRegistration"] = setting.Service.DisableRegistration | ||||
| ctx.Data["ShowRegistrationButton"] = false | ctx.Data["ShowRegistrationButton"] = false | ||||
| @@ -666,6 +669,8 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { | |||||
| ctx.Data["LinkAccountMode"] = true | ctx.Data["LinkAccountMode"] = true | ||||
| ctx.Data["LinkAccountModeSignIn"] = true | ctx.Data["LinkAccountModeSignIn"] = true | ||||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | 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["DisableRegistration"] = setting.Service.DisableRegistration | ||||
| ctx.Data["ShowRegistrationButton"] = false | ctx.Data["ShowRegistrationButton"] = false | ||||
| @@ -732,6 +737,8 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au | |||||
| ctx.Data["LinkAccountMode"] = true | ctx.Data["LinkAccountMode"] = true | ||||
| ctx.Data["LinkAccountModeRegister"] = true | ctx.Data["LinkAccountModeRegister"] = true | ||||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | 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["DisableRegistration"] = setting.Service.DisableRegistration | ||||
| ctx.Data["ShowRegistrationButton"] = false | ctx.Data["ShowRegistrationButton"] = false | ||||
| @@ -755,12 +762,21 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au | |||||
| return | 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.Data["Err_Captcha"] = true | ||||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form) | ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form) | ||||
| return | 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 { | if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype { | ||||
| ctx.Data["Err_Password"] = true | ctx.Data["Err_Password"] = true | ||||
| ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form) | 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["EnableCaptcha"] = setting.Service.EnableCaptcha | ||||
| ctx.Data["CaptchaType"] = setting.Service.CaptchaType | |||||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||||
| ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration | ||||
| ctx.HTML(200, tplSignUp) | 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["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 | //Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true | ||||
| if !setting.Service.ShowRegistrationButton { | if !setting.Service.ShowRegistrationButton { | ||||
| ctx.Error(403) | ctx.Error(403) | ||||
| @@ -882,12 +904,21 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo | |||||
| return | 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.Data["Err_Captcha"] = true | ||||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form) | ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form) | ||||
| return | 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 { | if form.Password != form.Retype { | ||||
| ctx.Data["Err_Password"] = true | ctx.Data["Err_Password"] = true | ||||
| ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form) | 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/context" | ||||
| "code.gitea.io/gitea/modules/generate" | "code.gitea.io/gitea/modules/generate" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/recaptcha" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "github.com/go-macaron/captcha" | "github.com/go-macaron/captcha" | ||||
| @@ -308,6 +309,8 @@ func RegisterOpenID(ctx *context.Context) { | |||||
| ctx.Data["PageIsOpenIDRegister"] = true | ctx.Data["PageIsOpenIDRegister"] = true | ||||
| ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp | ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp | ||||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | ||||
| ctx.Data["CaptchaType"] = setting.Service.CaptchaType | |||||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||||
| ctx.Data["OpenID"] = oid | ctx.Data["OpenID"] = oid | ||||
| userName, _ := ctx.Session.Get("openid_determined_username").(string) | userName, _ := ctx.Session.Get("openid_determined_username").(string) | ||||
| if userName != "" { | if userName != "" { | ||||
| @@ -333,14 +336,26 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si | |||||
| ctx.Data["PageIsOpenIDRegister"] = true | ctx.Data["PageIsOpenIDRegister"] = true | ||||
| ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp | ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp | ||||
| ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha | ||||
| ctx.Data["CaptchaType"] = setting.Service.CaptchaType | |||||
| ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey | |||||
| ctx.Data["OpenID"] = oid | 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.Data["Err_Captcha"] = true | ||||
| ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form) | ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form) | ||||
| return | 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 | len := setting.MinPasswordLength | ||||
| if len < 256 { | if len < 256 { | ||||
| len = 256 | len = 256 | ||||
| @@ -67,6 +67,11 @@ | |||||
| {{if .RequireU2F}} | {{if .RequireU2F}} | ||||
| <script src="{{AppSubUrl}}/vendor/plugins/u2f/index.js"></script> | <script src="{{AppSubUrl}}/vendor/plugins/u2f/index.js"></script> | ||||
| {{end}} | {{end}} | ||||
| {{if .EnableCaptcha}} | |||||
| {{if eq .CaptchaType "recaptcha"}} | |||||
| <script src="https://www.google.com/recaptcha/api.js" async></script> | |||||
| {{end}} | |||||
| {{end}} | |||||
| {{if .RequireTribute}} | {{if .RequireTribute}} | ||||
| <script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script> | <script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script> | ||||
| @@ -29,7 +29,7 @@ | |||||
| <label for="retype">{{.i18n.Tr "re_type"}}</label> | <label for="retype">{{.i18n.Tr "re_type"}}</label> | ||||
| <input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required> | <input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required> | ||||
| </div> | </div> | ||||
| {{if .EnableCaptcha}} | |||||
| {{if and .EnableCaptcha (eq .CaptchaType "image")}} | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label></label> | <label></label> | ||||
| {{.Captcha.CreateHtml}} | {{.Captcha.CreateHtml}} | ||||
| @@ -39,6 +39,11 @@ | |||||
| <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | ||||
| </div> | </div> | ||||
| {{end}} | {{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"> | <div class="inline field"> | ||||
| <label></label> | <label></label> | ||||
| @@ -20,7 +20,7 @@ | |||||
| <label for="email">{{.i18n.Tr "email"}}</label> | <label for="email">{{.i18n.Tr "email"}}</label> | ||||
| <input id="email" name="email" type="email" value="{{.email}}" required> | <input id="email" name="email" type="email" value="{{.email}}" required> | ||||
| </div> | </div> | ||||
| {{if .EnableCaptcha}} | |||||
| {{if and .EnableCaptcha (eq .CaptchaType "image")}} | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label></label> | <label></label> | ||||
| {{.Captcha.CreateHtml}} | {{.Captcha.CreateHtml}} | ||||
| @@ -30,6 +30,11 @@ | |||||
| <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | <input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off"> | ||||
| </div> | </div> | ||||
| {{end}} | {{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"> | <div class="inline field"> | ||||
| <label for="openid">OpenID URI</label> | <label for="openid">OpenID URI</label> | ||||
| <input id="openid" value="{{ .OpenID }}" readonly> | <input id="openid" value="{{ .OpenID }}" readonly> | ||||