Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): Botプロテクションの設定変更時は実際に検証を通過しないと保存できないようにする #15151

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
- Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正
- Fix: Botプロテクションの設定変更時は実際に検証を通過しないと保存できないように( #15137 )

### Server
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )
Expand Down
38 changes: 30 additions & 8 deletions packages/frontend/src/components/MkCaptcha.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ const scriptId = computed(() => `script-${props.provider}`);

const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);

watch(() => [props.instanceUrl, props.sitekey], async () => {
// 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない
if (available.value) {
callback(undefined);
await requestRender();
}
});

if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') {
available.value = true;
} else if (src.value !== null) {
Expand All @@ -106,20 +114,34 @@ if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha')
}

function reset() {
if (captcha.value.reset) captcha.value.reset();
if (captcha.value.reset) {
try {
captcha.value.reset();
} catch (error: unknown) {
// ignore
if (_DEV_) console.warn(error);
}
}
testcaptchaPassed.value = false;
testcaptchaInput.value = '';
}

async function requestRender() {
if (captcha.value.render && captchaEl.value instanceof Element) {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? 'dark' : 'light',
callback: callback,
'expired-callback': () => callback(undefined),
'error-callback': () => callback(undefined),
});
// 設定値の変更時などのタイミングで再レンダリングを行う際はリセットしておく必要がある
reset();

if (props.sitekey && props.sitekey.length > 0) {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? 'dark' : 'light',
callback: callback,
'expired-callback': () => callback(undefined),
'error-callback': () => callback(undefined),
});
} else {
captchaEl.value.innerHTML = '';
}
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
const { default: Widget } = await import('@mcaptcha/vanilla-glue');
new Widget({
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/src/components/MkFormFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.text">{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}</div>
<div style="margin-left: auto;" class="_buttons">
<MkButton danger rounded @click="form.discard"><i class="ti ti-x"></i> {{ i18n.ts.discard }}</MkButton>
<MkButton primary rounded @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
<MkButton primary rounded :disabled="!canSaving" @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</div>
</template>
Expand All @@ -26,6 +26,7 @@ const props = defineProps<{
discard: () => void;
save: () => void;
};
canSaving: boolean;
}>();
</script>

Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
http-equiv="Content-Security-Policy"
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
worker-src 'self';
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh;
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://*.recaptcha.net https://*.gstatic.com https://challenges.cloudflare.com https://esm.sh;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dev modeでreCAPTCHAが表示できなかったので

style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: 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;
Expand Down
65 changes: 55 additions & 10 deletions packages/frontend/src/pages/admin/bot-protection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
<template v-if="botProtectionForm.modified.value" #footer>
<MkFormFooter :form="botProtectionForm"/>
<template #footer>
<MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
</template>

<div class="_gaps_m">
Expand All @@ -36,11 +36,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
</MkInput>
<FormSlot>
<FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
<MkCaptcha v-model="hCaptchaResponse" provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey"/>
</FormSlot>
<MkInfo>
<div :class="$style.captchaInfoMsg">
<div>サイトキーに"10000000-ffff-ffff-ffff-000000000001"と入力することで動作をテスト出来ます。<br/>本番運用時には必ず正規のサイトキーを設定してください。</div>
<div>ref: <a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a></div>
</div>
</MkInfo>
</template>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

テスト用のサイトキーがデフォルトで入るようになっていたのをやめました。
実物のuiが表示されてしまい、正常に動作していると誤認しかねないと考えたため…


<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey">
<template #prefix><i class="ti ti-key"></i></template>
Expand All @@ -56,9 +63,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey" :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"/>
<MkCaptcha v-model="mCaptchaResponse" provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey" :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"/>
</FormSlot>
</template>

<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
<MkInput v-model="botProtectionForm.state.recaptchaSiteKey">
<template #prefix><i class="ti ti-key"></i></template>
Expand All @@ -70,9 +78,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="recaptcha" :sitekey="botProtectionForm.state.recaptchaSiteKey"/>
<MkCaptcha v-model="reCaptchaResponse" provider="recaptcha" :sitekey="botProtectionForm.state.recaptchaSiteKey"/>
</FormSlot>
<MkInfo>
<div :class="$style.captchaInfoMsg">
<div>サイトキーに"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"と入力することで動作をテスト出来ます。<br/>本番運用時には必ず正規のサイトキーを設定してください。</div>
<div>ref: <a href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do" target="_blank">reCAPTCHA FAQ</a></div>
</div>
</MkInfo>
</template>

<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
<MkInput v-model="botProtectionForm.state.turnstileSiteKey">
<template #prefix><i class="ti ti-key"></i></template>
Expand All @@ -82,24 +97,31 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
</MkInput>
<FormSlot>
<FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey || '1x00000000000000000000AA'"/>
<MkCaptcha v-model="turnstileResponse" provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey"/>
</FormSlot>
<MkInfo>
<div :class="$style.captchaInfoMsg">
<div>サイトキーに"1x00000000000000000000AA"と入力することで動作をテスト出来ます。<br/>本番運用時には必ず正規のサイトキーを設定してください。</div>
<div>ref: <a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a></div>
</div>
</MkInfo>
</template>

<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
<FormSlot>
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="testcaptcha"/>
<MkCaptcha v-model="testCaptchaResponse" provider="testcaptcha" :sitekey="null"/>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

型エラーが出てたので

</FormSlot>
</template>
</div>
</MkFolder>
</template>

<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
import { computed, defineAsyncComponent, ref } from 'vue';
import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue';
import FormSlot from '@/components/form/slot.vue';
Expand All @@ -116,6 +138,21 @@ const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'

const meta = await misskeyApi('admin/meta');

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 testCaptchaResponse = ref<string | null>(null);

const canSaving = computed((): boolean => {
return (botProtectionForm.state.provider === null) ||
(botProtectionForm.state.provider === 'hcaptcha' && !!hCaptchaResponse.value) ||
(botProtectionForm.state.provider === 'mcaptcha' && !!mCaptchaResponse.value) ||
(botProtectionForm.state.provider === 'recaptcha' && !!reCaptchaResponse.value) ||
(botProtectionForm.state.provider === 'turnstile' && !!turnstileResponse.value) ||
(botProtectionForm.state.provider === 'testcaptcha' && !!testCaptchaResponse.value);
});

const botProtectionForm = useForm({
provider: meta.enableHcaptcha
? 'hcaptcha'
Expand Down Expand Up @@ -157,3 +194,11 @@ const botProtectionForm = useForm({
fetchInstance(true);
});
</script>

<style lang="scss" module>
.captchaInfoMsg {
display: flex;
flex-direction: column;
gap: 8px;
}
</style>
Loading