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
|
@ -349,6 +349,10 @@ recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "reCAPTCHAを有効にする"
|
enableRecaptcha: "reCAPTCHAを有効にする"
|
||||||
recaptchaSiteKey: "サイトキー"
|
recaptchaSiteKey: "サイトキー"
|
||||||
recaptchaSecretKey: "シークレットキー"
|
recaptchaSecretKey: "シークレットキー"
|
||||||
|
turnstile: "Turnstile"
|
||||||
|
enableTurnstile: "Turnstileを有効にする"
|
||||||
|
turnstileSiteKey: "サイトキー"
|
||||||
|
turnstileSecretKey: "シークレットキー"
|
||||||
avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。"
|
avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。"
|
||||||
antennas: "アンテナ"
|
antennas: "アンテナ"
|
||||||
manageAntennas: "アンテナの管理"
|
manageAntennas: "アンテナの管理"
|
||||||
|
|
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}`;
|
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;
|
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', {
|
@Column('enum', {
|
||||||
enum: ['none', 'all', 'local', 'remote'],
|
enum: ['none', 'all', 'local', 'remote'],
|
||||||
default: 'none',
|
default: 'none',
|
||||||
|
|
|
@ -61,6 +61,12 @@ export class SignupApiService {
|
||||||
ctx.throw(400, e);
|
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'];
|
const username = body['username'];
|
||||||
|
|
|
@ -47,6 +47,14 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
enableTurnstile: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
turnstileSiteKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
swPublickey: {
|
swPublickey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
@ -197,6 +205,10 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: true, nullable: true,
|
optional: true, nullable: true,
|
||||||
},
|
},
|
||||||
|
turnstileSecretKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
}
|
||||||
sensitiveMediaDetection: {
|
sensitiveMediaDetection: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
|
@ -374,6 +386,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||||
enableRecaptcha: instance.enableRecaptcha,
|
enableRecaptcha: instance.enableRecaptcha,
|
||||||
recaptchaSiteKey: instance.recaptchaSiteKey,
|
recaptchaSiteKey: instance.recaptchaSiteKey,
|
||||||
|
enableTurnstile: instance.enableTurnstile,
|
||||||
|
turnstileSiteKey: instance.turnstileSiteKey,
|
||||||
swPublickey: instance.swPublicKey,
|
swPublickey: instance.swPublicKey,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
mascotImageUrl: instance.mascotImageUrl,
|
mascotImageUrl: instance.mascotImageUrl,
|
||||||
|
@ -400,6 +414,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
hcaptchaSecretKey: instance.hcaptchaSecretKey,
|
||||||
recaptchaSecretKey: instance.recaptchaSecretKey,
|
recaptchaSecretKey: instance.recaptchaSecretKey,
|
||||||
|
turnstileSecretKey: instance.turnstileSecretKey,
|
||||||
sensitiveMediaDetection: instance.sensitiveMediaDetection,
|
sensitiveMediaDetection: instance.sensitiveMediaDetection,
|
||||||
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
|
||||||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||||
|
|
|
@ -52,6 +52,9 @@ export const paramDef = {
|
||||||
enableRecaptcha: { type: 'boolean' },
|
enableRecaptcha: { type: 'boolean' },
|
||||||
recaptchaSiteKey: { type: 'string', nullable: true },
|
recaptchaSiteKey: { type: 'string', nullable: true },
|
||||||
recaptchaSecretKey: { 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'] },
|
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
|
||||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
||||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
setSensitiveFlagAutomatically: { type: 'boolean' },
|
||||||
|
@ -231,6 +234,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
set.recaptchaSecretKey = ps.recaptchaSecretKey;
|
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) {
|
if (ps.sensitiveMediaDetection !== undefined) {
|
||||||
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
|
set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,14 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
enableTurnstile: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
turnstileSiteKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
swPublickey: {
|
swPublickey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
@ -372,6 +380,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
hcaptchaSiteKey: instance.hcaptchaSiteKey,
|
||||||
enableRecaptcha: instance.enableRecaptcha,
|
enableRecaptcha: instance.enableRecaptcha,
|
||||||
recaptchaSiteKey: instance.recaptchaSiteKey,
|
recaptchaSiteKey: instance.recaptchaSiteKey,
|
||||||
|
enableTurnstile: instance.enableTurnstile,
|
||||||
|
turnstileSiteKey: instance.turnstileSiteKey,
|
||||||
swPublickey: instance.swPublicKey,
|
swPublickey: instance.swPublicKey,
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
mascotImageUrl: instance.mascotImageUrl,
|
mascotImageUrl: instance.mascotImageUrl,
|
||||||
|
@ -423,6 +433,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
elasticsearch: this.config.elasticsearch ? true : false,
|
elasticsearch: this.config.elasticsearch ? true : false,
|
||||||
hcaptcha: instance.enableHcaptcha,
|
hcaptcha: instance.enableHcaptcha,
|
||||||
recaptcha: instance.enableRecaptcha,
|
recaptcha: instance.enableRecaptcha,
|
||||||
|
turnstile: instance.enableTurnstile,
|
||||||
objectStorage: instance.useObjectStorage,
|
objectStorage: instance.useObjectStorage,
|
||||||
twitter: instance.enableTwitterIntegration,
|
twitter: instance.enableTwitterIntegration,
|
||||||
github: instance.enableGithubIntegration,
|
github: instance.enableGithubIntegration,
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Captcha = {
|
||||||
getResponse(id: string): string;
|
getResponse(id: string): string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CaptchaProvider = 'hcaptcha' | 'recaptcha';
|
type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
|
||||||
|
|
||||||
type CaptchaContainer = {
|
type CaptchaContainer = {
|
||||||
readonly [_ in CaptchaProvider]?: Captcha;
|
readonly [_ in CaptchaProvider]?: Captcha;
|
||||||
|
@ -48,6 +48,7 @@ const variable = computed(() => {
|
||||||
switch (props.provider) {
|
switch (props.provider) {
|
||||||
case 'hcaptcha': return 'hcaptcha';
|
case 'hcaptcha': return 'hcaptcha';
|
||||||
case 'recaptcha': return 'grecaptcha';
|
case 'recaptcha': return 'grecaptcha';
|
||||||
|
case 'turnstile': return 'turnstile';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ const src = computed(() => {
|
||||||
switch (props.provider) {
|
switch (props.provider) {
|
||||||
case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
|
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 '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>
|
</MkSwitch>
|
||||||
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
|
<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.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>
|
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -92,6 +93,7 @@ const host = toUnicode(config.host);
|
||||||
|
|
||||||
let hcaptcha = $ref();
|
let hcaptcha = $ref();
|
||||||
let recaptcha = $ref();
|
let recaptcha = $ref();
|
||||||
|
let turnstile = $ref();
|
||||||
|
|
||||||
let username: string = $ref('');
|
let username: string = $ref('');
|
||||||
let password: string = $ref('');
|
let password: string = $ref('');
|
||||||
|
@ -106,12 +108,14 @@ let submitting: boolean = $ref(false);
|
||||||
let ToSAgreement: boolean = $ref(false);
|
let ToSAgreement: boolean = $ref(false);
|
||||||
let hCaptchaResponse = $ref(null);
|
let hCaptchaResponse = $ref(null);
|
||||||
let reCaptchaResponse = $ref(null);
|
let reCaptchaResponse = $ref(null);
|
||||||
|
let turnstileResponse = $ref(null);
|
||||||
|
|
||||||
const shouldDisableSubmitting = $computed((): boolean => {
|
const shouldDisableSubmitting = $computed((): boolean => {
|
||||||
return submitting ||
|
return submitting ||
|
||||||
instance.tosUrl && !ToSAgreement ||
|
instance.tosUrl && !ToSAgreement ||
|
||||||
instance.enableHcaptcha && !hCaptchaResponse ||
|
instance.enableHcaptcha && !hCaptchaResponse ||
|
||||||
instance.enableRecaptcha && !reCaptchaResponse ||
|
instance.enableRecaptcha && !reCaptchaResponse ||
|
||||||
|
instance.enableTurnstile && !turnstileResponse ||
|
||||||
passwordRetypeState === 'not-match';
|
passwordRetypeState === 'not-match';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -198,6 +202,7 @@ function onSubmit(): void {
|
||||||
invitationCode,
|
invitationCode,
|
||||||
'hcaptcha-response': hCaptchaResponse,
|
'hcaptcha-response': hCaptchaResponse,
|
||||||
'g-recaptcha-response': reCaptchaResponse,
|
'g-recaptcha-response': reCaptchaResponse,
|
||||||
|
'turnstile-response': turnstileResponse,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (instance.emailRequiredForSignup) {
|
if (instance.emailRequiredForSignup) {
|
||||||
os.alert({
|
os.alert({
|
||||||
|
@ -222,6 +227,7 @@ function onSubmit(): void {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
hcaptcha.reset?.();
|
hcaptcha.reset?.();
|
||||||
recaptcha.reset?.();
|
recaptcha.reset?.();
|
||||||
|
turnstile.reset?.();
|
||||||
|
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
|
<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
|
||||||
<option value="hcaptcha">hCaptcha</option>
|
<option value="hcaptcha">hCaptcha</option>
|
||||||
<option value="recaptcha">reCAPTCHA</option>
|
<option value="recaptcha">reCAPTCHA</option>
|
||||||
|
<option value="turnstile">Turnstile</option>
|
||||||
</FormRadios>
|
</FormRadios>
|
||||||
|
|
||||||
<template v-if="provider === 'hcaptcha'">
|
<template v-if="provider === 'hcaptcha'">
|
||||||
|
@ -36,6 +37,20 @@
|
||||||
<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
|
<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
</template>
|
</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>
|
<FormButton primary @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,6 +76,8 @@ let hcaptchaSiteKey: string | null = $ref(null);
|
||||||
let hcaptchaSecretKey: string | null = $ref(null);
|
let hcaptchaSecretKey: string | null = $ref(null);
|
||||||
let recaptchaSiteKey: string | null = $ref(null);
|
let recaptchaSiteKey: string | null = $ref(null);
|
||||||
let recaptchaSecretKey: 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() {
|
async function init() {
|
||||||
const meta = await os.api('admin/meta');
|
const meta = await os.api('admin/meta');
|
||||||
|
@ -68,8 +85,10 @@ async function init() {
|
||||||
hcaptchaSecretKey = meta.hcaptchaSecretKey;
|
hcaptchaSecretKey = meta.hcaptchaSecretKey;
|
||||||
recaptchaSiteKey = meta.recaptchaSiteKey;
|
recaptchaSiteKey = meta.recaptchaSiteKey;
|
||||||
recaptchaSecretKey = meta.recaptchaSecretKey;
|
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() {
|
function save() {
|
||||||
|
@ -80,6 +99,9 @@ function save() {
|
||||||
enableRecaptcha: provider === 'recaptcha',
|
enableRecaptcha: provider === 'recaptcha',
|
||||||
recaptchaSiteKey,
|
recaptchaSiteKey,
|
||||||
recaptchaSecretKey,
|
recaptchaSecretKey,
|
||||||
|
enableTurnstile: provider === 'turnstile',
|
||||||
|
turnstileSiteKey,
|
||||||
|
turnstileSecretKey,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,7 +53,7 @@ let view = $ref(null);
|
||||||
let el = $ref(null);
|
let el = $ref(null);
|
||||||
let pageProps = $ref({});
|
let pageProps = $ref({});
|
||||||
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
|
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 noEmailServer = !instance.enableEmail;
|
||||||
let thereIsUnresolvedAbuseReport = $ref(false);
|
let thereIsUnresolvedAbuseReport = $ref(false);
|
||||||
let currentPage = $computed(() => router.currentRef.value.child);
|
let currentPage = $computed(() => router.currentRef.value.child);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<template #label>{{ i18n.ts.botProtection }}</template>
|
<template #label>{{ i18n.ts.botProtection }}</template>
|
||||||
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
|
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
|
||||||
<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</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>
|
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
|
||||||
|
|
||||||
<XBotProtection/>
|
<XBotProtection/>
|
||||||
|
@ -120,6 +121,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
let summalyProxy: string = $ref('');
|
let summalyProxy: string = $ref('');
|
||||||
let enableHcaptcha: boolean = $ref(false);
|
let enableHcaptcha: boolean = $ref(false);
|
||||||
let enableRecaptcha: boolean = $ref(false);
|
let enableRecaptcha: boolean = $ref(false);
|
||||||
|
let enableTurnstile: boolean = $ref(false);
|
||||||
let sensitiveMediaDetection: string = $ref('none');
|
let sensitiveMediaDetection: string = $ref('none');
|
||||||
let sensitiveMediaDetectionSensitivity: number = $ref(0);
|
let sensitiveMediaDetectionSensitivity: number = $ref(0);
|
||||||
let setSensitiveFlagAutomatically: boolean = $ref(false);
|
let setSensitiveFlagAutomatically: boolean = $ref(false);
|
||||||
|
@ -132,6 +134,7 @@ async function init() {
|
||||||
summalyProxy = meta.summalyProxy;
|
summalyProxy = meta.summalyProxy;
|
||||||
enableHcaptcha = meta.enableHcaptcha;
|
enableHcaptcha = meta.enableHcaptcha;
|
||||||
enableRecaptcha = meta.enableRecaptcha;
|
enableRecaptcha = meta.enableRecaptcha;
|
||||||
|
enableTurnstile = meta.enableTurnstile;
|
||||||
sensitiveMediaDetection = meta.sensitiveMediaDetection;
|
sensitiveMediaDetection = meta.sensitiveMediaDetection;
|
||||||
sensitiveMediaDetectionSensitivity =
|
sensitiveMediaDetectionSensitivity =
|
||||||
meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 :
|
meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 :
|
||||||
|
|
Loading…
Reference in a new issue