Skip to content

Commit 58de07e

Browse files
Gustedfsologureng
Gusted
andauthored
Add support mCaptcha as captcha provider (#20458)
https://mcaptcha.org/ Co-authored-by: Felipe Leopoldo Sologuren Gutiérrez <[email protected]>
1 parent 452272c commit 58de07e

File tree

20 files changed

+183
-6
lines changed

20 files changed

+183
-6
lines changed

custom/conf/app.example.ini

+10-3
Original file line numberDiff line numberDiff line change
@@ -698,9 +698,11 @@ ROUTER = console
698698
;; Enable captcha validation for registration
699699
;ENABLE_CAPTCHA = false
700700
;;
701-
;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha
701+
;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha.
702702
;CAPTCHA_TYPE = image
703703
;;
704+
;; Change this to use recaptcha.net or other recaptcha service
705+
;RECAPTCHA_URL = https://www.google.com/recaptcha/
704706
;; Enable recaptcha to use Google's recaptcha service
705707
;; Go to https://www.google.com/recaptcha/admin to sign up for a key
706708
;RECAPTCHA_SECRET =
@@ -710,8 +712,13 @@ ROUTER = console
710712
;HCAPTCHA_SECRET =
711713
;HCAPTCHA_SITEKEY =
712714
;;
713-
;; Change this to use recaptcha.net or other recaptcha service
714-
;RECAPTCHA_URL = https://www.google.com/recaptcha/
715+
;; Change this to use demo.mcaptcha.org or your self-hosted mcaptcha.org instance.
716+
;MCAPTCHA_URL = https://demo.mcaptcha.org
717+
;;
718+
;; Go to your configured mCaptcha instance and register a sitekey
719+
;; and use your account's secret.
720+
;MCAPTCHA_SECRET =
721+
;MCAPTCHA_SITEKEY =
715722
;;
716723
;; Default value for KeepEmailPrivate
717724
;; Each new user will get the value of this setting copied into their profile

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,16 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
579579
provided email rather than a generated email.
580580
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
581581
- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation
582-
even for External Accounts (i.e. GitHub, OpenID Connect, etc). You must `ENABLE_CAPTCHA` also.
583-
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha\]
582+
even for External Accounts (i.e. GitHub, OpenID Connect, etc). You also must enable `ENABLE_CAPTCHA`.
583+
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha\]
584584
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha.
585585
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
586586
- `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net.
587587
- `HCAPTCHA_SECRET`: **""**: Sign up at https://www.hcaptcha.com/ to get a secret for hcaptcha.
588588
- `HCAPTCHA_SITEKEY`: **""**: Sign up at https://www.hcaptcha.com/ to get a sitekey for hcaptcha.
589+
- `MCAPTCHA_SECRET`: **""**: Go to your mCaptcha instance to get a secret for mCaptcha.
590+
- `MCAPTCHA_SITEKEY`: **""**: Go to your mCaptcha instance to get a sitekey for mCaptcha.
591+
- `MCAPTCHA_URL` **https://demo.mcaptcha.org/**: Set the mCaptcha URL.
589592
- `DEFAULT_KEEP_EMAIL_PRIVATE`: **false**: By default set users to keep their email address private.
590593
- `DEFAULT_ALLOW_CREATE_ORGANIZATION`: **true**: Allow new users to create organizations by default.
591594
- `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.18
55
require (
66
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
77
code.gitea.io/sdk/gitea v0.15.1
8+
codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222
89
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
910
gitea.com/go-chi/cache v0.2.0
1011
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJE
6262
code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
6363
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
6464
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
65+
codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222 h1:PCW4i+gnQ9XxF8V+nBch3KWdGe4MiP3xXUCA/z0jhHk=
66+
codeberg.org/gusted/mcaptcha v0.0.0-20220722211632-55c1ffff1222/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
6567
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
6668
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
6769
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=

modules/mcaptcha/mcaptcha.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package mcaptcha
6+
7+
import (
8+
"context"
9+
"fmt"
10+
11+
"code.gitea.io/gitea/modules/setting"
12+
13+
"codeberg.org/gusted/mcaptcha"
14+
)
15+
16+
func Verify(ctx context.Context, token string) (bool, error) {
17+
valid, err := mcaptcha.Verify(ctx, &mcaptcha.VerifyOpts{
18+
InstanceURL: setting.Service.McaptchaURL,
19+
Sitekey: setting.Service.McaptchaSitekey,
20+
Secret: setting.Service.McaptchaSecret,
21+
Token: token,
22+
})
23+
if err != nil {
24+
return false, fmt.Errorf("wasn't able to verify mCaptcha: %v", err)
25+
}
26+
return valid, nil
27+
}

modules/setting/service.go

+6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ var Service = struct {
4747
RecaptchaURL string
4848
HcaptchaSecret string
4949
HcaptchaSitekey string
50+
McaptchaSecret string
51+
McaptchaSitekey string
52+
McaptchaURL string
5053
DefaultKeepEmailPrivate bool
5154
DefaultAllowCreateOrganization bool
5255
DefaultUserIsRestricted bool
@@ -133,6 +136,9 @@ func newService() {
133136
Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/")
134137
Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("")
135138
Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("")
139+
Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/")
140+
Service.McaptchaSecret = sec.Key("MCAPTCHA_SECRET").MustString("")
141+
Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")
136142
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
137143
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
138144
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)

modules/setting/setting.go

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const (
5959
ImageCaptcha = "image"
6060
ReCaptcha = "recaptcha"
6161
HCaptcha = "hcaptcha"
62+
MCaptcha = "mcaptcha"
6263
)
6364

6465
// settings

package-lock.json

+63
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
},
99
"dependencies": {
1010
"@claviska/jquery-minicolors": "2.3.6",
11+
"@mcaptcha/vanilla-glue": "0.1.0-alpha-2",
1112
"@primer/octicons": "17.4.0",
1213
"add-asset-webpack-plugin": "2.0.1",
1314
"css-loader": "6.7.1",

routers/web/auth/auth.go

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/modules/eventsource"
1919
"code.gitea.io/gitea/modules/hcaptcha"
2020
"code.gitea.io/gitea/modules/log"
21+
"code.gitea.io/gitea/modules/mcaptcha"
2122
"code.gitea.io/gitea/modules/password"
2223
"code.gitea.io/gitea/modules/recaptcha"
2324
"code.gitea.io/gitea/modules/session"
@@ -414,6 +415,8 @@ func SignUp(ctx *context.Context) {
414415
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
415416
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
416417
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
418+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
419+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
417420
ctx.Data["PageIsSignUp"] = true
418421

419422
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -435,6 +438,8 @@ func SignUpPost(ctx *context.Context) {
435438
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
436439
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
437440
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
441+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
442+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
438443
ctx.Data["PageIsSignUp"] = true
439444

440445
// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -458,6 +463,8 @@ func SignUpPost(ctx *context.Context) {
458463
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
459464
case setting.HCaptcha:
460465
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
466+
case setting.MCaptcha:
467+
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
461468
default:
462469
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
463470
return

routers/web/auth/linkaccount.go

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/context"
1717
"code.gitea.io/gitea/modules/hcaptcha"
1818
"code.gitea.io/gitea/modules/log"
19+
"code.gitea.io/gitea/modules/mcaptcha"
1920
"code.gitea.io/gitea/modules/recaptcha"
2021
"code.gitea.io/gitea/modules/session"
2122
"code.gitea.io/gitea/modules/setting"
@@ -40,6 +41,8 @@ func LinkAccount(ctx *context.Context) {
4041
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
4142
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
4243
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
44+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
45+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
4346
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
4447
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
4548
ctx.Data["ShowRegistrationButton"] = false
@@ -96,6 +99,8 @@ func LinkAccountPostSignIn(ctx *context.Context) {
9699
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
97100
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
98101
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
102+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
103+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
99104
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
100105
ctx.Data["ShowRegistrationButton"] = false
101106

@@ -195,6 +200,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
195200
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
196201
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
197202
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
203+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
204+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
198205
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
199206
ctx.Data["ShowRegistrationButton"] = false
200207

@@ -233,6 +240,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
233240
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
234241
case setting.HCaptcha:
235242
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
243+
case setting.MCaptcha:
244+
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
236245
default:
237246
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
238247
return

routers/web/auth/openid.go

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"code.gitea.io/gitea/modules/context"
1616
"code.gitea.io/gitea/modules/hcaptcha"
1717
"code.gitea.io/gitea/modules/log"
18+
"code.gitea.io/gitea/modules/mcaptcha"
1819
"code.gitea.io/gitea/modules/recaptcha"
1920
"code.gitea.io/gitea/modules/session"
2021
"code.gitea.io/gitea/modules/setting"
@@ -341,6 +342,8 @@ func RegisterOpenID(ctx *context.Context) {
341342
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
342343
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
343344
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
345+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
346+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
344347
ctx.Data["OpenID"] = oid
345348
userName, _ := ctx.Session.Get("openid_determined_username").(string)
346349
if userName != "" {
@@ -372,6 +375,8 @@ func RegisterOpenIDPost(ctx *context.Context) {
372375
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
373376
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
374377
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
378+
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
379+
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
375380
ctx.Data["OpenID"] = oid
376381

377382
if setting.Service.AllowOnlyInternalRegistration {
@@ -397,6 +402,12 @@ func RegisterOpenIDPost(ctx *context.Context) {
397402
return
398403
}
399404
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
405+
case setting.MCaptcha:
406+
if err := ctx.Req.ParseForm(); err != nil {
407+
ctx.ServerError("", err)
408+
return
409+
}
410+
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
400411
default:
401412
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
402413
return

services/forms/user_form.go

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ type RegisterForm struct {
9696
Retype string
9797
GRecaptchaResponse string `form:"g-recaptcha-response"`
9898
HcaptchaResponse string `form:"h-captcha-response"`
99+
McaptchaResponse string `form:"m-captcha-response"`
99100
}
100101

101102
// Validate validates the fields

services/forms/user_form_auth_openid.go

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type SignUpOpenIDForm struct {
3131
Email string `binding:"Required;Email;MaxSize(254)"`
3232
GRecaptchaResponse string `form:"g-recaptcha-response"`
3333
HcaptchaResponse string `form:"h-captcha-response"`
34+
McaptchaResponse string `form:"m-captcha-response"`
3435
}
3536

3637
// Validate validates the fields

templates/user/auth/signup_inner.tmpl

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@
5454
<div class="h-captcha" data-sitekey="{{ .HcaptchaSitekey }}"></div>
5555
</div>
5656
{{end}}
57+
{{if and .EnableCaptcha (eq .CaptchaType "mcaptcha")}}
58+
<div class="inline field df ac db-small">
59+
<span>{{.locale.Tr "captcha"}}</span>
60+
<div class="border-secondary w-100-small" id="mcaptcha__widget-container" style="width: 50%; height: 5em"></div>
61+
<div class="m-captcha" data-sitekey="{{ .McaptchaSitekey }}" data-instance-url="{{ .McaptchaURL }}"></div>
62+
</div>
63+
{{end}}
64+
5765

5866
<div class="inline field">
5967
<label></label>

templates/user/auth/signup_openid_register.tmpl

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
<div class="h-captcha" data-sitekey="{{ .HcaptchaSitekey }}"></div>
4141
</div>
4242
{{end}}
43+
{{if and .EnableCaptcha (eq .CaptchaType "mcaptcha")}}
44+
<div class="inline field required">
45+
<div class="m-captcha" data-sitekey="{{ .McaptchaSitekey }}" data-instance-url="{{ .McaptchaURL }}"></div>
46+
</div>
47+
{{end}}
4348
<div class="inline field">
4449
<label for="openid">OpenID URI</label>
4550
<input id="openid" value="{{ .OpenID }}" readonly>

web_src/js/features/mcaptcha.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export async function initMcaptcha() {
2+
const mCaptchaEl = document.querySelector('.m-captcha');
3+
if (!mCaptchaEl) return;
4+
5+
const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
6+
mCaptcha.INPUT_NAME = 'm-captcha-response';
7+
const siteKey = mCaptchaEl.getAttribute('data-sitekey');
8+
const instanceURL = mCaptchaEl.getAttribute('data-instance-url');
9+
10+
mCaptcha.default({
11+
siteKey: {
12+
instanceUrl: new URL(instanceURL),
13+
key: siteKey,
14+
}
15+
});
16+
}

0 commit comments

Comments
 (0)