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
				
			
		
							
								
								
									
										15
									
								
								packages/backend/migration/1664694635394-turnstile.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/backend/migration/1664694635394-turnstile.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| export class turnstile1664694635394 { | ||||
|     name = 'turnstile1664694635394' | ||||
| 
 | ||||
|     async up(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "enableTurnstile" boolean NOT NULL DEFAULT false`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSiteKey" character varying(64)`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" ADD "turnstileSecretKey" character varying(64)`); | ||||
|     } | ||||
| 
 | ||||
|     async down(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSecretKey"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "turnstileSiteKey"`); | ||||
|         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTurnstile"`); | ||||
|     } | ||||
| } | ||||
|  | @ -66,5 +66,16 @@ export class CaptchaService { | |||
| 			throw `hcaptcha-failed: ${errorCodes}`; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public async verifyTurnstile(secret: string, response: string): Promise<void> { | ||||
| 		const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(e => { | ||||
| 			throw `turnstile-request-failed: ${e}`; | ||||
| 		}); | ||||
| 
 | ||||
| 		if (result.success !== true) { | ||||
| 			const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; | ||||
| 			throw `turnstile-failed: ${errorCodes}`; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -188,6 +188,23 @@ export class Meta { | |||
| 	}) | ||||
| 	public recaptchaSecretKey: string | null; | ||||
| 
 | ||||
| 	@Column('boolean', { | ||||
| 		default: false, | ||||
| 	}) | ||||
| 	public enableTurnstile: boolean; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 64, | ||||
| 		nullable: true, | ||||
| 	}) | ||||
| 	public turnstileSiteKey: string | null; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 64, | ||||
| 		nullable: true, | ||||
| 	}) | ||||
| 	public turnstileSecretKey: string | null; | ||||
| 
 | ||||
| 	@Column('enum', { | ||||
| 		enum: ['none', 'all', 'local', 'remote'], | ||||
| 		default: 'none', | ||||
|  |  | |||
|  | @ -61,6 +61,12 @@ export class SignupApiService { | |||
| 					ctx.throw(400, e); | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
| 			if (instance.enableTurnstile && instance.turnstileSecretKey) { | ||||
| 				await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(e => { | ||||
| 					ctx.throw(400, e); | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 	 | ||||
| 		const username = body['username']; | ||||
|  |  | |||
|  | @ -47,6 +47,14 @@ export const meta = { | |||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			enableTurnstile: { | ||||
| 				type: 'boolean', | ||||
| 				optional: false, nullable: false, | ||||
| 			}, | ||||
| 			turnstileSiteKey: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			swPublickey: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
|  | @ -197,6 +205,10 @@ export const meta = { | |||
| 				type: 'string', | ||||
| 				optional: true, nullable: true, | ||||
| 			}, | ||||
| 			turnstileSecretKey: { | ||||
| 				type: 'string', | ||||
| 				optional: true, nullable: true, | ||||
| 			} | ||||
| 			sensitiveMediaDetection: { | ||||
| 				type: 'string', | ||||
| 				optional: true, nullable: false, | ||||
|  | @ -374,6 +386,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				hcaptchaSiteKey: instance.hcaptchaSiteKey, | ||||
| 				enableRecaptcha: instance.enableRecaptcha, | ||||
| 				recaptchaSiteKey: instance.recaptchaSiteKey, | ||||
| 				enableTurnstile: instance.enableTurnstile, | ||||
| 				turnstileSiteKey: instance.turnstileSiteKey, | ||||
| 				swPublickey: instance.swPublicKey, | ||||
| 				themeColor: instance.themeColor, | ||||
| 				mascotImageUrl: instance.mascotImageUrl, | ||||
|  | @ -400,6 +414,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				blockedHosts: instance.blockedHosts, | ||||
| 				hcaptchaSecretKey: instance.hcaptchaSecretKey, | ||||
| 				recaptchaSecretKey: instance.recaptchaSecretKey, | ||||
| 				turnstileSecretKey: instance.turnstileSecretKey, | ||||
| 				sensitiveMediaDetection: instance.sensitiveMediaDetection, | ||||
| 				sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, | ||||
| 				setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, | ||||
|  |  | |||
|  | @ -52,6 +52,9 @@ export const paramDef = { | |||
| 		enableRecaptcha: { type: 'boolean' }, | ||||
| 		recaptchaSiteKey: { type: 'string', nullable: true }, | ||||
| 		recaptchaSecretKey: { type: 'string', nullable: true }, | ||||
| 		enableTurnstile: { type: 'boolean' }, | ||||
| 		turnstileSiteKey: { type: 'string', nullable: true }, | ||||
| 		turnstileSecretKey: { type: 'string', nullable: true }, | ||||
| 		sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, | ||||
| 		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, | ||||
| 		setSensitiveFlagAutomatically: { type: 'boolean' }, | ||||
|  | @ -231,6 +234,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				set.recaptchaSecretKey = ps.recaptchaSecretKey; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.enableTurnstile !== undefined) { | ||||
| 				set.enableTurnstile = ps.enableTurnstile; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.turnstileSiteKey !== undefined) { | ||||
| 				set.turnstileSiteKey = ps.turnstileSiteKey; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.turnstileSecretKey !== undefined) { | ||||
| 				set.turnstileSecretKey = ps.turnstileSecretKey; | ||||
| 			} | ||||
| 
 | ||||
| 			if (ps.sensitiveMediaDetection !== undefined) { | ||||
| 				set.sensitiveMediaDetection = ps.sensitiveMediaDetection; | ||||
| 			} | ||||
|  |  | |||
|  | @ -119,6 +119,14 @@ export const meta = { | |||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			enableTurnstile: { | ||||
| 				type: 'boolean', | ||||
| 				optional: false, nullable: false, | ||||
| 			}, | ||||
| 			turnstileSiteKey: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 			}, | ||||
| 			swPublickey: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
|  | @ -372,6 +380,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				hcaptchaSiteKey: instance.hcaptchaSiteKey, | ||||
| 				enableRecaptcha: instance.enableRecaptcha, | ||||
| 				recaptchaSiteKey: instance.recaptchaSiteKey, | ||||
| 				enableTurnstile: instance.enableTurnstile, | ||||
| 				turnstileSiteKey: instance.turnstileSiteKey, | ||||
| 				swPublickey: instance.swPublicKey, | ||||
| 				themeColor: instance.themeColor, | ||||
| 				mascotImageUrl: instance.mascotImageUrl, | ||||
|  | @ -423,6 +433,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 					elasticsearch: this.config.elasticsearch ? true : false, | ||||
| 					hcaptcha: instance.enableHcaptcha, | ||||
| 					recaptcha: instance.enableRecaptcha, | ||||
| 					turnstile: instance.enableTurnstile, | ||||
| 					objectStorage: instance.useObjectStorage, | ||||
| 					twitter: instance.enableTwitterIntegration, | ||||
| 					github: instance.enableGithubIntegration, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue