This commit is contained in:
おさむのひと 2024-12-20 11:35:58 +09:00
parent ce7f2054c8
commit ddbb0a4c01
8 changed files with 105 additions and 114 deletions

View file

@ -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_save],
['admin/captcha/save', 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

@ -5,7 +5,8 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
import { captchaErrorCodes, CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin', 'captcha'],
@ -16,25 +17,42 @@ export const meta = {
kind: 'read:admin:captcha',
res: {
type: 'object',
properties: {
success: {
type: 'boolean',
},
error: {
type: 'object',
nullable: true,
optional: false,
properties: {
code: {
type: 'string',
},
message: {
type: 'string',
},
},
},
errors: {
invalidProvider: {
message: 'Invalid provider.',
code: 'INVALID_PROVIDER',
id: '14BF7AE1-80CC-4363-ACB2-4FD61D086AF0',
httpStatusCode: 400,
},
invalidParameters: {
message: 'Invalid parameters.',
code: 'INVALID_PARAMETERS',
id: '26654194-410E-44E2-B42E-460FF6F92476',
httpStatusCode: 400,
},
noResponseProvided: {
message: 'No response provided.',
code: 'NO_RESPONSE_PROVIDED',
id: '40ACBBA8-0937-41FB-BB3F-474514D40AFE',
httpStatusCode: 400,
},
requestFailed: {
message: 'Request failed.',
code: 'REQUEST_FAILED',
id: '0F4FE2F1-2C15-4D6E-B714-EFBFCDE231CD',
httpStatusCode: 500,
},
verificationFailed: {
message: 'Verification failed.',
code: 'VERIFICATION_FAILED',
id: 'C41C067F-24F3-4150-84B2-B5A3AE8C2214',
httpStatusCode: 400,
},
unknown: {
message: 'unknown',
code: 'UNKNOWN',
id: 'F868D509-E257-42A9-99C1-42614B031A97',
httpStatusCode: 500,
},
},
} as const;
@ -68,22 +86,43 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private captchaService: CaptchaService,
) {
super(meta, paramDef, async (ps) => {
const result = await this.captchaService.save(ps.provider, ps.captchaResult, {
const result = await this.captchaService.save(ps.provider, {
sitekey: ps.sitekey,
secret: ps.secret,
instanceUrl: ps.instanceUrl,
captchaResult: ps.captchaResult,
});
if (result.success) {
return { success: true, error: null };
} else {
return {
success: false,
error: {
code: result.error.code.toString(),
message: result.error.message,
},
};
if (!result.success) {
switch (result.error.code) {
case captchaErrorCodes.invalidProvider:
throw new ApiError({
...meta.errors.invalidProvider,
message: result.error.message,
});
case captchaErrorCodes.invalidParameters:
throw new ApiError({
...meta.errors.invalidParameters,
message: result.error.message,
});
case captchaErrorCodes.noResponseProvided:
throw new ApiError({
...meta.errors.noResponseProvided,
message: result.error.message,
});
case captchaErrorCodes.requestFailed:
throw new ApiError({
...meta.errors.requestFailed,
message: result.error.message,
});
case captchaErrorCodes.verificationFailed:
throw new ApiError({
...meta.errors.verificationFailed,
message: result.error.message,
});
default:
throw new ApiError(meta.errors.unknown);
}
}
});
}

View file

@ -142,10 +142,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
</FormSlot>
</template>
<MkInfo v-if="!verifyResult && verifyErrorText" warn>
{{ verifyErrorText }}
</MkInfo>
</div>
</MkFolder>
</template>
@ -170,16 +166,14 @@ const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'
const meta = await misskeyApi('admin/meta');
const captchaResult = ref<string | null>(null);
const verifyResult = ref<boolean>(false);
const verifyErrorText = ref<string | null>(null);
const canSaving = computed((): boolean => {
return (botProtectionForm.state.provider === null) ||
(botProtectionForm.state.provider === 'hcaptcha' && verifyResult.value) ||
(botProtectionForm.state.provider === 'mcaptcha' && verifyResult.value) ||
(botProtectionForm.state.provider === 'recaptcha' && verifyResult.value) ||
(botProtectionForm.state.provider === 'turnstile' && verifyResult.value) ||
(botProtectionForm.state.provider === 'testcaptcha' && verifyResult.value);
(botProtectionForm.state.provider === 'hcaptcha' && !!captchaResult.value) ||
(botProtectionForm.state.provider === 'mcaptcha' && !!captchaResult.value) ||
(botProtectionForm.state.provider === 'recaptcha' && !!captchaResult.value) ||
(botProtectionForm.state.provider === 'turnstile' && !!captchaResult.value) ||
(botProtectionForm.state.provider === 'testcaptcha' && !!captchaResult.value);
});
const botProtectionForm = useForm({
@ -204,36 +198,6 @@ const botProtectionForm = useForm({
turnstileSiteKey: meta.turnstileSiteKey,
turnstileSecretKey: meta.turnstileSecretKey,
}, async (state) => {
await os.apiWithDialog('admin/update-meta', {
enableHcaptcha: state.provider === 'hcaptcha',
hcaptchaSiteKey: state.hcaptchaSiteKey,
hcaptchaSecretKey: state.hcaptchaSecretKey,
enableMcaptcha: state.provider === 'mcaptcha',
mcaptchaSiteKey: state.mcaptchaSiteKey,
mcaptchaSecretKey: state.mcaptchaSecretKey,
mcaptchaInstanceUrl: state.mcaptchaInstanceUrl,
enableRecaptcha: state.provider === 'recaptcha',
recaptchaSiteKey: state.recaptchaSiteKey,
recaptchaSecretKey: state.recaptchaSecretKey,
enableTurnstile: state.provider === 'turnstile',
turnstileSiteKey: state.turnstileSiteKey,
turnstileSecretKey: state.turnstileSecretKey,
enableTestcaptcha: state.provider === 'testcaptcha',
});
fetchInstance(true);
});
watch(botProtectionForm.state, () => {
captchaResult.value = null;
if (botProtectionForm.state.provider === null) {
verifyResult.value = true;
} else {
verifyResult.value = false;
verifyErrorText.value = null;
}
});
watch(captchaResult, async () => {
const provider = botProtectionForm.state.provider;
const sitekey = provider === 'hcaptcha'
@ -256,22 +220,24 @@ watch(captchaResult, async () => {
: null;
if (captchaResult.value) {
const result = await misskeyApi('admin/captcha/test', {
provider: provider as Misskey.entities.AdminCaptchaTestRequest['provider'],
sitekey: sitekey,
secret: secret,
instanceUrl: botProtectionForm.state.mcaptchaInstanceUrl,
captchaResult: captchaResult.value,
});
await os.apiWithDialog(
'admin/captcha/save',
{
provider: provider as Misskey.entities.AdminCaptchaSaveRequest['provider'],
sitekey: sitekey,
secret: secret,
instanceUrl: botProtectionForm.state.mcaptchaInstanceUrl,
captchaResult: captchaResult.value,
},
);
verifyResult.value = result.success;
verifyErrorText.value = result.error
? result.error.message
: null;
} else {
verifyResult.value = false;
await fetchInstance(true);
}
});
watch(botProtectionForm.state, () => {
captchaResult.value = null;
});
</script>
<style lang="scss" module>

View file

@ -137,10 +137,7 @@ type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations
type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminCaptchaTestRequest = operations['admin___captcha___test']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminCaptchaTestResponse = operations['admin___captcha___test']['responses']['200']['content']['application/json'];
type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
@ -1267,8 +1264,7 @@ declare namespace entities {
AdminAvatarDecorationsListRequest,
AdminAvatarDecorationsListResponse,
AdminAvatarDecorationsUpdateRequest,
AdminCaptchaTestRequest,
AdminCaptchaTestResponse,
AdminCaptchaSaveRequest,
AdminDeleteAllFilesOfAUserRequest,
AdminUnsetUserAvatarRequest,
AdminUnsetUserBannerRequest,

View file

@ -256,7 +256,7 @@ declare module '../api.js' {
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes* / **Permission**: *read:admin:captcha*
*/
request<E extends 'admin/captcha/test', P extends Endpoints[E]['req']>(
request<E extends 'admin/captcha/save', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,

View file

@ -36,8 +36,7 @@ import type {
AdminAvatarDecorationsListRequest,
AdminAvatarDecorationsListResponse,
AdminAvatarDecorationsUpdateRequest,
AdminCaptchaTestRequest,
AdminCaptchaTestResponse,
AdminCaptchaSaveRequest,
AdminDeleteAllFilesOfAUserRequest,
AdminUnsetUserAvatarRequest,
AdminUnsetUserBannerRequest,
@ -606,7 +605,7 @@ export type Endpoints = {
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
'admin/captcha/test': { req: AdminCaptchaTestRequest; res: AdminCaptchaTestResponse };
'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse };
'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse };
'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse };
'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse };

View file

@ -39,8 +39,7 @@ export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-dec
export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json'];
export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
export type AdminCaptchaTestRequest = operations['admin___captcha___test']['requestBody']['content']['application/json'];
export type AdminCaptchaTestResponse = operations['admin___captcha___test']['responses']['200']['content']['application/json'];
export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];

View file

@ -215,15 +215,15 @@ export type paths = {
*/
post: operations['admin___avatar-decorations___update'];
};
'/admin/captcha/test': {
'/admin/captcha/save': {
/**
* admin/captcha/test
* admin/captcha/save
* @description No description provided.
*
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes* / **Permission**: *read:admin:captcha*
*/
post: operations['admin___captcha___test'];
post: operations['admin___captcha___save'];
};
'/admin/delete-all-files-of-a-user': {
/**
@ -6575,13 +6575,13 @@ export type operations = {
};
};
/**
* admin/captcha/test
* admin/captcha/save
* @description No description provided.
*
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes* / **Permission**: *read:admin:captcha*
*/
admin___captcha___test: {
admin___captcha___save: {
requestBody: {
content: {
'application/json': {
@ -6595,17 +6595,9 @@ export type operations = {
};
};
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': {
success: boolean;
error: {
code: string;
message: string;
} | null;
};
};
/** @description OK (without any results) */
204: {
content: never;
};
/** @description Client error */
400: {