mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-27 08:10:24 +01:00
fix
This commit is contained in:
parent
7fcf8af096
commit
2b32b16355
6 changed files with 194 additions and 84 deletions
47
locales/index.d.ts
vendored
47
locales/index.d.ts
vendored
|
@ -1670,10 +1670,6 @@ export interface Locale extends ILocale {
|
||||||
* 複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。
|
* 複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。
|
||||||
*/
|
*/
|
||||||
"avoidMultiCaptchaConfirm": string;
|
"avoidMultiCaptchaConfirm": string;
|
||||||
/**
|
|
||||||
* サイトキーに"{testSiteKey}"と入力することでプレビューを確認できます。
|
|
||||||
*/
|
|
||||||
"testSiteKeyMessage": ParameterizedString<"testSiteKey">;
|
|
||||||
/**
|
/**
|
||||||
* アンテナ
|
* アンテナ
|
||||||
*/
|
*/
|
||||||
|
@ -10605,6 +10601,49 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"sent": string;
|
"sent": string;
|
||||||
};
|
};
|
||||||
|
"_captcha": {
|
||||||
|
/**
|
||||||
|
* CAPTCHAを通過してください
|
||||||
|
*/
|
||||||
|
"verify": string;
|
||||||
|
/**
|
||||||
|
* サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。
|
||||||
|
* 詳細は下記ページをご確認ください。
|
||||||
|
*/
|
||||||
|
"testSiteKeyMessage": string;
|
||||||
|
"_error": {
|
||||||
|
"_requestFailed": {
|
||||||
|
/**
|
||||||
|
* CAPTCHAのリクエストに失敗しました
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* しばらく後に実行するか、設定をもう一度ご確認ください。
|
||||||
|
*/
|
||||||
|
"text": string;
|
||||||
|
};
|
||||||
|
"_verificationFailed": {
|
||||||
|
/**
|
||||||
|
* CAPTCHAの検証に失敗しました
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* 設定が正しいかどうかもう一度確認ください。
|
||||||
|
*/
|
||||||
|
"text": string;
|
||||||
|
};
|
||||||
|
"_unknown": {
|
||||||
|
/**
|
||||||
|
* CAPTCHAエラー
|
||||||
|
*/
|
||||||
|
"title": string;
|
||||||
|
/**
|
||||||
|
* 想定外のエラーが発生しました。
|
||||||
|
*/
|
||||||
|
"text": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
|
|
@ -413,7 +413,6 @@ enableTurnstile: "Turnstileを有効にする"
|
||||||
turnstileSiteKey: "サイトキー"
|
turnstileSiteKey: "サイトキー"
|
||||||
turnstileSecretKey: "シークレットキー"
|
turnstileSecretKey: "シークレットキー"
|
||||||
avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。"
|
avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。"
|
||||||
testSiteKeyMessage: "サイトキーに\"{testSiteKey}\"と入力することでプレビューを確認できます。"
|
|
||||||
antennas: "アンテナ"
|
antennas: "アンテナ"
|
||||||
manageAntennas: "アンテナの管理"
|
manageAntennas: "アンテナの管理"
|
||||||
name: "名前"
|
name: "名前"
|
||||||
|
@ -2827,3 +2826,17 @@ _selfXssPrevention:
|
||||||
_followRequest:
|
_followRequest:
|
||||||
recieved: "受け取った申請"
|
recieved: "受け取った申請"
|
||||||
sent: "送った申請"
|
sent: "送った申請"
|
||||||
|
|
||||||
|
_captcha:
|
||||||
|
verify: "CAPTCHAを通過してください"
|
||||||
|
testSiteKeyMessage: "サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。\n詳細は下記ページをご確認ください。"
|
||||||
|
_error:
|
||||||
|
_requestFailed:
|
||||||
|
title: "CAPTCHAのリクエストに失敗しました"
|
||||||
|
text: "しばらく後に実行するか、設定をもう一度ご確認ください。"
|
||||||
|
_verificationFailed:
|
||||||
|
title: "CAPTCHAの検証に失敗しました"
|
||||||
|
text: "設定が正しいかどうかもう一度確認ください。"
|
||||||
|
_unknown:
|
||||||
|
title: "CAPTCHAエラー"
|
||||||
|
text: "想定外のエラーが発生しました。"
|
||||||
|
|
|
@ -21,37 +21,37 @@ export const meta = {
|
||||||
invalidProvider: {
|
invalidProvider: {
|
||||||
message: 'Invalid provider.',
|
message: 'Invalid provider.',
|
||||||
code: 'INVALID_PROVIDER',
|
code: 'INVALID_PROVIDER',
|
||||||
id: '14BF7AE1-80CC-4363-ACB2-4FD61D086AF0',
|
id: '14bf7ae1-80cc-4363-acb2-4fd61d086af0',
|
||||||
httpStatusCode: 400,
|
httpStatusCode: 400,
|
||||||
},
|
},
|
||||||
invalidParameters: {
|
invalidParameters: {
|
||||||
message: 'Invalid parameters.',
|
message: 'Invalid parameters.',
|
||||||
code: 'INVALID_PARAMETERS',
|
code: 'INVALID_PARAMETERS',
|
||||||
id: '26654194-410E-44E2-B42E-460FF6F92476',
|
id: '26654194-410e-44e2-b42e-460ff6f92476',
|
||||||
httpStatusCode: 400,
|
httpStatusCode: 400,
|
||||||
},
|
},
|
||||||
noResponseProvided: {
|
noResponseProvided: {
|
||||||
message: 'No response provided.',
|
message: 'No response provided.',
|
||||||
code: 'NO_RESPONSE_PROVIDED',
|
code: 'NO_RESPONSE_PROVIDED',
|
||||||
id: '40ACBBA8-0937-41FB-BB3F-474514D40AFE',
|
id: '40acbba8-0937-41fb-bb3f-474514d40afe',
|
||||||
httpStatusCode: 400,
|
httpStatusCode: 400,
|
||||||
},
|
},
|
||||||
requestFailed: {
|
requestFailed: {
|
||||||
message: 'Request failed.',
|
message: 'Request failed.',
|
||||||
code: 'REQUEST_FAILED',
|
code: 'REQUEST_FAILED',
|
||||||
id: '0F4FE2F1-2C15-4D6E-B714-EFBFCDE231CD',
|
id: '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd',
|
||||||
httpStatusCode: 500,
|
httpStatusCode: 500,
|
||||||
},
|
},
|
||||||
verificationFailed: {
|
verificationFailed: {
|
||||||
message: 'Verification failed.',
|
message: 'Verification failed.',
|
||||||
code: 'VERIFICATION_FAILED',
|
code: 'VERIFICATION_FAILED',
|
||||||
id: 'C41C067F-24F3-4150-84B2-B5A3AE8C2214',
|
id: 'c41c067f-24f3-4150-84b2-b5a3ae8c2214',
|
||||||
httpStatusCode: 400,
|
httpStatusCode: 400,
|
||||||
},
|
},
|
||||||
unknown: {
|
unknown: {
|
||||||
message: 'unknown',
|
message: 'unknown',
|
||||||
code: 'UNKNOWN',
|
code: 'UNKNOWN',
|
||||||
id: 'F868D509-E257-42A9-99C1-42614B031A97',
|
id: 'f868d509-e257-42a9-99c1-42614b031a97',
|
||||||
httpStatusCode: 500,
|
httpStatusCode: 500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,6 +30,9 @@ import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmount
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
// APIs provided by Captcha services
|
// APIs provided by Captcha services
|
||||||
|
// see: https://docs.hcaptcha.com/configuration/#javascript-api
|
||||||
|
// see: https://developers.google.com/recaptcha/docs/display?hl=ja
|
||||||
|
// see: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#explicitly-render-the-turnstile-widget
|
||||||
export type Captcha = {
|
export type Captcha = {
|
||||||
render(container: string | Node, options: {
|
render(container: string | Node, options: {
|
||||||
readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown;
|
readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown;
|
||||||
|
@ -53,6 +56,7 @@ declare global {
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
provider: CaptchaProvider;
|
provider: CaptchaProvider;
|
||||||
sitekey: string | null; // null will show error on request
|
sitekey: string | null; // null will show error on request
|
||||||
|
secretKey?: string | null;
|
||||||
instanceUrl?: string | null;
|
instanceUrl?: string | null;
|
||||||
modelValue?: string | null;
|
modelValue?: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
@ -64,7 +68,7 @@ const emit = defineEmits<{
|
||||||
const available = ref(false);
|
const available = ref(false);
|
||||||
|
|
||||||
const captchaEl = shallowRef<HTMLDivElement | undefined>();
|
const captchaEl = shallowRef<HTMLDivElement | undefined>();
|
||||||
|
const captchaWidgetId = ref<string | undefined>(undefined);
|
||||||
const testcaptchaInput = ref('');
|
const testcaptchaInput = ref('');
|
||||||
const testcaptchaPassed = ref(false);
|
const testcaptchaPassed = ref(false);
|
||||||
|
|
||||||
|
@ -94,10 +98,11 @@ const scriptId = computed(() => `script-${props.provider}`);
|
||||||
|
|
||||||
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
|
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
|
||||||
|
|
||||||
watch(() => [props.instanceUrl, props.sitekey], async () => {
|
watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => {
|
||||||
// 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない
|
// 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない
|
||||||
if (available.value) {
|
if (available.value) {
|
||||||
callback(undefined);
|
callback(undefined);
|
||||||
|
clearWidget();
|
||||||
await requestRender();
|
await requestRender();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -114,9 +119,9 @@ if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha')
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
if (captcha.value.reset) {
|
if (captcha.value.reset && captchaWidgetId.value !== undefined) {
|
||||||
try {
|
try {
|
||||||
captcha.value.reset();
|
captcha.value.reset(captchaWidgetId.value);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
// ignore
|
// ignore
|
||||||
if (_DEV_) console.warn(error);
|
if (_DEV_) console.warn(error);
|
||||||
|
@ -126,28 +131,33 @@ function reset() {
|
||||||
testcaptchaInput.value = '';
|
testcaptchaInput.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remove() {
|
||||||
|
if (captcha.value.remove && captchaWidgetId.value) {
|
||||||
|
try {
|
||||||
|
if (_DEV_) console.log('remove', props.provider, captchaWidgetId.value);
|
||||||
|
captcha.value.remove(captchaWidgetId.value);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// ignore
|
||||||
|
if (_DEV_) console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function requestRender() {
|
async function requestRender() {
|
||||||
if (captcha.value.render && captchaEl.value instanceof Element) {
|
if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) {
|
||||||
// 設定値の変更時などのタイミングで再レンダリングを行う際はリセットしておく必要がある
|
// reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する.
|
||||||
reset();
|
// (同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので)
|
||||||
captchaEl.value.innerHTML = '';
|
const elem = document.createElement('div');
|
||||||
|
captchaEl.value.appendChild(elem);
|
||||||
|
|
||||||
if (props.sitekey && props.sitekey.length > 0) {
|
captchaWidgetId.value = captcha.value.render(elem, {
|
||||||
captcha.value.render(captchaEl.value, {
|
sitekey: props.sitekey,
|
||||||
sitekey: props.sitekey,
|
theme: defaultStore.state.darkMode ? 'dark' : 'light',
|
||||||
theme: defaultStore.state.darkMode ? 'dark' : 'light',
|
callback: callback,
|
||||||
callback: callback,
|
'expired-callback': () => callback(undefined),
|
||||||
'expired-callback': () => callback(undefined),
|
'error-callback': () => callback(undefined),
|
||||||
'error-callback': () => callback(undefined),
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
|
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
|
||||||
// 設定値の変更時などのタイミングで再レンダリングを行う際はコンテナ内をクリアしておかないと更新されるたびに増えていく
|
|
||||||
const container = document.getElementById('mcaptcha__widget-container');
|
|
||||||
if (container) {
|
|
||||||
container.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { default: Widget } = await import('@mcaptcha/vanilla-glue');
|
const { default: Widget } = await import('@mcaptcha/vanilla-glue');
|
||||||
new Widget({
|
new Widget({
|
||||||
siteKey: {
|
siteKey: {
|
||||||
|
@ -160,6 +170,23 @@ async function requestRender() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearWidget() {
|
||||||
|
if (props.provider === 'mcaptcha') {
|
||||||
|
const container = document.getElementById('mcaptcha__widget-container');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
remove();
|
||||||
|
|
||||||
|
if (captchaEl.value) {
|
||||||
|
// レンダリング先のコンテナの中身を掃除し、フォームが増殖するのを抑止
|
||||||
|
captchaEl.value.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function callback(response?: string) {
|
function callback(response?: string) {
|
||||||
emit('update:modelValue', typeof response === 'string' ? response : null);
|
emit('update:modelValue', typeof response === 'string' ? response : null);
|
||||||
}
|
}
|
||||||
|
@ -192,7 +219,7 @@ onUnmounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
reset();
|
clearWidget();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/scripts/form.js';
|
import type { Form, GetFormResultType } from '@/scripts/form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -28,15 +29,15 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||||
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||||
import { focusParent } from '@/scripts/focus.js';
|
import { focusParent } from '@/scripts/focus.js';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
|
||||||
|
|
||||||
export const openingWindowsCount = ref(0);
|
export const openingWindowsCount = ref(0);
|
||||||
|
|
||||||
|
export type ApiWithDialogCustomErrors = Record<string, { title?: string; text: string; }>;
|
||||||
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
|
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
data: P,
|
data: P,
|
||||||
token?: string | null | undefined,
|
token?: string | null | undefined,
|
||||||
customErrors?: Record<string, { title?: string; text: string; }>,
|
customErrors?: ApiWithDialogCustomErrors,
|
||||||
) => {
|
) => {
|
||||||
const promise = misskeyApi(endpoint, data, token);
|
const promise = misskeyApi(endpoint, data, token);
|
||||||
promiseDialog(promise, null, async (err) => {
|
promiseDialog(promise, null, async (err) => {
|
||||||
|
|
|
@ -28,21 +28,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkRadios>
|
</MkRadios>
|
||||||
|
|
||||||
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
|
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
|
||||||
<MkInput v-model="botProtectionForm.state.hcaptchaSiteKey">
|
<MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
|
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="botProtectionForm.state.hcaptchaSecretKey">
|
<MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
|
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
|
<FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
|
||||||
<template #label>{{ i18n.ts.preview }}</template>
|
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||||
<MkCaptcha v-model="captchaResult" provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey"/>
|
<MkCaptcha
|
||||||
|
v-model="captchaResult"
|
||||||
|
provider="hcaptcha"
|
||||||
|
:sitekey="botProtectionForm.state.hcaptchaSiteKey"
|
||||||
|
:secretKey="botProtectionForm.state.hcaptchaSecretKey"
|
||||||
|
/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
<MkInfo>
|
<MkInfo>
|
||||||
<div :class="$style.captchaInfoMsg">
|
<div :class="$style.captchaInfoMsg">
|
||||||
<div>{{ i18n.tsx.testSiteKeyMessage({ testSiteKey: '10000000-ffff-ffff-ffff-000000000001' }) }}</div>
|
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||||
<div>
|
<div>
|
||||||
<span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
|
<span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,46 +56,51 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
|
<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
|
||||||
<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey">
|
<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
|
<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="botProtectionForm.state.mcaptchaSecretKey">
|
<MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
|
<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl">
|
<MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
|
||||||
<template #prefix><i class="ti ti-link"></i></template>
|
<template #prefix><i class="ti ti-link"></i></template>
|
||||||
<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
|
<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
|
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
|
||||||
<template #label>{{ i18n.ts.preview }}</template>
|
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||||
<MkCaptcha
|
<MkCaptcha
|
||||||
v-model="captchaResult" provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey"
|
v-model="captchaResult"
|
||||||
|
provider="mcaptcha"
|
||||||
|
:sitekey="botProtectionForm.state.mcaptchaSiteKey"
|
||||||
|
:secretKey="botProtectionForm.state.mcaptchaSecretKey"
|
||||||
:instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
|
:instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
|
||||||
/>
|
/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
|
<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
|
||||||
<MkInput v-model="botProtectionForm.state.recaptchaSiteKey">
|
<MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
|
<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="botProtectionForm.state.recaptchaSecretKey">
|
<MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
|
<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
|
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
|
||||||
<template #label>{{ i18n.ts.preview }}</template>
|
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||||
<MkCaptcha
|
<MkCaptcha
|
||||||
v-model="captchaResult" provider="recaptcha"
|
v-model="captchaResult"
|
||||||
|
provider="recaptcha"
|
||||||
:sitekey="botProtectionForm.state.recaptchaSiteKey"
|
:sitekey="botProtectionForm.state.recaptchaSiteKey"
|
||||||
|
:secretKey="botProtectionForm.state.recaptchaSecretKey"
|
||||||
/>
|
/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
<MkInfo>
|
<MkInfo>
|
||||||
<div :class="$style.captchaInfoMsg">
|
<div :class="$style.captchaInfoMsg">
|
||||||
<div>{{ i18n.tsx.testSiteKeyMessage({ testSiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' }) }}</div>
|
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||||
<div>
|
<div>
|
||||||
<span>ref: </span>
|
<span>ref: </span>
|
||||||
<a
|
<a
|
||||||
|
@ -103,27 +113,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
|
<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
|
||||||
<MkInput v-model="botProtectionForm.state.turnstileSiteKey">
|
<MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
|
<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="botProtectionForm.state.turnstileSecretKey">
|
<MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
|
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
|
<FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
|
||||||
<template #label>{{ i18n.ts.preview }}</template>
|
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||||
<MkCaptcha
|
<MkCaptcha
|
||||||
v-model="captchaResult" provider="turnstile"
|
v-model="captchaResult"
|
||||||
|
provider="turnstile"
|
||||||
:sitekey="botProtectionForm.state.turnstileSiteKey"
|
:sitekey="botProtectionForm.state.turnstileSiteKey"
|
||||||
|
:secretKey="botProtectionForm.state.turnstileSecretKey"
|
||||||
/>
|
/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
<MkInfo>
|
<MkInfo>
|
||||||
<div :class="$style.captchaInfoMsg">
|
<div :class="$style.captchaInfoMsg">
|
||||||
<div :class="$style.noSpace">
|
<div>
|
||||||
{{ i18n.tsx.testSiteKeyMessage({ testSiteKey: '1x00000000000000000000AA' }) }}
|
{{ i18n.ts._captcha.testSiteKeyMessage }}
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.noSpace">
|
<div>
|
||||||
<span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
|
<span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,7 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
|
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
|
||||||
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
|
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
|
||||||
<FormSlot>
|
<FormSlot>
|
||||||
<template #label>{{ i18n.ts.preview }}</template>
|
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||||
<MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
|
<MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</template>
|
</template>
|
||||||
|
@ -155,9 +167,28 @@ import { useForm } from '@/scripts/use-form.js';
|
||||||
import MkFormFooter from '@/components/MkFormFooter.vue';
|
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import { ApiWithDialogCustomErrors } from '@/os.js';
|
||||||
|
|
||||||
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
|
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
|
||||||
|
|
||||||
|
const errorHandler: ApiWithDialogCustomErrors = {
|
||||||
|
// 検証リクエストそのものに失敗
|
||||||
|
'0f4fe2f1-2c15-4d6e-b714-efbfcde231cd': {
|
||||||
|
title: i18n.ts._captcha._error._requestFailed.title,
|
||||||
|
text: i18n.ts._captcha._error._requestFailed.text,
|
||||||
|
},
|
||||||
|
// 検証リクエストの結果が不正
|
||||||
|
'c41c067f-24f3-4150-84b2-b5a3ae8c2214': {
|
||||||
|
title: i18n.ts._captcha._error._verificationFailed.title,
|
||||||
|
text: i18n.ts._captcha._error._verificationFailed.text,
|
||||||
|
},
|
||||||
|
// 不明なエラー
|
||||||
|
'f868d509-e257-42a9-99c1-42614b031a97': {
|
||||||
|
title: i18n.ts._captcha._error._unknown.title,
|
||||||
|
text: i18n.ts._captcha._error._unknown.text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const captchaResult = ref<string | null>(null);
|
const captchaResult = ref<string | null>(null);
|
||||||
|
|
||||||
const meta = await misskeyApi('admin/captcha/current');
|
const meta = await misskeyApi('admin/captcha/current');
|
||||||
|
@ -174,32 +205,33 @@ const botProtectionForm = useForm({
|
||||||
turnstileSecretKey: meta.turnstile.secretKey,
|
turnstileSecretKey: meta.turnstile.secretKey,
|
||||||
}, async (state) => {
|
}, async (state) => {
|
||||||
const provider = state.provider;
|
const provider = state.provider;
|
||||||
|
|
||||||
const sitekey = provider === 'hcaptcha'
|
|
||||||
? state.hcaptchaSiteKey
|
|
||||||
: provider === 'mcaptcha'
|
|
||||||
? state.mcaptchaSiteKey
|
|
||||||
: provider === 'recaptcha'
|
|
||||||
? state.recaptchaSiteKey
|
|
||||||
: provider === 'turnstile'
|
|
||||||
? state.turnstileSiteKey
|
|
||||||
: null;
|
|
||||||
const secret = provider === 'hcaptcha'
|
|
||||||
? state.hcaptchaSecretKey
|
|
||||||
: provider === 'mcaptcha'
|
|
||||||
? state.mcaptchaSecretKey
|
|
||||||
: provider === 'recaptcha'
|
|
||||||
? state.recaptchaSecretKey
|
|
||||||
: provider === 'turnstile'
|
|
||||||
? state.turnstileSecretKey
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (provider === 'none') {
|
if (provider === 'none') {
|
||||||
await os.apiWithDialog(
|
await os.apiWithDialog(
|
||||||
'admin/captcha/save',
|
'admin/captcha/save',
|
||||||
{ provider: provider as Misskey.entities.AdminCaptchaSaveRequest['provider'] },
|
{ provider: provider as Misskey.entities.AdminCaptchaSaveRequest['provider'] },
|
||||||
|
undefined,
|
||||||
|
errorHandler,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const sitekey = provider === 'hcaptcha'
|
||||||
|
? state.hcaptchaSiteKey
|
||||||
|
: provider === 'mcaptcha'
|
||||||
|
? state.mcaptchaSiteKey
|
||||||
|
: provider === 'recaptcha'
|
||||||
|
? state.recaptchaSiteKey
|
||||||
|
: provider === 'turnstile'
|
||||||
|
? state.turnstileSiteKey
|
||||||
|
: null;
|
||||||
|
const secret = provider === 'hcaptcha'
|
||||||
|
? state.hcaptchaSecretKey
|
||||||
|
: provider === 'mcaptcha'
|
||||||
|
? state.mcaptchaSecretKey
|
||||||
|
: provider === 'recaptcha'
|
||||||
|
? state.recaptchaSecretKey
|
||||||
|
: provider === 'turnstile'
|
||||||
|
? state.turnstileSecretKey
|
||||||
|
: null;
|
||||||
|
|
||||||
await os.apiWithDialog(
|
await os.apiWithDialog(
|
||||||
'admin/captcha/save',
|
'admin/captcha/save',
|
||||||
{
|
{
|
||||||
|
@ -209,6 +241,8 @@ const botProtectionForm = useForm({
|
||||||
instanceUrl: state.mcaptchaInstanceUrl,
|
instanceUrl: state.mcaptchaInstanceUrl,
|
||||||
captchaResult: captchaResult.value,
|
captchaResult: captchaResult.value,
|
||||||
},
|
},
|
||||||
|
undefined,
|
||||||
|
errorHandler,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,8 +270,4 @@ const canSaving = computed((): boolean => {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noSpace {
|
|
||||||
white-space-collapse: discard;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue