Add Cloudflare Turnstile CAPTCHA support (#9111)
* Add Cloudflare Turnstile CAPTCHA support * Update packages/client/src/components/MkCaptcha.vue Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
This commit is contained in:
		
							parent
							
								
									166067f746
								
							
						
					
					
						commit
						1309367884
					
				
					 13 changed files with 130 additions and 3 deletions
				
			
		|  | @ -20,7 +20,7 @@ type Captcha = { | |||
| 	getResponse(id: string): string; | ||||
| }; | ||||
| 
 | ||||
| type CaptchaProvider = 'hcaptcha' | 'recaptcha'; | ||||
| type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile'; | ||||
| 
 | ||||
| type CaptchaContainer = { | ||||
| 	readonly [_ in CaptchaProvider]?: Captcha; | ||||
|  | @ -48,6 +48,7 @@ const variable = computed(() => { | |||
| 	switch (props.provider) { | ||||
| 		case 'hcaptcha': return 'hcaptcha'; | ||||
| 		case 'recaptcha': return 'grecaptcha'; | ||||
| 		case 'turnstile': return 'turnstile'; | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
|  | @ -57,6 +58,7 @@ const src = computed(() => { | |||
| 	switch (props.provider) { | ||||
| 		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'; | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -59,6 +59,7 @@ | |||
| 	</MkSwitch> | ||||
| 	<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/> | ||||
| 	<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> | ||||
| 	<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" class="_formBlock captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> | ||||
| 	<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton> | ||||
| </form> | ||||
| </template> | ||||
|  | @ -92,6 +93,7 @@ const host = toUnicode(config.host); | |||
| 
 | ||||
| let hcaptcha = $ref(); | ||||
| let recaptcha = $ref(); | ||||
| let turnstile = $ref(); | ||||
| 
 | ||||
| let username: string = $ref(''); | ||||
| let password: string = $ref(''); | ||||
|  | @ -106,12 +108,14 @@ let submitting: boolean = $ref(false); | |||
| let ToSAgreement: boolean = $ref(false); | ||||
| let hCaptchaResponse = $ref(null); | ||||
| let reCaptchaResponse = $ref(null); | ||||
| let turnstileResponse = $ref(null); | ||||
| 
 | ||||
| const shouldDisableSubmitting = $computed((): boolean => { | ||||
| 	return submitting || | ||||
| 		instance.tosUrl && !ToSAgreement || | ||||
| 		instance.enableHcaptcha && !hCaptchaResponse || | ||||
| 		instance.enableRecaptcha && !reCaptchaResponse || | ||||
| 		instance.enableTurnstile && !turnstileResponse || | ||||
| 		passwordRetypeState === 'not-match'; | ||||
| }); | ||||
| 
 | ||||
|  | @ -198,6 +202,7 @@ function onSubmit(): void { | |||
| 		invitationCode, | ||||
| 		'hcaptcha-response': hCaptchaResponse, | ||||
| 		'g-recaptcha-response': reCaptchaResponse, | ||||
| 		'turnstile-response': turnstileResponse, | ||||
| 	}).then(() => { | ||||
| 		if (instance.emailRequiredForSignup) { | ||||
| 			os.alert({ | ||||
|  | @ -222,6 +227,7 @@ function onSubmit(): void { | |||
| 		submitting = false; | ||||
| 		hcaptcha.reset?.(); | ||||
| 		recaptcha.reset?.(); | ||||
| 		turnstile.reset?.(); | ||||
| 
 | ||||
| 		os.alert({ | ||||
| 			type: 'error', | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 				<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option> | ||||
| 				<option value="hcaptcha">hCaptcha</option> | ||||
| 				<option value="recaptcha">reCAPTCHA</option> | ||||
| 				<option value="turnstile">Turnstile</option> | ||||
| 			</FormRadios> | ||||
| 
 | ||||
| 			<template v-if="provider === 'hcaptcha'"> | ||||
|  | @ -36,6 +37,20 @@ | |||
| 					<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> | ||||
| 				</FormSlot> | ||||
| 			</template> | ||||
| 			<template v-else-if="provider === 'turnstile'"> | ||||
| 				<FormInput v-model="turnstileSiteKey" class="_formBlock"> | ||||
| 					<template #prefix><i class="fas fa-key"></i></template> | ||||
| 					<template #label>{{ i18n.ts.turnstileSiteKey }}</template> | ||||
| 				</FormInput> | ||||
| 				<FormInput v-model="turnstileSecretKey" class="_formBlock"> | ||||
| 					<template #prefix><i class="fas fa-key"></i></template> | ||||
| 					<template #label>{{ i18n.ts.turnstileSecretKey }}</template> | ||||
| 				</FormInput> | ||||
| 				<FormSlot class="_formBlock"> | ||||
| 					<template #label>{{ i18n.ts.preview }}</template> | ||||
| 					<MkCaptcha provider="turnstile" :sitekey="turnstileSiteKey || '1x00000000000000000000AA'"/> | ||||
| 				</FormSlot> | ||||
| 			</template> | ||||
| 
 | ||||
| 			<FormButton primary @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> | ||||
| 		</div> | ||||
|  | @ -61,6 +76,8 @@ let hcaptchaSiteKey: string | null = $ref(null); | |||
| let hcaptchaSecretKey: string | null = $ref(null); | ||||
| let recaptchaSiteKey: string | null = $ref(null); | ||||
| let recaptchaSecretKey: string | null = $ref(null); | ||||
| let turnstileSiteKey: string | null = $ref(null); | ||||
| let turnstileSecretKey: string | null = $ref(null); | ||||
| 
 | ||||
| async function init() { | ||||
| 	const meta = await os.api('admin/meta'); | ||||
|  | @ -68,8 +85,10 @@ async function init() { | |||
| 	hcaptchaSecretKey = meta.hcaptchaSecretKey; | ||||
| 	recaptchaSiteKey = meta.recaptchaSiteKey; | ||||
| 	recaptchaSecretKey = meta.recaptchaSecretKey; | ||||
| 	turnstileSiteKey = meta.turnstileSiteKey; | ||||
| 	turnstileSecretKey = meta.turnstileSecretKey; | ||||
| 
 | ||||
| 	provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null; | ||||
| 	provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null; | ||||
| } | ||||
| 
 | ||||
| function save() { | ||||
|  | @ -80,6 +99,9 @@ function save() { | |||
| 		enableRecaptcha: provider === 'recaptcha', | ||||
| 		recaptchaSiteKey, | ||||
| 		recaptchaSecretKey, | ||||
| 		enableTurnstile: provider === 'turnstile', | ||||
| 		turnstileSiteKey, | ||||
| 		turnstileSecretKey, | ||||
| 	}).then(() => { | ||||
| 		fetchInstance(); | ||||
| 	}); | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ let view = $ref(null); | |||
| let el = $ref(null); | ||||
| let pageProps = $ref({}); | ||||
| let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); | ||||
| let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha; | ||||
| let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile; | ||||
| let noEmailServer = !instance.enableEmail; | ||||
| let thereIsUnresolvedAbuseReport = $ref(false); | ||||
| let currentPage = $computed(() => router.currentRef.value.child); | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| 					<template #label>{{ i18n.ts.botProtection }}</template> | ||||
| 					<template v-if="enableHcaptcha" #suffix>hCaptcha</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> | ||||
| 
 | ||||
| 					<XBotProtection/> | ||||
|  | @ -120,6 +121,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; | |||
| let summalyProxy: string = $ref(''); | ||||
| let enableHcaptcha: boolean = $ref(false); | ||||
| let enableRecaptcha: boolean = $ref(false); | ||||
| let enableTurnstile: boolean = $ref(false); | ||||
| let sensitiveMediaDetection: string = $ref('none'); | ||||
| let sensitiveMediaDetectionSensitivity: number = $ref(0); | ||||
| let setSensitiveFlagAutomatically: boolean = $ref(false); | ||||
|  | @ -132,6 +134,7 @@ async function init() { | |||
| 	summalyProxy = meta.summalyProxy; | ||||
| 	enableHcaptcha = meta.enableHcaptcha; | ||||
| 	enableRecaptcha = meta.enableRecaptcha; | ||||
| 	enableTurnstile = meta.enableTurnstile; | ||||
| 	sensitiveMediaDetection = meta.sensitiveMediaDetection; | ||||
| 	sensitiveMediaDetectionSensitivity = | ||||
| 		meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue