mirror of https://github.com/go-gitea/gitea.git
Make captcha and password optional for external accounts (#6606)
This commit is contained in:
parent
337d6915ff
commit
62d6127f1b
|
@ -216,6 +216,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
Requires `Mailer` to be enabled.
|
Requires `Mailer` to be enabled.
|
||||||
- `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create
|
- `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create
|
||||||
accounts for users.
|
accounts for users.
|
||||||
|
- `REQUIRE_EXTERNAL_REGISTRATION_PASSWORD`: **false**: Enable this to force externally created
|
||||||
|
accounts (via GitHub, OpenID Connect, etc) to create a password. Warning: enabling this will
|
||||||
|
decrease security, so you should only enable it if you know what you're doing.
|
||||||
- `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
|
- `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
|
||||||
- `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when
|
- `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when
|
||||||
something happens, like creating issues. Requires `Mailer` to be enabled.
|
something happens, like creating issues. Requires `Mailer` to be enabled.
|
||||||
|
@ -225,6 +228,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a
|
- `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a
|
||||||
provided email rather than a generated email.
|
provided email rather than a generated email.
|
||||||
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
|
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
|
||||||
|
- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation
|
||||||
|
even for External Accounts (i.e. GitHub, OpenID Connect, etc). You must `ENABLE_CAPTCHA` also.
|
||||||
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
|
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
|
||||||
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for 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.
|
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
|
||||||
type RegisterForm struct {
|
type RegisterForm struct {
|
||||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
|
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||||
Password string `binding:"Required;MaxSize(255)"`
|
Password string `binding:"MaxSize(255)"`
|
||||||
Retype string
|
Retype string
|
||||||
GRecaptchaResponse string `form:"g-recaptcha-response"`
|
GRecaptchaResponse string `form:"g-recaptcha-response"`
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,7 @@ func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Err
|
||||||
// SignInForm form for signing in with user/password
|
// SignInForm form for signing in with user/password
|
||||||
type SignInForm struct {
|
type SignInForm struct {
|
||||||
UserName string `binding:"Required;MaxSize(254)"`
|
UserName string `binding:"Required;MaxSize(254)"`
|
||||||
|
// TODO remove required from password for SecondFactorAuthentication
|
||||||
Password string `binding:"Required;MaxSize(255)"`
|
Password string `binding:"Required;MaxSize(255)"`
|
||||||
Remember bool
|
Remember bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ var Service struct {
|
||||||
EnableReverseProxyAutoRegister bool
|
EnableReverseProxyAutoRegister bool
|
||||||
EnableReverseProxyEmail bool
|
EnableReverseProxyEmail bool
|
||||||
EnableCaptcha bool
|
EnableCaptcha bool
|
||||||
|
RequireExternalRegistrationCaptcha bool
|
||||||
|
RequireExternalRegistrationPassword bool
|
||||||
CaptchaType string
|
CaptchaType string
|
||||||
RecaptchaSecret string
|
RecaptchaSecret string
|
||||||
RecaptchaSitekey string
|
RecaptchaSitekey string
|
||||||
|
@ -61,6 +63,8 @@ func newService() {
|
||||||
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
|
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
|
||||||
Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
|
Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
|
||||||
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
|
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").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)
|
Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
|
||||||
Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
|
Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
|
||||||
Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
|
Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
|
||||||
|
|
|
@ -697,9 +697,10 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ
|
||||||
|
|
||||||
// LinkAccount shows the page where the user can decide to login or create a new account
|
// LinkAccount shows the page where the user can decide to login or create a new account
|
||||||
func LinkAccount(ctx *context.Context) {
|
func LinkAccount(ctx *context.Context) {
|
||||||
|
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
|
||||||
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 && setting.Service.RequireExternalRegistrationCaptcha
|
||||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||||
|
@ -746,10 +747,11 @@ func LinkAccount(ctx *context.Context) {
|
||||||
|
|
||||||
// LinkAccountPostSignIn handle the coupling of external account with another account using signIn
|
// LinkAccountPostSignIn handle the coupling of external account with another account using signIn
|
||||||
func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
||||||
|
ctx.Data["DisablePassword"] = setting.Service.AllowOnlyExternalRegistration
|
||||||
ctx.Data["Title"] = ctx.Tr("link_account")
|
ctx.Data["Title"] = ctx.Tr("link_account")
|
||||||
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 && setting.Service.RequireExternalRegistrationCaptcha
|
||||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||||
|
@ -824,10 +826,13 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
||||||
|
|
||||||
// LinkAccountPostRegister handle the creation of a new account for an external account using signUp
|
// LinkAccountPostRegister handle the creation of a new account for an external account using signUp
|
||||||
func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
|
func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
|
||||||
|
// TODO Make insecure passwords optional for local accounts also,
|
||||||
|
// once email-based Second-Factor Auth is available
|
||||||
|
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
|
||||||
ctx.Data["Title"] = ctx.Tr("link_account")
|
ctx.Data["Title"] = ctx.Tr("link_account")
|
||||||
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 && setting.Service.RequireExternalRegistrationCaptcha
|
||||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||||
|
@ -854,14 +859,18 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
|
if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
|
||||||
ctx.Data["Err_Captcha"] = true
|
var valid bool
|
||||||
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
|
switch setting.Service.CaptchaType {
|
||||||
return
|
case setting.ImageCaptcha:
|
||||||
}
|
valid = cpt.VerifyReq(ctx.Req)
|
||||||
|
case setting.ReCaptcha:
|
||||||
|
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
|
||||||
|
default:
|
||||||
|
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
|
|
||||||
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
|
|
||||||
if !valid {
|
if !valid {
|
||||||
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)
|
||||||
|
@ -869,15 +878,24 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype {
|
if setting.Service.AllowOnlyExternalRegistration || !setting.Service.RequireExternalRegistrationPassword {
|
||||||
ctx.Data["Err_Password"] = true
|
// In models.User an empty password is classed as not set, so we set form.Password to empty.
|
||||||
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form)
|
// Eventually the database should be changed to indicate "Second Factor"-enabled accounts
|
||||||
return
|
// (accounts that do not introduce the security vulnerabilities of a password).
|
||||||
}
|
// If a user decides to circumvent second-factor security, and purposefully create a password,
|
||||||
if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
|
// they can still do so using the "Recover Account" option.
|
||||||
ctx.Data["Err_Password"] = true
|
form.Password = ""
|
||||||
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
|
} else {
|
||||||
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
|
||||||
|
ctx.Data["Err_Password"] = true
|
||||||
|
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.(goth.User).Provider)
|
loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.(goth.User).Provider)
|
||||||
|
@ -1000,14 +1018,18 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
|
if setting.Service.EnableCaptcha {
|
||||||
ctx.Data["Err_Captcha"] = true
|
var valid bool
|
||||||
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
|
switch setting.Service.CaptchaType {
|
||||||
return
|
case setting.ImageCaptcha:
|
||||||
}
|
valid = cpt.VerifyReq(ctx.Req)
|
||||||
|
case setting.ReCaptcha:
|
||||||
|
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
|
||||||
|
default:
|
||||||
|
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
|
|
||||||
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
|
|
||||||
if !valid {
|
if !valid {
|
||||||
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)
|
||||||
|
|
|
@ -357,19 +357,23 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
|
||||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||||
ctx.Data["OpenID"] = oid
|
ctx.Data["OpenID"] = oid
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
|
if setting.Service.EnableCaptcha {
|
||||||
ctx.Data["Err_Captcha"] = true
|
var valid bool
|
||||||
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
|
switch setting.Service.CaptchaType {
|
||||||
return
|
case setting.ImageCaptcha:
|
||||||
}
|
valid = cpt.VerifyReq(ctx.Req)
|
||||||
|
case setting.ReCaptcha:
|
||||||
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
|
err := ctx.Req.ParseForm()
|
||||||
err := ctx.Req.ParseForm()
|
if err != nil {
|
||||||
if err != nil {
|
ctx.ServerError("", err)
|
||||||
ctx.ServerError("", err)
|
return
|
||||||
|
}
|
||||||
|
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
|
||||||
|
default:
|
||||||
|
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
|
|
||||||
if !valid {
|
if !valid {
|
||||||
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)
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
||||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
|
{{if not .DisablePassword}}
|
||||||
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
{{if not .LinkAccountMode}}
|
{{if not .LinkAccountMode}}
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<label></label>
|
<label></label>
|
||||||
|
|
|
@ -25,14 +25,17 @@
|
||||||
<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>
|
||||||
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
|
||||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
{{if not .DisablePassword}}
|
||||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||||
</div>
|
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||||
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||||
<label for="retype">{{.i18n.Tr "re_type"}}</label>
|
</div>
|
||||||
<input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required>
|
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||||
</div>
|
<label for="retype">{{.i18n.Tr "re_type"}}</label>
|
||||||
|
<input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{if and .EnableCaptcha (eq .CaptchaType "image")}}
|
{{if and .EnableCaptcha (eq .CaptchaType "image")}}
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<label></label>
|
<label></label>
|
||||||
|
|
Loading…
Reference in New Issue