summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorChocolate Pie <106949016+chocolate-pie@users.noreply.github.com>2024-01-06 20:14:33 +0900
committerGitHub <noreply@github.com>2024-01-06 20:14:33 +0900
commit072f67d6e71af3d7fa6f5f4c73ae460d6844f511 (patch)
tree551589eed9eb21a0b177546f7859b9bead107990 /packages
parentrefactor(frontend): `scripts/form.ts`の型定義を修正してTS2344/TS2345... (diff)
downloadsharkey-072f67d6e71af3d7fa6f5f4c73ae460d6844f511.tar.gz
sharkey-072f67d6e71af3d7fa6f5f4c73ae460d6844f511.tar.bz2
sharkey-072f67d6e71af3d7fa6f5f4c73ae460d6844f511.zip
feat: Add support for mCaptcha (#12905)
* feat: Add support for mCaptcha * fix: Fix docker compose configuration * chore(frontend/docs): update changelog & fix eslint errors * `@mcaptcha/vanilla-glue`をダイナミックインポートするように * chore: Add missing prefix to CHANGELOG * refactor(backend): 適当につけた変数の名前を変更
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/migration/1704373210054-support-mcaptcha.js22
-rw-r--r--packages/backend/src/core/CaptchaService.ts31
-rw-r--r--packages/backend/src/models/Meta.ts25
-rw-r--r--packages/backend/src/server/api/SignupApiService.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts22
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts15
-rw-r--r--packages/frontend/package.json1
-rw-r--r--packages/frontend/src/components/MkCaptcha.vue41
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue4
-rw-r--r--packages/frontend/src/index.html6
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue34
-rw-r--r--packages/frontend/src/pages/admin/security.vue3
-rw-r--r--packages/misskey-js/src/autogen/apiClientJSDoc.ts2
-rw-r--r--packages/misskey-js/src/autogen/endpoint.ts2
-rw-r--r--packages/misskey-js/src/autogen/entities.ts2
-rw-r--r--packages/misskey-js/src/autogen/models.ts2
-rw-r--r--packages/misskey-js/src/autogen/types.ts13
18 files changed, 235 insertions, 17 deletions
diff --git a/packages/backend/migration/1704373210054-support-mcaptcha.js b/packages/backend/migration/1704373210054-support-mcaptcha.js
new file mode 100644
index 0000000000..ce42b90716
--- /dev/null
+++ b/packages/backend/migration/1704373210054-support-mcaptcha.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SupportMcaptcha1704373210054 {
+ name = 'SupportMcaptcha1704373210054'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
+ }
+}
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index f64196f4fc..6c5ee4835d 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -73,6 +73,37 @@ export class CaptchaService {
}
}
+ // https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
+ @bindThis
+ public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
+ if (response == null) {
+ throw new Error('mcaptcha-failed: no response provided');
+ }
+
+ const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
+ const result = await this.httpRequestService.send(endpointUrl.toString(), {
+ method: 'POST',
+ body: JSON.stringify({
+ key: siteKey,
+ secret: secret,
+ token: response,
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (result.status !== 200) {
+ throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
+ }
+
+ const resp = (await result.json()) as { valid: boolean };
+
+ if (!resp.valid) {
+ throw new Error('mcaptcha-request-failed');
+ }
+ }
+
@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index f5a75ed28a..3265e85dd7 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -194,6 +194,29 @@ export class MiMeta {
@Column('boolean', {
default: false,
})
+ public enableMcaptcha: boolean;
+
+ @Column('varchar', {
+ length: 1024,
+ nullable: true,
+ })
+ public mcaptchaSitekey: string | null;
+
+ @Column('varchar', {
+ length: 1024,
+ nullable: true,
+ })
+ public mcaptchaSecretKey: string | null;
+
+ @Column('varchar', {
+ length: 1024,
+ nullable: true,
+ })
+ public mcaptchaInstanceUrl: string | null;
+
+ @Column('boolean', {
+ default: false,
+ })
public enableRecaptcha: boolean;
@Column('varchar', {
@@ -467,7 +490,7 @@ export class MiMeta {
nullable: true,
})
public truemailInstance: string | null;
-
+
@Column('varchar', {
length: 1024,
nullable: true,
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 753984ef52..6b4d9d9f70 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -65,6 +65,7 @@ export class SignupApiService {
'hcaptcha-response'?: string;
'g-recaptcha-response'?: string;
'turnstile-response'?: string;
+ 'm-captcha-response'?: string;
}
}>,
reply: FastifyReply,
@@ -82,6 +83,12 @@ export class SignupApiService {
});
}
+ if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
+ await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
+ throw new FastifyReplyError(400, err);
+ });
+ }
+
if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
throw new FastifyReplyError(400, err);
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 281f6c484c..0627c5055c 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -41,6 +41,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ enableMcaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ mcaptchaSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ mcaptchaInstanceUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -163,6 +175,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ mcaptchaSecretKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
recaptchaSecretKey: {
type: 'string',
optional: false, nullable: true,
@@ -468,6 +484,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
+ enableMcaptcha: instance.enableMcaptcha,
+ mcaptchaSiteKey: instance.mcaptchaSitekey,
+ mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
@@ -498,6 +517,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
sensitiveWords: instance.sensitiveWords,
preservedUsernames: instance.preservedUsernames,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
+ mcaptchaSecretKey: instance.mcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
sensitiveMediaDetection: instance.sensitiveMediaDetection,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 3a6426435d..d76d3dfeea 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -63,6 +63,10 @@ export const paramDef = {
enableHcaptcha: { type: 'boolean' },
hcaptchaSiteKey: { type: 'string', nullable: true },
hcaptchaSecretKey: { type: 'string', nullable: true },
+ enableMcaptcha: { type: 'boolean' },
+ mcaptchaSiteKey: { type: 'string', nullable: true },
+ mcaptchaInstanceUrl: { type: 'string', nullable: true },
+ mcaptchaSecretKey: { type: 'string', nullable: true },
enableRecaptcha: { type: 'boolean' },
recaptchaSiteKey: { type: 'string', nullable: true },
recaptchaSecretKey: { type: 'string', nullable: true },
@@ -269,6 +273,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
}
+ if (ps.enableMcaptcha !== undefined) {
+ set.enableMcaptcha = ps.enableMcaptcha;
+ }
+
+ if (ps.mcaptchaSiteKey !== undefined) {
+ set.mcaptchaSitekey = ps.mcaptchaSiteKey;
+ }
+
+ if (ps.mcaptchaInstanceUrl !== undefined) {
+ set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl;
+ }
+
+ if (ps.mcaptchaSecretKey !== undefined) {
+ set.mcaptchaSecretKey = ps.mcaptchaSecretKey;
+ }
+
if (ps.enableRecaptcha !== undefined) {
set.enableRecaptcha = ps.enableRecaptcha;
}
@@ -472,7 +492,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.verifymailAuthKey = ps.verifymailAuthKey;
}
}
-
+
if (ps.enableTruemailApi !== undefined) {
set.enableTruemailApi = ps.enableTruemailApi;
}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index f7c2962bc2..529e82678d 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -108,6 +108,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ enableMcaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ mcaptchaSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ mcaptchaInstanceUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
enableRecaptcha: {
type: 'boolean',
optional: false, nullable: false,
@@ -351,6 +363,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
+ enableMcaptcha: instance.enableMcaptcha,
+ mcaptchaSiteKey: instance.mcaptchaSitekey,
+ mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 864779fd9d..7e7559d825 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -19,6 +19,7 @@
"dependencies": {
"@discordapp/twemoji": "15.0.2",
"@github/webauthn-json": "2.1.1",
+ "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5",
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 40bca11e64..f60c721eae 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -6,12 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span>
- <div ref="captchaEl"></div>
+ <div v-if="props.provider == 'mcaptcha'">
+ <div id="mcaptcha__widget-container" class="m-captcha-style"></div>
+ <div ref="captchaEl"></div>
+ </div>
+ <div v-else ref="captchaEl"></div>
</div>
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue';
+import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
@@ -26,7 +30,7 @@ export type Captcha = {
getResponse(id: string): string;
};
-export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
+export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
type CaptchaContainer = {
readonly [_ in CaptchaProvider]?: Captcha;
@@ -39,6 +43,7 @@ declare global {
const props = defineProps<{
provider: CaptchaProvider;
sitekey: string | null; // null will show error on request
+ instanceUrl?: string | null;
modelValue?: string | null;
}>();
@@ -55,6 +60,7 @@ const variable = computed(() => {
case 'hcaptcha': return 'hcaptcha';
case 'recaptcha': return 'grecaptcha';
case 'turnstile': return 'turnstile';
+ case 'mcaptcha': return 'mcaptcha';
}
});
@@ -65,6 +71,7 @@ const src = computed(() => {
case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
+ case 'mcaptcha': return null;
}
});
@@ -72,9 +79,9 @@ const scriptId = computed(() => `script-${props.provider}`);
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
-if (loaded) {
+if (loaded || props.provider === 'mcaptcha') {
available.value = true;
-} else {
+} else if (src.value !== null) {
(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
async: true,
id: scriptId.value,
@@ -87,7 +94,7 @@ function reset() {
if (captcha.value.reset) captcha.value.reset();
}
-function requestRender() {
+async function requestRender() {
if (captcha.value.render && captchaEl.value instanceof Element) {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
@@ -96,6 +103,15 @@ function requestRender() {
'expired-callback': callback,
'error-callback': callback,
});
+ } else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
+ const { default: Widget } = await import('@mcaptcha/vanilla-glue');
+ // @ts-expect-error avoid typecheck error
+ new Widget({
+ siteKey: {
+ instanceUrl: new URL(props.instanceUrl),
+ key: props.sitekey,
+ },
+ });
} else {
window.setTimeout(requestRender, 1);
}
@@ -105,14 +121,27 @@ function callback(response?: string) {
emit('update:modelValue', typeof response === 'string' ? response : null);
}
+function onReceivedMessage(message: MessageEvent) {
+ if (message.data.token) {
+ if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) {
+ callback(<string>message.data.token);
+ }
+ }
+}
+
onMounted(() => {
if (available.value) {
+ window.addEventListener('message', onReceivedMessage);
requestRender();
} else {
watch(available, requestRender);
}
});
+onUnmounted(() => {
+ window.removeEventListener('message', onReceivedMessage);
+});
+
onBeforeUnmount(() => {
reset();
});
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index c71330d62c..79e17c9aef 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -63,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkInput>
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
+ <MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
@@ -117,6 +118,7 @@ const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
const submitting = ref<boolean>(false);
const hCaptchaResponse = ref<string | null>(null);
+const mCaptchaResponse = ref<string | null>(null);
const reCaptchaResponse = ref<string | null>(null);
const turnstileResponse = ref<string | null>(null);
const usernameAbortController = ref<null | AbortController>(null);
@@ -125,6 +127,7 @@ const emailAbortController = ref<null | AbortController>(null);
const shouldDisableSubmitting = computed((): boolean => {
return submitting.value ||
instance.enableHcaptcha && !hCaptchaResponse.value ||
+ instance.enableMcaptcha && !mCaptchaResponse.value ||
instance.enableRecaptcha && !reCaptchaResponse.value ||
instance.enableTurnstile && !turnstileResponse.value ||
instance.emailRequiredForSignup && emailState.value !== 'ok' ||
@@ -252,6 +255,7 @@ async function onSubmit(): Promise<void> {
emailAddress: email.value,
invitationCode: invitationCode.value,
'hcaptcha-response': hCaptchaResponse.value,
+ 'm-captcha-response': mCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
'turnstile-response': turnstileResponse.value,
});
diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html
index 8de01e4802..13f800c72f 100644
--- a/packages/frontend/src/index.html
+++ b/packages/frontend/src/index.html
@@ -16,13 +16,13 @@
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
- content="default-src 'self';
+ content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
worker-src 'self';
- script-src 'self' 'unsafe-eval';
+ script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
- connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
+ connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;"
/>
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
<meta name="viewport" content="width=device-width, initial-scale=1">
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 99b8070b71..37f8227485 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkRadios v-model="provider">
<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
<option value="hcaptcha">hCaptcha</option>
+ <option value="mcaptcha">mCaptcha</option>
<option value="recaptcha">reCAPTCHA</option>
<option value="turnstile">Turnstile</option>
</MkRadios>
@@ -28,6 +29,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
</FormSlot>
</template>
+ <template v-else-if="provider === 'mcaptcha'">
+ <MkInput v-model="mcaptchaSiteKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
+ </MkInput>
+ <MkInput v-model="mcaptchaSecretKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
+ </MkInput>
+ <MkInput v-model="mcaptchaInstanceUrl">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
+ </MkInput>
+ <FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl">
+ <template #label>{{ i18n.ts.preview }}</template>
+ <MkCaptcha provider="mcaptcha" :sitekey="mcaptchaSiteKey" :instanceUrl="mcaptchaInstanceUrl"/>
+ </FormSlot>
+ </template>
<template v-else-if="provider === 'recaptcha'">
<MkInput v-model="recaptchaSiteKey">
<template #prefix><i class="ti ti-key"></i></template>
@@ -81,6 +100,9 @@ const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'
const provider = ref<CaptchaProvider | null>(null);
const hcaptchaSiteKey = ref<string | null>(null);
const hcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaSiteKey = ref<string | null>(null);
+const mcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaInstanceUrl = ref<string | null>(null);
const recaptchaSiteKey = ref<string | null>(null);
const recaptchaSecretKey = ref<string | null>(null);
const turnstileSiteKey = ref<string | null>(null);
@@ -90,12 +112,18 @@ async function init() {
const meta = await misskeyApi('admin/meta');
hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
+ mcaptchaSiteKey.value = meta.mcaptchaSiteKey;
+ mcaptchaSecretKey.value = meta.mcaptchaSecretKey;
+ mcaptchaInstanceUrl.value = meta.mcaptchaInstanceUrl;
recaptchaSiteKey.value = meta.recaptchaSiteKey;
recaptchaSecretKey.value = meta.recaptchaSecretKey;
turnstileSiteKey.value = meta.turnstileSiteKey;
turnstileSecretKey.value = meta.turnstileSecretKey;
- provider.value = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null;
+ provider.value = meta.enableHcaptcha ? 'hcaptcha' :
+ meta.enableRecaptcha ? 'recaptcha' :
+ meta.enableTurnstile ? 'turnstile' :
+ meta.enableMcaptcha ? 'mcaptcha' : null;
}
function save() {
@@ -103,6 +131,10 @@ function save() {
enableHcaptcha: provider.value === 'hcaptcha',
hcaptchaSiteKey: hcaptchaSiteKey.value,
hcaptchaSecretKey: hcaptchaSecretKey.value,
+ enableMcaptcha: provider.value === 'mcaptcha',
+ mcaptchaSiteKey: mcaptchaSiteKey.value,
+ mcaptchaSecretKey: mcaptchaSecretKey.value,
+ mcaptchaInstanceUrl: mcaptchaInstanceUrl.value,
enableRecaptcha: provider.value === 'recaptcha',
recaptchaSiteKey: recaptchaSiteKey.value,
recaptchaSecretKey: recaptchaSecretKey.value,
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index ec0c6166d0..a691d8ea1e 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #icon><i class="ti ti-shield"></i></template>
<template #label>{{ i18n.ts.botProtection }}</template>
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
+ <template v-else-if="enableMcaptcha" #suffix>mCaptcha</template>
<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
<template v-else-if="enableTurnstile" #suffix>Turnstile</template>
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
@@ -155,6 +156,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const summalyProxy = ref<string>('');
const enableHcaptcha = ref<boolean>(false);
+const enableMcaptcha = ref<boolean>(false);
const enableRecaptcha = ref<boolean>(false);
const enableTurnstile = ref<boolean>(false);
const sensitiveMediaDetection = ref<string>('none');
@@ -174,6 +176,7 @@ async function init() {
const meta = await misskeyApi('admin/meta');
summalyProxy.value = meta.summalyProxy;
enableHcaptcha.value = meta.enableHcaptcha;
+ enableMcaptcha.value = meta.enableMcaptcha;
enableRecaptcha.value = meta.enableRecaptcha;
enableTurnstile.value = meta.enableTurnstile;
sensitiveMediaDetection.value = meta.sensitiveMediaDetection;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index e8722cab3b..43d80734e9 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.449Z
+ * generatedAt: 2024-01-04T18:10:15.096Z
*/
import type { SwitchCaseResponseType } from '../api.js';
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 192a1a31e0..07ee46ace9 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.445Z
+ * generatedAt: 2024-01-04T18:10:15.094Z
*/
import type {
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index fd4d7372cc..546d90ce21 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.443Z
+ * generatedAt: 2024-01-04T18:10:15.093Z
*/
import { operations } from './types.js';
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index db0ada0f3b..59e4bc2f60 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.441Z
+ * generatedAt: 2024-01-04T18:10:15.091Z
*/
import { components } from './types.js';
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 37c8f58f58..b62bd90eea 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
/*
* version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:56.447Z
+ * generatedAt: 2024-01-04T18:10:15.023Z
*/
/**
@@ -4400,6 +4400,9 @@ export type operations = {
emailRequiredForSignup: boolean;
enableHcaptcha: boolean;
hcaptchaSiteKey: string | null;
+ enableMcaptcha: boolean;
+ mcaptchaSiteKey: string | null;
+ mcaptchaInstanceUrl: string | null;
enableRecaptcha: boolean;
recaptchaSiteKey: string | null;
enableTurnstile: boolean;
@@ -4425,6 +4428,7 @@ export type operations = {
bannedEmailDomains?: string[];
preservedUsernames: string[];
hcaptchaSecretKey: string | null;
+ mcaptchaSecretKey: string | null;
recaptchaSecretKey: string | null;
turnstileSecretKey: string | null;
sensitiveMediaDetection: string;
@@ -8197,6 +8201,10 @@ export type operations = {
enableHcaptcha?: boolean;
hcaptchaSiteKey?: string | null;
hcaptchaSecretKey?: string | null;
+ enableMcaptcha?: boolean;
+ mcaptchaSiteKey?: string | null;
+ mcaptchaInstanceUrl?: string | null;
+ mcaptchaSecretKey?: string | null;
enableRecaptcha?: boolean;
recaptchaSiteKey?: string | null;
recaptchaSecretKey?: string | null;
@@ -18704,6 +18712,9 @@ export type operations = {
emailRequiredForSignup: boolean;
enableHcaptcha: boolean;
hcaptchaSiteKey: string | null;
+ enableMcaptcha: boolean;
+ mcaptchaSiteKey: string | null;
+ mcaptchaInstanceUrl: string | null;
enableRecaptcha: boolean;
recaptchaSiteKey: string | null;
enableTurnstile: boolean;