mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-28 16:38:23 +01:00
fix
This commit is contained in:
parent
3dade7a577
commit
ce7f2054c8
7 changed files with 351 additions and 109 deletions
|
@ -6,8 +6,10 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiMeta } from '@/models/Meta.js';
|
||||
|
||||
export const supportedCaptchaProviders = ['hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const;
|
||||
export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const;
|
||||
export type CaptchaProvider = typeof supportedCaptchaProviders[number];
|
||||
|
||||
export const captchaErrorCodes = {
|
||||
|
@ -30,14 +32,14 @@ export class CaptchaError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export type ValidateSuccess = {
|
||||
export type CaptchaSaveSuccess = {
|
||||
success: true;
|
||||
}
|
||||
export type ValidateFailure = {
|
||||
export type CaptchaSaveFailure = {
|
||||
success: false;
|
||||
error: CaptchaError;
|
||||
}
|
||||
export type ValidateResult = ValidateSuccess | ValidateFailure;
|
||||
export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
|
||||
|
||||
type CaptchaResponse = {
|
||||
success: boolean;
|
||||
|
@ -48,6 +50,7 @@ type CaptchaResponse = {
|
|||
export class CaptchaService {
|
||||
constructor(
|
||||
private httpRequestService: HttpRequestService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -166,16 +169,15 @@ export class CaptchaService {
|
|||
}
|
||||
|
||||
/**
|
||||
* フロントエンド側で受け取ったcaptchaからの戻り値を検証します.
|
||||
* captchaの設定を更新します. その際、フロントエンド側で受け取ったcaptchaからの戻り値を検証し、passした場合のみ設定を更新します.
|
||||
* 実際の検証処理はサービス内で定義されている各captchaプロバイダの検証関数に委譲します.
|
||||
*
|
||||
* @param provider 検証するcaptchaのプロバイダ
|
||||
* @param params
|
||||
* @param params.provider 検証するcaptchaのプロバイダ
|
||||
* @param params.sitekey mcaptchaの場合に指定するsitekey. それ以外のプロバイダでは無視されます
|
||||
* @param params.sitekey hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsitekey. それ以外のプロバイダでは無視されます
|
||||
* @param params.secret hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsecret. それ以外のプロバイダでは無視されます
|
||||
* @param params.instanceUrl mcaptchaの場合に指定するインスタンスのURL. それ以外のプロバイダでは無視されます
|
||||
* @param params.captchaResult フロントエンド側で受け取ったcaptchaプロバイダからの戻り値. この値を使ってサーバサイドでの検証を行います
|
||||
*
|
||||
* @see verifyHcaptcha
|
||||
* @see verifyMcaptcha
|
||||
* @see verifyRecaptcha
|
||||
|
@ -183,56 +185,70 @@ export class CaptchaService {
|
|||
* @see verifyTestcaptcha
|
||||
*/
|
||||
@bindThis
|
||||
public async verify(params: {
|
||||
provider: CaptchaProvider;
|
||||
sitekey?: string;
|
||||
secret?: string;
|
||||
instanceUrl?: string;
|
||||
public async save(
|
||||
provider: CaptchaProvider,
|
||||
params?: {
|
||||
sitekey?: string | null;
|
||||
secret?: string | null;
|
||||
instanceUrl?: string | null;
|
||||
captchaResult?: string | null;
|
||||
}): Promise<ValidateResult> {
|
||||
if (!supportedCaptchaProviders.includes(params.provider)) {
|
||||
},
|
||||
): Promise<CaptchaSaveResult> {
|
||||
if (!supportedCaptchaProviders.includes(provider)) {
|
||||
return {
|
||||
success: false,
|
||||
error: new CaptchaError(captchaErrorCodes.invalidProvider, `Invalid captcha provider: ${params.provider}`),
|
||||
error: new CaptchaError(captchaErrorCodes.invalidProvider, `Invalid captcha provider: ${provider}`),
|
||||
};
|
||||
}
|
||||
|
||||
const operation = {
|
||||
none: async () => {
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
hcaptcha: async () => {
|
||||
if (!params.secret) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'hcaptcha-failed: secret and response are required');
|
||||
if (!params?.secret || !params.captchaResult) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'hcaptcha-failed: secret and captureResult are required');
|
||||
}
|
||||
|
||||
return this.verifyHcaptcha(params.secret, params.captchaResult);
|
||||
await this.verifyHcaptcha(params.secret, params.captchaResult);
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
mcaptcha: async () => {
|
||||
if (!params.secret || !params.sitekey || !params.instanceUrl) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'mcaptcha-failed: secret, sitekey, instanceUrl and response are required');
|
||||
if (!params?.secret || !params.sitekey || !params.instanceUrl || !params.captchaResult) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'mcaptcha-failed: secret, sitekey, instanceUrl and captureResult are required');
|
||||
}
|
||||
|
||||
return this.verifyMcaptcha(params.secret, params.sitekey, params.instanceUrl, params.captchaResult);
|
||||
await this.verifyMcaptcha(params.secret, params.sitekey, params.instanceUrl, params.captchaResult);
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
recaptcha: async () => {
|
||||
if (!params.secret) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'recaptcha-failed: secret and response are required');
|
||||
if (!params?.secret || !params.captchaResult) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'recaptcha-failed: secret and captureResult are required');
|
||||
}
|
||||
|
||||
return this.verifyRecaptcha(params.secret, params.captchaResult);
|
||||
await this.verifyRecaptcha(params.secret, params.captchaResult);
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
turnstile: async () => {
|
||||
if (!params.secret) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: secret and response are required');
|
||||
if (!params?.secret || !params.captchaResult) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: secret and captureResult are required');
|
||||
}
|
||||
|
||||
return this.verifyTurnstile(params.secret, params.captchaResult);
|
||||
await this.verifyTurnstile(params.secret, params.captchaResult);
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
testcaptcha: async () => {
|
||||
return this.verifyTestcaptcha(params.captchaResult);
|
||||
if (!params?.captchaResult) {
|
||||
throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: captureResult are required');
|
||||
}
|
||||
|
||||
await this.verifyTestcaptcha(params.captchaResult);
|
||||
await this.updateMeta(provider, params);
|
||||
},
|
||||
}[params.provider];
|
||||
}[provider];
|
||||
|
||||
return operation()
|
||||
.then(() => ({ success: true }) as ValidateSuccess)
|
||||
.then(() => ({ success: true }) as CaptchaSaveSuccess)
|
||||
.catch(err => {
|
||||
const error = err instanceof CaptchaError
|
||||
? err
|
||||
|
@ -243,5 +259,63 @@ export class CaptchaService {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async updateMeta(
|
||||
provider: CaptchaProvider,
|
||||
params?: {
|
||||
sitekey?: string | null;
|
||||
secret?: string | null;
|
||||
instanceUrl?: string | null;
|
||||
},
|
||||
) {
|
||||
const metaPartial: Partial<
|
||||
Pick<
|
||||
MiMeta,
|
||||
('enableHcaptcha' | 'hcaptchaSiteKey' | 'hcaptchaSecretKey') |
|
||||
('enableMcaptcha' | 'mcaptchaSitekey' | 'mcaptchaSecretKey' | 'mcaptchaInstanceUrl') |
|
||||
('enableRecaptcha' | 'recaptchaSiteKey' | 'recaptchaSecretKey') |
|
||||
('enableTurnstile' | 'turnstileSiteKey' | 'turnstileSecretKey') |
|
||||
('enableTestcaptcha')
|
||||
>
|
||||
> = {
|
||||
enableHcaptcha: provider === 'hcaptcha',
|
||||
enableMcaptcha: provider === 'mcaptcha',
|
||||
enableRecaptcha: provider === 'recaptcha',
|
||||
enableTurnstile: provider === 'turnstile',
|
||||
enableTestcaptcha: provider === 'testcaptcha',
|
||||
};
|
||||
|
||||
const updateIfNotUndefined = <K extends keyof typeof metaPartial>(key: K, value: typeof metaPartial[K]) => {
|
||||
if (value !== undefined) {
|
||||
metaPartial[key] = value;
|
||||
}
|
||||
};
|
||||
switch (provider) {
|
||||
case 'hcaptcha': {
|
||||
updateIfNotUndefined('hcaptchaSiteKey', params?.sitekey);
|
||||
updateIfNotUndefined('hcaptchaSecretKey', params?.secret);
|
||||
break;
|
||||
}
|
||||
case 'mcaptcha': {
|
||||
updateIfNotUndefined('mcaptchaSitekey', params?.sitekey);
|
||||
updateIfNotUndefined('mcaptchaSecretKey', params?.secret);
|
||||
updateIfNotUndefined('mcaptchaInstanceUrl', params?.instanceUrl);
|
||||
break;
|
||||
}
|
||||
case 'recaptcha': {
|
||||
updateIfNotUndefined('recaptchaSiteKey', params?.sitekey);
|
||||
updateIfNotUndefined('recaptchaSecretKey', params?.secret);
|
||||
break;
|
||||
}
|
||||
case 'turnstile': {
|
||||
updateIfNotUndefined('turnstileSiteKey', params?.sitekey);
|
||||
updateIfNotUndefined('turnstileSecretKey', params?.secret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.metaService.update(metaPartial);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
|
|||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||
import * as ep___admin_captcha_test from './endpoints/admin/captcha/test.js';
|
||||
import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js';
|
||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||
|
@ -417,7 +417,7 @@ const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-de
|
|||
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
||||
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
|
||||
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
|
||||
const $admin_captcha_test: Provider = { provide: 'ep:admin/captcha/test', useClass: ep___admin_captcha_test.default };
|
||||
const $admin_captcha_save: Provider = { provide: 'ep:admin/captcha/save', useClass: ep___admin_captcha_save.default };
|
||||
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
|
||||
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
|
||||
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
|
||||
|
@ -810,7 +810,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_avatarDecorations_delete,
|
||||
$admin_avatarDecorations_list,
|
||||
$admin_avatarDecorations_update,
|
||||
$admin_captcha_test,
|
||||
$admin_captcha_save,
|
||||
$admin_deleteAllFilesOfAUser,
|
||||
$admin_unsetUserAvatar,
|
||||
$admin_unsetUserBanner,
|
||||
|
@ -1197,7 +1197,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$admin_avatarDecorations_delete,
|
||||
$admin_avatarDecorations_list,
|
||||
$admin_avatarDecorations_update,
|
||||
$admin_captcha_test,
|
||||
$admin_captcha_save,
|
||||
$admin_deleteAllFilesOfAUser,
|
||||
$admin_unsetUserAvatar,
|
||||
$admin_unsetUserBanner,
|
||||
|
|
|
@ -33,7 +33,7 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
|
|||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||
import * as ep___admin_captcha_test from './endpoints/admin/captcha/test.js';
|
||||
import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js';
|
||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||
|
@ -421,7 +421,7 @@ const eps = [
|
|||
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
||||
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
|
||||
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
|
||||
['admin/captcha/test', ep___admin_captcha_test],
|
||||
['admin/captcha/test', ep___admin_captcha_save],
|
||||
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
|
||||
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
||||
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
||||
|
|
|
@ -46,17 +46,17 @@ export const paramDef = {
|
|||
type: 'string',
|
||||
enum: supportedCaptchaProviders,
|
||||
},
|
||||
captchaResult: {
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
sitekey: {
|
||||
type: 'string',
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
},
|
||||
captchaResult: {
|
||||
type: 'string',
|
||||
type: 'string', nullable: true,
|
||||
},
|
||||
},
|
||||
required: ['provider'],
|
||||
|
@ -67,13 +67,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
private captchaService: CaptchaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const result = await this.captchaService.verify({
|
||||
provider: ps.provider,
|
||||
super(meta, paramDef, async (ps) => {
|
||||
const result = await this.captchaService.save(ps.provider, ps.captchaResult, {
|
||||
sitekey: ps.sitekey,
|
||||
secret: ps.secret,
|
||||
instanceUrl: ps.instanceUrl,
|
||||
captchaResult: ps.captchaResult,
|
||||
});
|
||||
|
||||
if (result.success) {
|
|
@ -10,16 +10,19 @@ import {
|
|||
CaptchaError,
|
||||
CaptchaErrorCode,
|
||||
captchaErrorCodes,
|
||||
CaptchaSaveResult,
|
||||
CaptchaService,
|
||||
ValidateResult,
|
||||
} from '@/core/CaptchaService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiMeta } from '@/models/Meta.js';
|
||||
|
||||
describe('CaptchaService', () => {
|
||||
let app: TestingModule;
|
||||
let service: CaptchaService;
|
||||
let httpRequestService: jest.Mocked<HttpRequestService>;
|
||||
let metaService: jest.Mocked<MetaService>;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await Test.createTestingModule({
|
||||
|
@ -31,6 +34,9 @@ describe('CaptchaService', () => {
|
|||
{
|
||||
provide: HttpRequestService, useFactory: () => ({ send: jest.fn() }),
|
||||
},
|
||||
{
|
||||
provide: MetaService, useFactory: () => ({ update: jest.fn() }),
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
@ -38,10 +44,12 @@ describe('CaptchaService', () => {
|
|||
|
||||
service = app.get(CaptchaService);
|
||||
httpRequestService = app.get(HttpRequestService) as jest.Mocked<HttpRequestService>;
|
||||
metaService = app.get(MetaService) as jest.Mocked<MetaService>;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
httpRequestService.send.mockClear();
|
||||
metaService.update.mockClear();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -76,7 +84,6 @@ describe('CaptchaService', () => {
|
|||
await test();
|
||||
expect(false).toBe(true);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
expect(e instanceof CaptchaError).toBe(true);
|
||||
|
||||
const _e = e as CaptchaError;
|
||||
|
@ -184,81 +191,194 @@ describe('CaptchaService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('validateSettings', () => {
|
||||
describe('save', () => {
|
||||
const host = 'https://localhost';
|
||||
|
||||
describe('success', () => {
|
||||
describe('[success] 検証に成功した時だけ保存できる+他のプロバイダの設定値を誤って更新しない', () => {
|
||||
beforeEach(() => {
|
||||
successMock({ success: true, valid: true });
|
||||
});
|
||||
|
||||
async function assertSuccess(promise: Promise<ValidateResult>) {
|
||||
async function assertSuccess(promise: Promise<CaptchaSaveResult>, expectMeta: Partial<MiMeta>) {
|
||||
await expect(promise)
|
||||
.resolves
|
||||
.toStrictEqual({ success: true });
|
||||
const partialParams = metaService.update.mock.calls[0][0];
|
||||
expect(partialParams).toStrictEqual(expectMeta);
|
||||
}
|
||||
|
||||
test('none', async () => {
|
||||
await assertSuccess(
|
||||
service.save('none'),
|
||||
{
|
||||
enableHcaptcha: false,
|
||||
enableMcaptcha: false,
|
||||
enableRecaptcha: false,
|
||||
enableTurnstile: false,
|
||||
enableTestcaptcha: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('hcaptcha', async () => {
|
||||
await assertSuccess(service.verify({ provider: 'hcaptcha', secret: 'secret', captchaResult: 'response' }));
|
||||
await assertSuccess(
|
||||
service.save('hcaptcha', {
|
||||
sitekey: 'hcaptcha-sitekey',
|
||||
secret: 'hcaptcha-secret',
|
||||
captchaResult: 'hcaptcha-passed',
|
||||
}),
|
||||
{
|
||||
enableHcaptcha: true,
|
||||
enableMcaptcha: false,
|
||||
enableRecaptcha: false,
|
||||
enableTurnstile: false,
|
||||
enableTestcaptcha: false,
|
||||
hcaptchaSiteKey: 'hcaptcha-sitekey',
|
||||
hcaptchaSecretKey: 'hcaptcha-secret',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('mcaptcha', async () => {
|
||||
await assertSuccess(service.verify({
|
||||
provider: 'mcaptcha',
|
||||
secret: 'secret',
|
||||
sitekey: 'sitekey',
|
||||
await assertSuccess(
|
||||
service.save('mcaptcha', {
|
||||
sitekey: 'mcaptcha-sitekey',
|
||||
secret: 'mcaptcha-secret',
|
||||
instanceUrl: host,
|
||||
captchaResult: 'response',
|
||||
}));
|
||||
captchaResult: 'mcaptcha-passed',
|
||||
}),
|
||||
{
|
||||
enableHcaptcha: false,
|
||||
enableMcaptcha: true,
|
||||
enableRecaptcha: false,
|
||||
enableTurnstile: false,
|
||||
enableTestcaptcha: false,
|
||||
mcaptchaSitekey: 'mcaptcha-sitekey',
|
||||
mcaptchaSecretKey: 'mcaptcha-secret',
|
||||
mcaptchaInstanceUrl: host,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('recaptcha', async () => {
|
||||
await assertSuccess(service.verify({ provider: 'recaptcha', secret: 'secret', captchaResult: 'response' }));
|
||||
await assertSuccess(
|
||||
service.save('recaptcha', {
|
||||
sitekey: 'recaptcha-sitekey',
|
||||
secret: 'recaptcha-secret',
|
||||
captchaResult: 'recaptcha-passed',
|
||||
}),
|
||||
{
|
||||
enableHcaptcha: false,
|
||||
enableMcaptcha: false,
|
||||
enableRecaptcha: true,
|
||||
enableTurnstile: false,
|
||||
enableTestcaptcha: false,
|
||||
recaptchaSiteKey: 'recaptcha-sitekey',
|
||||
recaptchaSecretKey: 'recaptcha-secret',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('turnstile', async () => {
|
||||
await assertSuccess(service.verify({ provider: 'turnstile', secret: 'secret', captchaResult: 'response' }));
|
||||
await assertSuccess(
|
||||
service.save('turnstile', {
|
||||
sitekey: 'turnstile-sitekey',
|
||||
secret: 'turnstile-secret',
|
||||
captchaResult: 'turnstile-passed',
|
||||
}),
|
||||
{
|
||||
enableHcaptcha: false,
|
||||
enableMcaptcha: false,
|
||||
enableRecaptcha: false,
|
||||
enableTurnstile: true,
|
||||
enableTestcaptcha: false,
|
||||
turnstileSiteKey: 'turnstile-sitekey',
|
||||
turnstileSecretKey: 'turnstile-secret',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('testcaptcha', async () => {
|
||||
await assertSuccess(service.verify({ provider: 'testcaptcha', captchaResult: 'testcaptcha-passed' }));
|
||||
await assertSuccess(
|
||||
service.save('testcaptcha', {
|
||||
sitekey: 'testcaptcha-sitekey',
|
||||
secret: 'testcaptcha-secret',
|
||||
captchaResult: 'testcaptcha-passed',
|
||||
}),
|
||||
{
|
||||
enableHcaptcha: false,
|
||||
enableMcaptcha: false,
|
||||
enableRecaptcha: false,
|
||||
enableTurnstile: false,
|
||||
enableTestcaptcha: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('failure', () => {
|
||||
async function assertFailure(code: CaptchaErrorCode, promise: Promise<ValidateResult>) {
|
||||
describe('[failure] 検証に失敗した場合は保存できない+設定値の更新そのものが発生しない', () => {
|
||||
async function assertFailure(code: CaptchaErrorCode, promise: Promise<CaptchaSaveResult>) {
|
||||
const res = await promise;
|
||||
expect(res.success).toBe(false);
|
||||
if (!res.success) {
|
||||
expect(res.error.code).toBe(code);
|
||||
}
|
||||
expect(metaService.update).not.toBeCalled();
|
||||
}
|
||||
|
||||
describe('noResponseProvided', () => {
|
||||
describe('invalidParameters', () => {
|
||||
test('hcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.noResponseProvided, service.verify({ provider: 'hcaptcha', secret: 'secret', captchaResult: null }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.invalidParameters,
|
||||
service.save('hcaptcha', {
|
||||
sitekey: 'hcaptcha-sitekey',
|
||||
secret: 'hcaptcha-secret',
|
||||
captchaResult: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('mcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.noResponseProvided, service.verify({
|
||||
provider: 'mcaptcha',
|
||||
secret: 'secret',
|
||||
sitekey: 'sitekey',
|
||||
await assertFailure(
|
||||
captchaErrorCodes.invalidParameters,
|
||||
service.save('mcaptcha', {
|
||||
sitekey: 'mcaptcha-sitekey',
|
||||
secret: 'mcaptcha-secret',
|
||||
instanceUrl: host,
|
||||
captchaResult: null,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('recaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.noResponseProvided, service.verify({ provider: 'recaptcha', secret: 'secret', captchaResult: null }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.invalidParameters,
|
||||
service.save('recaptcha', {
|
||||
sitekey: 'recaptcha-sitekey',
|
||||
secret: 'recaptcha-secret',
|
||||
captchaResult: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('turnstile', async () => {
|
||||
await assertFailure(captchaErrorCodes.noResponseProvided, service.verify({ provider: 'turnstile', secret: 'secret', captchaResult: null }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.invalidParameters,
|
||||
service.save('turnstile', {
|
||||
sitekey: 'turnstile-sitekey',
|
||||
secret: 'turnstile-secret',
|
||||
captchaResult: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('testcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.noResponseProvided, service.verify({ provider: 'testcaptcha', captchaResult: null }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.invalidParameters,
|
||||
service.save('testcaptcha', {
|
||||
captchaResult: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -268,29 +388,51 @@ describe('CaptchaService', () => {
|
|||
});
|
||||
|
||||
test('hcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.requestFailed, service.verify({ provider: 'hcaptcha', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.requestFailed,
|
||||
service.save('hcaptcha', {
|
||||
sitekey: 'hcaptcha-sitekey',
|
||||
secret: 'hcaptcha-secret',
|
||||
captchaResult: 'hcaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('mcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.requestFailed, service.verify({
|
||||
provider: 'mcaptcha',
|
||||
secret: 'secret',
|
||||
sitekey: 'sitekey',
|
||||
await assertFailure(
|
||||
captchaErrorCodes.requestFailed,
|
||||
service.save('mcaptcha', {
|
||||
sitekey: 'mcaptcha-sitekey',
|
||||
secret: 'mcaptcha-secret',
|
||||
instanceUrl: host,
|
||||
captchaResult: 'res',
|
||||
}));
|
||||
captchaResult: 'mcaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('recaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.requestFailed, service.verify({ provider: 'recaptcha', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.requestFailed,
|
||||
service.save('recaptcha', {
|
||||
sitekey: 'recaptcha-sitekey',
|
||||
secret: 'recaptcha-secret',
|
||||
captchaResult: 'recaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('turnstile', async () => {
|
||||
await assertFailure(captchaErrorCodes.requestFailed, service.verify({ provider: 'turnstile', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.requestFailed,
|
||||
service.save('turnstile', {
|
||||
sitekey: 'turnstile-sitekey',
|
||||
secret: 'turnstile-secret',
|
||||
captchaResult: 'turnstile-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// testcaptchaはrequestFailedが発生しない
|
||||
// test('testcaptcha', () => {});
|
||||
// testchapchaはrequestFailedがない
|
||||
});
|
||||
|
||||
describe('verificationFailed', () => {
|
||||
|
@ -299,29 +441,57 @@ describe('CaptchaService', () => {
|
|||
});
|
||||
|
||||
test('hcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.verificationFailed, service.verify({ provider: 'hcaptcha', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.verificationFailed,
|
||||
service.save('hcaptcha', {
|
||||
sitekey: 'hcaptcha-sitekey',
|
||||
secret: 'hcaptcha-secret',
|
||||
captchaResult: 'hccaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('mcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.verificationFailed, service.verify({
|
||||
provider: 'mcaptcha',
|
||||
secret: 'secret',
|
||||
sitekey: 'sitekey',
|
||||
await assertFailure(
|
||||
captchaErrorCodes.verificationFailed,
|
||||
service.save('mcaptcha', {
|
||||
sitekey: 'mcaptcha-sitekey',
|
||||
secret: 'mcaptcha-secret',
|
||||
instanceUrl: host,
|
||||
captchaResult: 'res',
|
||||
}));
|
||||
captchaResult: 'mcaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('recaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.verificationFailed, service.verify({ provider: 'recaptcha', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.verificationFailed,
|
||||
service.save('recaptcha', {
|
||||
sitekey: 'recaptcha-sitekey',
|
||||
secret: 'recaptcha-secret',
|
||||
captchaResult: 'recaptcha-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('turnstile', async () => {
|
||||
await assertFailure(captchaErrorCodes.verificationFailed, service.verify({ provider: 'turnstile', secret: 'secret', captchaResult: 'res' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.verificationFailed,
|
||||
service.save('turnstile', {
|
||||
sitekey: 'turnstile-sitekey',
|
||||
secret: 'turnstile-secret',
|
||||
captchaResult: 'turnstile-passed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('testcaptcha', async () => {
|
||||
await assertFailure(captchaErrorCodes.verificationFailed, service.verify({ provider: 'testcaptcha', captchaResult: 'testcaptcha-failed' }));
|
||||
await assertFailure(
|
||||
captchaErrorCodes.verificationFailed,
|
||||
service.save('testcaptcha', {
|
||||
captchaResult: 'testcaptcha-failed',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -258,9 +258,9 @@ watch(captchaResult, async () => {
|
|||
if (captchaResult.value) {
|
||||
const result = await misskeyApi('admin/captcha/test', {
|
||||
provider: provider as Misskey.entities.AdminCaptchaTestRequest['provider'],
|
||||
sitekey: sitekey ?? undefined,
|
||||
secret: secret ?? undefined,
|
||||
instanceUrl: botProtectionForm.state.mcaptchaInstanceUrl ?? undefined,
|
||||
sitekey: sitekey,
|
||||
secret: secret,
|
||||
instanceUrl: botProtectionForm.state.mcaptchaInstanceUrl,
|
||||
captchaResult: captchaResult.value,
|
||||
});
|
||||
|
||||
|
|
|
@ -6586,11 +6586,11 @@ export type operations = {
|
|||
content: {
|
||||
'application/json': {
|
||||
/** @enum {string} */
|
||||
provider: 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha';
|
||||
sitekey?: string;
|
||||
secret?: string;
|
||||
instanceUrl?: string;
|
||||
captchaResult?: string;
|
||||
provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha';
|
||||
captchaResult?: string | null;
|
||||
sitekey?: string | null;
|
||||
secret?: string | null;
|
||||
instanceUrl?: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue