This commit is contained in:
おさむのひと 2024-12-20 10:57:48 +09:00
parent 3dade7a577
commit ce7f2054c8
7 changed files with 351 additions and 109 deletions

View File

@ -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;
captchaResult?: string | null;
}): Promise<ValidateResult> {
if (!supportedCaptchaProviders.includes(params.provider)) {
public async save(
provider: CaptchaProvider,
params?: {
sitekey?: string | null;
secret?: string | null;
instanceUrl?: string | null;
captchaResult?: string | null;
},
): 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);
}
}

View File

@ -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,

View File

@ -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],

View File

@ -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) {

View File

@ -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',
instanceUrl: host,
captchaResult: 'response',
}));
await assertSuccess(
service.save('mcaptcha', {
sitekey: 'mcaptcha-sitekey',
secret: 'mcaptcha-secret',
instanceUrl: host,
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',
instanceUrl: host,
captchaResult: null,
}));
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',
instanceUrl: host,
captchaResult: 'res',
}));
await assertFailure(
captchaErrorCodes.requestFailed,
service.save('mcaptcha', {
sitekey: 'mcaptcha-sitekey',
secret: 'mcaptcha-secret',
instanceUrl: host,
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',
instanceUrl: host,
captchaResult: 'res',
}));
await assertFailure(
captchaErrorCodes.verificationFailed,
service.save('mcaptcha', {
sitekey: 'mcaptcha-sitekey',
secret: 'mcaptcha-secret',
instanceUrl: host,
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',
}),
);
});
});
});

View File

@ -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,
});

View File

@ -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;
};
};
};