diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 83b3048bc6..ab22899955 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -698,9 +698,11 @@ ROUTER = console ;; Enable captcha validation for registration ;ENABLE_CAPTCHA = false ;; -;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha +;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha. ;CAPTCHA_TYPE = image ;; +;; Change this to use recaptcha.net or other recaptcha service +;RECAPTCHA_URL = https://www.google.com/recaptcha/ ;; Enable recaptcha to use Google's recaptcha service ;; Go to https://www.google.com/recaptcha/admin to sign up for a key ;RECAPTCHA_SECRET = @@ -710,8 +712,13 @@ ROUTER = console ;HCAPTCHA_SECRET = ;HCAPTCHA_SITEKEY = ;; -;; Change this to use recaptcha.net or other recaptcha service -;RECAPTCHA_URL = https://www.google.com/recaptcha/ +;; Change this to use demo.mcaptcha.org or your self-hosted mcaptcha.org instance. +;MCAPTCHA_URL = https://demo.mcaptcha.org +;; +;; Go to your configured mCaptcha instance and register a sitekey +;; and use your account's secret. +;MCAPTCHA_SECRET = +;MCAPTCHA_SITEKEY = ;; ;; Default value for KeepEmailPrivate ;; Each new user will get the value of this setting copied into their profile 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 6b050e12b8..ad971e866f 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -579,13 +579,16 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o provided email rather than a generated email. - `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, hcaptcha\] + even for External Accounts (i.e. GitHub, OpenID Connect, etc). You also must enable `ENABLE_CAPTCHA`. +- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha\] - `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_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net. - `HCAPTCHA_SECRET`: **""**: Sign up at https://www.hcaptcha.com/ to get a secret for hcaptcha. - `HCAPTCHA_SITEKEY`: **""**: Sign up at https://www.hcaptcha.com/ to get a sitekey for hcaptcha. +- `MCAPTCHA_SECRET`: **""**: Go to your mCaptcha instance to get a secret for mCaptcha. +- `MCAPTCHA_SITEKEY`: **""**: Go to your mCaptcha instance to get a sitekey for mCaptcha. +- `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: Set the mCaptcha URL. - `DEFAULT_KEEP_EMAIL_PRIVATE`: **false**: By default set users to keep their email address private. - `DEFAULT_ALLOW_CREATE_ORGANIZATION`: **true**: Allow new users to create organizations by default. - `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default diff --git a/go.mod b/go.mod index 6d41af507d..fa6fb911db 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b code.gitea.io/sdk/gitea v0.15.1 + codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222 gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 diff --git a/go.sum b/go.sum index 124e65a727..7f7ed7fe2d 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJE code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222 h1:PCW4i+gnQ9XxF8V+nBch3KWdGe4MiP3xXUCA/z0jhHk= +codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= diff --git a/modules/mcaptcha/mcaptcha.go b/modules/mcaptcha/mcaptcha.go new file mode 100644 index 0000000000..b889cf423b --- /dev/null +++ b/modules/mcaptcha/mcaptcha.go @@ -0,0 +1,27 @@ +// Copyright 2022 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 mcaptcha + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/modules/setting" + + "codeberg.org/gusted/mcaptcha" +) + +func Verify(ctx context.Context, token string) (bool, error) { + valid, err := mcaptcha.Verify(ctx, &mcaptcha.VerifyOpts{ + InstanceURL: setting.Service.McaptchaURL, + Sitekey: setting.Service.McaptchaSitekey, + Secret: setting.Service.McaptchaSecret, + Token: token, + }) + if err != nil { + return false, fmt.Errorf("wasn't able to verify mCaptcha: %v", err) + } + return valid, nil +} diff --git a/modules/setting/service.go b/modules/setting/service.go index bd97e10b0f..af8a72cc6d 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -47,6 +47,9 @@ var Service = struct { RecaptchaURL string HcaptchaSecret string HcaptchaSitekey string + McaptchaSecret string + McaptchaSitekey string + McaptchaURL string DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool DefaultUserIsRestricted bool @@ -133,6 +136,9 @@ func newService() { Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/") Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("") Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("") + Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/") + Service.McaptchaSecret = sec.Key("MCAPTCHA_SECRET").MustString("") + Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("") Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 465dc75cad..0af743dd97 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -59,6 +59,7 @@ const ( ImageCaptcha = "image" ReCaptcha = "recaptcha" HCaptcha = "hcaptcha" + MCaptcha = "mcaptcha" ) // settings diff --git a/package-lock.json b/package-lock.json index 9451935385..aabbd84fd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "license": "MIT", "dependencies": { "@claviska/jquery-minicolors": "2.3.6", + "@mcaptcha/vanilla-glue": "0.1.0-alpha-2", "@primer/octicons": "17.4.0", "add-asset-webpack-plugin": "2.0.1", "css-loader": "6.7.1", @@ -1662,6 +1663,55 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@mcaptcha/core-glue": { + "version": "0.1.0-alpha-3", + "resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-3.tgz", + "integrity": "sha512-avphBVgf3PPDWuUoDsB2qiXAss2pc00lUILswJaMQofr8FQyflzkhha8H2Z+qGFiX0Iib/yyP2TOtBDbHqE9Tg==", + "funding": [ + { + "type": "individual", + "url": "http://mcaptcha.org/donate" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mcaptcha" + }, + { + "type": "individual", + "url": "http://batsense.net/donate" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/realaravinth" + } + ] + }, + "node_modules/@mcaptcha/vanilla-glue": { + "version": "0.1.0-alpha-2", + "resolved": "https://registry.npmjs.org/@mcaptcha/vanilla-glue/-/vanilla-glue-0.1.0-alpha-2.tgz", + "integrity": "sha512-cQOg3EIhdjk1xoZtjD9SVPwQAnd49FCvHKchwFZZuhdNTeFs7SUHynOCekuGow2Ip0RJZuMZGcRxvWMgd0ogng==", + "funding": [ + { + "type": "individual", + "url": "http://mcaptcha.org/donate" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mcaptcha" + }, + { + "type": "individual", + "url": "http://batsense.net/donate" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/realaravinth" + } + ], + "dependencies": { + "@mcaptcha/core-glue": "^0.1.0-alpha-3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -14144,6 +14194,19 @@ "dev": true, "requires": {} }, + "@mcaptcha/core-glue": { + "version": "0.1.0-alpha-3", + "resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-3.tgz", + "integrity": "sha512-avphBVgf3PPDWuUoDsB2qiXAss2pc00lUILswJaMQofr8FQyflzkhha8H2Z+qGFiX0Iib/yyP2TOtBDbHqE9Tg==" + }, + "@mcaptcha/vanilla-glue": { + "version": "0.1.0-alpha-2", + "resolved": "https://registry.npmjs.org/@mcaptcha/vanilla-glue/-/vanilla-glue-0.1.0-alpha-2.tgz", + "integrity": "sha512-cQOg3EIhdjk1xoZtjD9SVPwQAnd49FCvHKchwFZZuhdNTeFs7SUHynOCekuGow2Ip0RJZuMZGcRxvWMgd0ogng==", + "requires": { + "@mcaptcha/core-glue": "^0.1.0-alpha-3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index a3e13eb609..42cba24f85 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "@claviska/jquery-minicolors": "2.3.6", + "@mcaptcha/vanilla-glue": "0.1.0-alpha-2", "@primer/octicons": "17.4.0", "add-asset-webpack-plugin": "2.0.1", "css-loader": "6.7.1", diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 610e4d2904..8a4c12d57b 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/mcaptcha" "code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/session" @@ -414,6 +415,8 @@ func SignUp(ctx *context.Context) { ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["PageIsSignUp"] = true // Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true @@ -435,6 +438,8 @@ func SignUpPost(ctx *context.Context) { ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["PageIsSignUp"] = true // Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true @@ -458,6 +463,8 @@ func SignUpPost(ctx *context.Context) { valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse) case setting.HCaptcha: valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse) + case setting.MCaptcha: + valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index a2d76e9c5a..4f3f2062b6 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/mcaptcha" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" @@ -40,6 +41,8 @@ func LinkAccount(ctx *context.Context) { ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration ctx.Data["ShowRegistrationButton"] = false @@ -96,6 +99,8 @@ func LinkAccountPostSignIn(ctx *context.Context) { ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -195,6 +200,8 @@ func LinkAccountPostRegister(ctx *context.Context) { ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration ctx.Data["ShowRegistrationButton"] = false @@ -233,6 +240,8 @@ func LinkAccountPostRegister(ctx *context.Context) { valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse) case setting.HCaptcha: valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse) + case setting.MCaptcha: + valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 32ae91da47..3b1065189d 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/hcaptcha" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/mcaptcha" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" @@ -341,6 +342,8 @@ func RegisterOpenID(ctx *context.Context) { ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["OpenID"] = oid userName, _ := ctx.Session.Get("openid_determined_username").(string) if userName != "" { @@ -372,6 +375,8 @@ func RegisterOpenIDPost(ctx *context.Context) { ctx.Data["CaptchaType"] = setting.Service.CaptchaType ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey + ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey + ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL ctx.Data["OpenID"] = oid if setting.Service.AllowOnlyInternalRegistration { @@ -397,6 +402,12 @@ func RegisterOpenIDPost(ctx *context.Context) { return } valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse) + case setting.MCaptcha: + if err := ctx.Req.ParseForm(); err != nil { + ctx.ServerError("", err) + return + } + valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse) default: ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType)) return diff --git a/services/forms/user_form.go b/services/forms/user_form.go index c8f2b02d8c..8ce1d85c57 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -96,6 +96,7 @@ type RegisterForm struct { Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` HcaptchaResponse string `form:"h-captcha-response"` + McaptchaResponse string `form:"m-captcha-response"` } // Validate validates the fields diff --git a/services/forms/user_form_auth_openid.go b/services/forms/user_form_auth_openid.go index fd3368d303..992517f34f 100644 --- a/services/forms/user_form_auth_openid.go +++ b/services/forms/user_form_auth_openid.go @@ -31,6 +31,7 @@ type SignUpOpenIDForm struct { Email string `binding:"Required;Email;MaxSize(254)"` GRecaptchaResponse string `form:"g-recaptcha-response"` HcaptchaResponse string `form:"h-captcha-response"` + McaptchaResponse string `form:"m-captcha-response"` } // Validate validates the fields diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index 356c3e1cdc..58380f57d8 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -54,6 +54,14 @@
{{end}} + {{if and .EnableCaptcha (eq .CaptchaType "mcaptcha")}} +