]> source.dussan.org Git - gitea.git/commitdiff
Add support mCaptcha as captcha provider (#20458)
authorGusted <williamzijl7@hotmail.com>
Wed, 10 Aug 2022 13:20:10 +0000 (13:20 +0000)
committerGitHub <noreply@github.com>
Wed, 10 Aug 2022 13:20:10 +0000 (15:20 +0200)
https://mcaptcha.org/

Co-authored-by: Felipe Leopoldo Sologuren GutiƩrrez <fsologureng@users.noreply.github.com>
20 files changed:
custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
go.mod
go.sum
modules/mcaptcha/mcaptcha.go [new file with mode: 0644]
modules/setting/service.go
modules/setting/setting.go
package-lock.json
package.json
routers/web/auth/auth.go
routers/web/auth/linkaccount.go
routers/web/auth/openid.go
services/forms/user_form.go
services/forms/user_form_auth_openid.go
templates/user/auth/signup_inner.tmpl
templates/user/auth/signup_openid_register.tmpl
web_src/js/features/mcaptcha.js [new file with mode: 0644]
web_src/js/index.js
web_src/less/_form.less
web_src/less/helpers.less

index 83b3048bc6b5c5964aee7ecc4a40ef50becdf0ff..ab228999559e83c93a0293bb8f9196d4f0ec5836 100644 (file)
@@ -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
index 6b050e12b81ffdb799beabb53f83f218a920d495..ad971e866f8177bb37f1fd1a49375ad5c445669c 100644 (file)
@@ -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 6d41af507d35142087a86c9fbaf19f8c3e3fd0aa..fa6fb911db1abb49336bc85083eb9be8966b4181 100644 (file)
--- 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 124e65a727ad9971fd00be3966baec7b2196798e..7f7ed7fe2d3e927559623ac483e85f1cbb4e75f9 100644 (file)
--- 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 (file)
index 0000000..b889cf4
--- /dev/null
@@ -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
+}
index bd97e10b0f0dcf9d57de5cece5e71c6c3ce573b5..af8a72cc6dba6b3d0bf98200b1b98f2a72bc85e0 100644 (file)
@@ -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)
index 465dc75cad16ef36a5420bb12fe21b8e91631666..0af743dd97c27c83b6ca21de170c3a6133fad0e6 100644 (file)
@@ -59,6 +59,7 @@ const (
        ImageCaptcha = "image"
        ReCaptcha    = "recaptcha"
        HCaptcha     = "hcaptcha"
+       MCaptcha     = "mcaptcha"
 )
 
 // settings
index 945193538591e6a0d0467b5665c64496308eee50..aabbd84fd9bc9c7dbafc58c93d05ffa01303abc9 100644 (file)
@@ -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",
         "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",
       "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",
index a3e13eb609bb1fe73c64318c5341e47cf25a09f4..42cba24f8515891788ce8ff208bd7881e3df6220 100644 (file)
@@ -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",
index 610e4d29045d6eaebf3b1f459c0a4ebcb9bd5c35..8a4c12d57b5734524b31517abe46aad11b66ed30 100644 (file)
@@ -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
index a2d76e9c5a34eda6e027b099726c6f80c46b8885..4f3f2062b68969c136e683a38889e6c39c89d3bd 100644 (file)
@@ -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
index 32ae91da47b8492393e7641e2c6f250b47d9a735..3b1065189d9dc87d9d90d1c545248166d970cea3 100644 (file)
@@ -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
index c8f2b02d8c80930d649e3594b5ff4a63a657374e..8ce1d85c57781c6acd8ecbb87e264de79b4faad0 100644 (file)
@@ -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
index fd3368d303a904df50e663372cf30806da3325b4..992517f34f0f0da1cc0d931b77367feabeb2e4c7 100644 (file)
@@ -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
index 356c3e1cdcfc252b3ec22f0558d3905c601c0c21..58380f57d83e7e7c125a5fcb57ebad29938ae9f6 100644 (file)
                                                <div class="h-captcha" data-sitekey="{{ .HcaptchaSitekey }}"></div>
                                        </div>
                                {{end}}
+                               {{if and .EnableCaptcha (eq .CaptchaType "mcaptcha")}}
+                                       <div class="inline field df ac db-small">
+                                               <span>{{.locale.Tr "captcha"}}</span>
+                                               <div class="border-secondary w-100-small" id="mcaptcha__widget-container" style="width: 50%; height: 5em"></div>
+                                               <div class="m-captcha" data-sitekey="{{ .McaptchaSitekey }}" data-instance-url="{{ .McaptchaURL }}"></div>
+                                       </div>
+                               {{end}}
+
 
                                <div class="inline field">
                                        <label></label>
index 5edd7966ffa0c700aa962b6e2ad1e84ec10b342e..9fe0a9de1bcbcebc54475ec87cd4c2e9a05c746e 100644 (file)
                                                        <div class="h-captcha" data-sitekey="{{ .HcaptchaSitekey }}"></div>
                                                </div>
                                        {{end}}
+                                       {{if and .EnableCaptcha (eq .CaptchaType "mcaptcha")}}
+                                               <div class="inline field required">
+                                                       <div class="m-captcha" data-sitekey="{{ .McaptchaSitekey }}" data-instance-url="{{ .McaptchaURL }}"></div>
+                                               </div>
+                                       {{end}}
                                        <div class="inline field">
                                                <label for="openid">OpenID URI</label>
                                                <input id="openid" value="{{ .OpenID }}" readonly>
diff --git a/web_src/js/features/mcaptcha.js b/web_src/js/features/mcaptcha.js
new file mode 100644 (file)
index 0000000..725e2e2
--- /dev/null
@@ -0,0 +1,16 @@
+export async function initMcaptcha() {
+  const mCaptchaEl = document.querySelector('.m-captcha');
+  if (!mCaptchaEl) return;
+
+  const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
+  mCaptcha.INPUT_NAME = 'm-captcha-response';
+  const siteKey = mCaptchaEl.getAttribute('data-sitekey');
+  const instanceURL = mCaptchaEl.getAttribute('data-instance-url');
+
+  mCaptcha.default({
+    siteKey: {
+      instanceUrl: new URL(instanceURL),
+      key: siteKey,
+    }
+  });
+}
index b96e79c3c87f1217c88024b0964ac1971686410f..bd56fff7731956ae013f81de3d261591e62eb239 100644 (file)
@@ -86,6 +86,7 @@ import {initCommonOrganization} from './features/common-organization.js';
 import {initRepoWikiForm} from './features/repo-wiki.js';
 import {initRepoCommentForm, initRepository} from './features/repo-legacy.js';
 import {initFormattingReplacements} from './features/formatting.js';
+import {initMcaptcha} from './features/mcaptcha.js';
 
 // Run time-critical code as soon as possible. This is safe to do because this
 // script appears at the end of <body> and rendered HTML is accessible at that point.
@@ -182,6 +183,7 @@ $(document).ready(() => {
   initRepository();
 
   initCommitStatuses();
+  initMcaptcha();
 
   initUserAuthLinkAccountView();
   initUserAuthOauth2();
index eeab07c475028765e109f013e65b92d539e2cdec..c958763216669668c2311f4a2617976e57220e48 100644 (file)
@@ -156,7 +156,8 @@ textarea:focus,
         padding-left: @create-page-form-input-padding+30px;
       }
 
-      .inline.field > label {
+      .inline.field > label,
+      .inline.field > span {
         text-align: right;
         width: @create-page-form-input-padding;
         word-wrap: break-word;
index cacf7d9c8ef955711a2af61a6ef6c89e5ce8042b..5510ee5b236c338ca534bb1ceaab74a53e866147 100644 (file)
 .py-3 { padding-top: .5rem !important; padding-bottom: .5rem !important; }
 .py-4 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
 .py-5 { padding-top: 2rem !important; padding-bottom: 2rem !important; }
+
+@media @mediaSm {
+  .db-small { display: block !important; }
+  .w-100-small { width: 100% !important; }
+}